summaryrefslogtreecommitdiff
path: root/passlib/utils/decor.py
diff options
context:
space:
mode:
Diffstat (limited to 'passlib/utils/decor.py')
-rw-r--r--passlib/utils/decor.py191
1 files changed, 189 insertions, 2 deletions
diff --git a/passlib/utils/decor.py b/passlib/utils/decor.py
index 9319c2e..9041d5d 100644
--- a/passlib/utils/decor.py
+++ b/passlib/utils/decor.py
@@ -6,15 +6,62 @@ passlib.utils.decor -- helper decorators & properties
#=============================================================================
# core
from __future__ import absolute_import, division, print_function
-from functools import wraps
+import logging
+log = logging.getLogger(__name__)
+from functools import wraps, update_wrapper
+import types
+from warnings import warn
# site
# pkg
+from passlib.utils.compat import PY3
# local
__all__ = [
- "memoize_single_value"
+ "classproperty",
+ "hybrid_method",
+
+ "memoize_single_value",
+ "memoized_property",
+
+ "deprecated_function",
+ "deprecated_method",
]
#=============================================================================
+# class-level decorators
+#=============================================================================
+class classproperty(object):
+ """Function decorator which acts like a combination of classmethod+property (limited to read-only properties)"""
+
+ def __init__(self, func):
+ self.im_func = func
+
+ def __get__(self, obj, cls):
+ return self.im_func(cls)
+
+ @property
+ def __func__(self):
+ """py3 compatible alias"""
+ return self.im_func
+
+class hybrid_method(object):
+ """
+ decorator which invokes function with class if called as class method,
+ and with object if called at instance level.
+ """
+
+ def __init__(self, func):
+ self.func = func
+ update_wrapper(self, func)
+
+ def __get__(self, obj, cls):
+ if obj is None:
+ obj = cls
+ if PY3:
+ return types.MethodType(self.func, obj)
+ else:
+ return types.MethodType(self.func, obj, cls)
+
+#=============================================================================
# memoization
#=============================================================================
@@ -41,6 +88,146 @@ def memoize_single_value(func):
return wrapper
+class memoized_property(object):
+ """
+ decorator which invokes method once, then replaces attr with result
+ """
+ def __init__(self, func):
+ self.__func__ = func
+ self.__name__ = func.__name__
+ self.__doc__ = func.__doc__
+
+ def __get__(self, obj, cls):
+ if obj is None:
+ return self
+ value = self.__func__(obj)
+ setattr(obj, self.__name__, value)
+ return value
+
+ if not PY3:
+
+ @property
+ def im_func(self):
+ """py2 alias"""
+ return self.__func__
+
+ def clear_cache(self, obj):
+ """
+ class-level helper to clear stored value (if any).
+
+ usage: :samp:`type(self).{attr}.clear_cache(self)`
+ """
+ obj.__dict__.pop(self.__name__, None)
+
+ def peek_cache(self, obj, default=None):
+ """
+ class-level helper to peek at stored value
+
+ usage: :samp:`value = type(self).{attr}.clear_cache(self)`
+ """
+ return obj.__dict__.get(self.__name__, default)
+
+# works but not used
+##class memoized_class_property(object):
+## """function decorator which calls function as classmethod,
+## and replaces itself with result for current and all future invocations.
+## """
+## def __init__(self, func):
+## self.im_func = func
+##
+## def __get__(self, obj, cls):
+## func = self.im_func
+## value = func(cls)
+## setattr(cls, func.__name__, value)
+## return value
+##
+## @property
+## def __func__(self):
+## "py3 compatible alias"
+
+#=============================================================================
+# deprecation
+#=============================================================================
+def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True,
+ replacement=None, _is_method=False,
+ func_module=None):
+ """decorator to deprecate a function.
+
+ :arg msg: optional msg, default chosen if omitted
+ :kwd deprecated: version when function was first deprecated
+ :kwd removed: version when function will be removed
+ :kwd replacement: alternate name / instructions for replacing this function.
+ :kwd updoc: add notice to docstring (default ``True``)
+ """
+ if msg is None:
+ if _is_method:
+ msg = "the method %(mod)s.%(klass)s.%(name)s() is deprecated"
+ else:
+ msg = "the function %(mod)s.%(name)s() is deprecated"
+ if deprecated:
+ msg += " as of Passlib %(deprecated)s"
+ if removed:
+ msg += ", and will be removed in Passlib %(removed)s"
+ if replacement:
+ msg += ", use %s instead" % replacement
+ msg += "."
+ def build(func):
+ is_classmethod = _is_method and isinstance(func, classmethod)
+ if is_classmethod:
+ # NOTE: PY26 doesn't support "classmethod().__func__" directly...
+ func = func.__get__(None, type).__func__
+ opts = dict(
+ mod=func_module or func.__module__,
+ name=func.__name__,
+ deprecated=deprecated,
+ removed=removed,
+ )
+ if _is_method:
+ def wrapper(*args, **kwds):
+ tmp = opts.copy()
+ klass = args[0] if is_classmethod else args[0].__class__
+ tmp.update(klass=klass.__name__, mod=klass.__module__)
+ warn(msg % tmp, DeprecationWarning, stacklevel=2)
+ return func(*args, **kwds)
+ else:
+ text = msg % opts
+ def wrapper(*args, **kwds):
+ warn(text, DeprecationWarning, stacklevel=2)
+ return func(*args, **kwds)
+ update_wrapper(wrapper, func)
+ if updoc and (deprecated or removed) and \
+ wrapper.__doc__ and ".. deprecated::" not in wrapper.__doc__:
+ txt = deprecated or ''
+ if removed or replacement:
+ txt += "\n "
+ if removed:
+ txt += "and will be removed in version %s" % (removed,)
+ if replacement:
+ if removed:
+ txt += ", "
+ txt += "use %s instead" % replacement
+ txt += "."
+ if not wrapper.__doc__.strip(" ").endswith("\n"):
+ wrapper.__doc__ += "\n"
+ wrapper.__doc__ += "\n.. deprecated:: %s\n" % (txt,)
+ if is_classmethod:
+ wrapper = classmethod(wrapper)
+ return wrapper
+ return build
+
+def deprecated_method(msg=None, deprecated=None, removed=None, updoc=True,
+ replacement=None):
+ """decorator to deprecate a method.
+
+ :arg msg: optional msg, default chosen if omitted
+ :kwd deprecated: version when method was first deprecated
+ :kwd removed: version when method will be removed
+ :kwd replacement: alternate name / instructions for replacing this method.
+ :kwd updoc: add notice to docstring (default ``True``)
+ """
+ return deprecated_function(msg, deprecated, removed, updoc, replacement,
+ _is_method=True)
+
#=============================================================================
# eof
#=============================================================================