Source code for cfx.views

"""View classes for projecting Config trees into custom namespaces.

A view binds to one or more `Config` instances and exposes a curated or
auto-generated subset of their fields under new names.  Reads and writes
delegate through to the underlying config — views carry no field data of
their own.

Public API
----------
ConfigView
    Base class for hand-written curated projections.
AliasedView
    Auto-generates prefixed aliases for every field in each component.
FlatView
    Like AliasedView but with no prefix — raises on name conflicts.
"""

from typing import ClassVar

from .refs import ComponentRef
from .types import Alias
from .utils import walk, walk_set

__all__ = ["ConfigView", "AliasedView", "FlatView"]


#############################################################################
# ConfigView
#############################################################################


[docs] class ConfigView: """Base class for curated projections over a `Config` instance. Subclass `ConfigView` and declare `Alias` attributes to expose a selected subset of fields from one or more configs under names that suit the consumer's context. All reads and writes delegate to the bound config — the view itself holds no values:: class CalibSummaryView(ConfigView): psf_kernel = Alias(PSFFittingConfig.kernel_estimate) zero_point = Alias(PhotometryConfig.zero_point) view = CalibSummaryView(pipeline.calibration) view.psf_kernel # reads pipeline.calibration.psf_fitting... view.psf_kernel = 3.5 # writes through Views can span multiple sub-configs by binding to a common parent:: class PipelineSummaryView(ConfigView): psf_kernel = Alias(CalibrationConfig.psf_fitting.kernel_estimate) threshold = Alias(DetectionConfig.threshold) view = PipelineSummaryView(pipeline_cfg) Parameters ---------- config : `Config` The config instance to bind to. All alias paths are resolved relative to this object. """ _aliases: ClassVar[dict[str, Alias]]
[docs] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) cls._aliases = { k: v for k, v in vars(cls).items() if isinstance(v, Alias) }
def __init__(self, config): self._alias_root = config
[docs] def to_dict(self): """Return a plain dict of the aliased fields and their current values. Returns ------- d : `dict` Mapping of alias name to current value for every declared `Alias`. """ return { name: walk(self._alias_root, a._path) for name, a in type(self)._aliases.items() }
[docs] @classmethod def from_dict(cls, d, config): """Bind *config* and apply values from *d* through the view's aliases. Parameters ---------- d : `dict` Alias names and replacement values. config : `Config` The config instance to bind and write into. Returns ------- view : `ConfigView` A bound view with *d* values applied. """ view = cls(config) for k, v in d.items(): if k in cls._aliases: walk_set(config, cls._aliases[k]._path, v) return view
[docs] def __repr__(self): vals = { name: walk(self._alias_root, a._path) for name, a in type(self)._aliases.items() } return f"{type(self).__name__}({vals!r})"
@classmethod def _repr_html_(cls): rows = "".join( f"<tr><td><code>{name}</code></td>" f"<td><code>{a._path}</code></td></tr>" for name, a in cls._aliases.items() ) return ( f"<table><thead><tr><th>Alias</th><th>Path</th></tr></thead>" f"<tbody>{rows}</tbody></table>" )
############################################################################# # AliasedView #############################################################################
[docs] class AliasedView(ConfigView): """A self-contained view that owns its component instances. Subclass ``AliasedView`` and pass ``components=`` to auto-generate ``Alias`` descriptors for every field in each component. By default each alias is prefixed with the component's ``confid``:: class CalibView(AliasedView, components=[PSFConfig, PhotoConfig]): pass v = CalibView() v.psf_kernel = 3.5 # writes through to v.psf.kernel_estimate Supply ``aliases=`` to override the prefix for each component (use ``None`` for a flat, unprefixed namespace):: class CalibView(AliasedView, components=[PSFConfig, PhotoConfig], aliases=["psf", None]): pass Name conflicts among auto-generated aliases raise ``ValueError`` at class-definition time. Unlike `ConfigView`, an ``AliasedView`` is constructed with no arguments — it creates and owns a fresh instance of each component. """ _component_classes: ClassVar[dict[str, type]]
[docs] def __init_subclass__(cls, components=None, aliases=None, **kwargs): if components is None: cls._component_classes = {} super().__init_subclass__(**kwargs) return if aliases is not None: prefixes = aliases else: prefixes = [c.confid for c in components] if len(prefixes) != len(components): raise ValueError( "aliases= must have the same length as components=" ) seen: set[str] = set() for comp_cls, prefix in zip(components, prefixes, strict=True): for fname in comp_cls._fields: alias_name = fname if prefix is None else f"{prefix}_{fname}" if alias_name in seen: raise ValueError( f"Name conflict {alias_name!r} among auto-generated " f"aliases — use a different prefix or declare an " f"explicit Alias" ) seen.add(alias_name) descriptor = Alias(f"{comp_cls.confid}.{fname}") descriptor.__set_name__(cls, alias_name) setattr(cls, alias_name, descriptor) ref = ComponentRef(comp_cls.confid, comp_cls) setattr(cls, comp_cls.confid, ref) cls._component_classes = {c.confid: c for c in components} super().__init_subclass__(**kwargs)
def __init__(self): self._alias_root = self for confid, comp_cls in type(self)._component_classes.items(): object.__setattr__(self, confid, comp_cls())
[docs] @classmethod def from_dict(cls, d): """Create a fresh view and apply field values from *d*. Parameters ---------- d : `dict` Alias names and replacement values. Returns ------- view : `AliasedView` A new view with *d* values applied. """ view = cls() for k, v in d.items(): if k in cls._aliases: walk_set(view, cls._aliases[k]._path, v) return view
############################################################################# # FlatView #############################################################################
[docs] class FlatView(AliasedView): """An ``AliasedView`` with no component prefix. Every component field is exposed directly by its own name. A ``ValueError`` is raised at class-definition time if two components share a field name:: class InstrumentView( FlatView, components=[ CameraConfig, FilterConfig, ]): pass v = InstrumentView() v.gain = 2.0 # writes through to v.camera.gain """
[docs] def __init_subclass__(cls, components=None, **kwargs): n = len(components) if components is not None else 0 super().__init_subclass__( components=components, aliases=[None] * n, **kwargs )