Source code for zensols.config.nestedfac
"""A factory that can create nested objects.
"""
from __future__ import annotations
from typing import ClassVar, Any
from zensols.config import Settings, DictionaryConfig
from . import ImportConfigFactory
[docs]
class NestedImportConfigFactory(ImportConfigFactory):
"""Creates nested objects that reflect nested instances of :class:`dict`.
The class must be created with a
:class:`~zensols.config.dictconfig.DictionaryConfig` with the dictionary
that was used to create it. It uses it to create temporary objects that are
then used as input to the super class to create the objects. In this way,
we get the best of both worlds to create factory objects
(i.e. ``instance:``) and nested classes.
"""
_OBJECT_TEMP_KEY: ClassVar[str] = '__obj'
"""Temporary object prefix to store."""
[docs]
def __init__(self, config: DictionaryConfig, **kwargs):
super().__init__(config=config, **kwargs)
self._dconfig: dict[str, Any] = config.config
self._is_nested = False
def _map_class_name(self, class_name: str) -> str:
"""Munge the class name for factories that want to use something other
than the fully qualified class name.
"""
return class_name
def _map_params(self, class_name: str, data: dict[str, Any]) -> \
dict[str, Any]:
return data
def _new_nested_instance(self, value: Any, depth: int,
args: list = None, kwargs: dict = None) -> Any:
depth += 1
if isinstance(value, Settings):
value = value.asdict()
if isinstance(value, dict):
class_name: str = value.pop(self.CLASS_NAME, None)
if class_name is not None:
key: str = f'{self._OBJECT_TEMP_KEY}_{class_name}_{depth}'
data: dict[str, Any] = dict(value)
args = () if args is None else args
if kwargs is not None:
data.update(kwargs)
data = dict(map(
lambda t: (t[0], self._new_nested_instance(t[1], depth)),
data.items()))
self._dconfig[key] = data
data = self._map_params(class_name, data)
data[self.CLASS_NAME] = self._map_class_name(class_name)
try:
value = super().new_instance(key)
finally:
del self._dconfig[key]
if depth > 0 and \
self.NAME_ATTRIBUTE not in data and \
hasattr(value, self.NAME_ATTRIBUTE):
value.name = None
else:
value = dict(map(
lambda t: (t[0], self._new_nested_instance(t[1], depth)),
value.items()))
elif isinstance(value, (tuple, list)):
value = tuple(map(
lambda v: self._new_nested_instance(v, depth), value))
return value
[docs]
def instance(self, name: str | None, *args, **kwargs):
inst: Any
params: dict[str, Any] = self.config[name]
if self._is_nested:
inst = super().instance(name, *args, **kwargs)
else:
if self._shared is not None:
inst = self._shared.get(name)
if inst is None:
if isinstance(params, Settings):
params = params.asdict()
self._is_nested = True
try:
inst = self._new_nested_instance(params, 0, args, kwargs)
if hasattr(inst, self.NAME_ATTRIBUTE):
setattr(inst, self.NAME_ATTRIBUTE, name)
finally:
self._is_nested = False
if self._shared is not None:
self._shared[name] = inst
return inst