codekingpro/portable-devtools
114k
1"""passlib.utils.compat - python 2/3 compatibility helpers"""2#=============================================================================3# figure out what we're running4#=============================================================================5 6#------------------------------------------------------------------------7# python version8#------------------------------------------------------------------------9import sys10PY2 = sys.version_info < (3,0)11PY3 = sys.version_info >= (3,0)12 13# make sure it's not an unsupported version, even if we somehow got this far14if sys.version_info < (2,6) or (3,0) <= sys.version_info < (3,2):15 raise RuntimeError("Passlib requires Python 2.6, 2.7, or >= 3.2 (as of passlib 1.7)")16 17PY26 = sys.version_info < (2,7)18 19#------------------------------------------------------------------------20# python implementation21#------------------------------------------------------------------------22JYTHON = sys.platform.startswith('java')23 24PYPY = hasattr(sys, "pypy_version_info")25 26if PYPY and sys.pypy_version_info < (2,0):27 raise RuntimeError("passlib requires pypy >= 2.0 (as of passlib 1.7)")28 29# e.g. '2.7.7\n[Pyston 0.5.1]'30# NOTE: deprecated support 2019-1131PYSTON = "Pyston" in sys.version32 33#=============================================================================34# common imports35#=============================================================================36import logging; log = logging.getLogger(__name__)37if PY3:38 import builtins39else:40 import __builtin__ as builtins41 42def add_doc(obj, doc):43 """add docstring to an object"""44 obj.__doc__ = doc45 46#=============================================================================47# the default exported vars48#=============================================================================49__all__ = [50 # python versions51 'PY2', 'PY3', 'PY26',52 53 # io54 'BytesIO', 'StringIO', 'NativeStringIO', 'SafeConfigParser',55 'print_',56 57 # type detection58## 'is_mapping',59 'int_types',60 'num_types',61 'unicode_or_bytes_types',62 'native_string_types',63 64 # unicode/bytes types & helpers65 'u',66 'unicode',67 'uascii_to_str', 'bascii_to_str',68 'str_to_uascii', 'str_to_bascii',69 'join_unicode', 'join_bytes',70 'join_byte_values', 'join_byte_elems',71 'byte_elem_value',72 'iter_byte_values',73 74 # iteration helpers75 'irange', #'lrange',76 'imap', 'lmap',77 'iteritems', 'itervalues',78 'next',79 80 # collections81 'OrderedDict',82 83 # context helpers84 'nullcontext',85 86 # introspection87 'get_method_function', 'add_doc',88]89 90# begin accumulating mapping of lazy-loaded attrs,91# 'merged' into module at bottom92_lazy_attrs = dict()93 94#=============================================================================95# unicode & bytes types96#=============================================================================97if PY3:98 unicode = str99 100 # TODO: once we drop python 3.2 support, can use u'' again!101 def u(s):102 assert isinstance(s, str)103 return s104 105 unicode_or_bytes_types = (str, bytes)106 native_string_types = (unicode,)107 108else:109 unicode = builtins.unicode110 111 def u(s):112 assert isinstance(s, str)113 return s.decode("unicode_escape")114 115 unicode_or_bytes_types = (basestring,)116 native_string_types = (basestring,)117 118# shorter preferred aliases119unicode_or_bytes = unicode_or_bytes_types120unicode_or_str = native_string_types121 122# unicode -- unicode type, regardless of python version123# bytes -- bytes type, regardless of python version124# unicode_or_bytes_types -- types that text can occur in, whether encoded or not125# native_string_types -- types that native python strings (dict keys etc) can occur in.126 127#=============================================================================128# unicode & bytes helpers129#=============================================================================130# function to join list of unicode strings131join_unicode = u('').join132 133# function to join list of byte strings134join_bytes = b''.join135 136if PY3:137 def uascii_to_str(s):138 assert isinstance(s, unicode)139 return s140 141 def bascii_to_str(s):142 assert isinstance(s, bytes)143 return s.decode("ascii")144 145 def str_to_uascii(s):146 assert isinstance(s, str)147 return s148 149 def str_to_bascii(s):150 assert isinstance(s, str)151 return s.encode("ascii")152 153 join_byte_values = join_byte_elems = bytes154 155 def byte_elem_value(elem):156 assert isinstance(elem, int)157 return elem158 159 def iter_byte_values(s):160 assert isinstance(s, bytes)161 return s162 163 def iter_byte_chars(s):164 assert isinstance(s, bytes)165 # FIXME: there has to be a better way to do this166 return (bytes([c]) for c in s)167 168else:169 def uascii_to_str(s):170 assert isinstance(s, unicode)171 return s.encode("ascii")172 173 def bascii_to_str(s):174 assert isinstance(s, bytes)175 return s176 177 def str_to_uascii(s):178 assert isinstance(s, str)179 return s.decode("ascii")180 181 def str_to_bascii(s):182 assert isinstance(s, str)183 return s184 185 def join_byte_values(values):186 return join_bytes(chr(v) for v in values)187 188 join_byte_elems = join_bytes189 190 byte_elem_value = ord191 192 def iter_byte_values(s):193 assert isinstance(s, bytes)194 return (ord(c) for c in s)195 196 def iter_byte_chars(s):197 assert isinstance(s, bytes)198 return s199 200add_doc(uascii_to_str, "helper to convert ascii unicode -> native str")201add_doc(bascii_to_str, "helper to convert ascii bytes -> native str")202add_doc(str_to_uascii, "helper to convert ascii native str -> unicode")203add_doc(str_to_bascii, "helper to convert ascii native str -> bytes")204 205# join_byte_values -- function to convert list of ordinal integers to byte string.206 207# join_byte_elems -- function to convert list of byte elements to byte string;208# i.e. what's returned by ``b('a')[0]``...209# this is b('a') under PY2, but 97 under PY3.210 211# byte_elem_value -- function to convert byte element to integer -- a noop under PY3212 213add_doc(iter_byte_values, "iterate over byte string as sequence of ints 0-255")214add_doc(iter_byte_chars, "iterate over byte string as sequence of 1-byte strings")215 216#=============================================================================217# numeric218#=============================================================================219if PY3:220 int_types = (int,)221 num_types = (int, float)222else:223 int_types = (int, long)224 num_types = (int, long, float)225 226#=============================================================================227# iteration helpers228#229# irange - range iterable / view (xrange under py2, range under py3)230# lrange - range list (range under py2, list(range()) under py3)231#232# imap - map to iterator233# lmap - map to list234#=============================================================================235if PY3:236 irange = range237 ##def lrange(*a,**k):238 ## return list(range(*a,**k))239 240 def lmap(*a, **k):241 return list(map(*a,**k))242 imap = map243 244 def iteritems(d):245 return d.items()246 def itervalues(d):247 return d.values()248 249 def nextgetter(obj):250 return obj.__next__251 252 izip = zip253 254else:255 irange = xrange256 ##lrange = range257 258 lmap = map259 from itertools import imap, izip260 261 def iteritems(d):262 return d.iteritems()263 def itervalues(d):264 return d.itervalues()265 266 def nextgetter(obj):267 return obj.next268 269add_doc(nextgetter, "return function that yields successive values from iterable")270 271#=============================================================================272# typing273#=============================================================================274##def is_mapping(obj):275## # non-exhaustive check, enough to distinguish from lists, etc276## return hasattr(obj, "items")277 278#=============================================================================279# introspection280#=============================================================================281if PY3:282 method_function_attr = "__func__"283else:284 method_function_attr = "im_func"285 286def get_method_function(func):287 """given (potential) method, return underlying function"""288 return getattr(func, method_function_attr, func)289 290def get_unbound_method_function(func):291 """given unbound method, return underlying function"""292 return func if PY3 else func.__func__293 294def error_from(exc, # *,295 cause=None):296 """297 backward compat hack to suppress exception cause in python3.3+298 299 one python < 3.3 support is dropped, can replace all uses with "raise exc from None"300 """301 exc.__cause__ = cause302 exc.__suppress_context__ = True303 return exc304 305# legacy alias306suppress_cause = error_from307 308#=============================================================================309# input/output310#=============================================================================311if PY3:312 _lazy_attrs = dict(313 BytesIO="io.BytesIO",314 UnicodeIO="io.StringIO",315 NativeStringIO="io.StringIO",316 SafeConfigParser="configparser.ConfigParser",317 )318 319 print_ = getattr(builtins, "print")320 321else:322 _lazy_attrs = dict(323 BytesIO="cStringIO.StringIO",324 UnicodeIO="StringIO.StringIO",325 NativeStringIO="cStringIO.StringIO",326 SafeConfigParser="ConfigParser.SafeConfigParser",327 )328 329 def print_(*args, **kwds):330 """The new-style print function."""331 # extract kwd args332 fp = kwds.pop("file", sys.stdout)333 sep = kwds.pop("sep", None)334 end = kwds.pop("end", None)335 if kwds:336 raise TypeError("invalid keyword arguments")337 338 # short-circuit if no target339 if fp is None:340 return341 342 # use unicode or bytes ?343 want_unicode = isinstance(sep, unicode) or isinstance(end, unicode) or \344 any(isinstance(arg, unicode) for arg in args)345 346 # pick default end sequence347 if end is None:348 end = u("\n") if want_unicode else "\n"349 elif not isinstance(end, unicode_or_bytes_types):350 raise TypeError("end must be None or a string")351 352 # pick default separator353 if sep is None:354 sep = u(" ") if want_unicode else " "355 elif not isinstance(sep, unicode_or_bytes_types):356 raise TypeError("sep must be None or a string")357 358 # write to buffer359 first = True360 write = fp.write361 for arg in args:362 if first:363 first = False364 else:365 write(sep)366 if not isinstance(arg, basestring):367 arg = str(arg)368 write(arg)369 write(end)370 371#=============================================================================372# collections373#=============================================================================374if PY26:375 _lazy_attrs['OrderedDict'] = 'passlib.utils.compat._ordered_dict.OrderedDict'376else:377 _lazy_attrs['OrderedDict'] = 'collections.OrderedDict'378 379#=============================================================================380# context managers381#=============================================================================382 383try:384 # new in py37385 from contextlib import nullcontext386except ImportError:387 388 class nullcontext(object):389 """390 Context manager that does no additional processing.391 """392 def __init__(self, enter_result=None):393 self.enter_result = enter_result394 395 def __enter__(self):396 return self.enter_result397 398 def __exit__(self, *exc_info):399 pass400 401#=============================================================================402# lazy overlay module403#=============================================================================404from types import ModuleType405 406def _import_object(source):407 """helper to import object from module; accept format `path.to.object`"""408 modname, modattr = source.rsplit(".",1)409 mod = __import__(modname, fromlist=[modattr], level=0)410 return getattr(mod, modattr)411 412class _LazyOverlayModule(ModuleType):413 """proxy module which overlays original module,414 and lazily imports specified attributes.415 416 this is mainly used to prevent importing of resources417 that are only needed by certain password hashes,418 yet allow them to be imported from a single location.419 420 used by :mod:`passlib.utils`, :mod:`passlib.crypto`,421 and :mod:`passlib.utils.compat`.422 """423 424 @classmethod425 def replace_module(cls, name, attrmap):426 orig = sys.modules[name]427 self = cls(name, attrmap, orig)428 sys.modules[name] = self429 return self430 431 def __init__(self, name, attrmap, proxy=None):432 ModuleType.__init__(self, name)433 self.__attrmap = attrmap434 self.__proxy = proxy435 self.__log = logging.getLogger(name)436 437 def __getattr__(self, attr):438 proxy = self.__proxy439 if proxy and hasattr(proxy, attr):440 return getattr(proxy, attr)441 attrmap = self.__attrmap442 if attr in attrmap:443 source = attrmap[attr]444 if callable(source):445 value = source()446 else:447 value = _import_object(source)448 setattr(self, attr, value)449 self.__log.debug("loaded lazy attr %r: %r", attr, value)450 return value451 raise AttributeError("'module' object has no attribute '%s'" % (attr,))452 453 def __repr__(self):454 proxy = self.__proxy455 if proxy:456 return repr(proxy)457 else:458 return ModuleType.__repr__(self)459 460 def __dir__(self):461 attrs = set(dir(self.__class__))462 attrs.update(self.__dict__)463 attrs.update(self.__attrmap)464 proxy = self.__proxy465 if proxy is not None:466 attrs.update(dir(proxy))467 return list(attrs)468 469# replace this module with overlay that will lazily import attributes.470_LazyOverlayModule.replace_module(__name__, _lazy_attrs)471 472#=============================================================================473# eof474#=============================================================================475 