Source code for haddock.gear.yaml2cfg

"""
Parse data from HADDOCK3 to YAML and YAML to HADDOCK3 and related.

Accross this file you will see references to "yaml" as variables and
function names. In these cases, we always mean the HADDOCK3 YAML
configuration files which have specific keys.
"""
import os
from collections.abc import Mapping
from pathlib import Path
from typing import Union

from haddock import _hidden_level, config_expert_levels
from haddock.core.exceptions import ConfigurationError
from haddock.core.typing import (
    ExpertLevel,
    FilePath,
    Optional,
    ParamDict,
    ParamMap,
)
from haddock.libs.libio import read_from_yaml


[docs]def yaml2cfg_text( ymlcfg: dict, module: str, explevel: str, details: bool = False ) -> str: """ Convert HADDOCK3 YAML config to HADDOCK3 user config text. Adds commentaries with help strings. Parameters ---------- ymlcfg : dict The dictionary representing the HADDOCK3 config file. module : str The module to which the config belongs to. explevel : str The expert level to consider. Provides all parameters for that level and those of inferior hierarchy. If you give "all", all parameters will be considered. details : bool Whether to add the 'long' description of each parameter. """ new_config: list[str] = [] if module is not None: new_config.append(f"[{module}]") new_config.append( _yaml2cfg_text( ymlcfg, module, explevel, details=details, ) ) return os.linesep.join(new_config) + os.linesep
def _yaml2cfg_text( ymlcfg: dict, module: str, explevel: str, details: bool = False ) -> str: """ Convert HADDOCK3 YAML config to HADDOCK3 user config text. Does not consider expert levels. See :func:`yaml2cfg_text_with_explevels` instead. Parameters ---------- ymlcfg : dict The dictionary representing the HADDOCK3 YAML configuration. This configuration should NOT have the expertise levels. It expectes the first level of keys to be the parameter name. module : str The module to which the config belongs to. explevel : str The expert level to consider. Provides all parameters for that level and those of inferior hierarchy. If you give "all", all parameters will be considered. details : bool Whether to add the 'long' description of each parameter. """ params: list[str] = [] exp_levels = { _el: i for i, _el in enumerate(config_expert_levels + ("all", _hidden_level)) } exp_level_idx = exp_levels[explevel] # define set of undesired parameter keys undesired = ("default", "explevel", "type") if not details: undesired = undesired + ("long",) # type: ignore for param_name, param in ymlcfg.items(): # treats parameters that are subdictionaries of parameters if isinstance(param, Mapping) and "default" not in param: params.append("") # give extra space if module is not None: curr_module = f"{module}.{param_name}" else: curr_module = param_name params.append(f"[{curr_module}]") _ = _yaml2cfg_text( param, # type: ignore module=curr_module, explevel=explevel, details=details, ) params.append(_) # treats normal parameters elif isinstance(param, Mapping): if exp_levels[param["explevel"]] > exp_level_idx: # ignore this parameter because is of an expert level # superior to the one request: continue comment: list[str] = [] for _comment, cvalue in param.items(): if _comment in undesired: continue if cvalue == "": continue comment.append(f"${_comment} {cvalue}") default_value = param["default"] # boolean values have to be lower for compatibility with toml cfg if isinstance(default_value, bool): default_value = str(default_value).lower() param_line = "{} = {} # {}" else: param_line = "{} = {!r} # {}" params.append( param_line.format( param_name, default_value, " / ".join(comment), ) ) if param["type"] == "list": params.append(os.linesep) else: # ignore some other parameters that are defined for sections. continue return os.linesep.join(params)
[docs]def read_from_yaml_config( cfg_file: Union[Path, str], default_only: bool = True ) -> dict: """Read config from yaml by collapsing the expert levels. Parameters ---------- cfg_file : Path to a .yaml configuration file default_only : bool Set the return value of this function; if True (default value), only returns default values, else return the fully loaded configuration file Return ------ ycfg : dict The full default configuration file as a dict OR cfg : dict A dictionary containing only the default parameters values """ ycfg = read_from_yaml(cfg_file) if default_only: # there's no need to make a deep copy here, a shallow copy suffices. cfg = {} cfg.update(flat_yaml_cfg(ycfg)) return cfg return ycfg
[docs]def flat_yaml_cfg(cfg: ParamMap) -> ParamDict: """Flat a yaml config.""" new: ParamDict = {} for param, values in cfg.items(): try: new_value = values["default"] except KeyError: new_value = flat_yaml_cfg(values) except TypeError: # happens when values is a string for example, # addresses `explevel` in `mol*` topoaa. continue else: # coordinates with tests. # users should not edit yaml files. So this error triggers # only during development if "explevel" not in values: emsg = f"`explevel` not defined for: {param!r}" raise ConfigurationError(emsg) new[param] = new_value return new