1
"""passlib.utils.compat - python 2/3 compatibility helpers"""
2
#=============================================================================
3
# figure out what we're running
4
#=============================================================================
6
#------------------------------------------------------------------------
8
#------------------------------------------------------------------------
10
PY2 = sys.version_info < (3,0)
11
PY3 = sys.version_info >= (3,0)
12
PY_MAX_25 = sys.version_info < (2,6) # py 2.5 or earlier
13
PY27 = sys.version_info[:2] == (2,7) # supports last 2.x release
14
PY_MIN_32 = sys.version_info >= (3,2) # py 3.2 or later
16
#------------------------------------------------------------------------
17
# python implementation
18
#------------------------------------------------------------------------
19
PYPY = hasattr(sys, "pypy_version_info")
20
JYTHON = sys.platform.startswith('java')
22
#------------------------------------------------------------------------
24
#------------------------------------------------------------------------
26
# __dir__() added in py2.6
27
SUPPORTS_DIR_METHOD = not PY_MAX_25 and not (PYPY and sys.pypy_version_info < (1,6))
29
#=============================================================================
31
#=============================================================================
32
import logging; log = logging.getLogger(__name__)
36
import __builtin__ as builtins
38
def add_doc(obj, doc):
39
"""add docstring to an object"""
42
#=============================================================================
43
# the default exported vars
44
#=============================================================================
47
'PY2', 'PY3', 'PY_MAX_25', 'PY27', 'PY_MIN_32',
50
'BytesIO', 'StringIO', 'NativeStringIO', 'SafeConfigParser',
60
# unicode/bytes types & helpers
63
'uascii_to_str', 'bascii_to_str',
64
'str_to_uascii', 'str_to_bascii',
65
'join_unicode', 'join_bytes',
66
'join_byte_values', 'join_byte_elems',
73
'iteritems', 'itervalues',
77
'exc_err', 'get_method_function', 'add_doc',
80
# begin accumulating mapping of lazy-loaded attrs,
81
# 'merged' into module at bottom
84
#=============================================================================
85
# unicode & bytes types
86
#=============================================================================
89
bytes = builtins.bytes
92
assert isinstance(s, str)
96
assert isinstance(s, str)
97
return s.encode("latin-1")
99
base_string_types = (unicode, bytes)
102
unicode = builtins.unicode
103
bytes = str if PY_MAX_25 else builtins.bytes
106
assert isinstance(s, str)
107
return s.decode("unicode_escape")
110
assert isinstance(s, str)
113
base_string_types = basestring
115
#=============================================================================
116
# unicode & bytes helpers
117
#=============================================================================
118
# function to join list of unicode strings
119
join_unicode = u('').join
121
# function to join list of byte strings
122
join_bytes = b('').join
125
def uascii_to_str(s):
126
assert isinstance(s, unicode)
129
def bascii_to_str(s):
130
assert isinstance(s, bytes)
131
return s.decode("ascii")
133
def str_to_uascii(s):
134
assert isinstance(s, str)
137
def str_to_bascii(s):
138
assert isinstance(s, str)
139
return s.encode("ascii")
141
join_byte_values = join_byte_elems = bytes
143
def byte_elem_value(elem):
144
assert isinstance(elem, int)
147
def iter_byte_values(s):
148
assert isinstance(s, bytes)
151
def iter_byte_chars(s):
152
assert isinstance(s, bytes)
153
# FIXME: there has to be a better way to do this
154
return (bytes([c]) for c in s)
157
def uascii_to_str(s):
158
assert isinstance(s, unicode)
159
return s.encode("ascii")
161
def bascii_to_str(s):
162
assert isinstance(s, bytes)
165
def str_to_uascii(s):
166
assert isinstance(s, str)
167
return s.decode("ascii")
169
def str_to_bascii(s):
170
assert isinstance(s, str)
173
def join_byte_values(values):
174
return join_bytes(chr(v) for v in values)
176
join_byte_elems = join_bytes
178
byte_elem_value = ord
180
def iter_byte_values(s):
181
assert isinstance(s, bytes)
182
return (ord(c) for c in s)
184
def iter_byte_chars(s):
185
assert isinstance(s, bytes)
188
add_doc(uascii_to_str, "helper to convert ascii unicode -> native str")
189
add_doc(bascii_to_str, "helper to convert ascii bytes -> native str")
190
add_doc(str_to_uascii, "helper to convert ascii native str -> unicode")
191
add_doc(str_to_bascii, "helper to convert ascii native str -> bytes")
193
# join_byte_values -- function to convert list of ordinal integers to byte string.
195
# join_byte_elems -- function to convert list of byte elements to byte string;
196
# i.e. what's returned by ``b('a')[0]``...
197
# this is b('a') under PY2, but 97 under PY3.
199
# byte_elem_value -- function to convert byte element to integer -- a noop under PY3
201
add_doc(iter_byte_values, "iterate over byte string as sequence of ints 0-255")
202
add_doc(iter_byte_chars, "iterate over byte string as sequence of 1-byte strings")
204
#=============================================================================
206
#=============================================================================
209
num_types = (int, float)
211
int_types = (int, long)
212
num_types = (int, long, float)
214
#=============================================================================
217
# irange - range iterable / view (xrange under py2, range under py3)
218
# lrange - range list (range under py2, list(range()) under py3)
220
# imap - map to iterator
222
#=============================================================================
225
##def lrange(*a,**k):
226
## return list(range(*a,**k))
229
return list(map(*a,**k))
237
next_method_attr = "__next__"
244
from itertools import imap
249
return d.itervalues()
251
next_method_attr = "next"
255
def next(itr, default=_undef):
256
"compat wrapper for next()"
257
if default is _undef:
261
except StopIteration:
266
#=============================================================================
268
#=============================================================================
269
##def is_mapping(obj):
270
## # non-exhaustive check, enough to distinguish from lists, etc
271
## return hasattr(obj, "items")
273
if (3,0) <= sys.version_info < (3,2):
274
# callable isn't dead, it's just resting
275
from collections import Callable
277
return isinstance(obj, Callable)
279
callable = builtins.callable
281
#=============================================================================
283
#=============================================================================
285
"return current error object (to avoid try/except syntax change)"
286
return sys.exc_info()[1]
289
method_function_attr = "__func__"
291
method_function_attr = "im_func"
293
def get_method_function(func):
294
"given (potential) method, return underlying function"
295
return getattr(func, method_function_attr, func)
297
#=============================================================================
299
#=============================================================================
302
BytesIO="io.BytesIO",
303
UnicodeIO="io.StringIO",
304
NativeStringIO="io.StringIO",
305
SafeConfigParser="configparser.SafeConfigParser",
307
if sys.version_info >= (3,2):
308
# py32 renamed this, removing old ConfigParser
309
_lazy_attrs["SafeConfigParser"] = "configparser.ConfigParser"
311
print_ = getattr(builtins, "print")
315
BytesIO="cStringIO.StringIO",
316
UnicodeIO="StringIO.StringIO",
317
NativeStringIO="cStringIO.StringIO",
318
SafeConfigParser="ConfigParser.SafeConfigParser",
321
def print_(*args, **kwds):
322
"""The new-style print function."""
324
fp = kwds.pop("file", sys.stdout)
325
sep = kwds.pop("sep", None)
326
end = kwds.pop("end", None)
328
raise TypeError("invalid keyword arguments")
330
# short-circuit if no target
334
# use unicode or bytes ?
335
want_unicode = isinstance(sep, unicode) or isinstance(end, unicode) or \
336
any(isinstance(arg, unicode) for arg in args)
338
# pick default end sequence
340
end = u("\n") if want_unicode else "\n"
341
elif not isinstance(end, base_string_types):
342
raise TypeError("end must be None or a string")
344
# pick default separator
346
sep = u(" ") if want_unicode else " "
347
elif not isinstance(sep, base_string_types):
348
raise TypeError("sep must be None or a string")
358
if not isinstance(arg, basestring):
363
#=============================================================================
364
# lazy overlay module
365
#=============================================================================
366
from types import ModuleType
368
def _import_object(source):
369
"helper to import object from module; accept format `path.to.object`"
370
modname, modattr = source.rsplit(".",1)
371
mod = __import__(modname, fromlist=[modattr], level=0)
372
return getattr(mod, modattr)
374
class _LazyOverlayModule(ModuleType):
375
"""proxy module which overlays original module,
376
and lazily imports specified attributes.
378
this is mainly used to prevent importing of resources
379
that are only needed by certain password hashes,
380
yet allow them to be imported from a single location.
382
used by :mod:`passlib.utils`, :mod:`passlib.utils.crypto`,
383
and :mod:`passlib.utils.compat`.
387
def replace_module(cls, name, attrmap):
388
orig = sys.modules[name]
389
self = cls(name, attrmap, orig)
390
sys.modules[name] = self
393
def __init__(self, name, attrmap, proxy=None):
394
ModuleType.__init__(self, name)
395
self.__attrmap = attrmap
397
self.__log = logging.getLogger(name)
399
def __getattr__(self, attr):
401
if proxy and hasattr(proxy, attr):
402
return getattr(proxy, attr)
403
attrmap = self.__attrmap
405
source = attrmap[attr]
409
value = _import_object(source)
410
setattr(self, attr, value)
411
self.__log.debug("loaded lazy attr %r: %r", attr, value)
413
raise AttributeError("'module' object has no attribute '%s'" % (attr,))
420
return ModuleType.__repr__(self)
423
attrs = set(dir(self.__class__))
424
attrs.update(self.__dict__)
425
attrs.update(self.__attrmap)
427
if proxy is not None:
428
attrs.update(dir(proxy))
431
# replace this module with overlay that will lazily import attributes.
432
_LazyOverlayModule.replace_module(__name__, _lazy_attrs)
434
#=============================================================================
436
#=============================================================================