Source code for mypythontools.property.property_internal

"""Module with functions for 'property' subpackage."""

from __future__ import annotations

from typing import Generic, TypeVar, Callable, Type, overload, Any

from typeguard import check_type

from .. import types


T = TypeVar("T")
U = TypeVar("U")


# Needs to inherit from property to be able to use help tooltip
[docs]class MyPropertyClass(property, Generic[T]): """Python property on steroids. Check module docstrings for more info.""" # Property is inherited just for formatting help in IDE, so not called from init def __init__(self, fget: Callable[..., T], doc=None): # pylint: disable=super-init-not-called """Init property.""" if fget: self.allowed_types = types.get_return_type_hints(fget) self.init_function = fget if isinstance(fget, staticmethod): inner_func = fget.__func__ else: inner_func = fget if doc: self.__doc__ = doc elif inner_func.__doc__: self.__doc__ = fget.__doc__ else: self.__doc__ = None self.public_name = "" self.private_name = ""
[docs] def default_fset(self, used_object, content) -> None: """Define how new values will be stored.""" object.__setattr__(used_object, self.private_name, content)
def __set_name__(self, _, name): """Define names. Private usually with underscore.""" self.public_name = name self.private_name = "_" + name @overload def __get__(self, used_object: None, objtype: Any = None) -> MyPropertyClass[T]: ... @overload def __get__(self, used_object: U, objtype: Type[U] = None) -> T: ... def __get__(self, used_object, objtype=None): """Define what happens when you want access config attribute. If used on class, class itself is returned. """ if not used_object: return self # Expected value can be nominal value or function, that return that value content = getattr(used_object, self.private_name) if isinstance(content, staticmethod): content = content.__func__ if not hasattr(content, "myproperties_list") and callable(content): # Depends whether it's staticmethod or not error = None try: value = content(used_object) except TypeError as errOrig: try: value = content() except Exception as err: # If the error is missing self parameter, we know the orig error is root cause if str(err.args[0]).endswith("'self'"): error = errOrig else: error = err except Exception as err: error = err if error: raise error else: value = content return value def __set__(self, used_object, content: T | Callable[..., T]): """Define what happen if user set new config value.""" # You can setup value or function, that return that value if not hasattr(content, "myproperties_list") and callable(content): result = content(used_object) else: result = content if self.allowed_types: check_type(expected_type=self.allowed_types, value=result, argname=self.public_name) self.default_fset(used_object, result)
# Define as static method as it can be used directly
[docs]def init_my_properties(self): """Init private values of properties. Property usually get value of some private variable. This leads to many unnecessary code sometimes. """ if not hasattr(self, "myproperties_list"): setattr(self, "myproperties_list", []) for i in vars(type(self)).values(): if isinstance(i, MyPropertyClass): self.myproperties_list.append(i.public_name) setattr( self, i.private_name, i.init_function, )
[docs]def MyProperty(f: Callable[..., T]) -> MyPropertyClass[T]: # pylint: disable=invalid-name """Wrap MyProperty to work as expected. If not using this workaround, but use class decorator, IDE complains that property has no defined setter. Args: f(Callable[..., T]): Usually it's decorated method from class. It can be static as well. """ return MyPropertyClass[T](f)
# TODO - Use PEP 614 and define type just i n class decorator # Python 3.9 necessary