Source code for foxes.utils.factory

from .dict import Dict
import foxes.variables as FV


[docs] class Factory: """ Constructs objects from a choice of allowed constructor parameters Attributes ---------- base: class The class of which objects are to be created name_template: str The name template, e.g. 'name_<A>_<B>_<C>' for variables A, B, C args: tuple Fixed arguments for the base class kwargs: dict Fixed arguments for the base class var2arg: dict Mapping from variable to constructor argument hints: dict Hints for print_toc, only for variables for which the options are functions or missing options: dict For each variable, e.g. A, B or C, the list or dict or function that maps a str to the actual value :group: utils """
[docs] def __init__( self, base, name_template, args=(), kwargs={}, var2arg={}, hints={}, **options, ): """ Constructor. Parameters ---------- base: class The class of which objects are to be created name_template: str The name template, e.g. 'name_<A>_<B>_<C>' for variables A, B, C args: tuple Fixed arguments for the base class kwargs: dict Fixed arguments for the base class var2arg: dict Mapping from variable to constructor argument hints: dict Hints for print_toc, only for variables for which the options are functions or missing options: dict For each variable, e.g. A, B or C, the list or dict or function that maps a str to the actual value """ self.base = base self.name_template = name_template self.args = args self.kwargs = kwargs self.var2arg = var2arg self.hints = hints self._vars = [] self._pre = [] parts = name_template.split(">") for i, p in enumerate(parts): if i < len(parts) - 1: parts2 = p.split("<") if len(parts2) != 2: raise ValueError( f"Factory '{name_template}': incomplete pattern brackets '<..>' between variables, e.g. '_'" ) if i > 0 and len(parts2[0]) == 0: raise ValueError( f"Factory '{name_template}': Missing seperator like '_' in template between variables '{self._vars[-1]}' and '{parts[1]}'" ) self._pre.append(parts2[0]) self._vars.append(parts2[1]) else: self._pre.append(p) if len(self.variables) > 1: for vi, v in enumerate(self.variables): p = self._pre[vi] if vi < len(self.variables) - 1 and p == "": raise ValueError( f"Factory '{name_template}': Require indicator before variable '{v}' in template, e.g. '{v}<{v}>'" ) self.options = Dict(name=f"{self._pre[0]}_options") for v, o in options.items(): if v not in self.variables: raise KeyError( f"Factory '{name_template}': Variable '{v}' found in options, but not in template" ) if isinstance(o, list) or isinstance(o, tuple): o = {str(k): k for k in o} if isinstance(o, dict): for k in o.keys(): if not isinstance(k, str): raise TypeError( f"Factory '{name_template}': Found option for variable '{v}' that is not a str, {k}" ) self.options[v] = Dict(name=f"{self._pre[0]}_options_{v}", **o) elif hasattr(o, "__call__"): self.options[v] = o else: raise ValueError( f"Factory '{name_template}': Variable '{v}' has option of type '{type(v).__name__}'. Only list, tuple, dict or function are supported" )
@property def name_prefix(self): """ The beginning of the name template Returns ------- nbase: str The beginning of the name template """ return self._pre[0] @property def name_suffix(self): """ The ending of the name template Returns ------- nbase: str The ending of the name template """ return self._pre[-1] @property def variables(self): """ The list of variables Returns ------- vrs: list of str The variables """ return self._vars
[docs] def __str__(self): """String representation""" s = f"{self.name_template}: {self.base.__name__} with" for k, d in self.kwargs.items(): s += f"\n {k}={d}" for v in self.variables: if v in self.options and isinstance(self.options[v], dict): s += f"\n {v} from {list(self.options[v])}" else: s += f"\n {v}={self.hints.get(v, '(value)')}" return s
[docs] def check_match(self, name): """ Tests if a name matches the template Parameters ---------- name: str The name to be checked Returns ------- success: bool True if the template is matched """ data_str = name for vi in range(len(self.variables)): p = self._pre[vi] i = data_str.find(p) j = i + len(p) if i < 0 or len(data_str) <= j: return False data_str = data_str[j:] q = self._pre[vi + 1] if q != "": i = data_str.find(q) j = i + len(q) if i < 0 or len(data_str) <= j: return False else: data_str = "" return True
[docs] def construct(self, name): """ Create an object of the base class. Parameters ---------- name: str The name, matching the template Returns ------- obj: object The instance of the base class """ j = 0 wlist = [] for pi, p in enumerate(self._pre): if len(p) > 0: i = name[j:].find(p) if i < 0 or (pi == 0 and i > 0): raise ValueError( f"Factory '{self.name_template}': Name '{name}' not matching template" ) w = name[j : j + i] j += i + len(p) else: w = name[j:] if pi > 0: wlist.append(w) kwargs = {} for vi, v in enumerate(self.variables): w = self.var2arg.get(v, v) data = wlist[vi] if v in self.options: o = self.options[v] if hasattr(o, "__call__"): kwargs[w] = o(data) else: kwargs[w] = self.options[v][data] else: kwargs[w] = data kwargs.update(self.kwargs) return self.base(*self.args, **kwargs)
[docs] class WakeKFactory: """ A factory that automatically handles wake_k parameters Attributes ---------- factories: list of Factory The individual factories :group: utils """
[docs] def __init__(self, base, name_template, *args, hints={}, **kwargs): """ Constructor. Parameters ---------- base: class The class of which objects are to be created name_template: str The name template, e.g. 'name_<A>_<B>_<C>' for variables A, B, C. Indicate wake_k part by '_[wake_k]' args: tuple, optional Additional arguments for Factory hints: dict Hints for print_toc, only for variables for which the options are functions or missing kwargs: dict Additional arguments for Factory """ self._base = base self._kwargs = kwargs self._template0 = name_template self.factories = [] i0 = name_template.find("_[wake_k]") i1 = i0 + len("_[wake_k]") kw = kwargs.pop("kwargs", {}) if i0 < 0: raise ValueError( f"String '_[wake_k]' not found in name template '{name_template}'" ) # add case ka, kb: t = name_template[:i0] + "_ka<ka>_kb<kb>" if len(name_template) > i1: t += name_template[i1:] h = hints.copy() h["ka"] = "(Value, e.g. 04 for 0.4)" h["kb"] = "(Value, e.g. 001 for 0.01)" kw["ti_var"] = FV.TI self.factories.append( Factory( base, t, *args, hints=h, kwargs=kw.copy(), **kwargs, ka=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)), kb=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)), ) ) # add case ambient ka, kb: t = name_template[:i0] + "_ambka<ka>_kb<kb>" if len(name_template) > i1: t += name_template[i1:] h = hints.copy() h["ka"] = "(Value, e.g. 04 for 0.4)" h["kb"] = "(Value, e.g. 001 for 0.01)" kw["ti_var"] = FV.AMB_TI self.factories.append( Factory( base, t, *args, hints=h, kwargs=kw.copy(), **kwargs, ka=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)), kb=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)), ) ) # add case ka: t = name_template[:i0] + "_ka<ka>" if len(name_template) > i1: t += name_template[i1:] h = hints.copy() h["ka"] = "(Value, e.g. 04 for 0.4)" kw["ti_var"] = FV.TI self.factories.append( Factory( base, t, *args, hints=h, kwargs=kw.copy(), **kwargs, ka=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)), ) ) # add case ka: t = name_template[:i0] + "_ambka<ka>" if len(name_template) > i1: t += name_template[i1:] h = hints.copy() h["ka"] = "(Value, e.g. 04 for 0.4)" kw["ti_var"] = FV.AMB_TI self.factories.append( Factory( base, t, *args, hints=h, kwargs=kw.copy(), **kwargs, ka=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)), ) ) # add case k: t = name_template[:i0] + "_k<k>" if len(name_template) > i1: t += name_template[i1:] h = hints.copy() h["k"] = "(Value, e.g. 004 for 0.04)" kw["ti_var"] = FV.TI self.factories.append( Factory( base, t, *args, hints=h, kwargs=kw.copy(), **kwargs, k=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)), ) ) # add case without k: t = name_template[:i0] if len(name_template) > i1: t += name_template[i1:] kw["ti_var"] = FV.TI self.factories.append( Factory( base, t, *args, hints=hints, kwargs=kw.copy(), **kwargs, ) )
[docs] def __str__(self): """String representation""" s = f"{self._template0}: {self._base.__name__} with" for k, d in self._kwargs.items(): s += f"\n {k}={d}" f0 = self.factories[-1] for v in f0.variables: if v in f0.options and isinstance(f0.options[v], dict): s += f"\n {v} from {list(f0.options[v])}" else: s += f"\n {v}={f0.hints.get(v, '(value)')}" s += ( f"\n [wake_k]=(None or k<k> or ka<ka> or ka<ka>_kb<kb>, e.g. 004 for 0.04)" ) return s
[docs] class FDict(Dict): """ A dictionary with factory support Attributes ---------- store_created: bool Flag for storing created objects factories: list of foxes.utils.Factory The factories :group: utils """
[docs] def __init__(self, *args, store_created=True, **kwargs): """ Constructor. Parameters ---------- args: tuple, optional Parameters for the base class store_created: bool Flag for storing created objects kwargs: dict, optional Parameters for the base class """ super().__init__(*args, **kwargs) self.store_created = store_created self.factories = []
[docs] def add_factory(self, *args, factory=None, **kwargs): """ Adds a Factory object. Parameters ---------- args: tuple, optional Parameters for the Factory constructor factory: Factory, optional The factory object kwargs: dict, optional Parameters for the Factory constructor """ f = Factory(*args, **kwargs) if factory is None else factory i = len(self.factories) for gi in range(len(self.factories) - 1, -1, -1): g = self.factories[gi] if ( g.name_prefix == f.name_prefix and g.name_suffix == f.name_suffix and len(f.variables) > len(g.variables) ): i = gi if i == len(self.factories): self.factories.append(f) else: self.factories.insert(i, f)
[docs] def add_k_factory(self, *args, **kwargs): """ Adds a WakeKFactory. Parameters ---------- args: tuple, optional Parameters for the Factory constructor kwargs: dict, optional Parameters for the Factory constructor """ for f in WakeKFactory(*args, **kwargs).factories: self.add_factory(factory=f)
[docs] def __contains__(self, key): found = super().__contains__(key) if not found: for f in self.factories: if f.check_match(key): return True return found
[docs] def __getitem__(self, key): try: return super().__getitem__(key) except KeyError: for f in self.factories: try: obj = f.construct(key) if self.store_created: self[key] = obj return obj except ValueError: pass k = ", ".join(sorted(list(self.keys()))) e = f"{self.name}: Cannot find key '{key}', also no factory matches. Known keys: {k}. Known factories: {[f.name_template for f in self.factories]}" raise KeyError(e)