src/pyams_utils/property.py
changeset 289 c8e21d7dd685
child 292 b338586588ad
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/property.py	Wed Dec 05 12:45:56 2018 +0100
@@ -0,0 +1,118 @@
+#
+# 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.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+
+# import packages
+
+
+class cached(object):
+    """Custom property decorator to define a property or function which is calculated only once
+
+    When applied on a function, caching is based on input arguments
+    """
+
+    def __init__(self, function):
+        self._function = function
+        self._cache = {}
+
+    def __call__(self, *args):
+        try:
+            return self._cache[args]
+        except KeyError:
+            self._cache[args] = self._function(*args)
+            return self._cache[args]
+
+    def expire(self, *args):
+        del self._cache[args]
+
+
+class cached_property(object):
+    """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
+
+
+class classproperty:
+    """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__)
+
+    def getter(self, fget):
+        return type(self)(fget, self.fset, self.fdel, self.__doc__)
+
+    def setter(self, fset):
+        return type(self)(self.fget, fset, self.fdel, self.__doc__)
+
+    def deleter(self, fdel):
+        return type(self)(self.fget, self.fset, fdel, self.__doc__)
+
+
+def classproperty_support(cls):
+    """Class decorator to add metaclass to a class.
+    
+    Metaclass uses to add descriptors to class attributes
+    """
+    class Meta(type):
+        pass
+
+    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):
+        pass
+    return Wrapper