mypythontools.property — mypythontools 21-08-2021 documentation

# Source code for mypythontools.property

    """Module contains MyProperty class that edit normal python property to add new features.

    There is default setter, it's possible to auto init values on class init and values in setter can be
    validated.

    It's possible to set function as a value and it's evaluated during call.

    Example of how can it be used is in module config.

    Examples:
    =========

        >>> class MyClass:
        ...     # Init all default values of defined properties
        ...     def __init__(self):
        ...        for j in vars(type(self)).values():
        ...            if type(j) is MyProperty:
        ...                setattr(self, j.private_name, j.init_function)
        ...
        ...     @MyProperty(int)  # New value will be validated whether it's int
        ...     def var() -> int:  # This is for type hints in IDE.
        ...         '''
        ...         Type:
        ...             int
        ...
        ...         Default:
        ...             123
        ...
        ...         This is docstrings (also visible in IDE, because not defined dynamically).'''
        ...
        ...         return 123  # This is initial value that can be edited.
        ...
        ...     @MyProperty()  # Even if you don't need any params, use empty brackets
        ...     def var2():
        ...         return 111
        ...
        >>> myobject = MyClass()
        >>> myobject.var
        123
        >>> myobject.var = 124
        >>> myobject.var
        124
    """

    import types as types_lib
    from .misc import validate

    import mylogging

    class MyProperty(property):
        """Python property on steroids. Check module docstrings for more info."""

        def __init__(self, types=None, options=None, fget=None, fset=None, doc=None):

            if isinstance(types, types_lib.FunctionType):
                raise SyntaxError(
                    mylogging.return_str("@MyProperty decorator has to be called (parentheses at the end).")
                )
            self.fget_new = fget if fget else self.default_fget
            self.fset_new = fset if fset else self.default_fset
            self.__doc__ = doc
            self.types = types
            self.options = options

        def __call__(self, init_function):
            self.init_function = init_function
            self.__doc__ = init_function.__doc__

            return self

        def default_fget(self, object):
            return getattr(object, self.private_name)

        def default_fset(self, object, content):
            setattr(object, self.private_name, content)

        def __set_name__(self, _, name):
            self.public_name = name
            self.private_name = "_" + name

        def __get__(self, object, objtype=None):
            # If getting MyProperty class, not object, return MyProperty itself
            if not object:
                return self

            # Expected value can be nominal value or function, that return that value
            content = self.fget_new(object)
            if callable(content):
                if not len(content.__code__.co_varnames):
                    value = content()
                else:
                    value = content(object)
            else:
                value = content

            return value

        def __set__(self, object, content):

            # You can setup value or function, that return that value
            if callable(content):
                result = content(object)
            else:
                result = content

            validate(result, self.types, self.options, self.public_name)

            # Method defined can be pass as static method
            try:
                self.fset_new(object, content)
            except TypeError:
                self.fset_new(self, object, content)