Source code for tesliper.extraction.parameters_parser

"""Parser for reading spectra calculation parameters from file."""
import logging
import re
from configparser import ConfigParser, MissingSectionHeaderError, ParsingError
from pathlib import Path
from typing import Callable, Union

from .. import datawork as dw
from .parser_base import _PARSERS

logger = logging.getLogger(__name__)

# from Python re docs
FLOATCRE = re.compile(r"[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?")
FITTINGCRE = re.compile(r"lorentzian|gaussian")


[docs]def quantity(s: str) -> float: """Convert to float first occurrence of float-looking part of string *s*, ignoring anything else. Raise :class:`configparser.ParsingError` if float cannot be found. Parameters ---------- s : str string containing a float Returns ------- float extracted float value """ match = FLOATCRE.search(s) if match: return float(match.group()) else: raise ParsingError("Cannot interpret any part of given string as float.")
[docs]def fitting(s: str) -> Callable: """Get fitting function mentioned in a given string *s*, ignoring anything else. Raise :class:`configparser.ParsingError` if known function name cannot be found. Parameters ---------- s : str string containing name of fitting function Returns ------- callable an identified fitting function """ match = FITTINGCRE.search(s.lower()) try: return getattr(dw, match.group()) except AttributeError: raise ParsingError(f"No such fitting function: {s}.")
[docs]class ParametersParser: """Parser for configuration files containing spectra calculation parameters. Configuration file should be in format similar to .ini files: a list of key-value pairs, separated with "=" or ":", one pair for line. Standard parameters (width, start, stop, step, and fitting) will be converted to appropriate data type, i.e. float or function reference. If parameter value cannot be converted to its target type, it will be ignored and warning will be emitted. Any other (unexpected) parameters are included in the output and left as they are. The parser is case-insensitive and knows some alias names of expected parameters: for instance, "hwhm", "half width of band in half height", "half width at half maximum" will be all recognized as "width" parameter. If you wish to add custom aliases, update :attr:`ParametersParser.ALIASES` dictionary with appropriate "alias": "target" pair. Notes ----- :class:`ParametersParser` is using Python's configparser, so it will parse files that contain a section header, enclosed in braces. However, the section name will be ignored and there may be only one such section, otherwise an exception is raised. """ purpose = "parameters" extensions = ("txt", "cfg", "ini") ALIASES = { "half width of band in half height": "width", "half width at half maximum": "width", "hwhm": "width", "start range": "start", "stop range": "stop", "fitting function": "fitting", } def __init__(self): self._parser = ConfigParser( converters={"quantity": quantity, "fitting": fitting} ) self._parser.optionxform = self.optionxform self._transformers = { "width": self._parser.getquantity, "start": self._parser.getquantity, "stop": self._parser.getquantity, "step": self._parser.getquantity, "fitting": self._parser.getfitting, }
[docs] def optionxform(self, optionstr: str) -> str: """Translates option names to desired form - lowercase and standard wording, as defined in ALIASES. """ optionstr = optionstr.lower() if optionstr in self.ALIASES: optionstr = self.ALIASES[optionstr] return optionstr
@property def parameters(self) -> dict: """Dictionary of parameters for calculating spectra extracted from parsed file and converted to appropriate type.""" params = {} try: section = self._parser.sections()[0] except IndexError: return params for option in self._parser.options(section): try: params[option] = self._transformers[option](section, option) except ParsingError: logger.warning( f"Cannot understand value for {option}, it will be ignored." ) except KeyError: logger.warning( f"Unknown parameter in settings: '{option}'," f" it will not be converted." ) params[option] = self._parser.get(section, option) return params
[docs] def parse(self, source: Union[str, Path]): """Parse given source file to get stored parameters. Parameters ---------- source : str or Path Path to file with calculations' parameters. Returns ------- dict Parsed parameters. """ source = Path(source) with source.open() as handle: text = handle.read() try: self._parser.read_string(text) except MissingSectionHeaderError: self._parser.read_string(f"[PARAMETERS]\n{text}") if len(self._parser.sections()) > 1: raise ParsingError( "Multiple sections in parameters setup file are not supported." ) return self.parameters
_PARSERS[ParametersParser.purpose] = ParametersParser