Source code for jitx.property

"""
Property system
===============

This module provides the base Property class. Objects can be assigned properties,
and properties can be retrieved from objects.
"""

from collections.abc import Iterable
from itertools import chain
from typing import Any, overload, cast
from ._structural import Structural, Proxy, Instantiable


[docs] class Property: """Property base class. To declare a new property, subclass this class. Note that properties should be immutable, or care should be taken that a mutable property is not reused across multiple objects (unless intentional, of course). For example a property holding a list of things to be appended to per object should be carefully constructed so the same list is not attached to multiple objects. >>> @dataclass(frozen=True) ... class MyProperty(Property): ... some_string_value: str >>> other_object = Port() # some object to assign property, such as a Port >>> MyProperty("abc").assign(other_object) >>> MyProperty.get(other_object) MyProperty(some_string_value="abc") """
[docs] def assign[T](self, obj: T, /, *objs: Any) -> T: if isinstance(obj, Instantiable): if objs: raise TypeError( "Instantiable property assignment with multiple arguments is not supported" ) return cast(T, Instantiable(lambda ob: self.assign(ob), (obj,), {})) # bypass proxy forwarding with Proxy.override(): for obs in chain((obj,), objs): if not isinstance(obs, Iterable) or isinstance(obs, Structural): obs = (obs,) for ob in obs: props = getattr(ob, "_Property__dict", None) if not isinstance(props, dict): # if it was a Proxy.Dict, it has been populated by the parent. props = {} # bypass frozen checks object.__setattr__(ob, "_Property__dict", props) t = type(self) props[t] = self._set(props.get(t)) return obj
# Any|None here to indicate that it will be called with an object of its # own type or None, but can't use Self here since that would make the type # signature incompatible. def _set(self, other: Any | None): # can be used to make sure a list-property is unique by cloning on set return self @overload @classmethod def get[T, D](cls: type[T], ob: Any, default: D) -> T | D: ... @overload @classmethod def get[T](cls: type[T], ob: Any) -> T | None: ...
[docs] @classmethod def get(cls: type, ob: Any, default: Any = None): props: None | dict = None with Proxy.override(): while props is None and ob is not None: props = getattr(ob, "_Property__dict", None) if props and cls in props: return props[cls] elif isinstance(ob, Proxy): ob = Proxy.of(ob) else: break return default