Source code for pylhc_submitter.utils.iotools

"""
IO-Tools
--------

Tools for input and output.
"""

from __future__ import annotations

from collections.abc import Iterable
from datetime import datetime
from pathlib import Path

from generic_parser.entry_datatypes import get_instance_faker_meta
from generic_parser.entrypoint_parser import save_options_to_config

from pylhc_submitter.constants.general import TIME

# Output -----------------------------------------------------------------------


[docs] def save_config(output_dir: Path, opt: dict, script: str): """ Quick wrapper for ``save_options_to_config``. Args: output_dir (Path): Path to the output directory (does not need to exist). opt (dict): opt-structure to be saved. script (str): path/name of the invoking script (becomes name of the .ini) usually ``__file__``. """ output_dir.mkdir(parents=True, exist_ok=True) opt = convert_paths_in_dict_to_strings(opt) opt = escape_percentage_signs(opt) # TODO: datetime.utcnow() is deprecated in 3.12 and the recommended replacement # was introduced in 3.11. When the time comes, replace the following line with: # time = datetime.now(UTC).strftime(TIME) time = datetime.utcnow().strftime(TIME) save_options_to_config(output_dir / f"{script:s}_{time:s}.ini", dict(sorted(opt.items())))
[docs] def escape_percentage_signs(dict_: dict) -> dict: """Escape all percentage signs. They are used for interpolation in the config-parser.""" for key, value in dict_.items(): if isinstance(value, dict): dict_[key] = escape_percentage_signs(value) elif isinstance(value, str): dict_[key] = value.replace("%(", "%%(") return dict_
[docs] def convert_paths_in_dict_to_strings(dict_: dict) -> dict: """Converts all Paths in the dict to strings, including those in iterables.""" dict_ = dict_.copy() for key, value in dict_.items(): if isinstance(value, Path): dict_[key] = str(value) else: try: list_ = list(value) except TypeError: pass else: has_changed = False for idx, item in enumerate(list_): if isinstance(item, Path): list_[idx] = str(item) has_changed = True if has_changed: dict_[key] = list_ return dict_
# Input ------------------------------------------------------------------------
[docs] class PathOrStr(metaclass=get_instance_faker_meta(Path, str)): """A class that behaves like a Path when possible, otherwise like a string.""" def __new__(cls, value): if value is None: return None if isinstance(value, str): # Behave like dict parser, IMPORTANT FOR EVERY STRING-FAKER value = value.strip("'\"") return Path(value)
[docs] def make_replace_entries_iterable(replace_dict: dict) -> dict: """Makes all entries in replace-dict iterable.""" for key, value in replace_dict.items(): if isinstance(value, str) or not isinstance(value, Iterable): replace_dict[key] = [value] return replace_dict
[docs] def keys_to_path(dict_, *keys): """Convert all keys to Path, if they are not None.""" for key in keys: value = dict_[key] dict_[key] = None if value is None else Path(value) return dict_