Skip to content

Commit

Permalink
Docstrings (#146)
Browse files Browse the repository at this point in the history
Extensive rework of documentation (still ongoing):
- Adds numpy docstrings to all functions.
- Adds TypedDict return docs to all file-parsers (except complex ones
such as cell/param/castep).
- Makes several file-parsers slightly more consistent in handling of
params.
- Add docstring tests to relevant utility functions.

Fixes #132 
Address #10
  • Loading branch information
oerc0122 authored Sep 11, 2024
1 parent bad6325 commit f83e671
Show file tree
Hide file tree
Showing 34 changed files with 2,136 additions and 575 deletions.
2 changes: 1 addition & 1 deletion castep_outputs/__main__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Run main castep parser
Run main castep parser.
"""

from .cli.castep_outputs_main import main
Expand Down
1 change: 1 addition & 0 deletions castep_outputs/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Main access points and command line control."""
73 changes: 53 additions & 20 deletions castep_outputs/cli/args.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Argument parser
Module containing argument parser and processing.
"""
from __future__ import annotations

Expand All @@ -13,38 +13,59 @@
# pylint: disable=line-too-long


AP = argparse.ArgumentParser(
#: Main argument parser for castep outputs.
ARG_PARSER = argparse.ArgumentParser(
prog="castep_outputs",
description=f"""Attempts to find all files for seedname, filtered by `inc` args (default: all).
Explicit files can be passed using longname arguments.
castep_outputs can parse most castep outputs including: {', '.join(CASTEP_FILE_FORMATS)}""",
)

AP.add_argument("seedname", nargs=argparse.REMAINDER, help="Seed name for data")
AP.add_argument("-V", "--version", action="version", version="%(prog)s v0.1")
AP.add_argument("-L", "--log", help="Verbose output",
choices=("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"), default="WARNING")
AP.add_argument("-o", "--output", help="File to write output, default: screen", default=None)
AP.add_argument("-f", "--out-format",
help="Output format", choices=SUPPORTED_FORMATS, default="json")
ARG_PARSER.add_argument("seedname", nargs=argparse.REMAINDER, help="Seed name for data")
ARG_PARSER.add_argument("-V", "--version", action="version", version="%(prog)s v0.1")
ARG_PARSER.add_argument("-L", "--log", help="Verbose output",
choices=("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"),
default="WARNING")
ARG_PARSER.add_argument("-o", "--output", help="File to write output, default: screen",
default=None)
ARG_PARSER.add_argument("-f", "--out-format", help="Output format", choices=SUPPORTED_FORMATS,
default="json")

AP.add_argument("-t", "--testing",
action="store_true", help="Set testing mode to produce flat outputs")
ARG_PARSER.add_argument("-t", "--testing", action="store_true",
help="Set testing mode to produce flat outputs")

AP.add_argument("-A", "--inc-all", action="store_true", help="Extract all available information")
ARG_PARSER.add_argument("-A", "--inc-all", action="store_true",
help="Extract all available information")

for output_name in CASTEP_OUTPUT_NAMES:
AP.add_argument(f"--inc-{output_name}",
action="store_true", help=f"Extract .{output_name} information")
ARG_PARSER.add_argument(f"--inc-{output_name}", action="store_true",
help=f"Extract .{output_name} information")

for output_name in CASTEP_OUTPUT_NAMES:
AP.add_argument(f"--{output_name}", nargs="*",
help=f"Extract from {output_name.upper()} as .{output_name} type", default=[])
ARG_PARSER.add_argument(f"--{output_name}", nargs="*",
help=f"Extract from {output_name.upper()} as .{output_name} type",
default=[])


def parse_args(to_parse: Sequence[str] = ()) -> argparse.Namespace:
""" Parse all arguments and add those caught by flags """
args = AP.parse_args()
"""
Parse all arguments and add those caught by flags.
Parameters
----------
to_parse : Sequence[str]
Arguments to handle in this call.
Returns
-------
argparse.Namespace
Parsed args.
Examples
--------
>>> parse_args()
"""
args = ARG_PARSER.parse_args()

parse_all = args.inc_all or not any(getattr(args, f"inc_{typ}") for typ in CASTEP_OUTPUT_NAMES)

Expand Down Expand Up @@ -74,6 +95,18 @@ def parse_args(to_parse: Sequence[str] = ()) -> argparse.Namespace:
return args


def args_to_dict(args: argparse.Namespace) -> dict[str, list[str]]:
""" Convert args namespace to dictionary """
def extract_parsables(args: argparse.Namespace) -> dict[str, list[str]]:
"""
Extract the files to parse from the ``Namespace``.
Parameters
----------
args : argparse.Namespace
Namespace to process.
Returns
-------
dict[str, list[str]]
Files to parse.
"""
return {typ: getattr(args, typ) for typ in CASTEP_OUTPUT_NAMES}
69 changes: 59 additions & 10 deletions castep_outputs/cli/castep_outputs_main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Run main castep parser
Main castep parser access routines.
"""
from __future__ import annotations

Expand All @@ -14,17 +14,43 @@
from ..utilities.constants import OutFormats
from ..utilities.dumpers import get_dumpers
from ..utilities.utility import flatten_dict, json_safe, normalise
from .args import args_to_dict, parse_args
from .args import extract_parsables, parse_args


def parse_single(in_file: str | Path | TextIO,
parser: Callable[[TextIO], list[dict[str, Any]]] | None = None,
out_format: OutFormats = "print",
*, loglevel: int = logging.WARNING, testing: bool = False):
*,
loglevel: int = logging.WARNING,
testing: bool = False) -> list[dict]:
"""
Parse a file using the given parser and post-process according to options
Parse a file using the given parser and post-process according to options.
Parameters
----------
in_file : str or Path or TextIO
Input file to parse.
parser : Parser, optional
Castep parser to use. If `None` will be determined from extension.
out_format : OutFormats, optional
Format to dump as.
loglevel : int, optional
Logging level.
testing : bool, optional
Whether used for test suite (disable processing fragile properties).
Returns
-------
dict
Parsed data.
Raises
------
KeyError
If invalid `parser` provided.
AssertionError
Parser loading error.
"""

