Source code for ampel.config.AmpelConfig

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# File:                Ampel-interface/ampel/config/AmpelConfig.py
# License:             BSD-3-Clause
# Author:              valery brinnel <firstname.lastname@gmail.com>
# Date:                22.10.2019
# Last Modified Date:  04.08.2021
# Last Modified By:    valery brinnel <firstname.lastname@gmail.com>

import json
from collections.abc import Sequence
from typing import Any, Literal, TypeVar, Union, get_origin, overload

import yaml

from ampel.util.freeze import recursive_freeze
from ampel.util.mappings import try_int
from ampel.view.ReadOnlyDict import ReadOnlyDict

UJson = Union[None, str, int, float, bool, list[Any], dict[str, Any]] # noqa: UP007
JT = TypeVar('JT', None, str, int, float, bool, bytes, list[Any], dict[str, Any])


[docs] class AmpelConfig: """Container for the central Ampel configuration""" _config: dict _check_types: int = 1 @classmethod def load(cls, config_file_path: str, freeze: bool = True) -> 'AmpelConfig': with open(config_file_path) as f: config = yaml.safe_load(f) # Convert potentially stringified int keys (JSON compatibility) back to int for s in ('channel', 'confid'): for k in list(config[s]): config[s][try_int(k)] = config[s].pop(k) return cls(config, freeze) def __init__(self, config: dict, freeze: bool = False) -> None: """ :raises: ValueError if provided config is None or empty """ if config is None or not config: raise ValueError("Please provide a config") self._config: dict = recursive_freeze(config) if freeze else config if 'general' in config and 'check_types' in config['general']: self._check_types = config['general']['check_types'] # Overloads for method call without 'entry' @overload def get(self) -> dict[str, Any]: """ config.get() """ @overload def get(self, entry: None) -> dict[str, Any]: """ config.get(None) """ @overload def get(self, entry: None, ret_type: Any) -> dict[str, Any]: """ config.get(None, None/dict) """ @overload def get(self, entry: None, ret_type: Any, *, raise_exc: bool) -> dict[str, Any]: """ config.get(None, None/dict, raise_exc=True/False) """ @overload def get(self, entry: None, *, raise_exc: bool) -> dict[str, Any]: """ config.get(None, raise_exc=True/False) """ # Overloads for method call with 'entry' but without return type @overload def get(self, entry: str | int | Sequence[str | int]) -> UJson: """ config.get('logging') """ @overload def get(self, entry: str | int | Sequence[str | int], ret_type: None) -> UJson: """ config.get('logging', None) """ @overload def get(self, entry: str | int | Sequence[str | int], *, raise_exc: bool) -> UJson: """ config.get('logging', raise_exc=False/True) """ @overload def get(self, entry: str | int | Sequence[str | int], ret_type: None, *, raise_exc: bool) -> UJson: """ config.get('logging', None, raise_exc=False/True) """ # Overloads for method call with 'entry' and return type @overload def get(self, entry: str | int | Sequence[str | int], ret_type: type[JT]) -> None | JT: """ config.get('logging', dict) """ @overload def get(self, entry: str | int | Sequence[str | int], ret_type: type[JT], *, raise_exc: Literal[False]) -> None | JT: """ config.get('logging', dict, raise_exc=False) """ @overload def get(self, entry: str | int | Sequence[str | int], ret_type: type[JT], *, raise_exc: Literal[True]) -> JT: """ config.get('logging', dict, raise_exc=True) """
[docs] def get(self, # type: ignore[misc] entry: None | str | int | Sequence[str | int] = None, ret_type: None | type[JT] = None, *, raise_exc: bool = False ) -> UJson | None | JT: """ Optional arguments: :param ret_type: expected return type (str, int, dict, list, ...). :param entry: sub-config element will be returned. Examples:: get("channel.HU_RANDOM") :: get(['foo', 'bar', 'baz']) :raise ValueError: if the retrieved value has not the expected type """ if entry is None: return self._config if isinstance(entry, str): entry = entry.split(".") ret = self._config # pointer # Integerizes int path elements encoded as str for el in [entry] if isinstance(entry, int) else (try_int(el) for el in entry): if el not in ret: if raise_exc: raise ValueError(f'Config element {entry!r} not found') return None ret = ret[el] if ret_type: if origin := get_origin(ret_type): ret_type = origin if not isinstance(ret, ret_type): # type: ignore[arg-type] raise ValueError( f"Retrieved value has not the expected type.\n" f"Expected: {ret_type}\n" f"Found: {type(ret)}" ) return ret
def get_conf_id(self, conf_id: int) -> dict[str, Any]: if conf_id not in self._config['confid']: raise ValueError(f"Config with id {conf_id} not found") return self._config['confid'][conf_id] def print(self, entry: None | str = None, format: Literal['json', 'yaml'] = 'yaml' ) -> None: out = self.get(entry) print( # noqa: T201 yaml.dump(out) if format == 'yaml' else json.dumps(out, indent=4) ) def freeze(self) -> None: if not self.is_frozen(): self._config = recursive_freeze(self._config) def is_frozen(self) -> bool: return isinstance(self._config, ReadOnlyDict)