comparison MoinMoin/support/passlib/utils/__init__.py @ 6096:86a41c2bedec

upgrade passlib from 1.6.2 to 1.6.5
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Mon, 05 Sep 2016 23:44:04 +0200
parents d72a5e95c7c0
children 7f0616feeae9
comparison
equal deleted inserted replaced
6095:465cb6f5c6d7 6096:86a41c2bedec
128 def __get__(self, obj, cls): 128 def __get__(self, obj, cls):
129 return self.im_func(cls) 129 return self.im_func(cls)
130 130
131 @property 131 @property
132 def __func__(self): 132 def __func__(self):
133 "py3 compatible alias" 133 """py3 compatible alias"""
134 return self.im_func 134 return self.im_func
135 135
136 def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True, 136 def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True,
137 replacement=None, _is_method=False): 137 replacement=None, _is_method=False):
138 """decorator to deprecate a function. 138 """decorator to deprecate a function.
219 setattr(obj, func.__name__, value) 219 setattr(obj, func.__name__, value)
220 return value 220 return value
221 221
222 @property 222 @property
223 def __func__(self): 223 def __func__(self):
224 "py3 alias" 224 """py3 alias"""
225 return self.im_func 225 return self.im_func
226 226
227 # works but not used 227 # works but not used
228 ##class memoized_class_property(object): 228 ##class memoized_class_property(object):
229 ## """function decorator which calls function as classmethod, 229 ## """function decorator which calls function as classmethod,
251 This is functionally equivalent to ``left == right``, 251 This is functionally equivalent to ``left == right``,
252 but attempts to take constant time relative to the size of the righthand input. 252 but attempts to take constant time relative to the size of the righthand input.
253 253
254 The purpose of this function is to help prevent timing attacks 254 The purpose of this function is to help prevent timing attacks
255 during digest comparisons: the standard ``==`` operator aborts 255 during digest comparisons: the standard ``==`` operator aborts
256 after the first mismatched character, causing it's runtime to be 256 after the first mismatched character, causing its runtime to be
257 proportional to the longest prefix shared by the two inputs. 257 proportional to the longest prefix shared by the two inputs.
258 If an attacker is able to predict and control one of the two 258 If an attacker is able to predict and control one of the two
259 inputs, repeated queries can be leveraged to reveal information about 259 inputs, repeated queries can be leveraged to reveal information about
260 the content of the second argument. To minimize this risk, :func:`!consteq` 260 the content of the second argument. To minimize this risk, :func:`!consteq`
261 is designed to take ``THETA(len(right))`` time, regardless 261 is designed to take ``THETA(len(right))`` time, regardless
454 return data 454 return data
455 455
456 # replace saslprep() with stub when stringprep is missing 456 # replace saslprep() with stub when stringprep is missing
457 if stringprep is None: # pragma: no cover -- runtime detection 457 if stringprep is None: # pragma: no cover -- runtime detection
458 def saslprep(source, param="value"): 458 def saslprep(source, param="value"):
459 "stub for saslprep()" 459 """stub for saslprep()"""
460 raise NotImplementedError("saslprep() support requires the 'stringprep' " 460 raise NotImplementedError("saslprep() support requires the 'stringprep' "
461 "module, which is " + _stringprep_missing_reason) 461 "module, which is " + _stringprep_missing_reason)
462 462
463 #============================================================================= 463 #=============================================================================
464 # bytes helpers 464 # bytes helpers
501 501
502 add_doc(bytes_to_int, "decode byte string as single big-endian integer") 502 add_doc(bytes_to_int, "decode byte string as single big-endian integer")
503 add_doc(int_to_bytes, "encode integer as single big-endian byte string") 503 add_doc(int_to_bytes, "encode integer as single big-endian byte string")
504 504
505 def xor_bytes(left, right): 505 def xor_bytes(left, right):
506 "Perform bitwise-xor of two byte strings (must be same size)" 506 """Perform bitwise-xor of two byte strings (must be same size)"""
507 return int_to_bytes(bytes_to_int(left) ^ bytes_to_int(right), len(left)) 507 return int_to_bytes(bytes_to_int(left) ^ bytes_to_int(right), len(left))
508 508
509 def repeat_string(source, size): 509 def repeat_string(source, size):
510 "repeat or truncate <source> string, so it has length <size>" 510 """repeat or truncate <source> string, so it has length <size>"""
511 cur = len(source) 511 cur = len(source)
512 if size > cur: 512 if size > cur:
513 mult = (size+cur-1)//cur 513 mult = (size+cur-1)//cur
514 return (source*mult)[:size] 514 return (source*mult)[:size]
515 else: 515 else:
517 517
518 _BNULL = b("\x00") 518 _BNULL = b("\x00")
519 _UNULL = u("\x00") 519 _UNULL = u("\x00")
520 520
521 def right_pad_string(source, size, pad=None): 521 def right_pad_string(source, size, pad=None):
522 "right-pad or truncate <source> string, so it has length <size>" 522 """right-pad or truncate <source> string, so it has length <size>"""
523 cur = len(source) 523 cur = len(source)
524 if size > cur: 524 if size > cur:
525 if pad is None: 525 if pad is None:
526 pad = _UNULL if isinstance(source, unicode) else _BNULL 526 pad = _UNULL if isinstance(source, unicode) else _BNULL
527 return source+pad*(size-cur) 527 return source+pad*(size-cur)
533 #============================================================================= 533 #=============================================================================
534 _ASCII_TEST_BYTES = b("\x00\n aA:#!\x7f") 534 _ASCII_TEST_BYTES = b("\x00\n aA:#!\x7f")
535 _ASCII_TEST_UNICODE = _ASCII_TEST_BYTES.decode("ascii") 535 _ASCII_TEST_UNICODE = _ASCII_TEST_BYTES.decode("ascii")
536 536
537 def is_ascii_codec(codec): 537 def is_ascii_codec(codec):
538 "Test if codec is compatible with 7-bit ascii (e.g. latin-1, utf-8; but not utf-16)" 538 """Test if codec is compatible with 7-bit ascii (e.g. latin-1, utf-8; but not utf-16)"""
539 return _ASCII_TEST_UNICODE.encode(codec) == _ASCII_TEST_BYTES 539 return _ASCII_TEST_UNICODE.encode(codec) == _ASCII_TEST_BYTES
540 540
541 def is_same_codec(left, right): 541 def is_same_codec(left, right):
542 "Check if two codec names are aliases for same codec" 542 """Check if two codec names are aliases for same codec"""
543 if left == right: 543 if left == right:
544 return True 544 return True
545 if not (left and right): 545 if not (left and right):
546 return False 546 return False
547 return _lookup_codec(left).name == _lookup_codec(right).name 547 return _lookup_codec(left).name == _lookup_codec(right).name
548 548
549 _B80 = b('\x80')[0] 549 _B80 = b('\x80')[0]
550 _U80 = u('\x80') 550 _U80 = u('\x80')
551 def is_ascii_safe(source): 551 def is_ascii_safe(source):
552 "Check if string (bytes or unicode) contains only 7-bit ascii" 552 """Check if string (bytes or unicode) contains only 7-bit ascii"""
553 r = _B80 if isinstance(source, bytes) else _U80 553 r = _B80 if isinstance(source, bytes) else _U80
554 return all(c < r for c in source) 554 return all(c < r for c in source)
555 555
556 def to_bytes(source, encoding="utf-8", param="value", source_encoding=None): 556 def to_bytes(source, encoding="utf-8", param="value", source_encoding=None):
557 """Helper to normalize input to bytes. 557 """Helper to normalize input to bytes.
654 :returns: :class:`str` instance 654 :returns: :class:`str` instance
655 """) 655 """)
656 656
657 @deprecated_function(deprecated="1.6", removed="1.7") 657 @deprecated_function(deprecated="1.6", removed="1.7")
658 def to_hash_str(source, encoding="ascii"): # pragma: no cover -- deprecated & unused 658 def to_hash_str(source, encoding="ascii"): # pragma: no cover -- deprecated & unused
659 "deprecated, use to_native_str() instead" 659 """deprecated, use to_native_str() instead"""
660 return to_native_str(source, encoding, param="hash") 660 return to_native_str(source, encoding, param="hash")
661 661
662 #============================================================================= 662 #=============================================================================
663 # base64-variant encoding 663 # base64-variant encoding
664 #============================================================================= 664 #=============================================================================
669 669
670 :arg charmap: 670 :arg charmap:
671 A string of 64 unique characters, 671 A string of 64 unique characters,
672 which will be used to encode successive 6-bit chunks of data. 672 which will be used to encode successive 6-bit chunks of data.
673 A character's position within the string should correspond 673 A character's position within the string should correspond
674 to it's 6-bit value. 674 to its 6-bit value.
675 675
676 :param big: 676 :param big:
677 Whether the encoding should be big-endian (default False). 677 Whether the encoding should be big-endian (default False).
678 678
679 .. note:: 679 .. note::
781 ## raise ValueError("padding must be single character") 781 ## raise ValueError("padding must be single character")
782 ##self.padding = padding 782 ##self.padding = padding
783 783
784 @property 784 @property
785 def charmap(self): 785 def charmap(self):
786 "charmap as unicode" 786 """charmap as unicode"""
787 return self.bytemap.decode("latin-1") 787 return self.bytemap.decode("latin-1")
788 788
789 #=================================================================== 789 #===================================================================
790 # encoding byte strings 790 # encoding byte strings
791 #=================================================================== 791 #===================================================================
809 ## if padding: 809 ## if padding:
810 ## out += padding * (3-tail) 810 ## out += padding * (3-tail)
811 return out 811 return out
812 812
813 def _encode_bytes_little(self, next_value, chunks, tail): 813 def _encode_bytes_little(self, next_value, chunks, tail):
814 "helper used by encode_bytes() to handle little-endian encoding" 814 """helper used by encode_bytes() to handle little-endian encoding"""
815 # 815 #
816 # output bit layout: 816 # output bit layout:
817 # 817 #
818 # first byte: v1 543210 818 # first byte: v1 543210
819 # 819 #
848 yield v1 & 0x3f 848 yield v1 & 0x3f
849 yield ((v2 & 0x0f)<<2)|(v1>>6) 849 yield ((v2 & 0x0f)<<2)|(v1>>6)
850 yield v2>>4 850 yield v2>>4
851 851
852 def _encode_bytes_big(self, next_value, chunks, tail): 852 def _encode_bytes_big(self, next_value, chunks, tail):
853 "helper used by encode_bytes() to handle big-endian encoding" 853 """helper used by encode_bytes() to handle big-endian encoding"""
854 # 854 #
855 # output bit layout: 855 # output bit layout:
856 # 856 #
857 # first byte: v1 765432 857 # first byte: v1 765432
858 # 858 #
914 except KeyError: 914 except KeyError:
915 err = exc_err() 915 err = exc_err()
916 raise ValueError("invalid character: %r" % (err.args[0],)) 916 raise ValueError("invalid character: %r" % (err.args[0],))
917 917
918 def _decode_bytes_little(self, next_value, chunks, tail): 918 def _decode_bytes_little(self, next_value, chunks, tail):
919 "helper used by decode_bytes() to handle little-endian encoding" 919 """helper used by decode_bytes() to handle little-endian encoding"""
920 # 920 #
921 # input bit layout: 921 # input bit layout:
922 # 922 #
923 # first byte: v1 ..543210 923 # first byte: v1 ..543210
924 # +v2 10...... 924 # +v2 10......
949 # NOTE: 2 msb of v3 are ignored (should be 0) 949 # NOTE: 2 msb of v3 are ignored (should be 0)
950 v3 = next_value() 950 v3 = next_value()
951 yield (v2>>2) | ((v3 & 0xF) << 4) 951 yield (v2>>2) | ((v3 & 0xF) << 4)
952 952
953 def _decode_bytes_big(self, next_value, chunks, tail): 953 def _decode_bytes_big(self, next_value, chunks, tail):
954 "helper used by decode_bytes() to handle big-endian encoding" 954 """helper used by decode_bytes() to handle big-endian encoding"""
955 # 955 #
956 # input bit layout: 956 # input bit layout:
957 # 957 #
958 # first byte: v1 543210.. 958 # first byte: v1 543210..
959 # +v2 ......54 959 # +v2 ......54
991 991
992 # padmap2/3 - dict mapping last char of string -> 992 # padmap2/3 - dict mapping last char of string ->
993 # equivalent char with no padding bits set. 993 # equivalent char with no padding bits set.
994 994
995 def __make_padset(self, bits): 995 def __make_padset(self, bits):
996 "helper to generate set of valid last chars & bytes" 996 """helper to generate set of valid last chars & bytes"""
997 pset = set(c for i,c in enumerate(self.bytemap) if not i & bits) 997 pset = set(c for i,c in enumerate(self.bytemap) if not i & bits)
998 pset.update(c for i,c in enumerate(self.charmap) if not i & bits) 998 pset.update(c for i,c in enumerate(self.charmap) if not i & bits)
999 return frozenset(pset) 999 return frozenset(pset)
1000 1000
1001 @memoized_property 1001 @memoized_property
1002 def _padinfo2(self): 1002 def _padinfo2(self):
1003 "mask to clear padding bits, and valid last bytes (for strings 2 % 4)" 1003 """mask to clear padding bits, and valid last bytes (for strings 2 % 4)"""
1004 # 4 bits of last char unused (lsb for big, msb for little) 1004 # 4 bits of last char unused (lsb for big, msb for little)
1005 bits = 15 if self.big else (15<<2) 1005 bits = 15 if self.big else (15<<2)
1006 return ~bits, self.__make_padset(bits) 1006 return ~bits, self.__make_padset(bits)
1007 1007
1008 @memoized_property 1008 @memoized_property
1009 def _padinfo3(self): 1009 def _padinfo3(self):
1010 "mask to clear padding bits, and valid last bytes (for strings 3 % 4)" 1010 """mask to clear padding bits, and valid last bytes (for strings 3 % 4)"""
1011 # 2 bits of last char unused (lsb for big, msb for little) 1011 # 2 bits of last char unused (lsb for big, msb for little)
1012 bits = 3 if self.big else (3<<4) 1012 bits = 3 if self.big else (3<<4)
1013 return ~bits, self.__make_padset(bits) 1013 return ~bits, self.__make_padset(bits)
1014 1014
1015 def check_repair_unused(self, source): 1015 def check_repair_unused(self, source):
1070 1070
1071 #=================================================================== 1071 #===================================================================
1072 # transposed encoding/decoding 1072 # transposed encoding/decoding
1073 #=================================================================== 1073 #===================================================================
1074 def encode_transposed_bytes(self, source, offsets): 1074 def encode_transposed_bytes(self, source, offsets):
1075 "encode byte string, first transposing source using offset list" 1075 """encode byte string, first transposing source using offset list"""
1076 if not isinstance(source, bytes): 1076 if not isinstance(source, bytes):
1077 raise TypeError("source must be bytes, not %s" % (type(source),)) 1077 raise TypeError("source must be bytes, not %s" % (type(source),))
1078 tmp = join_byte_elems(source[off] for off in offsets) 1078 tmp = join_byte_elems(source[off] for off in offsets)
1079 return self.encode_bytes(tmp) 1079 return self.encode_bytes(tmp)
1080 1080
1081 def decode_transposed_bytes(self, source, offsets): 1081 def decode_transposed_bytes(self, source, offsets):
1082 "decode byte string, then reverse transposition described by offset list" 1082 """decode byte string, then reverse transposition described by offset list"""
1083 # NOTE: if transposition does not use all bytes of source, 1083 # NOTE: if transposition does not use all bytes of source,
1084 # the original can't be recovered... and join_byte_elems() will throw 1084 # the original can't be recovered... and join_byte_elems() will throw
1085 # an error because 1+ values in <buf> will be None. 1085 # an error because 1+ values in <buf> will be None.
1086 tmp = self.decode_bytes(source) 1086 tmp = self.decode_bytes(source)
1087 buf = [None] * len(offsets) 1087 buf = [None] * len(offsets)
1131 #--------------------------------------------------------------- 1131 #---------------------------------------------------------------
1132 # optimized versions for common integer sizes 1132 # optimized versions for common integer sizes
1133 #--------------------------------------------------------------- 1133 #---------------------------------------------------------------
1134 1134
1135 def decode_int6(self, source): 1135 def decode_int6(self, source):
1136 "decode single character -> 6 bit integer" 1136 """decode single character -> 6 bit integer"""
1137 if not isinstance(source, bytes): 1137 if not isinstance(source, bytes):
1138 raise TypeError("source must be bytes, not %s" % (type(source),)) 1138 raise TypeError("source must be bytes, not %s" % (type(source),))
1139 if len(source) != 1: 1139 if len(source) != 1:
1140 raise ValueError("source must be exactly 1 byte") 1140 raise ValueError("source must be exactly 1 byte")
1141 if PY3: 1141 if PY3:
1145 return self._decode64(source) 1145 return self._decode64(source)
1146 except KeyError: 1146 except KeyError:
1147 raise ValueError("invalid character") 1147 raise ValueError("invalid character")
1148 1148
1149 def decode_int12(self, source): 1149 def decode_int12(self, source):
1150 "decodes 2 char string -> 12-bit integer" 1150 """decodes 2 char string -> 12-bit integer"""
1151 if not isinstance(source, bytes): 1151 if not isinstance(source, bytes):
1152 raise TypeError("source must be bytes, not %s" % (type(source),)) 1152 raise TypeError("source must be bytes, not %s" % (type(source),))
1153 if len(source) != 2: 1153 if len(source) != 2:
1154 raise ValueError("source must be exactly 2 bytes") 1154 raise ValueError("source must be exactly 2 bytes")
1155 decode = self._decode64 1155 decode = self._decode64
1160 return decode(source[0]) + (decode(source[1])<<6) 1160 return decode(source[0]) + (decode(source[1])<<6)
1161 except KeyError: 1161 except KeyError:
1162 raise ValueError("invalid character") 1162 raise ValueError("invalid character")
1163 1163
1164 def decode_int24(self, source): 1164 def decode_int24(self, source):
1165 "decodes 4 char string -> 24-bit integer" 1165 """decodes 4 char string -> 24-bit integer"""
1166 if not isinstance(source, bytes): 1166 if not isinstance(source, bytes):
1167 raise TypeError("source must be bytes, not %s" % (type(source),)) 1167 raise TypeError("source must be bytes, not %s" % (type(source),))
1168 if len(source) != 4: 1168 if len(source) != 4:
1169 raise ValueError("source must be exactly 4 bytes") 1169 raise ValueError("source must be exactly 4 bytes")
1170 decode = self._decode64 1170 decode = self._decode64
1214 #--------------------------------------------------------------- 1214 #---------------------------------------------------------------
1215 # optimized versions for common integer sizes 1215 # optimized versions for common integer sizes
1216 #--------------------------------------------------------------- 1216 #---------------------------------------------------------------
1217 1217
1218 def encode_int6(self, value): 1218 def encode_int6(self, value):
1219 "encodes 6-bit integer -> single hash64 character" 1219 """encodes 6-bit integer -> single hash64 character"""
1220 if value < 0 or value > 63: 1220 if value < 0 or value > 63:
1221 raise ValueError("value out of range") 1221 raise ValueError("value out of range")
1222 if PY3: 1222 if PY3:
1223 return self.bytemap[value:value+1] 1223 return self.bytemap[value:value+1]
1224 else: 1224 else:
1225 return self._encode64(value) 1225 return self._encode64(value)
1226 1226
1227 def encode_int12(self, value): 1227 def encode_int12(self, value):
1228 "encodes 12-bit integer -> 2 char string" 1228 """encodes 12-bit integer -> 2 char string"""
1229 if value < 0 or value > 0xFFF: 1229 if value < 0 or value > 0xFFF:
1230 raise ValueError("value out of range") 1230 raise ValueError("value out of range")
1231 raw = [value & 0x3f, (value>>6) & 0x3f] 1231 raw = [value & 0x3f, (value>>6) & 0x3f]
1232 if self.big: 1232 if self.big:
1233 raw = reversed(raw) 1233 raw = reversed(raw)
1234 return join_byte_elems(imap(self._encode64, raw)) 1234 return join_byte_elems(imap(self._encode64, raw))
1235 1235
1236 def encode_int24(self, value): 1236 def encode_int24(self, value):
1237 "encodes 24-bit integer -> 4 char string" 1237 """encodes 24-bit integer -> 4 char string"""
1238 if value < 0 or value > 0xFFFFFF: 1238 if value < 0 or value > 0xFFFFFF:
1239 raise ValueError("value out of range") 1239 raise ValueError("value out of range")
1240 raw = [value & 0x3f, (value>>6) & 0x3f, 1240 raw = [value & 0x3f, (value>>6) & 0x3f,
1241 (value>>12) & 0x3f, (value>>18) & 0x3f] 1241 (value>>12) & 0x3f, (value>>18) & 0x3f]
1242 if self.big: 1242 if self.big:
1256 #=================================================================== 1256 #===================================================================
1257 # eof 1257 # eof
1258 #=================================================================== 1258 #===================================================================
1259 1259
1260 class LazyBase64Engine(Base64Engine): 1260 class LazyBase64Engine(Base64Engine):
1261 "Base64Engine which delays initialization until it's accessed" 1261 """Base64Engine which delays initialization until it's accessed"""
1262 _lazy_opts = None 1262 _lazy_opts = None
1263 1263
1264 def __init__(self, *args, **kwds): 1264 def __init__(self, *args, **kwds):
1265 self._lazy_opts = (args, kwds) 1265 self._lazy_opts = (args, kwds)
1266 1266
1329 #============================================================================= 1329 #=============================================================================
1330 1330
1331 try: 1331 try:
1332 from crypt import crypt as _crypt 1332 from crypt import crypt as _crypt
1333 except ImportError: # pragma: no cover 1333 except ImportError: # pragma: no cover
1334 _crypt = None
1334 has_crypt = False 1335 has_crypt = False
1335 def safe_crypt(secret, hash): 1336 def safe_crypt(secret, hash):
1336 return None 1337 return None
1337 else: 1338 else:
1338 has_crypt = True 1339 has_crypt = True
1449 has_urandom = True 1450 has_urandom = True
1450 except NotImplementedError: # pragma: no cover 1451 except NotImplementedError: # pragma: no cover
1451 has_urandom = False 1452 has_urandom = False
1452 1453
1453 def genseed(value=None): 1454 def genseed(value=None):
1454 "generate prng seed value from system resources" 1455 """generate prng seed value from system resources"""
1455 from hashlib import sha512 1456 from hashlib import sha512
1456 text = u("%s %s %s %s %.15f %.15f %s") % ( 1457 text = u("%s %s %s %s %.15f %.15f %s") % (
1457 # if caller specified a seed value, mix it in 1458 # if caller specified a seed value, mix it in
1458 value, 1459 value,
1459 1460
1460 # if caller's seed value was an RNG, mix in bits from it's state 1461 # if caller's seed value was an RNG, mix in bits from its state
1461 value.getrandbits(1<<15) if hasattr(value, "getrandbits") else None, 1462 value.getrandbits(1<<15) if hasattr(value, "getrandbits") else None,
1462 1463
1463 # add current process id 1464 # add current process id
1464 # NOTE: not available in some environments, e.g. GAE 1465 # NOTE: not available in some environments, e.g. GAE
1465 os.getpid() if hasattr(os, "getpid") else None, 1466 os.getpid() if hasattr(os, "getpid") else None,
1570 "genconfig", "genhash", 1571 "genconfig", "genhash",
1571 "verify", "encrypt", "identify", 1572 "verify", "encrypt", "identify",
1572 ) 1573 )
1573 1574
1574 def is_crypt_handler(obj): 1575 def is_crypt_handler(obj):
1575 "check if object follows the :ref:`password-hash-api`" 1576 """check if object follows the :ref:`password-hash-api`"""
1576 # XXX: change to use isinstance(obj, PasswordHash) under py26+? 1577 # XXX: change to use isinstance(obj, PasswordHash) under py26+?
1577 return all(hasattr(obj, name) for name in _handler_attrs) 1578 return all(hasattr(obj, name) for name in _handler_attrs)
1578 1579
1579 _context_attrs = ( 1580 _context_attrs = (
1580 "needs_update", 1581 "needs_update",
1581 "genconfig", "genhash", 1582 "genconfig", "genhash",
1582 "verify", "encrypt", "identify", 1583 "verify", "encrypt", "identify",
1583 ) 1584 )
1584 1585
1585 def is_crypt_context(obj): 1586 def is_crypt_context(obj):
1586 "check if object appears to be a :class:`~passlib.context.CryptContext` instance" 1587 """check if object appears to be a :class:`~passlib.context.CryptContext` instance"""
1587 # XXX: change to use isinstance(obj, CryptContext)? 1588 # XXX: change to use isinstance(obj, CryptContext)?
1588 return all(hasattr(obj, name) for name in _context_attrs) 1589 return all(hasattr(obj, name) for name in _context_attrs)
1589 1590
1590 ##def has_many_backends(handler): 1591 ##def has_many_backends(handler):
1591 ## "check if handler provides multiple baceknds" 1592 ## "check if handler provides multiple baceknds"
1592 ## # NOTE: should also provide get_backend(), .has_backend(), and .backends attr 1593 ## # NOTE: should also provide get_backend(), .has_backend(), and .backends attr
1593 ## return hasattr(handler, "set_backend") 1594 ## return hasattr(handler, "set_backend")
1594 1595
1595 def has_rounds_info(handler): 1596 def has_rounds_info(handler):
1596 "check if handler provides the optional :ref:`rounds information <rounds-attributes>` attributes" 1597 """check if handler provides the optional :ref:`rounds information <rounds-attributes>` attributes"""
1597 return ('rounds' in handler.setting_kwds and 1598 return ('rounds' in handler.setting_kwds and
1598 getattr(handler, "min_rounds", None) is not None) 1599 getattr(handler, "min_rounds", None) is not None)
1599 1600
1600 def has_salt_info(handler): 1601 def has_salt_info(handler):
1601 "check if handler provides the optional :ref:`salt information <salt-attributes>` attributes" 1602 """check if handler provides the optional :ref:`salt information <salt-attributes>` attributes"""
1602 return ('salt' in handler.setting_kwds and 1603 return ('salt' in handler.setting_kwds and
1603 getattr(handler, "min_salt_size", None) is not None) 1604 getattr(handler, "min_salt_size", None) is not None)
1604 1605
1605 ##def has_raw_salt(handler): 1606 ##def has_raw_salt(handler):
1606 ## "check if handler takes in encoded salt as unicode (False), or decoded salt as bytes (True)" 1607 ## "check if handler takes in encoded salt as unicode (False), or decoded salt as bytes (True)"