def load_fields_values(obj: Any, fields: Iterable[konfi.Field], mapping: Mapping, *, ignore_unknown: bool = False) -> None: """Load the values for the fields from the mapping. This is done recursively. Args: obj: Object to load fields into fields: Fields to load mapping: Mapping to get field values from ignore_unknown: If `True`, excessive keys in the mapping are ignored. Raises: PathError: If there is an issue with a field. If multiple fields have an error, `MultiPathError` is raised. """ _field_by_keys: Dict[str, konfi.Field] = {field.key: field for field in fields} field_errors: List[PathError] = [] for key, value in mapping.items(): try: field = _field_by_keys[key] except KeyError: if ignore_unknown: continue else: raise PathError([key], f"unexpected config key: {key!r}") if konfi.is_template_like(field.value_type) and isinstance( value, Mapping): sub_obj = _get_sub_obj(obj, field) loader = functools.partial(load_fields_values, sub_obj, konfi.fields(sub_obj), value) else: loader = functools.partial(load_field_value, obj, field, value) try: loader() except PathError as e: field_errors.append(e) except Exception as e: err = FieldError([key], field, str(e)) err.__cause__ = e field_errors.append(err) if len(field_errors) == 1: raise field_errors[0] elif field_errors: raise MultiPathError((), field_errors, "")
def iter_fields(parent: Any, path: List[str], fields: Iterable[konfi.Field]) -> None: for field in fields: key_path = [*path, field.key] if konfi.is_template_like(field.value_type): sub_obj = _get_sub_obj(obj, field) yield from iter_fields(sub_obj, key_path, konfi.fields(field.value_type)) else: yield QualifiedField(parent, key_path, field)
def test_template_mro(): class NonTemplate: a: str @konfi.template() class Template: b: int c: int @konfi.template() class Normal(NonTemplate, Template): d: str assert tuple(field.attribute for field in konfi.fields(Normal)) == ("b", "c", "d") @konfi.template(template_bases_only=False) class Inherit(NonTemplate, Template): c: str assert set(field.attribute for field in konfi.fields(Inherit)) == {"b", "c", "a"}
def load_into(self, obj: Any, template: Type) -> None: try: import yaml except ImportError as e: raise ImportError("Couldn't import 'pyyaml' package. Make sure it's installed.") from e try: with open(self._path, "r") as f: data = yaml.safe_load(f) except FileNotFoundError as e: if self._ignore_not_found: return else: raise e load_fields_values(obj, konfi.fields(template), data)
def load_into(self, obj: Any, template: type) -> None: try: import toml except ImportError as e: raise ImportError( "Couldn't import the 'toml' package. Make sure it's installed." ) from e try: data = toml.load(self._path) except FileNotFoundError as e: if self._ignore_not_found: return else: raise e load_fields_values(obj, konfi.fields(template), data)
def iter_fields_recursively(obj: Any, template: Any) -> Iterator[QualifiedField]: """Iterate over all fields of the template recursively. Yields: `QualifiedField` instances. """ def iter_fields(parent: Any, path: List[str], fields: Iterable[konfi.Field]) -> None: for field in fields: key_path = [*path, field.key] if konfi.is_template_like(field.value_type): sub_obj = _get_sub_obj(obj, field) yield from iter_fields(sub_obj, key_path, konfi.fields(field.value_type)) else: yield QualifiedField(parent, key_path, field) yield from iter_fields(obj, [], konfi.fields(template))