summaryrefslogtreecommitdiff
path: root/passlib/utils
diff options
context:
space:
mode:
Diffstat (limited to 'passlib/utils')
-rw-r--r--passlib/utils/__init__.py185
-rw-r--r--passlib/utils/binary.py88
-rw-r--r--passlib/utils/compat/__init__.py284
-rw-r--r--passlib/utils/compat/_ordered_dict.py242
-rw-r--r--passlib/utils/decor.py27
-rw-r--r--passlib/utils/des.py21
-rw-r--r--passlib/utils/handlers.py167
-rw-r--r--passlib/utils/pbkdf2.py19
8 files changed, 194 insertions, 839 deletions
diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py
index 6147886..94833ad 100644
--- a/passlib/utils/__init__.py
+++ b/passlib/utils/__init__.py
@@ -61,11 +61,10 @@ from passlib.utils.decor import (
hybrid_method,
)
from passlib.exc import ExpectedStringError, ExpectedTypeError
-from passlib.utils.compat import (add_doc, join_bytes, join_byte_values,
- join_byte_elems, irange, imap, PY3, u,
- join_unicode, unicode, byte_elem_value, nextgetter,
- unicode_or_str, unicode_or_bytes_types,
- get_method_function, suppress_cause, PYPY)
+from passlib.utils.compat import (add_doc, join_bytes,
+ join_unicode,
+ unicode_or_bytes,
+ get_method_function, PYPY)
# local
__all__ = [
# constants
@@ -113,7 +112,7 @@ __all__ = [
#=============================================================================
# bitsize of system architecture (32 or 64)
-sys_bits = int(math.log(sys.maxsize if PY3 else sys.maxint, 2) + 1.5)
+sys_bits = int(math.log(sys.maxsize, 2) + 1.5)
# list of hashes algs supported by crypt() on at least one OS.
# XXX: move to .registry for passlib 2.0?
@@ -128,13 +127,10 @@ unix_crypt_schemes = [
# list of rounds_cost constants
rounds_cost_values = [ "linear", "log2" ]
-# legacy import, will be removed in 1.8
-from passlib.exc import MissingBackendError
-
# internal helpers
_BEMPTY = b''
-_UEMPTY = u("")
-_USPACE = u(" ")
+_UEMPTY = u""
+_USPACE = u" "
# maximum password size which passlib will allow; see exc.PasswordSizeError
MAX_PASSWORD_SIZE = int(os.environ.get("PASSLIB_MAX_PASSWORD_SIZE") or 4096)
@@ -169,30 +165,23 @@ class SequenceMixin(object):
def __ne__(self, other):
return not self.__eq__(other)
-if PY3:
- # getargspec() is deprecated, use this under py3.
- # even though it's a lot more awkward to get basic info :|
-
- _VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD
- _VAR_ANY_SET = set([_VAR_KEYWORD, inspect.Parameter.VAR_POSITIONAL])
+# getargspec() is deprecated, use this under py3.
+# even though it's a lot more awkward to get basic info :|
- def accepts_keyword(func, key):
- """test if function accepts specified keyword"""
- params = inspect.signature(get_method_function(func)).parameters
- if not params:
- return False
- arg = params.get(key)
- if arg and arg.kind not in _VAR_ANY_SET:
- return True
- # XXX: annoying what we have to do to determine if VAR_KWDS in use.
- return params[list(params)[-1]].kind == _VAR_KEYWORD
+_VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD
+_VAR_ANY_SET = {_VAR_KEYWORD, inspect.Parameter.VAR_POSITIONAL}
-else:
+def accepts_keyword(func, key):
+ """test if function accepts specified keyword"""
+ params = inspect.signature(get_method_function(func)).parameters
+ if not params:
+ return False
+ arg = params.get(key)
+ if arg and arg.kind not in _VAR_ANY_SET:
+ return True
+ # XXX: annoying what we have to do to determine if VAR_KWDS in use.
+ return params[list(params)[-1]].kind == _VAR_KEYWORD
- def accepts_keyword(func, key):
- """test if function accepts specified keyword"""
- spec = inspect.getargspec(get_method_function(func))
- return key in spec.args or spec.keywords is not None
def update_mixin_classes(target, add=None, remove=None, append=False,
before=None, after=None, dryrun=False):
@@ -333,16 +322,16 @@ def consteq(left, right):
# http://bugs.python.org/issue14955
# validate types
- if isinstance(left, unicode):
- if not isinstance(right, unicode):
- raise TypeError("inputs must be both unicode or both bytes")
- is_py3_bytes = False
+ if isinstance(left, str):
+ if not isinstance(right, str):
+ raise TypeError("inputs must be both str or both bytes")
+ is_bytes = False
elif isinstance(left, bytes):
if not isinstance(right, bytes):
- raise TypeError("inputs must be both unicode or both bytes")
- is_py3_bytes = PY3
+ raise TypeError("inputs must be both str or both bytes")
+ is_bytes = True
else:
- raise TypeError("inputs must be both unicode or both bytes")
+ raise TypeError("inputs must be both str or both bytes")
# do size comparison.
# NOTE: the double-if construction below is done deliberately, to ensure
@@ -361,8 +350,7 @@ def consteq(left, right):
result = 1
# run constant-time string comparision
- # TODO: use izip instead (but first verify it's faster than zip for this case)
- if is_py3_bytes:
+ if is_bytes:
for l,r in zip(tmp, right):
result |= l ^ r
else:
@@ -442,8 +430,8 @@ def saslprep(source, param="value"):
# validate type
# XXX: support bytes (e.g. run through want_unicode)?
# might be easier to just integrate this into cryptcontext.
- if not isinstance(source, unicode):
- raise TypeError("input must be unicode string, not %s" %
+ if not isinstance(source, str):
+ raise TypeError("input must be string, not %s" %
(type(source),))
# mapping stage
@@ -557,19 +545,11 @@ def render_bytes(source, *args):
else arg for arg in args)
return result.encode("latin-1")
-if PY3:
- # new in py32
- def bytes_to_int(value):
- return int.from_bytes(value, 'big')
- def int_to_bytes(value, count):
- return value.to_bytes(count, 'big')
-else:
- # XXX: can any of these be sped up?
- from binascii import hexlify, unhexlify
- def bytes_to_int(value):
- return int(hexlify(value),16)
- def int_to_bytes(value, count):
- return unhexlify(('%%0%dx' % (count<<1)) % value)
+def bytes_to_int(value):
+ return int.from_bytes(value, 'big')
+
+def int_to_bytes(value, count):
+ return value.to_bytes(count, 'big')
add_doc(bytes_to_int, "decode byte string as single big-endian integer")
add_doc(int_to_bytes, "encode integer as single big-endian byte string")
@@ -595,14 +575,14 @@ def utf8_repeat_string(source, size):
_BNULL = b"\x00"
-_UNULL = u("\x00")
+_UNULL = u"\x00"
def right_pad_string(source, size, pad=None):
"""right-pad or truncate <source> string, so it has length <size>"""
cur = len(source)
if size > cur:
if pad is None:
- pad = _UNULL if isinstance(source, unicode) else _BNULL
+ pad = _UNULL if isinstance(source, str) else _BNULL
return source+pad*(size-cur)
else:
return source[:size]
@@ -648,7 +628,7 @@ def utf8_truncate(source, index):
# loop until we find non-continuation byte
while index < end:
- if byte_elem_value(source[index]) & 0xC0 != 0x80:
+ if source[index] & 0xC0 != 0x80:
# found single-char byte, or start-char byte.
break
# else: found continuation byte.
@@ -694,7 +674,7 @@ def is_same_codec(left, right):
return _lookup_codec(left).name == _lookup_codec(right).name
_B80 = b'\x80'[0]
-_U80 = u('\x80')
+_U80 = u'\x80'
def is_ascii_safe(source):
"""Check if string (bytes or unicode) contains only 7-bit ascii"""
r = _B80 if isinstance(source, bytes) else _U80
@@ -717,7 +697,7 @@ def to_bytes(source, encoding="utf-8", param="value", source_encoding=None):
the source will be transcoded from *source_encoding* to *encoding*
(via unicode).
- :raises TypeError: if source is not unicode or bytes.
+ :raises TypeError: if source is not str or bytes.
:returns:
* unicode strings will be encoded using *encoding*, and returned.
@@ -732,7 +712,7 @@ def to_bytes(source, encoding="utf-8", param="value", source_encoding=None):
return source.decode(source_encoding).encode(encoding)
else:
return source
- elif isinstance(source, unicode):
+ elif isinstance(source, str):
return source.encode(encoding)
else:
raise ExpectedStringError(source, param)
@@ -749,42 +729,34 @@ def to_unicode(source, encoding="utf-8", param="value"):
:param param:
optional name of variable/noun to reference when raising errors.
- :raises TypeError: if source is not unicode or bytes.
+ :raises TypeError: if source is not str or bytes.
:returns:
* returns unicode strings unchanged.
* returns bytes strings decoded using *encoding*
"""
assert encoding
- if isinstance(source, unicode):
+ if isinstance(source, str):
return source
elif isinstance(source, bytes):
return source.decode(encoding)
else:
raise ExpectedStringError(source, param)
-if PY3:
- def to_native_str(source, encoding="utf-8", param="value"):
- if isinstance(source, bytes):
- return source.decode(encoding)
- elif isinstance(source, unicode):
- return source
- else:
- raise ExpectedStringError(source, param)
-else:
- def to_native_str(source, encoding="utf-8", param="value"):
- if isinstance(source, bytes):
- return source
- elif isinstance(source, unicode):
- return source.encode(encoding)
- else:
- raise ExpectedStringError(source, param)
+
+def to_native_str(source, encoding="utf-8", param="value"):
+ if isinstance(source, bytes):
+ return source.decode(encoding)
+ elif isinstance(source, str):
+ return source
+ else:
+ raise ExpectedStringError(source, param)
+
add_doc(to_native_str,
- """Take in unicode or bytes, return native string.
+ """Take in str or bytes, returns str.
- Python 2: encodes unicode using specified encoding, leaves bytes alone.
- Python 3: leaves unicode alone, decodes bytes using specified encoding.
+ leaves str alone, decodes bytes using specified encoding.
:raises TypeError: if source is not unicode or bytes.
@@ -816,7 +788,7 @@ def as_bool(value, none=None, param="boolean"):
recognizes strings such as "true", "false"
"""
assert none in [True, False, None]
- if isinstance(value, unicode_or_bytes_types):
+ if isinstance(value, unicode_or_bytes):
clean = value.lower().strip()
if clean in _true_set:
return True
@@ -840,7 +812,7 @@ def is_safe_crypt_input(value):
"""
UT helper --
test if value is safe to pass to crypt.crypt();
- under PY3, can't pass non-UTF8 bytes to crypt.crypt.
+ since PY3 won't let us pass non-UTF8 bytes to crypt.crypt.
"""
if crypt_accepts_bytes or not isinstance(value, bytes):
return True
@@ -883,9 +855,9 @@ else:
# returning NULL / None. examples include ":", ":0", "*0", etc.
# safe_crypt() returns None for any string starting with one of the
# chars in this string...
- _invalid_prefixes = u("*:!")
+ _invalid_prefixes = u"*:!"
- if PY3:
+ if True: # legacy block from PY3 compat
# * pypy3 (as of v7.3.1) has a crypt which accepts bytes, or ASCII-only unicode.
# * whereas CPython3 (as of v3.9) has a crypt which doesn't take bytes,
@@ -905,11 +877,11 @@ else:
if crypt_accepts_bytes:
# PyPy3 -- all bytes accepted, but unicode encoded to ASCII,
# so handling that ourselves.
- if isinstance(secret, unicode):
+ if isinstance(secret, str):
secret = secret.encode("utf-8")
if _BNULL in secret:
raise ValueError("null character in secret")
- if isinstance(hash, unicode):
+ if isinstance(hash, str):
hash = hash.encode("ascii")
else:
# CPython3's crypt() doesn't take bytes, only unicode; unicode which is then
@@ -945,27 +917,7 @@ else:
if not result or result[0] in _invalid_prefixes:
return None
return result
- else:
- #: see feature-detection in PY3 fork above
- crypt_accepts_bytes = True
-
- # Python 2 crypt handler
- def safe_crypt(secret, hash):
- if isinstance(secret, unicode):
- secret = secret.encode("utf-8")
- if _NULL in secret:
- raise ValueError("null character in secret")
- if isinstance(hash, unicode):
- hash = hash.encode("ascii")
- with _safe_crypt_lock:
- result = _crypt(secret, hash)
- if not result:
- return None
- result = result.decode("ascii")
- if result[0] in _invalid_prefixes:
- return None
- return result
add_doc(safe_crypt, """Wrapper around stdlib's crypt.
@@ -1006,9 +958,9 @@ def test_crypt(secret, hash):
# safe_crypt() always returns unicode, which means that for py3,
# 'hash' can't be bytes, or "== hash" will never be True.
# under py2 unicode & str(bytes) will compare fine;
- # so just enforcing "unicode_or_str" limitation
- assert isinstance(hash, unicode_or_str), \
- "hash must be unicode_or_str, got %s" % type(hash)
+ # so just enforcing "str" limitation
+ assert isinstance(hash, str), \
+ "hash must be str, got %s" % type(hash)
assert hash, "hash must be non-empty"
return safe_crypt(secret, hash) == hash
@@ -1055,7 +1007,7 @@ def genseed(value=None):
# this method throws error for e.g. SystemRandom instances,
# so fall back to extracting 4k of state
value = value.getrandbits(1 << 15)
- text = u("%s %s %s %.15f %.15f %s") % (
+ text = u"%s %s %s %.15f %.15f %s" % (
# if caller specified a seed value, mix it in
value,
@@ -1106,7 +1058,8 @@ def getrandbytes(rng, count):
yield value & 0xff
value >>= 3
i += 1
- return join_byte_values(helper())
+ return bytes(helper())
+
def getrandstr(rng, charset, count):
"""return string containing *count* number of chars/bytes, whose elements are drawn from specified charset, using specified rng"""
@@ -1132,10 +1085,10 @@ def getrandstr(rng, charset, count):
value //= letters
i += 1
- if isinstance(charset, unicode):
+ if isinstance(charset, str):
return join_unicode(helper())
else:
- return join_byte_elems(helper())
+ return bytes(helper())
_52charset = '2346789ABCDEFGHJKMNPQRTUVWXYZabcdefghjkmnpqrstuvwxyz'
diff --git a/passlib/utils/binary.py b/passlib/utils/binary.py
index 521b64a..f60ddf2 100644
--- a/passlib/utils/binary.py
+++ b/passlib/utils/binary.py
@@ -5,7 +5,6 @@ passlib.utils.binary - binary data encoding/decoding/manipulation
# imports
#=============================================================================
# core
-from __future__ import absolute_import, division, print_function
from base64 import (
b64encode,
b64decode,
@@ -19,10 +18,9 @@ log = logging.getLogger(__name__)
# pkg
from passlib import exc
from passlib.utils.compat import (
- PY3, bascii_to_str,
- irange, imap, iter_byte_chars, join_byte_values, join_byte_elems,
- nextgetter, suppress_cause,
- u, unicode, unicode_or_bytes_types,
+ bascii_to_str,
+ iter_byte_chars,
+ unicode_or_bytes,
)
from passlib.utils.decor import memoized_property
# from passlib.utils import BASE64_CHARS, HASH64_CHARS
@@ -64,28 +62,28 @@ __all__ = [
#-------------------------------------------------------------
#: standard base64 charmap
-BASE64_CHARS = u("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
+BASE64_CHARS = u"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
#: alt base64 charmap -- "." instead of "+"
-AB64_CHARS = u("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./")
+AB64_CHARS = u"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./"
#: charmap used by HASH64 encoding.
-HASH64_CHARS = u("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
+HASH64_CHARS = u"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
#: charmap used by BCrypt
-BCRYPT_CHARS = u("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
+BCRYPT_CHARS = u"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
#: std base64 chars + padding char
-PADDED_BASE64_CHARS = BASE64_CHARS + u("=")
+PADDED_BASE64_CHARS = BASE64_CHARS + u"="
#: all hex chars
-HEX_CHARS = u("0123456789abcdefABCDEF")
+HEX_CHARS = u"0123456789abcdefABCDEF"
#: upper case hex chars
-UPPER_HEX_CHARS = u("0123456789ABCDEF")
+UPPER_HEX_CHARS = u"0123456789ABCDEF"
#: lower case hex chars
-LOWER_HEX_CHARS = u("0123456789abcdef")
+LOWER_HEX_CHARS = u"0123456789abcdef"
#-------------------------------------------------------------
# byte strings
@@ -93,7 +91,7 @@ LOWER_HEX_CHARS = u("0123456789abcdef")
#: special byte string containing all possible byte values
#: NOTE: for efficiency, this is treated as singleton by some of the code
-ALL_BYTE_VALUES = join_byte_values(irange(256))
+ALL_BYTE_VALUES = bytes(range(256))
#: some string constants we reuse
B_EMPTY = b''
@@ -128,10 +126,10 @@ def compile_byte_translation(mapping, source=None):
assert isinstance(source, bytes) and len(source) == 255
target = list(iter_byte_chars(source))
for k, v in mapping.items():
- if isinstance(k, unicode_or_bytes_types):
+ if isinstance(k, unicode_or_bytes):
k = ord(k)
assert isinstance(k, int) and 0 <= k < 256
- if isinstance(v, unicode):
+ if isinstance(v, str):
v = v.encode("ascii")
assert isinstance(v, bytes) and len(v) == 1
target[k] = v
@@ -152,12 +150,12 @@ def b64s_decode(data):
decode from shortened base64 format which omits padding & whitespace.
uses default ``+/`` altchars.
"""
- if isinstance(data, unicode):
+ if isinstance(data, str):
# needs bytes for replace() call, but want to accept ascii-unicode ala a2b_base64()
try:
data = data.encode("ascii")
except UnicodeEncodeError:
- raise suppress_cause(ValueError("string argument should contain only ASCII characters"))
+ raise ValueError("string argument should contain only ASCII characters") from None
off = len(data) & 3
if off == 0:
pass
@@ -170,7 +168,7 @@ def b64s_decode(data):
try:
return a2b_base64(data)
except _BinAsciiError as err:
- raise suppress_cause(TypeError(err))
+ raise TypeError(err) from None
#=============================================================================
# adapted-base64 encoding
@@ -198,12 +196,12 @@ def ab64_decode(data):
it is primarily used by Passlib's custom pbkdf2 hashes.
"""
- if isinstance(data, unicode):
+ if isinstance(data, str):
# needs bytes for replace() call, but want to accept ascii-unicode ala a2b_base64()
try:
data = data.encode("ascii")
except UnicodeEncodeError:
- raise suppress_cause(ValueError("string argument should contain only ASCII characters"))
+ raise ValueError("string argument should contain only ASCII characters") from None
return b64s_decode(data.replace(b".", b"+"))
#=============================================================================
@@ -233,7 +231,7 @@ def b32decode(source):
padding optional, ignored if present.
"""
# encode & correct for typos
- if isinstance(source, unicode):
+ if isinstance(source, str):
source = source.encode("ascii")
source = source.translate(_b32_translate)
@@ -336,7 +334,7 @@ class Base64Engine(object):
#===================================================================
def __init__(self, charmap, big=False):
# validate charmap, generate encode64/decode64 helper functions.
- if isinstance(charmap, unicode):
+ if isinstance(charmap, str):
charmap = charmap.encode("latin-1")
elif not isinstance(charmap, bytes):
raise exc.ExpectedStringError(charmap, "charmap")
@@ -385,12 +383,9 @@ class Base64Engine(object):
if not isinstance(source, bytes):
raise TypeError("source must be bytes, not %s" % (type(source),))
chunks, tail = divmod(len(source), 3)
- if PY3:
- next_value = nextgetter(iter(source))
- else:
- next_value = nextgetter(ord(elem) for elem in source)
+ next_value = iter(source).__next__
gen = self._encode_bytes(next_value, chunks, tail)
- out = join_byte_elems(imap(self._encode64, gen))
+ out = bytes(map(self._encode64, gen))
##if tail:
## padding = self.padding
## if padding:
@@ -495,9 +490,9 @@ class Base64Engine(object):
if tail == 1:
# only 6 bits left, can't encode a whole byte!
raise ValueError("input string length cannot be == 1 mod 4")
- next_value = nextgetter(imap(self._decode64, source))
+ next_value = map(self._decode64, source).__next__
try:
- return join_byte_values(self._decode_bytes(next_value, chunks, tail))
+ return bytes(self._decode_bytes(next_value, chunks, tail))
except KeyError as err:
raise ValueError("invalid character: %r" % (err.args[0],))
@@ -626,7 +621,7 @@ class Base64Engine(object):
# we have dirty bits - repair the string by decoding last char,
# clearing the padding bits via <mask>, and encoding new char.
- if isinstance(source, unicode):
+ if isinstance(source, str):
cm = self.charmap
last = cm[cm.index(last) & mask]
assert last in padset, "failed to generate valid padding char"
@@ -635,8 +630,7 @@ class Base64Engine(object):
# all chars used by encoding are 7-bit ascii.
last = self._encode64(self._decode64(last) & mask)
assert last in padset, "failed to generate valid padding char"
- if PY3:
- last = bytes([last])
+ last = bytes([last])
return True, source[:-1] + last
def repair_unused(self, source):
@@ -661,19 +655,19 @@ class Base64Engine(object):
"""encode byte string, first transposing source using offset list"""
if not isinstance(source, bytes):
raise TypeError("source must be bytes, not %s" % (type(source),))
- tmp = join_byte_elems(source[off] for off in offsets)
+ tmp = bytes(source[off] for off in offsets)
return self.encode_bytes(tmp)
def decode_transposed_bytes(self, source, offsets):
"""decode byte string, then reverse transposition described by offset list"""
# NOTE: if transposition does not use all bytes of source,
- # the original can't be recovered... and join_byte_elems() will throw
+ # the original can't be recovered... and bytes() will throw
# an error because 1+ values in <buf> will be None.
tmp = self.decode_bytes(source)
buf = [None] * len(offsets)
for off, char in zip(offsets, tmp):
buf[off] = char
- return join_byte_elems(buf)
+ return bytes(buf)
#===================================================================
# integer decoding helpers - mainly used by des_crypt family
@@ -724,9 +718,8 @@ class Base64Engine(object):
raise TypeError("source must be bytes, not %s" % (type(source),))
if len(source) != 1:
raise ValueError("source must be exactly 1 byte")
- if PY3:
- # convert to 8bit int before doing lookup
- source = source[0]
+ # convert to 8bit int before doing lookup
+ source = source[0]
try:
return self._decode64(source)
except KeyError:
@@ -792,13 +785,13 @@ class Base64Engine(object):
pad = -bits % 6
bits += pad
if self.big:
- itr = irange(bits-6, -6, -6)
+ itr = range(bits-6, -6, -6)
# shift to add lsb padding.
value <<= pad
else:
- itr = irange(0, bits, 6)
+ itr = range(0, bits, 6)
# padding is msb, so no change needed.
- return join_byte_elems(imap(self._encode64,
+ return bytes(map(self._encode64,
((value>>off) & 0x3f for off in itr)))
#---------------------------------------------------------------
@@ -809,10 +802,7 @@ class Base64Engine(object):
"""encodes 6-bit integer -> single hash64 character"""
if value < 0 or value > 63:
raise ValueError("value out of range")
- if PY3:
- return self.bytemap[value:value+1]
- else:
- return self._encode64(value)
+ return self.bytemap[value:value+1]
def encode_int12(self, value):
"""encodes 12-bit integer -> 2 char string"""
@@ -821,7 +811,7 @@ class Base64Engine(object):
raw = [value & 0x3f, (value>>6) & 0x3f]
if self.big:
raw = reversed(raw)
- return join_byte_elems(imap(self._encode64, raw))
+ return bytes(map(self._encode64, raw))
def encode_int24(self, value):
"""encodes 24-bit integer -> 4 char string"""
@@ -831,7 +821,7 @@ class Base64Engine(object):
(value>>12) & 0x3f, (value>>18) & 0x3f]
if self.big:
raw = reversed(raw)
- return join_byte_elems(imap(self._encode64, raw))
+ return bytes(map(self._encode64, raw))
def encode_int30(self, value):
"""decode 5 char string -> 30 bit integer"""
@@ -862,7 +852,7 @@ class LazyBase64Engine(Base64Engine):
def _lazy_init(self):
args, kwds = self._lazy_opts
- super(LazyBase64Engine, self).__init__(*args, **kwds)
+ super().__init__(*args, **kwds)
del self._lazy_opts
self.__class__ = Base64Engine
diff --git a/passlib/utils/compat/__init__.py b/passlib/utils/compat/__init__.py
index f6ead24..06bd266 100644
--- a/passlib/utils/compat/__init__.py
+++ b/passlib/utils/compat/__init__.py
@@ -7,14 +7,10 @@
# python version
#------------------------------------------------------------------------
import sys
-PY2 = sys.version_info < (3,0)
-PY3 = sys.version_info >= (3,0)
# make sure it's not an unsupported version, even if we somehow got this far
-if sys.version_info < (2,6) or (3,0) <= sys.version_info < (3,2):
- raise RuntimeError("Passlib requires Python 2.6, 2.7, or >= 3.2 (as of passlib 1.7)")
-
-PY26 = sys.version_info < (2,7)
+if sys.version_info < (3, 5):
+ raise RuntimeError("Passlib requires Python >= 3.5 (as of passlib 1.8)")
#------------------------------------------------------------------------
# python implementation
@@ -26,18 +22,10 @@ PYPY = hasattr(sys, "pypy_version_info")
if PYPY and sys.pypy_version_info < (2,0):
raise RuntimeError("passlib requires pypy >= 2.0 (as of passlib 1.7)")
-# e.g. '2.7.7\n[Pyston 0.5.1]'
-# NOTE: deprecated support 2019-11
-PYSTON = "Pyston" in sys.version
-
#=============================================================================
# common imports
#=============================================================================
import logging; log = logging.getLogger(__name__)
-if PY3:
- import builtins
-else:
- import __builtin__ as builtins
def add_doc(obj, doc):
"""add docstring to an object"""
@@ -47,38 +35,15 @@ def add_doc(obj, doc):
# the default exported vars
#=============================================================================
__all__ = [
- # python versions
- 'PY2', 'PY3', 'PY26',
-
- # io
- 'BytesIO', 'StringIO', 'NativeStringIO', 'SafeConfigParser',
- 'print_',
-
# type detection
## 'is_mapping',
- 'int_types',
'num_types',
- 'unicode_or_bytes_types',
- 'native_string_types',
+ 'unicode_or_bytes',
# unicode/bytes types & helpers
- 'u',
- 'unicode',
- 'uascii_to_str', 'bascii_to_str',
- 'str_to_uascii', 'str_to_bascii',
+ 'bascii_to_str',
+ 'str_to_bascii',
'join_unicode', 'join_bytes',
- 'join_byte_values', 'join_byte_elems',
- 'byte_elem_value',
- 'iter_byte_values',
-
- # iteration helpers
- 'irange', #'lrange',
- 'imap', 'lmap',
- 'iteritems', 'itervalues',
- 'next',
-
- # collections
- 'OrderedDict',
# context helpers
'nullcontext',
@@ -94,179 +59,47 @@ _lazy_attrs = dict()
#=============================================================================
# unicode & bytes types
#=============================================================================
-if PY3:
- unicode = str
-
- # TODO: once we drop python 3.2 support, can use u'' again!
- def u(s):
- assert isinstance(s, str)
- return s
-
- unicode_or_bytes_types = (str, bytes)
- native_string_types = (unicode,)
-else:
- unicode = builtins.unicode
-
- def u(s):
- assert isinstance(s, str)
- return s.decode("unicode_escape")
-
- unicode_or_bytes_types = (basestring,)
- native_string_types = (basestring,)
-
-# shorter preferred aliases
-unicode_or_bytes = unicode_or_bytes_types
-unicode_or_str = native_string_types
-
-# unicode -- unicode type, regardless of python version
-# bytes -- bytes type, regardless of python version
-# unicode_or_bytes_types -- types that text can occur in, whether encoded or not
-# native_string_types -- types that native python strings (dict keys etc) can occur in.
+#: alias for isinstance() tests to detect any string type
+unicode_or_bytes = (str, bytes)
#=============================================================================
# unicode & bytes helpers
#=============================================================================
# function to join list of unicode strings
-join_unicode = u('').join
+join_unicode = u''.join
# function to join list of byte strings
join_bytes = b''.join
-if PY3:
- def uascii_to_str(s):
- assert isinstance(s, unicode)
- return s
+if True: # legacy PY3 indent
def bascii_to_str(s):
assert isinstance(s, bytes)
return s.decode("ascii")
- def str_to_uascii(s):
- assert isinstance(s, str)
- return s
-
def str_to_bascii(s):
assert isinstance(s, str)
return s.encode("ascii")
- join_byte_values = join_byte_elems = bytes
-
- def byte_elem_value(elem):
- assert isinstance(elem, int)
- return elem
-
- def iter_byte_values(s):
- assert isinstance(s, bytes)
- return s
-
def iter_byte_chars(s):
assert isinstance(s, bytes)
# FIXME: there has to be a better way to do this
return (bytes([c]) for c in s)
-else:
- def uascii_to_str(s):
- assert isinstance(s, unicode)
- return s.encode("ascii")
-
- def bascii_to_str(s):
- assert isinstance(s, bytes)
- return s
-
- def str_to_uascii(s):
- assert isinstance(s, str)
- return s.decode("ascii")
-
- def str_to_bascii(s):
- assert isinstance(s, str)
- return s
-
- def join_byte_values(values):
- return join_bytes(chr(v) for v in values)
-
- join_byte_elems = join_bytes
-
- byte_elem_value = ord
-
- def iter_byte_values(s):
- assert isinstance(s, bytes)
- return (ord(c) for c in s)
-
- def iter_byte_chars(s):
- assert isinstance(s, bytes)
- return s
-
-add_doc(uascii_to_str, "helper to convert ascii unicode -> native str")
+# TODO: move docstrings to funcs...
add_doc(bascii_to_str, "helper to convert ascii bytes -> native str")
-add_doc(str_to_uascii, "helper to convert ascii native str -> unicode")
add_doc(str_to_bascii, "helper to convert ascii native str -> bytes")
-# join_byte_values -- function to convert list of ordinal integers to byte string.
-
-# join_byte_elems -- function to convert list of byte elements to byte string;
-# i.e. what's returned by ``b('a')[0]``...
-# this is b('a') under PY2, but 97 under PY3.
-
# byte_elem_value -- function to convert byte element to integer -- a noop under PY3
-add_doc(iter_byte_values, "iterate over byte string as sequence of ints 0-255")
add_doc(iter_byte_chars, "iterate over byte string as sequence of 1-byte strings")
#=============================================================================
# numeric
#=============================================================================
-if PY3:
- int_types = (int,)
- num_types = (int, float)
-else:
- int_types = (int, long)
- num_types = (int, long, float)
-
-#=============================================================================
-# iteration helpers
-#
-# irange - range iterable / view (xrange under py2, range under py3)
-# lrange - range list (range under py2, list(range()) under py3)
-#
-# imap - map to iterator
-# lmap - map to list
-#=============================================================================
-if PY3:
- irange = range
- ##def lrange(*a,**k):
- ## return list(range(*a,**k))
-
- def lmap(*a, **k):
- return list(map(*a,**k))
- imap = map
-
- def iteritems(d):
- return d.items()
- def itervalues(d):
- return d.values()
-
- def nextgetter(obj):
- return obj.__next__
-
- izip = zip
-
-else:
- irange = xrange
- ##lrange = range
- lmap = map
- from itertools import imap, izip
-
- def iteritems(d):
- return d.iteritems()
- def itervalues(d):
- return d.itervalues()
-
- def nextgetter(obj):
- return obj.next
-
-add_doc(nextgetter, "return function that yields successive values from iterable")
+num_types = (int, float)
#=============================================================================
# typing
@@ -278,103 +111,10 @@ add_doc(nextgetter, "return function that yields successive values from iterable
#=============================================================================
# introspection
#=============================================================================
-if PY3:
- method_function_attr = "__func__"
-else:
- method_function_attr = "im_func"
def get_method_function(func):
"""given (potential) method, return underlying function"""
- return getattr(func, method_function_attr, func)
-
-def get_unbound_method_function(func):
- """given unbound method, return underlying function"""
- return func if PY3 else func.__func__
-
-def error_from(exc, # *,
- cause=None):
- """
- backward compat hack to suppress exception cause in python3.3+
-
- one python < 3.3 support is dropped, can replace all uses with "raise exc from None"
- """
- exc.__cause__ = cause
- exc.__suppress_context__ = True
- return exc
-
-# legacy alias
-suppress_cause = error_from
-
-#=============================================================================
-# input/output
-#=============================================================================
-if PY3:
- _lazy_attrs = dict(
- BytesIO="io.BytesIO",
- UnicodeIO="io.StringIO",
- NativeStringIO="io.StringIO",
- SafeConfigParser="configparser.ConfigParser",
- )
-
- print_ = getattr(builtins, "print")
-
-else:
- _lazy_attrs = dict(
- BytesIO="cStringIO.StringIO",
- UnicodeIO="StringIO.StringIO",
- NativeStringIO="cStringIO.StringIO",
- SafeConfigParser="ConfigParser.SafeConfigParser",
- )
-
- def print_(*args, **kwds):
- """The new-style print function."""
- # extract kwd args
- fp = kwds.pop("file", sys.stdout)
- sep = kwds.pop("sep", None)
- end = kwds.pop("end", None)
- if kwds:
- raise TypeError("invalid keyword arguments")
-
- # short-circuit if no target
- if fp is None:
- return
-
- # use unicode or bytes ?
- want_unicode = isinstance(sep, unicode) or isinstance(end, unicode) or \
- any(isinstance(arg, unicode) for arg in args)
-
- # pick default end sequence
- if end is None:
- end = u("\n") if want_unicode else "\n"
- elif not isinstance(end, unicode_or_bytes_types):
- raise TypeError("end must be None or a string")
-
- # pick default separator
- if sep is None:
- sep = u(" ") if want_unicode else " "
- elif not isinstance(sep, unicode_or_bytes_types):
- raise TypeError("sep must be None or a string")
-
- # write to buffer
- first = True
- write = fp.write
- for arg in args:
- if first:
- first = False
- else:
- write(sep)
- if not isinstance(arg, basestring):
- arg = str(arg)
- write(arg)
- write(end)
-
-#=============================================================================
-# collections
-#=============================================================================
-if PY26:
- _lazy_attrs['OrderedDict'] = 'passlib.utils.compat._ordered_dict.OrderedDict'
-else:
- _lazy_attrs['OrderedDict'] = 'collections.OrderedDict'
+ return getattr(func, "__func__", func)
#=============================================================================
# context managers
diff --git a/passlib/utils/compat/_ordered_dict.py b/passlib/utils/compat/_ordered_dict.py
deleted file mode 100644
index cfd766d..0000000
--- a/passlib/utils/compat/_ordered_dict.py
+++ /dev/null
@@ -1,242 +0,0 @@
-"""passlib.utils.compat._ordered_dict -- backport of collections.OrderedDict for py26
-
-taken from stdlib-suggested recipe at http://code.activestate.com/recipes/576693/
-
-this should be imported from passlib.utils.compat.OrderedDict, not here.
-"""
-
-try:
- from thread import get_ident as _get_ident
-except ImportError:
- from dummy_thread import get_ident as _get_ident
-
-class OrderedDict(dict):
- """Dictionary that remembers insertion order"""
- # An inherited dict maps keys to values.
- # The inherited dict provides __getitem__, __len__, __contains__, and get.
- # The remaining methods are order-aware.
- # Big-O running times for all methods are the same as for regular dictionaries.
-
- # The internal self.__map dictionary maps keys to links in a doubly linked list.
- # The circular doubly linked list starts and ends with a sentinel element.
- # The sentinel element never gets deleted (this simplifies the algorithm).
- # Each link is stored as a list of length three: [PREV, NEXT, KEY].
-
- def __init__(self, *args, **kwds):
- '''Initialize an ordered dictionary. Signature is the same as for
- regular dictionaries, but keyword arguments are not recommended
- because their insertion order is arbitrary.
-
- '''
- if len(args) > 1:
- raise TypeError('expected at most 1 arguments, got %d' % len(args))
- try:
- self.__root
- except AttributeError:
- self.__root = root = [] # sentinel node
- root[:] = [root, root, None]
- self.__map = {}
- self.__update(*args, **kwds)
-
- def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
- 'od.__setitem__(i, y) <==> od[i]=y'
- # Setting a new item creates a new link which goes at the end of the linked
- # list, and the inherited dictionary is updated with the new key/value pair.
- if key not in self:
- root = self.__root
- last = root[0]
- last[1] = root[0] = self.__map[key] = [last, root, key]
- dict_setitem(self, key, value)
-
- def __delitem__(self, key, dict_delitem=dict.__delitem__):
- 'od.__delitem__(y) <==> del od[y]'
- # Deleting an existing item uses self.__map to find the link which is
- # then removed by updating the links in the predecessor and successor nodes.
- dict_delitem(self, key)
- link_prev, link_next, key = self.__map.pop(key)
- link_prev[1] = link_next
- link_next[0] = link_prev
-
- def __iter__(self):
- 'od.__iter__() <==> iter(od)'
- root = self.__root
- curr = root[1]
- while curr is not root:
- yield curr[2]
- curr = curr[1]
-
- def __reversed__(self):
- 'od.__reversed__() <==> reversed(od)'
- root = self.__root
- curr = root[0]
- while curr is not root:
- yield curr[2]
- curr = curr[0]
-
- def clear(self):
- 'od.clear() -> None. Remove all items from od.'
- try:
- for node in self.__map.itervalues():
- del node[:]
- root = self.__root
- root[:] = [root, root, None]
- self.__map.clear()
- except AttributeError:
- pass
- dict.clear(self)
-
- def popitem(self, last=True):
- '''od.popitem() -> (k, v), return and remove a (key, value) pair.
- Pairs are returned in LIFO order if last is true or FIFO order if false.
-
- '''
- if not self:
- raise KeyError('dictionary is empty')
- root = self.__root
- if last:
- link = root[0]
- link_prev = link[0]
- link_prev[1] = root
- root[0] = link_prev
- else:
- link = root[1]
- link_next = link[1]
- root[1] = link_next
- link_next[0] = root
- key = link[2]
- del self.__map[key]
- value = dict.pop(self, key)
- return key, value
-
- # -- the following methods do not depend on the internal structure --
-
- def keys(self):
- 'od.keys() -> list of keys in od'
- return list(self)
-
- def values(self):
- 'od.values() -> list of values in od'
- return [self[key] for key in self]
-
- def items(self):
- 'od.items() -> list of (key, value) pairs in od'
- return [(key, self[key]) for key in self]
-
- def iterkeys(self):
- 'od.iterkeys() -> an iterator over the keys in od'
- return iter(self)
-
- def itervalues(self):
- 'od.itervalues -> an iterator over the values in od'
- for k in self:
- yield self[k]
-
- def iteritems(self):
- 'od.iteritems -> an iterator over the (key, value) items in od'
- for k in self:
- yield (k, self[k])
-
- def update(*args, **kwds):
- '''od.update(E, **F) -> None. Update od from dict/iterable E and F.
-
- If E is a dict instance, does: for k in E: od[k] = E[k]
- If E has a .keys() method, does: for k in E.keys(): od[k] = E[k]
- Or if E is an iterable of items, does: for k, v in E: od[k] = v
- In either case, this is followed by: for k, v in F.items(): od[k] = v
-
- '''
- if len(args) > 2:
- raise TypeError('update() takes at most 2 positional '
- 'arguments (%d given)' % (len(args),))
- elif not args:
- raise TypeError('update() takes at least 1 argument (0 given)')
- self = args[0]
- # Make progressively weaker assumptions about "other"
- other = ()
- if len(args) == 2:
- other = args[1]
- if isinstance(other, dict):
- for key in other:
- self[key] = other[key]
- elif hasattr(other, 'keys'):
- for key in other.keys():
- self[key] = other[key]
- else:
- for key, value in other:
- self[key] = value
- for key, value in kwds.items():
- self[key] = value
-
- __update = update # let subclasses override update without breaking __init__
-
- __marker = object()
-
- def pop(self, key, default=__marker):
- '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
- If key is not found, d is returned if given, otherwise KeyError is raised.
-
- '''
- if key in self:
- result = self[key]
- del self[key]
- return result
- if default is self.__marker:
- raise KeyError(key)
- return default
-
- def setdefault(self, key, default=None):
- 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
- if key in self:
- return self[key]
- self[key] = default
- return default
-
- def __repr__(self, _repr_running={}):
- 'od.__repr__() <==> repr(od)'
- call_key = id(self), _get_ident()
- if call_key in _repr_running:
- return '...'
- _repr_running[call_key] = 1
- try:
- if not self:
- return '%s()' % (self.__class__.__name__,)
- return '%s(%r)' % (self.__class__.__name__, self.items())
- finally:
- del _repr_running[call_key]
-
- def __reduce__(self):
- 'Return state information for pickling'
- items = [[k, self[k]] for k in self]
- inst_dict = vars(self).copy()
- for k in vars(OrderedDict()):
- inst_dict.pop(k, None)
- if inst_dict:
- return (self.__class__, (items,), inst_dict)
- return self.__class__, (items,)
-
- def copy(self):
- 'od.copy() -> a shallow copy of od'
- return self.__class__(self)
-
- @classmethod
- def fromkeys(cls, iterable, value=None):
- '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
- and values equal to v (which defaults to None).
-
- '''
- d = cls()
- for key in iterable:
- d[key] = value
- return d
-
- def __eq__(self, other):
- '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
- while comparison to a regular mapping is order-insensitive.
-
- '''
- if isinstance(other, OrderedDict):
- return len(self)==len(other) and self.items() == other.items()
- return dict.__eq__(self, other)
-
- def __ne__(self, other):
- return not self == other
diff --git a/passlib/utils/decor.py b/passlib/utils/decor.py
index 9041d5d..e1fd601 100644
--- a/passlib/utils/decor.py
+++ b/passlib/utils/decor.py
@@ -5,7 +5,6 @@ passlib.utils.decor -- helper decorators & properties
# imports
#=============================================================================
# core
-from __future__ import absolute_import, division, print_function
import logging
log = logging.getLogger(__name__)
from functools import wraps, update_wrapper
@@ -13,7 +12,6 @@ import types
from warnings import warn
# site
# pkg
-from passlib.utils.compat import PY3
# local
__all__ = [
"classproperty",
@@ -33,15 +31,12 @@ 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
+ # XXX: rename to .fget to match property?
+ self.__func__ = func
def __get__(self, obj, cls):
- return self.im_func(cls)
+ return self.__func__(cls)
- @property
- def __func__(self):
- """py3 compatible alias"""
- return self.im_func
class hybrid_method(object):
"""
@@ -50,16 +45,14 @@ class hybrid_method(object):
"""
def __init__(self, func):
+ # XXX: rename to .fget to match property?
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)
+ return types.MethodType(self.func, obj)
#=============================================================================
# memoization
@@ -104,13 +97,6 @@ class memoized_property(object):
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).
@@ -174,8 +160,7 @@ def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True,
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__
+ func = func.__func__
opts = dict(
mod=func_module or func.__module__,
name=func.__name__,
diff --git a/passlib/utils/des.py b/passlib/utils/des.py
index 034bfc4..1c407b9 100644
--- a/passlib/utils/des.py
+++ b/passlib/utils/des.py
@@ -17,30 +17,15 @@ warn("the 'passlib.utils.des' module has been relocated to 'passlib.crypto.des'
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",
+expand_des_key = deprecated_function(deprecated="1.7", removed="2.0",
replacement="passlib.crypto.des.expand_des_key")(expand_des_key)
-des_encrypt_block = deprecated_function(deprecated="1.7", removed="1.8",
+des_encrypt_block = deprecated_function(deprecated="1.7", removed="2.0",
replacement="passlib.crypto.des.des_encrypt_block")(des_encrypt_block)
-des_encrypt_int_block = deprecated_function(deprecated="1.7", removed="1.8",
+des_encrypt_int_block = deprecated_function(deprecated="1.7", removed="2.0",
replacement="passlib.crypto.des.des_encrypt_int_block")(des_encrypt_int_block)
#=============================================================================
-# deprecated functions -- not carried over to passlib.crypto.des
-#=============================================================================
-import struct
-_unpack_uint64 = struct.Struct(">Q").unpack
-
-@deprecated_function(deprecated="1.6", removed="1.8",
- replacement="passlib.crypto.des.des_encrypt_int_block()")
-def mdes_encrypt_int_block(key, input, salt=0, rounds=1): # pragma: no cover -- deprecated & unused
- if isinstance(key, bytes):
- if len(key) == 7:
- key = expand_des_key(key)
- key = _unpack_uint64(key)[0]
- return des_encrypt_int_block(key, input, salt, rounds)
-
-#=============================================================================
# eof
#=============================================================================
diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py
index f8681fa..cea096c 100644
--- a/passlib/utils/handlers.py
+++ b/passlib/utils/handlers.py
@@ -2,7 +2,6 @@
#=============================================================================
# imports
#=============================================================================
-from __future__ import with_statement
# core
import inspect
import logging; log = logging.getLogger(__name__)
@@ -27,9 +26,7 @@ from passlib.utils.binary import (
HEX_CHARS, UPPER_HEX_CHARS, LOWER_HEX_CHARS,
ALL_BYTE_VALUES,
)
-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.compat import join_unicode, unicode_or_bytes
from passlib.utils.decor import classproperty, deprecated_method
# local
__all__ = [
@@ -113,19 +110,19 @@ def extract_settings_kwds(handler, kwds):
#=============================================================================
# parsing helpers
#=============================================================================
-_UDOLLAR = u("$")
-_UZERO = u("0")
+_UDOLLAR = u"$"
+_UZERO = u"0"
def validate_secret(secret):
"""ensure secret has correct type & size"""
- if not isinstance(secret, unicode_or_bytes_types):
+ if not isinstance(secret, unicode_or_bytes):
raise exc.ExpectedStringError(secret, "secret")
if len(secret) > MAX_PASSWORD_SIZE:
raise exc.PasswordSizeError(MAX_PASSWORD_SIZE)
def to_unicode_for_identify(hash):
"""convert hash to unicode for identify method"""
- if isinstance(hash, unicode):
+ if isinstance(hash, str):
return hash
elif isinstance(hash, bytes):
# try as utf-8, but if it fails, use foolproof latin-1,
@@ -144,8 +141,8 @@ def parse_mc2(hash, prefix, sep=_UDOLLAR, handler=None):
this expects a hash of the format :samp:`{prefix}{salt}[${checksum}]`,
such as md5_crypt, and parses it into salt / checksum portions.
- :arg hash: the hash to parse (bytes or unicode)
- :arg prefix: the identifying prefix (unicode)
+ :arg hash: the hash to parse (bytes or str)
+ :arg prefix: the identifying prefix (str)
:param sep: field separator (unicode, defaults to ``$``).
:param handler: handler class to pass to error constructors.
@@ -154,12 +151,12 @@ def parse_mc2(hash, prefix, sep=_UDOLLAR, handler=None):
"""
# detect prefix
hash = to_unicode(hash, "ascii", "hash")
- assert isinstance(prefix, unicode)
+ assert isinstance(prefix, str)
if not hash.startswith(prefix):
raise exc.InvalidHashError(handler)
# parse 2-part hash or 1-part config string
- assert isinstance(sep, unicode)
+ assert isinstance(sep, str)
parts = hash[len(prefix):].split(sep)
if len(parts) == 2:
salt, chk = parts
@@ -193,12 +190,12 @@ def parse_mc3(hash, prefix, sep=_UDOLLAR, rounds_base=10,
"""
# detect prefix
hash = to_unicode(hash, "ascii", "hash")
- assert isinstance(prefix, unicode)
+ assert isinstance(prefix, str)
if not hash.startswith(prefix):
raise exc.InvalidHashError(handler)
# parse 3-part hash or 2-part config string
- assert isinstance(sep, unicode)
+ assert isinstance(sep, str)
parts = hash[len(prefix):].split(sep)
if len(parts) == 3:
rounds, salt, chk = parts
@@ -266,7 +263,7 @@ def parse_int(source, base=10, default=None, param="value", handler=None):
#=============================================================================
# formatting helpers
#=============================================================================
-def render_mc2(ident, salt, checksum, sep=u("$")):
+def render_mc2(ident, salt, checksum, sep=u"$"):
"""format hash using 2-part modular crypt format; inverse of parse_mc2()
returns native string with format :samp:`{ident}{salt}[${checksum}]`,
@@ -284,9 +281,9 @@ def render_mc2(ident, salt, checksum, sep=u("$")):
parts = [ident, salt, sep, checksum]
else:
parts = [ident, salt]
- return uascii_to_str(join_unicode(parts))
+ return join_unicode(parts)
-def render_mc3(ident, rounds, salt, checksum, sep=u("$"), rounds_base=10):
+def render_mc3(ident, rounds, salt, checksum, sep=u"$", rounds_base=10):
"""format hash using 3-part modular crypt format; inverse of parse_mc3()
returns native string with format :samp:`{ident}[{rounds}$]{salt}[${checksum}]`,
@@ -303,17 +300,17 @@ def render_mc3(ident, rounds, salt, checksum, sep=u("$"), rounds_base=10):
config or hash (native str)
"""
if rounds is None:
- rounds = u('')
+ rounds = u''
elif rounds_base == 16:
- rounds = u("%x") % rounds
+ rounds = u"%x" % rounds
else:
assert rounds_base == 10
- rounds = unicode(rounds)
+ rounds = str(rounds)
if checksum:
parts = [ident, rounds, sep, salt, sep, checksum]
else:
parts = [ident, rounds, sep, salt]
- return uascii_to_str(join_unicode(parts))
+ return join_unicode(parts)
def mask_value(value, show=4, pct=0.125, char=u"*"):
@@ -336,12 +333,12 @@ def mask_value(value, show=4, pct=0.125, char=u"*"):
"""
if value is None:
return None
- if not isinstance(value, unicode):
+ if not isinstance(value, str):
if isinstance(value, bytes):
from passlib.utils.binary import ab64_encode
value = ab64_encode(value).decode("ascii")
else:
- value = unicode(value)
+ value = str(value)
size = len(value)
show = min(show, int(size * pct))
return value[:show] + char * (size - show)
@@ -372,7 +369,7 @@ def norm_integer(handler, value, min=1, max=None, # *
:returns: validated value
"""
# check type
- if not isinstance(value, int_types):
+ if not isinstance(value, int):
raise exc.ExpectedTypeError(value, "integer", param)
# check minimum
@@ -452,7 +449,7 @@ class TruncateMixin(MinimalHandler):
@classmethod
def using(cls, truncate_error=None, **kwds):
- subcls = super(TruncateMixin, cls).using(**kwds)
+ subcls = super().using(**kwds)
if truncate_error is not None:
truncate_error = as_bool(truncate_error, param="truncate_error")
if truncate_error is not None:
@@ -618,7 +615,7 @@ class GenericHandler(MinimalHandler):
#===================================================================
def __init__(self, checksum=None, use_defaults=False, **kwds):
self.use_defaults = use_defaults
- super(GenericHandler, self).__init__(**kwds)
+ super().__init__(**kwds)
if checksum is not None:
# XXX: do we need to set .relaxed for checksum coercion?
self.checksum = self._norm_checksum(checksum)
@@ -641,12 +638,12 @@ class GenericHandler(MinimalHandler):
if not isinstance(checksum, bytes):
raise exc.ExpectedTypeError(checksum, "bytes", "checksum")
- elif not isinstance(checksum, unicode):
+ elif not isinstance(checksum, str):
if isinstance(checksum, bytes) and relaxed:
warn("checksum should be unicode, not bytes", PasslibHashWarning)
checksum = checksum.decode("ascii")
else:
- raise exc.ExpectedTypeError(checksum, "unicode", "checksum")
+ raise exc.ExpectedTypeError(checksum, "str", "checksum")
# check size
cc = self.checksum_size
@@ -713,8 +710,7 @@ class GenericHandler(MinimalHandler):
:returns:
hash string with salt & digest included.
- should return native string type (ascii-bytes under python 2,
- unicode under python 3)
+ should return native str.
"""
raise NotImplementedError("%s must implement from_string()" % (self.__class__,))
@@ -752,7 +748,7 @@ class GenericHandler(MinimalHandler):
string, taking config from object state
calc checksum implementations may assume secret is always
- either unicode or bytes, checks are performed by verify/etc.
+ either str or bytes, checks are performed by verify/etc.
"""
raise NotImplementedError("%s must implement _calc_checksum()" %
(self.__class__,))
@@ -906,7 +902,7 @@ class GenericHandler(MinimalHandler):
def bitsize(cls, **kwds):
"""[experimental method] return info about bitsizes of hash"""
try:
- info = super(GenericHandler, cls).bitsize(**kwds)
+ info = super().bitsize(**kwds)
except AttributeError:
info = {}
cc = ALL_BYTE_VALUES if cls._checksum_is_bytes else cls.checksum_chars
@@ -940,7 +936,7 @@ class StaticHandler(GenericHandler):
setting_kwds = ()
# optional constant prefix subclasses can specify
- _hash_prefix = u("")
+ _hash_prefix = u""
@classmethod
def from_string(cls, hash, **context):
@@ -966,46 +962,7 @@ class StaticHandler(GenericHandler):
return hash
def to_string(self):
- return uascii_to_str(self._hash_prefix + self.checksum)
-
- # per-subclass: stores dynamically created subclass used by _calc_checksum() stub
- __cc_compat_hack = None
-
- def _calc_checksum(self, secret):
- """given secret; calcuate and return encoded checksum portion of hash
- string, taking config from object state
- """
- # NOTE: prior to 1.6, StaticHandler required classes implement genhash
- # instead of this method. so if we reach here, we try calling genhash.
- # if that succeeds, we issue deprecation warning. if it fails,
- # we'll just recurse back to here, but in a different instance.
- # so before we call genhash, we create a subclass which handles
- # throwing the NotImplementedError.
- cls = self.__class__
- assert cls.__module__ != __name__
- wrapper_cls = cls.__cc_compat_hack
- if wrapper_cls is None:
- def inner(self, secret):
- raise NotImplementedError("%s must implement _calc_checksum()" %
- (cls,))
- wrapper_cls = cls.__cc_compat_hack = type(cls.__name__ + "_wrapper",
- (cls,), dict(_calc_checksum=inner, __module__=cls.__module__))
- context = dict((k,getattr(self,k)) for k in self.context_kwds)
- # NOTE: passing 'config=None' here even though not currently allowed by ifc,
- # since it *is* allowed under the old 1.5 ifc we're checking for here.
- try:
- hash = wrapper_cls.genhash(secret, None, **context)
- except TypeError as err:
- if str(err) == "config must be string":
- raise NotImplementedError("%s must implement _calc_checksum()" %
- (cls,))
- else:
- raise
- warn("%r should be updated to implement StaticHandler._calc_checksum() "
- "instead of StaticHandler.genhash(), support for the latter "
- "style will be removed in Passlib 1.8" % cls,
- DeprecationWarning)
- return str_to_uascii(hash)
+ return self._hash_prefix + self.checksum
#=============================================================================
# GenericHandler mixin classes
@@ -1016,7 +973,7 @@ class HasEncodingContext(GenericHandler):
default_encoding = "utf-8"
def __init__(self, encoding=None, **kwds):
- super(HasEncodingContext, self).__init__(**kwds)
+ super().__init__(**kwds)
self.encoding = encoding or self.default_encoding
class HasUserContext(GenericHandler):
@@ -1024,7 +981,7 @@ class HasUserContext(GenericHandler):
context_kwds = ("user",)
def __init__(self, user=None, **kwds):
- super(HasUserContext, self).__init__(**kwds)
+ super().__init__(**kwds)
self.user = user
# XXX: would like to validate user input here, but calls to from_string()
@@ -1033,16 +990,16 @@ class HasUserContext(GenericHandler):
# wrap funcs to accept 'user' as positional arg for ease of use.
@classmethod
def hash(cls, secret, user=None, **context):
- return super(HasUserContext, cls).hash(secret, user=user, **context)
+ return super().hash(secret, user=user, **context)
@classmethod
def verify(cls, secret, hash, user=None, **context):
- return super(HasUserContext, cls).verify(secret, hash, user=user, **context)
+ return super().verify(secret, hash, user=user, **context)
@deprecated_method(deprecated="1.7", removed="2.0")
@classmethod
def genhash(cls, secret, config, user=None, **context):
- return super(HasUserContext, cls).genhash(secret, config, user=user, **context)
+ return super().genhash(secret, config, user=user, **context)
# XXX: how to guess the entropy of a username?
# most of these hashes are for a system (e.g. Oracle)
@@ -1051,7 +1008,7 @@ class HasUserContext(GenericHandler):
# need to find good reference about this.
##@classmethod
##def bitsize(cls, **kwds):
- ## info = super(HasUserContext, cls).bitsize(**kwds)
+ ## info = super().bitsize(**kwds)
## info['user'] = xxx
## return info
@@ -1097,7 +1054,7 @@ class HasManyIdents(GenericHandler):
#===================================================================
# class attrs
#===================================================================
- default_ident = None # should be unicode
+ default_ident = None # should be str
ident_values = None # should be list of unicode strings
ident_aliases = None # should be dict of unicode -> unicode
# NOTE: any aliases provided to norm_ident() as bytes
@@ -1134,7 +1091,7 @@ class HasManyIdents(GenericHandler):
default_ident = ident
# create subclass
- subcls = super(HasManyIdents, cls).using(**kwds)
+ subcls = super().using(**kwds)
# add custom default ident
# (NOTE: creates instance to run value through _norm_ident())
@@ -1146,7 +1103,7 @@ class HasManyIdents(GenericHandler):
# init
#===================================================================
def __init__(self, ident=None, **kwds):
- super(HasManyIdents, self).__init__(**kwds)
+ super().__init__(**kwds)
# init ident
if ident is not None:
@@ -1333,12 +1290,12 @@ class HasSalt(GenericHandler):
default_salt_size = salt_size
# generate new subclass
- subcls = super(HasSalt, cls).using(**kwds)
+ subcls = super().using(**kwds)
# replace default_rounds
relaxed = kwds.get("relaxed")
if default_salt_size is not None:
- if isinstance(default_salt_size, native_string_types):
+ if isinstance(default_salt_size, str):
default_salt_size = int(default_salt_size)
subcls.default_salt_size = subcls._clip_to_valid_salt_size(default_salt_size,
param="salt_size",
@@ -1406,7 +1363,7 @@ class HasSalt(GenericHandler):
# init
#===================================================================
def __init__(self, salt=None, **kwds):
- super(HasSalt, self).__init__(**kwds)
+ super().__init__(**kwds)
if salt is not None:
salt = self._parse_salt(salt)
elif self.use_defaults:
@@ -1446,12 +1403,12 @@ class HasSalt(GenericHandler):
if not isinstance(salt, bytes):
raise exc.ExpectedTypeError(salt, "bytes", "salt")
else:
- if not isinstance(salt, unicode):
+ if not isinstance(salt, str):
# NOTE: allowing bytes under py2 so salt can be native str.
- if isinstance(salt, bytes) and (PY2 or relaxed):
+ if relaxed and isinstance(salt, bytes):
salt = salt.decode("ascii")
else:
- raise exc.ExpectedTypeError(salt, "unicode", "salt")
+ raise exc.ExpectedTypeError(salt, "str", "salt")
# check charset
sc = cls.salt_chars
@@ -1495,7 +1452,7 @@ class HasSalt(GenericHandler):
@classmethod
def bitsize(cls, salt_size=None, **kwds):
"""[experimental method] return info about bitsizes of hash"""
- info = super(HasSalt, cls).bitsize(**kwds)
+ info = super().bitsize(**kwds)
if salt_size is None:
salt_size = cls.default_salt_size
# FIXME: this may overestimate size due to padding bits
@@ -1650,7 +1607,7 @@ class HasRounds(GenericHandler):
default_rounds = rounds
# generate new subclass
- subcls = super(HasRounds, cls).using(**kwds)
+ subcls = super().using(**kwds)
# replace min_desired_rounds
relaxed = kwds.get("relaxed")
@@ -1659,7 +1616,7 @@ class HasRounds(GenericHandler):
min_desired_rounds = cls.min_desired_rounds
else:
explicit_min_rounds = True
- if isinstance(min_desired_rounds, native_string_types):
+ if isinstance(min_desired_rounds, str):
min_desired_rounds = int(min_desired_rounds)
subcls.min_desired_rounds = subcls._norm_rounds(min_desired_rounds,
param="min_desired_rounds",
@@ -1669,7 +1626,7 @@ class HasRounds(GenericHandler):
if max_desired_rounds is None:
max_desired_rounds = cls.max_desired_rounds
else:
- if isinstance(max_desired_rounds, native_string_types):
+ if isinstance(max_desired_rounds, str):
max_desired_rounds = int(max_desired_rounds)
if min_desired_rounds and max_desired_rounds < min_desired_rounds:
msg = "%s: max_desired_rounds (%r) below min_desired_rounds (%r)" % \
@@ -1685,7 +1642,7 @@ class HasRounds(GenericHandler):
# replace default_rounds
if default_rounds is not None:
- if isinstance(default_rounds, native_string_types):
+ if isinstance(default_rounds, str):
default_rounds = int(default_rounds)
if min_desired_rounds and default_rounds < min_desired_rounds:
raise ValueError("%s: default_rounds (%r) below min_desired_rounds (%r)" %
@@ -1703,7 +1660,7 @@ class HasRounds(GenericHandler):
# replace / set vary_rounds
if vary_rounds is not None:
- if isinstance(vary_rounds, native_string_types):
+ if isinstance(vary_rounds, str):
if vary_rounds.endswith("%"):
vary_rounds = float(vary_rounds[:-1]) * 0.01
elif "." in vary_rounds:
@@ -1782,7 +1739,7 @@ class HasRounds(GenericHandler):
vary_rounds = int(default_rounds * vary_rounds)
# calculate bounds based on default_rounds +/- vary_rounds
- assert vary_rounds >= 0 and isinstance(vary_rounds, int_types)
+ assert vary_rounds >= 0 and isinstance(vary_rounds, int)
lower = linear_to_native(default_rounds - vary_rounds, False)
upper = linear_to_native(default_rounds + vary_rounds, True)
return cls._clip_to_desired_rounds(lower), cls._clip_to_desired_rounds(upper)
@@ -1791,7 +1748,7 @@ class HasRounds(GenericHandler):
# init
#===================================================================
def __init__(self, rounds=None, **kwds):
- super(HasRounds, self).__init__(**kwds)
+ super().__init__(**kwds)
if rounds is not None:
rounds = self._parse_rounds(rounds)
elif self.use_defaults:
@@ -1873,7 +1830,7 @@ class HasRounds(GenericHandler):
max_desired_rounds = self.max_desired_rounds
if max_desired_rounds and self.rounds > max_desired_rounds:
return True
- return super(HasRounds, self)._calc_needs_update(**kwds)
+ return super()._calc_needs_update(**kwds)
#===================================================================
# experimental methods
@@ -1881,7 +1838,7 @@ class HasRounds(GenericHandler):
@classmethod
def bitsize(cls, rounds=None, vary_rounds=.1, **kwds):
"""[experimental method] return info about bitsizes of hash"""
- info = super(HasRounds, cls).bitsize(**kwds)
+ info = super().bitsize(**kwds)
# NOTE: this essentially estimates how many bits of "salt"
# can be added by varying the rounds value just a little bit.
if cls.rounds_cost != "log2":
@@ -1930,9 +1887,9 @@ class ParallelismMixin(GenericHandler):
@classmethod
def using(cls, parallelism=None, **kwds):
- subcls = super(ParallelismMixin, cls).using(**kwds)
+ subcls = super().using(**kwds)
if parallelism is not None:
- if isinstance(parallelism, native_string_types):
+ if isinstance(parallelism, str):
parallelism = int(parallelism)
subcls.parallelism = subcls._norm_parallelism(parallelism, relaxed=kwds.get("relaxed"))
return subcls
@@ -1941,7 +1898,7 @@ class ParallelismMixin(GenericHandler):
# init
#===================================================================
def __init__(self, parallelism=None, **kwds):
- super(ParallelismMixin, self).__init__(**kwds)
+ super().__init__(**kwds)
# init parallelism
if parallelism is None:
@@ -1965,7 +1922,7 @@ class ParallelismMixin(GenericHandler):
# XXX: for now, marking all hashes which don't have matching parallelism setting
if self.parallelism != type(self).parallelism:
return True
- return super(ParallelismMixin, self)._calc_needs_update(**kwds)
+ return super()._calc_needs_update(**kwds)
#===================================================================
# eoc
@@ -2308,7 +2265,7 @@ class SubclassBackendMixin(BackendMixin):
@classmethod
def _set_backend(cls, name, dryrun):
# invoke backend loader (will throw error if fails)
- super(SubclassBackendMixin, cls)._set_backend(name, dryrun)
+ super()._set_backend(name, dryrun)
# sanity check call args (should trust .set_backend, but will really
# foul things up if this isn't the owner)
@@ -2486,7 +2443,7 @@ class PrefixWrapper(object):
#: list of attributes which should be cloned by .using()
_using_clone_attrs = ()
- def __init__(self, name, wrapped, prefix=u(''), orig_prefix=u(''), lazy=False,
+ def __init__(self, name, wrapped, prefix=u'', orig_prefix=u'', lazy=False,
doc=None, ident=None):
self.name = name
if isinstance(prefix, bytes):
@@ -2652,7 +2609,7 @@ class PrefixWrapper(object):
if not hash.startswith(orig_prefix):
raise exc.InvalidHashError(self.wrapped)
wrapped = self.prefix + hash[len(orig_prefix):]
- return uascii_to_str(wrapped)
+ return wrapped
#: set by _using(), helper for test harness' handler_derived_from()
_derived_from = None
diff --git a/passlib/utils/pbkdf2.py b/passlib/utils/pbkdf2.py
index 273143b..d9bd083 100644
--- a/passlib/utils/pbkdf2.py
+++ b/passlib/utils/pbkdf2.py
@@ -6,20 +6,14 @@ maybe rename to "kdf" since it's getting more key derivation functions added.
#=============================================================================
# imports
#=============================================================================
-from __future__ import division
# core
import logging; log = logging.getLogger(__name__)
# site
# pkg
from passlib.exc import ExpectedTypeError
-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
+from passlib.crypto.digest import lookup_hash, pbkdf1 as _pbkdf1, pbkdf2_hmac, compile_hmac
# local
__all__ = [
- # hash utils
- "norm_hash_name",
-
# prf utils
"get_prf",
@@ -38,13 +32,6 @@ warn("the module 'passlib.utils.pbkdf2' is deprecated as of Passlib 1.7, "
DeprecationWarning)
#=============================================================================
-# hash helpers
-#=============================================================================
-
-norm_hash_name = deprecated_function(deprecated="1.7", removed="1.8", func_module=__name__,
- replacement="passlib.crypto.digest.norm_hash_name")(norm_hash_name)
-
-#=============================================================================
# prf lookup
#=============================================================================
@@ -97,7 +84,7 @@ def get_prf(name):
global _prf_cache
if name in _prf_cache:
return _prf_cache[name]
- if isinstance(name, native_string_types):
+ if isinstance(name, str):
if not name.startswith(_HMAC_PREFIXES):
raise ValueError("unknown prf algorithm: %r" % (name,))
digest = lookup_hash(name[5:]).name
@@ -183,7 +170,7 @@ def pbkdf2(secret, salt, rounds, keylen=None, prf="hmac-sha1"):
This has been deprecated in favor of :func:`passlib.crypto.digest.pbkdf2_hmac`,
and will be removed in Passlib 2.0. *Note the call signature has changed.*
"""
- if callable(prf) or (isinstance(prf, native_string_types) and not prf.startswith(_HMAC_PREFIXES)):
+ if callable(prf) or (isinstance(prf, str) and not prf.startswith(_HMAC_PREFIXES)):
raise NotImplementedError("non-HMAC prfs are not supported as of Passlib 1.7")
digest = prf[5:]
return pbkdf2_hmac(digest, secret, salt, rounds, keylen)