You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
4.4 KiB
134 lines
4.4 KiB
# Custom property-like classes |
|
from typing import MutableMapping, NewType, Optional, Sequence, Generic, TypeVar, Callable, Type, Any, overload, Union |
|
|
|
try: |
|
from typing import Self |
|
except ImportError: |
|
try: |
|
from typing_extensions import Self |
|
except ImportError: |
|
Self = Any |
|
|
|
|
|
T = TypeVar('T') |
|
O = TypeVar('O') |
|
Ow = TypeVar('Ow', covariant=True) |
|
|
|
|
|
class CustomProperty(property, Generic[T]): |
|
""" Subclass property for IDE support. |
|
Subclasses need not call super().__init__(), |
|
but might want to initialize self.property_name if such information is available """ |
|
|
|
property_name: str = "<<name unknown>>" |
|
|
|
def __init__(self): |
|
# Overwrite property constructor |
|
pass |
|
|
|
def __set_name__(self, owner: Type[O], name: str): |
|
""" Set name given in class body. |
|
May not be called if assigned outside class definition """ |
|
self.property_name = name |
|
|
|
@overload # type: ignore |
|
def __get__(self, obj: None, cls: Type[O]) -> Self: ... |
|
@overload |
|
def __get__(self, obj: O, cls: Type[O]) -> T: ... |
|
|
|
def __get__(self, obj: Optional[O], cls: Type[O]): |
|
if obj is None: |
|
return self |
|
raise AttributeError(f"Cannot read property {self.property_name} of {obj!r}") |
|
|
|
def __set__(self, obj: O, value: T): |
|
raise AttributeError(f"Cannot write property {self.property_name} of {obj!r}") |
|
|
|
def __delete__(self, obj: O): |
|
raise AttributeError(f"Cannot delete property {self.property_name} of {obj!r}") |
|
|
|
|
|
class CachedProperty(CustomProperty[T], Generic[T, Ow]): |
|
""" A property that is only computed once per instance and then replaces |
|
itself with an ordinary attribute. Deleting the attribute resets the |
|
property. |
|
|
|
Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76 |
|
""" |
|
|
|
def __init__(self, func: Callable[[Ow], T]): |
|
self.__doc__ = getattr(func, '__doc__') |
|
self.func = func |
|
self.property_name = func.__name__ |
|
|
|
def __get__(self, obj: Optional[Ow], cls: Type[Ow]): # type: ignore[override] |
|
if obj is None: |
|
return self |
|
value = obj.__dict__[self.property_name] = self.func(obj) |
|
return value |
|
|
|
def __delete__(self, obj: Ow): # type: ignore[override,misc] |
|
del obj.__dict__[self.property_name] |
|
|
|
|
|
class SettableCachedProperty(CachedProperty[T, O]): |
|
def __set__(self, obj: O, value: T): #type: ignore[override] |
|
obj.__dict__[self.property_name] = value |
|
|
|
|
|
class DictPathRoProperty(CustomProperty[T]): |
|
_NoDefault = NewType("_NoDefault", object) |
|
_nodefault = _NoDefault(object()) |
|
|
|
def __init__(self, source_member: str, path: Sequence[str], |
|
default: Union[T, _NoDefault]=_nodefault, type: Callable[[Any], T]=lambda x: x): |
|
self.source_member = source_member |
|
self.path = path |
|
self.default = default |
|
self.type = type |
|
|
|
def _get_parent(self, obj: O, *, create=False) -> MutableMapping[str, Any]: |
|
d: MutableMapping[str, Any] = getattr(obj, self.source_member) |
|
for pc in self.path[:-1]: |
|
try: |
|
d = d[pc] |
|
except KeyError: |
|
if not create: |
|
raise |
|
nd: MutableMapping[str, Any] = {} |
|
d[pc] = nd |
|
d = nd |
|
return d |
|
|
|
def __get__(self, obj: Optional[O], cls: Type[O]): # type: ignore |
|
if obj is None: |
|
return self |
|
try: |
|
val = self._get_parent(obj)[self.path[-1]] |
|
except KeyError: |
|
if self.default is not self._nodefault: |
|
return self.default |
|
raise |
|
else: |
|
return self.type(val) |
|
|
|
|
|
class DictPathProperty(DictPathRoProperty[T]): |
|
def __init__(self, *args, allow_create_parents=True, **kwds): |
|
super().__init__(*args, **kwds) |
|
self.allow_create_parents = allow_create_parents |
|
|
|
def __set__(self, obj, value): |
|
self._get_parent(obj, create=self.allow_create_parents)[self.path[-1]] = value |
|
|
|
def __delete__(self, obj): |
|
del self._get_parent(obj)[self.path[-1]] |
|
|
|
|
|
# functools.cached_property polyfill |
|
try: |
|
from functools import cached_property |
|
except ImportError: |
|
cached_property = CachedProperty # type: ignore[assignment,misc] |
|
|
|
__all__ = ['CustomProperty', 'CachedProperty', 'SettableCachedProperty', 'DictPathRoProperty', 'DictPathProperty', 'cached_property']
|
|
|