logging.basicConfig(format="%(levelname)s: %(message)s", level=loglevel)

if isinstance(in_file, str):
Expand Down Expand Up @@ -60,9 +86,30 @@ def parse_single(in_file: str | Path | TextIO,
return data


def parse_all(output: Path | None = None, out_format: OutFormats = "json",
*, loglevel: int = logging.WARNING, testing: bool = False, **files):
""" Parse all files in files dict """
def parse_all(
output: str | Path | TextIO | None = None,
out_format: OutFormats = "json",
*,
loglevel: int = logging.WARNING,
testing: bool = False,
**files,
) -> None:
"""
Parse all files in files dict.
Parameters
----------
output : str or Path or TextIO
Filepath or handle to dump output to.
out_format : OutFormats
Format to dump as.
loglevel : int, optional
Logging level.
testing : bool, optional
Whether used for test suite (disable processing fragile properties).
**files : dict[str, Sequence[Path]]
Dictionary of {parser needed: Sequence of paths to parse}.
"""
file_dumper = get_dumpers(out_format)

data = {}
Expand All @@ -85,9 +132,11 @@ def parse_all(output: Path | None = None, out_format: OutFormats = "json",


def main():
""" Run the main program from command line """
"""
Run the main program from command line.
"""
args = parse_args()
dict_args = args_to_dict(args)
dict_args = extract_parsables(args)

parse_all(output=args.output,
loglevel=getattr(logging, args.log.upper()),
Expand Down
7 changes: 6 additions & 1 deletion castep_outputs/parsers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
List of parsers for file formats
List of parsers for file formats.
"""
from __future__ import annotations

Expand Down Expand Up @@ -48,6 +48,7 @@
"parse_phonon_file"]


#: Dictionary of available parsers.
PARSERS: dict[str, Callable] = {
"castep": parse_castep_file,
"cell": parse_cell_param_file,
Expand All @@ -70,5 +71,9 @@
"err": parse_err_file,
"phonon": parse_phonon_file,
}

#: Names of parsers/parsable file extensions (without ``"."``).
CASTEP_OUTPUT_NAMES: tuple[str, ...] = tuple(PARSERS.keys())

#: Names of parsable file extensions.
CASTEP_FILE_FORMATS: tuple[str, ...] = tuple(f".{typ}" for typ in CASTEP_OUTPUT_NAMES)
50 changes: 45 additions & 5 deletions castep_outputs/parsers/bands_file_parser.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,63 @@
"""
Parse the following castep outputs:
.bands
- .bands
"""
from __future__ import annotations

import re
from collections import defaultdict
from typing import Any, TextIO
from typing import TextIO, TypedDict

from ..utilities import castep_res as REs
from ..utilities.datatypes import ThreeVector
from ..utilities.filewrapper import Block
from ..utilities.utility import fix_data_types
from .parse_utilities import parse_regular_header


def parse_bands_file(bands_file: TextIO) -> dict[str, Any]:
""" Parse castep .bonds file """
class BandsQData(TypedDict, total=False):
"""
Per k-point info of band.
"""
#: List of band eigenvalues.
band: ThreeVector
#: List of eigenvalues for up component of band.
band_up: ThreeVector
#: List of eigenvalues for down component of band.
band_dn: ThreeVector
#: Position in space.
qpt: ThreeVector
#: Current spin component.
spin_comp: int
#: K point weight.
weight: float


class BandsFileInfo(TypedDict, total=False):
"""
Bands eigenvalue info of a bands calculation.
"""
#: Bands info in file.
bands: list[BandsQData]


def parse_bands_file(bands_file: TextIO) -> BandsFileInfo:
"""
Parse castep .bands file.
Parameters
----------
bands_file : ~typing.TextIO
Open handle to file to parse.
Returns
-------
BandsFileInfo
Parsed info.
"""

bands_info = defaultdict(list)
bands_info: BandsFileInfo = defaultdict(list)
qdata = {}

block = Block.from_re("", bands_file, "", REs.THREEVEC_RE, n_end=3)
Expand Down
2 changes: 1 addition & 1 deletion castep_outputs/parsers/castep_file_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1855,7 +1855,7 @@ def _process_memory_est(block: Block) -> dict[str, MemoryEst]:

for line in block:
if match := re.match(r"\s*\|([A-Za-z ]+)" +
labelled_floats(("memory", "disk"), suff=" MB"), line):
labelled_floats(("memory", "disk"), suffix=" MB"), line):
key, memory, disk = match.groups()
accum[normalise_key(key)] = {"memory": float(memory),
"disk": float(disk)}
Expand Down
28 changes: 24 additions & 4 deletions castep_outputs/parsers/cell_param_file_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,19 @@


def parse_cell_param_file(cell_param_file: TextIO) -> list[dict[str, str | dict[str, str]]]:
""" Parse .cell/.param files into dict ready to JSONise """
"""
Parse castep .cell and param files.
Parameters
----------
cell_param_file : ~typing.TextIO
Open handle to file to parse.
Returns
-------
list[dict[str, str | dict[str, str]]]
Parsed info.
"""

logger = log_factory(cell_param_file)
curr = {}
Expand Down Expand Up @@ -57,7 +69,9 @@ def parse_cell_param_file(cell_param_file: TextIO) -> list[dict[str, str | dict[


def _parse_devel_code_block(in_block: Block) -> dict[str, str | float | int]:
""" Parse devel_code block to dict """
"""
Parse devel_code block to dict.
"""

in_block = str(in_block)
in_block = " ".join(in_block.splitlines())
Expand Down Expand Up @@ -97,7 +111,10 @@ def _parse_devel_code_block(in_block: Block) -> dict[str, str | float | int]:
return devel_code_parsed


def _parse_ionic_constraints(block: Block) -> dict[AtomIndex, tuple[float, float, float]]:
def _parse_ionic_constraints(block: Block) -> dict[AtomIndex, ThreeVector]:
"""
Parse ionic constraints to dict.
"""
accum = defaultdict(list)

for line in block:
Expand All @@ -109,7 +126,10 @@ def _parse_ionic_constraints(block: Block) -> dict[AtomIndex, tuple[float, float
return accum


def _parse_nonlinear_constraints(block: Block):
def _parse_nonlinear_constraints(block: Block) -> dict[AtomIndex, ThreeVector]:
"""
Parse nonlinear constraints to dict.
"""
accum = []

for line in block:
Expand Down
Loading

0 comments on commit f83e671

Please sign in to comment.