diff options
author | Eli Collins <elic@assurancetechnologies.com> | 2016-11-22 16:49:46 -0500 |
---|---|---|
committer | Eli Collins <elic@assurancetechnologies.com> | 2016-11-22 16:49:46 -0500 |
commit | 4f03b94b1c26468400c4839c0932f411e6667fe7 (patch) | |
tree | b48d34e6f534ed9a47b18a0ca2ccef4e73376534 | |
parent | 659c6afdcb85aa5715e18c4bd2c93f0cdc6de17e (diff) | |
download | passlib-4f03b94b1c26468400c4839c0932f411e6667fe7.tar.gz |
passlib.utils: relocated a bunch of properties & decorators to .utils.decor
-rw-r--r-- | passlib/apache.py | 3 | ||||
-rw-r--r-- | passlib/context.py | 5 | ||||
-rw-r--r-- | passlib/crypto/digest.py | 3 | ||||
-rw-r--r-- | passlib/ext/django/utils.py | 2 | ||||
-rw-r--r-- | passlib/handlers/ldap_digests.py | 3 | ||||
-rw-r--r-- | passlib/handlers/scrypt.py | 3 | ||||
-rw-r--r-- | passlib/ifc.py | 3 | ||||
-rw-r--r-- | passlib/pwd.py | 3 | ||||
-rw-r--r-- | passlib/tests/test_crypto_scrypt.py | 3 | ||||
-rw-r--r-- | passlib/tests/test_ext_django.py | 2 | ||||
-rw-r--r-- | passlib/tests/test_utils.py | 6 | ||||
-rw-r--r-- | passlib/tests/utils.py | 3 | ||||
-rw-r--r-- | passlib/totp.py | 3 | ||||
-rw-r--r-- | passlib/utils/__init__.py | 182 | ||||
-rw-r--r-- | passlib/utils/decor.py | 191 | ||||
-rw-r--r-- | passlib/utils/des.py | 2 | ||||
-rw-r--r-- | passlib/utils/handlers.py | 5 | ||||
-rw-r--r-- | passlib/utils/pbkdf2.py | 2 |
18 files changed, 228 insertions, 196 deletions
diff --git a/passlib/apache.py b/passlib/apache.py index e2db7ca..41a4315 100644 --- a/passlib/apache.py +++ b/passlib/apache.py @@ -14,7 +14,8 @@ from passlib import exc, registry from passlib.context import CryptContext from passlib.exc import ExpectedStringError from passlib.hash import htdigest -from passlib.utils import render_bytes, to_bytes, deprecated_method, is_ascii_codec +from passlib.utils import render_bytes, to_bytes, is_ascii_codec +from passlib.utils.decor import deprecated_method from passlib.utils.compat import join_bytes, unicode, BytesIO, PY3 # local __all__ = [ diff --git a/passlib/context.py b/passlib/context.py index 4926bb0..98cee6c 100644 --- a/passlib/context.py +++ b/passlib/context.py @@ -13,8 +13,8 @@ from warnings import warn # pkg from passlib.exc import ExpectedStringError, ExpectedTypeError, PasslibConfigWarning from passlib.registry import get_crypt_handler, _validate_handler_name -from passlib.utils import (handlers as uh, to_bytes, deprecated_method, - to_unicode, splitcomma, memoized_property, +from passlib.utils import (handlers as uh, to_bytes, + to_unicode, splitcomma, as_bool, timer, rng, getrandstr, BASE64_CHARS, ) from passlib.utils.compat import (iteritems, num_types, irange, @@ -22,6 +22,7 @@ from passlib.utils.compat import (iteritems, num_types, irange, NativeStringIO, BytesIO, unicode_or_bytes_types, native_string_types, ) +from passlib.utils.decor import deprecated_method, memoized_property # local __all__ = [ 'CryptContext', diff --git a/passlib/crypto/digest.py b/passlib/crypto/digest.py index 2d8006a..18dce85 100644 --- a/passlib/crypto/digest.py +++ b/passlib/crypto/digest.py @@ -32,8 +32,9 @@ except ImportError: # pkg from passlib import exc from passlib.utils import join_bytes, to_native_str, join_byte_values, to_bytes, \ - SequenceMixin, memoized_property + SequenceMixin from passlib.utils.compat import irange, int_types, unicode_or_bytes_types, PY3 +from passlib.utils.decor import memoized_property # local __all__ = [ # hash utils diff --git a/passlib/ext/django/utils.py b/passlib/ext/django/utils.py index 6ed202c..33c8785 100644 --- a/passlib/ext/django/utils.py +++ b/passlib/ext/django/utils.py @@ -19,8 +19,8 @@ except ImportError: from passlib import exc, registry from passlib.context import CryptContext from passlib.exc import PasslibRuntimeWarning -from passlib.utils import memoized_property from passlib.utils.compat import get_method_function, iteritems, OrderedDict +from passlib.utils.decor import memoized_property # local __all__ = [ "DJANGO_VERSION", diff --git a/passlib/handlers/ldap_digests.py b/passlib/handlers/ldap_digests.py index 791c37e..4356f07 100644 --- a/passlib/handlers/ldap_digests.py +++ b/passlib/handlers/ldap_digests.py @@ -11,8 +11,9 @@ import re # site # pkg from passlib.handlers.misc import plaintext -from passlib.utils import unix_crypt_schemes, classproperty, to_unicode +from passlib.utils import unix_crypt_schemes, to_unicode from passlib.utils.compat import uascii_to_str, unicode, u +from passlib.utils.decor import classproperty import passlib.utils.handlers as uh # local __all__ = [ diff --git a/passlib/handlers/scrypt.py b/passlib/handlers/scrypt.py index f7364b7..1d0d893 100644 --- a/passlib/handlers/scrypt.py +++ b/passlib/handlers/scrypt.py @@ -8,8 +8,9 @@ import logging; log = logging.getLogger(__name__) # site # pkg from passlib.crypto import scrypt as _scrypt -from passlib.utils import h64, to_bytes, classproperty, b64s_decode, b64s_encode +from passlib.utils import h64, to_bytes, b64s_decode, b64s_encode from passlib.utils.compat import u, bascii_to_str, suppress_cause +from passlib.utils.decor import classproperty import passlib.utils.handlers as uh # local __all__ = [ diff --git a/passlib/ifc.py b/passlib/ifc.py index b376d52..606fd05 100644 --- a/passlib/ifc.py +++ b/passlib/ifc.py @@ -7,8 +7,7 @@ import logging; log = logging.getLogger(__name__) import sys # site # pkg -from passlib.utils.compat import unicode -from passlib.utils import deprecated_method +from passlib.utils.decor import deprecated_method # local __all__ = [ "PasswordHash", diff --git a/passlib/pwd.py b/passlib/pwd.py index 1410817..274666a 100644 --- a/passlib/pwd.py +++ b/passlib/pwd.py @@ -14,7 +14,8 @@ import os # pkg from passlib import exc from passlib.utils.compat import PY2, irange, itervalues, int_types -from passlib.utils import rng, getrandstr, to_unicode, memoized_property +from passlib.utils import rng, getrandstr, to_unicode +from passlib.utils.decor import memoized_property # local __all__ = [ "genword", "default_charsets", diff --git a/passlib/tests/test_crypto_scrypt.py b/passlib/tests/test_crypto_scrypt.py index 9a3d4c4..06b8b29 100644 --- a/passlib/tests/test_crypto_scrypt.py +++ b/passlib/tests/test_crypto_scrypt.py @@ -11,8 +11,9 @@ import warnings # site # pkg from passlib import exc -from passlib.utils import classproperty, getrandbytes +from passlib.utils import getrandbytes from passlib.utils.compat import PYPY, u, bascii_to_str +from passlib.utils.decor import classproperty from passlib.tests.utils import TestCase, skipUnless, TEST_MODE, hb # subject from passlib.crypto import scrypt as scrypt_mod diff --git a/passlib/tests/test_ext_django.py b/passlib/tests/test_ext_django.py index 0598fd0..e8ae175 100644 --- a/passlib/tests/test_ext_django.py +++ b/passlib/tests/test_ext_django.py @@ -15,7 +15,7 @@ from passlib.ext.django.utils import ( DJANGO_VERSION, MIN_DJANGO_VERSION, DjangoTranslator, ) from passlib.utils.compat import iteritems, get_method_function, u -from passlib.utils import memoized_property +from passlib.utils.decor import memoized_property # tests from passlib.tests.utils import TestCase, TEST_MODE, handler_derived_from from passlib.tests.test_handlers import get_handler_case, conditionally_available_hashes diff --git a/passlib/tests/test_utils.py b/passlib/tests/test_utils.py index b49d48c..d6a4555 100644 --- a/passlib/tests/test_utils.py +++ b/passlib/tests/test_utils.py @@ -34,7 +34,7 @@ class MiscTest(TestCase): self.assertTrue('irange' in dir(compat)) def test_classproperty(self): - from passlib.utils import classproperty + from passlib.utils.decor import classproperty class test(object): xvar = 1 @@ -47,7 +47,7 @@ class MiscTest(TestCase): self.assertIs(prop.im_func, prop.__func__) def test_deprecated_function(self): - from passlib.utils import deprecated_function + from passlib.utils.decor import deprecated_function # NOTE: not comprehensive, just tests the basic behavior @deprecated_function(deprecated="1.6", removed="1.8") @@ -65,7 +65,7 @@ class MiscTest(TestCase): self.assertEqual(test_func(1,2), (1,2)) def test_memoized_property(self): - from passlib.utils import memoized_property + from passlib.utils.decor import memoized_property class dummy(object): counter = 0 diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py index bd72652..d974a41 100644 --- a/passlib/tests/utils.py +++ b/passlib/tests/utils.py @@ -26,9 +26,10 @@ from passlib.exc import MissingBackendError import passlib.registry as registry from passlib.tests.backports import TestCase as _TestCase, skip, skipIf, skipUnless, SkipTest from passlib.utils import has_rounds_info, has_salt_info, rounds_cost_values, \ - classproperty, rng as sys_rng, getrandstr, is_ascii_safe, to_native_str, \ + rng as sys_rng, getrandstr, is_ascii_safe, to_native_str, \ repeat_string, tick, batch from passlib.utils.compat import iteritems, irange, u, unicode, PY2 +from passlib.utils.decor import classproperty import passlib.utils.handlers as uh # local __all__ = [ diff --git a/passlib/totp.py b/passlib/totp.py index 547e1ae..a2e91e1 100644 --- a/passlib/totp.py +++ b/passlib/totp.py @@ -35,10 +35,11 @@ except ImportError: # pkg from passlib import exc from passlib.exc import TokenError, MalformedTokenError, InvalidTokenError, UsedTokenError -from passlib.utils import (to_unicode, to_bytes, consteq, memoized_property, hybrid_method, +from passlib.utils import (to_unicode, to_bytes, consteq, getrandbytes, rng, SequenceMixin, xor_bytes, getrandstr, BASE64_CHARS) from passlib.utils.compat import (u, unicode, native_string_types, bascii_to_str, int_types, num_types, irange, byte_elem_value, UnicodeIO, suppress_cause) +from passlib.utils.decor import hybrid_method, memoized_property from passlib.crypto.digest import lookup_hash, compile_hmac, pbkdf2_hmac from passlib.hash import pbkdf2_sha256 # local diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py index a9485e7..701557d 100644 --- a/passlib/utils/__init__.py +++ b/passlib/utils/__init__.py @@ -34,6 +34,14 @@ import types from warnings import warn # site # pkg +from passlib.utils.decor import ( + # [remove these aliases in 2.0] + deprecated_function, + deprecated_method, + memoized_property, + classproperty, + hybrid_method, +) from passlib.exc import ExpectedStringError from passlib.utils.compat import (add_doc, join_bytes, join_byte_values, join_byte_elems, irange, imap, PY3, u, @@ -48,12 +56,6 @@ __all__ = [ 'unix_crypt_schemes', 'rounds_cost_values', - # decorators - "classproperty", -## "deprecated_function", -## "relocated_function", -## "memoized_class_property", - # unicode helpers 'consteq', 'saslprep', @@ -125,174 +127,8 @@ _USPACE = u(" ") MAX_PASSWORD_SIZE = int(os.environ.get("PASSLIB_MAX_PASSWORD_SIZE") or 4096) #============================================================================= -# decorators and meta helpers +# type helpers #============================================================================= -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 - -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) - -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" - -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) class SequenceMixin(object): """ 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 #============================================================================= diff --git a/passlib/utils/des.py b/passlib/utils/des.py index 62adac4..034bfc4 100644 --- a/passlib/utils/des.py +++ b/passlib/utils/des.py @@ -14,7 +14,7 @@ warn("the 'passlib.utils.des' module has been relocated to 'passlib.crypto.des' #============================================================================= # relocated functions #============================================================================= -from passlib.utils import deprecated_function +from passlib.utils.decor import deprecated_function from passlib.crypto.des import expand_des_key, des_encrypt_block, des_encrypt_int_block expand_des_key = deprecated_function(deprecated="1.7", removed="1.8", diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py index d9c2c1e..876edec 100644 --- a/passlib/utils/handlers.py +++ b/passlib/utils/handlers.py @@ -17,14 +17,15 @@ from passlib.exc import MissingBackendError, PasslibConfigWarning, \ from passlib.ifc import PasswordHash from passlib.registry import get_crypt_handler from passlib.utils import ( - classproperty, consteq, getrandstr, getrandbytes, + consteq, getrandstr, getrandbytes, BASE64_CHARS, HASH64_CHARS, rng, to_native_str, - is_crypt_handler, to_unicode, deprecated_method, + is_crypt_handler, to_unicode, MAX_PASSWORD_SIZE, accepts_keyword, as_bool, update_mixin_classes) from passlib.utils.compat import join_byte_values, irange, u, native_string_types, \ uascii_to_str, join_unicode, unicode, str_to_uascii, \ join_unicode, unicode_or_bytes_types, PY2, int_types +from passlib.utils.decor import classproperty, deprecated_method # local __all__ = [ # helpers for implementing MCF handlers diff --git a/passlib/utils/pbkdf2.py b/passlib/utils/pbkdf2.py index 3a6aff9..273143b 100644 --- a/passlib/utils/pbkdf2.py +++ b/passlib/utils/pbkdf2.py @@ -12,7 +12,7 @@ import logging; log = logging.getLogger(__name__) # site # pkg from passlib.exc import ExpectedTypeError -from passlib.utils import deprecated_function +from passlib.utils.decor import deprecated_function from passlib.utils.compat import native_string_types from passlib.crypto.digest import norm_hash_name, lookup_hash, pbkdf1 as _pbkdf1, pbkdf2_hmac, compile_hmac # local |