Source code for pyams_utils.property

#
# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#

"""PyAMS_utils.property module

This module is used to define:
 - a cached property; this read-only property is evaluated only once; it's value is stored into
   object's attributes, and so should be freed with the object (so it should behave like a
   Pyramid's "reify" decorator, but we have kept it for compatibility of existing code)
 - a class property; this decorator is working like a classic property, but can be assigned to a
   class; to support class properties, this class also have to decorated with the
   "classproperty_support" decorator

    >>> from pyams_utils.property import cached_property

    >>> class ClassWithCache:
    ...     '''Class with cache'''
    ...     @cached_property
    ...     def cached_value(self):
    ...         print("This is a cached value")
    ...         return 1

    >>> obj = ClassWithCache()
    >>> obj.cached_value
    This is a cached value
    1

On following calls, cached property method shouldn't be called again:

    >>> obj.cached_value
    1

Class properties are used to define properties on class level:

    >>> from pyams_utils.property import classproperty, classproperty_support

    >>> @classproperty_support
    ... class ClassWithProperties:
    ...     '''Class with class properties'''
    ...
    ...     class_attribute = 1
    ...
    ...     @classproperty
    ...     def my_class_property(cls):
    ...         return cls.class_attribute

    >>> ClassWithProperties.my_class_property
    1
"""

__docformat__ = 'restructuredtext'


[docs]class cached_property: # pylint: disable=invalid-name """A read-only property decorator that is only evaluated once. The value is cached on the object itself rather than the function or class; this should prevent memory leakage. """ def __init__(self, fget, doc=None): self.fget = fget self.__doc__ = doc or fget.__doc__ self.__name__ = fget.__name__ self.__module__ = fget.__module__ def __get__(self, obj, cls): if obj is None: return self obj.__dict__[self.__name__] = result = self.fget(obj) return result
[docs]class classproperty: # pylint: disable=invalid-name """Same decorator as property(), but passes obj.__class__ instead of obj to fget/fset/fdel. Original code for property emulation: https://docs.python.org/3.5/howto/descriptor.html#properties """ def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel if doc is None and fget is not None: doc = fget.__doc__ self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError("Unreadable attribute") return self.fget(obj.__class__) def __set__(self, obj, value): if self.fset is None: raise AttributeError("Can't set attribute") self.fset(obj.__class__, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError("Can't delete attribute") self.fdel(obj.__class__)
[docs] def getter(self, fget): """Property getter""" return type(self)(fget, self.fset, self.fdel, self.__doc__)
[docs] def setter(self, fset): """Property setter""" return type(self)(self.fget, fset, self.fdel, self.__doc__)
[docs] def deleter(self, fdel): """Property deleter""" return type(self)(self.fget, self.fset, fdel, self.__doc__)
[docs]def classproperty_support(cls): """Class decorator to add metaclass to a class. Metaclass uses to add descriptors to class attributes """ class Meta(type): """Meta class""" for name, obj in vars(cls).items(): if isinstance(obj, classproperty): setattr(Meta, name, property(obj.fget, obj.fset, obj.fdel)) class Wrapper(cls, metaclass=Meta): """Wrapper class""" return Wrapper