Source code for drymass.cli.config

import pathlib

from . import definitions
from . import parse_funcs
from .._version import version

#: DryMass configuration file name
FILE_CONFIG = "drymass.cfg"


[docs]class ConfigFile(object): def __init__(self, path): """DryMass configuration file management Manage configuration file of an experimental data set with restrictions imposed by :data:`drymass.cli.definitions.config`. Parameters ---------- path: str path to the configuration file or a folder containing the configuration file :data:`FILE_CONFIG`. """ path = pathlib.Path(path).resolve() if path.is_dir(): path = path / FILE_CONFIG if not path.exists(): path.touch() self.path = path def __getitem__(self, section): """Get a configuration section Parameters ---------- section: str the configuration section Returns ------- sectiondict: dict the configuration section dictionary """ datadict = self._parse() if section in datadict: return datadict[section] elif section in definitions.config: # return default values secd = {} for kk in definitions.config[section]: secd[kk] = definitions.config[section][kk][0] # write result datadict[section] = secd self._write(datadict) return secd else: raise ValueError("Unknown section title: {}".format(section)) def __setitem__(self, section, sectiondict): """Replace a section in the configuration file Parameters ---------- section: str the section name sectiondict: dict the configuration dictionary Notes ----- The previous content of the configuration section in the configuration file is replaced. """ datadict = self._parse() for key in sectiondict: self._check_value(section, key, sectiondict[key]) datadict[section] = sectiondict self._write(datadict) def _check_value(self, section, key, value): """Check if a section/key/value pair is valid Raises `ValueError` if this is not the case. Returns `None`. """ if section not in definitions.config: raise ValueError("Unknown section title: {}".format(section)) if key not in definitions.config[section]: raise ValueError("Unknown key: {}: {}".format(section, key)) # For versions > 0.8.1, unknown configuration keys are `None`. # Prior versions also used `np.nan`. Keep "nan" in the list # below for backwards compatibility. if value in [None, "nan", "none", "None", "()", "[]"]: if definitions.config[section][key][0] is not None: msg = "Unset value '{}' not allowed for [{}]: {}!".format( value, section, key) raise ValueError(msg) ret_value = None else: type_func = definitions.config[section][key][1] try: type_func(value) except BaseException as e: msg = "Failed to parse: '[{}]: {}={}'".format(section, key, value) e.args = ("{}; {}".format(msg, ", ".join(e.args)),) raise ret_value = type_func(value) return ret_value def _parse_compat(self, section, key, value): if section == "bg": # drymass < 0.1.3: API changed in qpimage 0.1.6 if (key in ["amplitude profile", "phase profile"] and value == "ramp"): value = "tilt" elif section == "roi": # drymass <= 0.1.5: keys were renamed to reflect pixel units if key in ["dist border", "exclude overlap", "pad border"]: key += " px" return key, value def _parse(self, autocomplete=True): """Return configuration dictionary Parameters ---------- autocomplete: bool whether to fill in default configuration values when the corresponding keys are missing in a given section. Note that missing sections are not added. If missing keys are found, the original configuration file is overridden with the new data. Disabling autocompletion also prevents writing to the configuration file. Returns ------- datadict: dict of dicts configuration dictionary Notes ----- This function is private, because the autocomplete feature is actually a desired behavior to keep the configuration file human-readable. Normal users should not be able to use it, because the concept could be considered confusing. """ with self.path.open() as fd: data = fd.readlines() outdict = {} for line in data: line = line.strip() if (line.startswith("#") or len(line) == 0): pass elif line.startswith("["): sec = line.strip("[]") outdict[sec] = {} else: key, val = line.split("=") key = key.strip() val = val.strip() # backwards compatibility: key, val = self._parse_compat(sec, key, val) val = self._check_value(sec, key, val) outdict[sec][key] = val if autocomplete: # Insert default variables where missing must_write = False for sec in outdict: for key in definitions.config[sec]: if key not in outdict[sec]: outdict[sec][key] = definitions.config[sec][key][0] must_write = True if must_write: # Update the configuration file self._write(outdict) return outdict def _write(self, datadict): """Write configuration dictionary Parameters ---------- datadict: dict of dicts the full configuration Notes ----- The configuration key values are converted to the correct dtype before writing using the definitions given in definitions.py. """ keys = sorted(list(datadict.keys())) lines = ["# DryMass version {}".format(version), "# Configuration file documented at: ", "# https://drymass.readthedocs.io/en/stable/" + "sec_gs_configuration_file.html", ] for kk in keys: lines.append("") lines.append("[{}]".format(kk)) subkeys = sorted(list(datadict[kk].keys())) for sk in subkeys: value = datadict[kk][sk] typefunc = definitions.config[kk][sk][1] if value is not None: value = typefunc(value) if typefunc in [parse_funcs.strlist, parse_funcs.strlist_vsort]: # cosmetics for e.g. '[roi]: ignore data' value = ", ".join(value) lines.append("{} = {}".format(sk, value)) for ii in range(len(lines)): lines[ii] += "\n" with self.path.open("w") as fd: fd.writelines(lines)
[docs] def remove_section(self, section): """Remove a section from the configuration file""" datadict = self._parse(autocomplete=False) datadict.pop(section) self._write(datadict)
[docs] def set_value(self, section, key, value): """Set a configuration key value Parameters ---------- section: str the configuration section key: str the configuration key in `section` value: the configuration key value Notes ----- Valid section and key names are defined in definitions.py """ # load, update, and save sec = self[section] sec[key] = value self[section] = sec
[docs] def update(self, other): """Update the current configuration with data from another Parameters ---------- other: ConfigFile the configuration file from which data is imported into the current configuration Notes ----- None-valued keys are ignored. """ other_dict = other._parse(autocomplete=False) for sec in other_dict: for key in other_dict[sec]: value = other_dict[sec][key] if value is not None: self.set_value(section=sec, key=key, value=value)