diff --git a/examples/template/build_template_example.py b/examples/template/build_template_example.py new file mode 100644 index 00000000..01967b9c --- /dev/null +++ b/examples/template/build_template_example.py @@ -0,0 +1,8 @@ +import yaml + +from pyaml.configuration.factory import Factory + +cc = yaml.safe_load(open("templated_config.yaml")) + +obj = Factory.build(cc) +print(obj) diff --git a/examples/template/templated_config.yaml b/examples/template/templated_config.yaml new file mode 100644 index 00000000..b9c8b3e4 --- /dev/null +++ b/examples/template/templated_config.yaml @@ -0,0 +1,24 @@ +- class: pyaml.configuration.template.Template + template: | + class: pyaml.magnet.hcorrector.HCorrector + name: %template_parameter% + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + unit: rad + hardware_unit: str + calibration_factor: 1.0 + powerconverter: MAGNET/%template_parameter%/B1L + string_to_replace: "%template_parameter%" + parameter_list: + - PKDK_WL_108 + - PKDK_WL_93 + - PKDK_WL_79 + - PKDK_WL_64 +- class: pyaml.magnet.hcorrector.HCorrector + name: ANOTHER + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + unit: rad + hardware_unit: str + calibration_factor: 1.0 + powerconverter: MAGNET/ANOTHER/B1L diff --git a/pyaml/configuration/factory.py b/pyaml/configuration/factory.py index 445ce966..3345569b 100644 --- a/pyaml/configuration/factory.py +++ b/pyaml/configuration/factory.py @@ -396,7 +396,17 @@ def _build_list(self, items: list[Any], ignore_external: bool = False): ignore_external : bool If ``True``, ignore unavailable external modules. """ - return [self._build(item, ignore_external) for item in items] + list_out = [] + for item in items: + obj = self._build(item, ignore_external) + from collections.abc import Iterator + + if isinstance(obj, Iterator): + expanded_iterator = [*obj] + list_out.extend(expanded_iterator) + else: + list_out.append(obj) + return list_out def _build_dict(self, data: dict, ignore_external: bool = False): """ diff --git a/pyaml/configuration/template.py b/pyaml/configuration/template.py new file mode 100644 index 00000000..eb81eea0 --- /dev/null +++ b/pyaml/configuration/template.py @@ -0,0 +1,40 @@ +import json + +import yaml +from pydantic import BaseModel + +from ..common.exception import PyAMLConfigException +from ..configuration.factory import Factory +from ..validation import StaticValidation, register_schema + + +def load_json_or_yaml(string_to_load: str) -> dict: + try: + loaded_dict = json.loads(string_to_load) + return "json" + except json.JSONDecodeError: + pass + + try: + loaded_dict = yaml.safe_load(string_to_load) + except yaml.YAMLError as exc: + raise PyAMLConfigException("Template class is not a valid YAML or JSON string.") from exc + + return loaded_dict + + +class TemplateValidationModel(BaseModel): + template: str + string_to_replace: str + parameter_list: list[str] + + +@register_schema +class Template(StaticValidation): + validation_model = TemplateValidationModel + + def __new__(cls, template: str, string_to_replace: str, parameter_list: list[str]): + for par in parameter_list: + new_string = template.replace(string_to_replace, par) + new_dict = load_json_or_yaml(new_string) + yield Factory.build(new_dict) diff --git a/pyaml/validation/validation_models.py b/pyaml/validation/validation_models.py index 8c5d7703..1bd682da 100644 --- a/pyaml/validation/validation_models.py +++ b/pyaml/validation/validation_models.py @@ -111,7 +111,12 @@ def __call__(cls, *args: Any, **kwargs: Any): raise TypeError(f"{cls.__name__} must define validation_model.") # Inspect the signature of the class - signature = inspect.signature(cls.__init__) + if "__init__" in cls.__dict__: + signature = inspect.signature(cls.__init__) + elif "__new__" in cls.__dict__: + signature = inspect.signature(cls.__new__) + else: + raise Exception # Map arguments to parameters bound = signature.bind(None, *args, **kwargs)