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