1# util/compat.py 2# Copyright (C) 2005-2019 the SQLAlchemy authors and contributors 3# <see AUTHORS file> 4# 5# This module is part of SQLAlchemy and is released under 6# the MIT License: http://www.opensource.org/licenses/mit-license.php 7 8"""Handle Python version/platform incompatibilities.""" 9 10import collections 11from collections import namedtuple # noqa 12from contextlib import contextmanager 13from operator import attrgetter as dottedgetter # noqa 14import sys 15import time 16 17try: 18 import threading 19except ImportError: 20 import dummy_threading as threading # noqa 21 22py36 = sys.version_info >= (3, 6) 23py33 = sys.version_info >= (3, 3) 24py35 = sys.version_info >= (3, 5) 25py32 = sys.version_info >= (3, 2) 26py3k = sys.version_info >= (3, 0) 27py2k = sys.version_info < (3, 0) 28py265 = sys.version_info >= (2, 6, 5) 29jython = sys.platform.startswith("java") 30pypy = hasattr(sys, "pypy_version_info") 31win32 = sys.platform.startswith("win") 32cpython = not pypy and not jython # TODO: something better for this ? 33 34next = next # noqa 35 36if py3k: 37 import pickle 38else: 39 try: 40 import cPickle as pickle 41 except ImportError: 42 import pickle # noqa 43 44# work around http://bugs.python.org/issue2646 45if py265: 46 safe_kwarg = lambda arg: arg # noqa 47else: 48 safe_kwarg = str 49 50ArgSpec = collections.namedtuple( 51 "ArgSpec", ["args", "varargs", "keywords", "defaults"] 52) 53 54if py3k: 55 import builtins 56 57 from inspect import getfullargspec as inspect_getfullargspec 58 from urllib.parse import ( 59 quote_plus, 60 unquote_plus, 61 parse_qsl, 62 quote, 63 unquote, 64 ) 65 import configparser 66 from io import StringIO 67 68 from io import BytesIO as byte_buffer 69 70 def inspect_getargspec(func): 71 return ArgSpec(*inspect_getfullargspec(func)[0:4]) 72 73 string_types = (str,) 74 binary_types = (bytes,) 75 binary_type = bytes 76 text_type = str 77 int_types = (int,) 78 iterbytes = iter 79 80 def u(s): 81 return s 82 83 def ue(s): 84 return s 85 86 def b(s): 87 return s.encode("latin-1") 88 89 if py32: 90 callable = callable # noqa 91 else: 92 93 def callable(fn): # noqa 94 return hasattr(fn, "__call__") 95 96 def decode_backslashreplace(text, encoding): 97 return text.decode(encoding, errors="backslashreplace") 98 99 def cmp(a, b): 100 return (a > b) - (a < b) 101 102 from functools import reduce 103 104 print_ = getattr(builtins, "print") 105 106 import_ = getattr(builtins, "__import__") 107 108 import itertools 109 110 itertools_filterfalse = itertools.filterfalse 111 itertools_filter = filter 112 itertools_imap = map 113 from itertools import zip_longest 114 115 import base64 116 117 def b64encode(x): 118 return base64.b64encode(x).decode("ascii") 119 120 def b64decode(x): 121 return base64.b64decode(x.encode("ascii")) 122 123 124else: 125 from inspect import getargspec as inspect_getfullargspec 126 127 inspect_getargspec = inspect_getfullargspec 128 from urllib import quote_plus, unquote_plus, quote, unquote # noqa 129 from urlparse import parse_qsl # noqa 130 import ConfigParser as configparser # noqa 131 from StringIO import StringIO # noqa 132 from cStringIO import StringIO as byte_buffer # noqa 133 134 string_types = (basestring,) # noqa 135 binary_types = (bytes,) 136 binary_type = str 137 text_type = unicode # noqa 138 int_types = int, long # noqa 139 140 def iterbytes(buf): 141 return (ord(byte) for byte in buf) 142 143 def u(s): 144 # this differs from what six does, which doesn't support non-ASCII 145 # strings - we only use u() with 146 # literal source strings, and all our source files with non-ascii 147 # in them (all are tests) are utf-8 encoded. 148 return unicode(s, "utf-8") # noqa 149 150 def ue(s): 151 return unicode(s, "unicode_escape") # noqa 152 153 def b(s): 154 return s 155 156 def import_(*args): 157 if len(args) == 4: 158 args = args[0:3] + ([str(arg) for arg in args[3]],) 159 return __import__(*args) 160 161 callable = callable # noqa 162 cmp = cmp 163 reduce = reduce 164 165 import base64 166 167 b64encode = base64.b64encode 168 b64decode = base64.b64decode 169 170 def print_(*args, **kwargs): 171 fp = kwargs.pop("file", sys.stdout) 172 if fp is None: 173 return 174 for arg in enumerate(args): 175 if not isinstance(arg, basestring): # noqa 176 arg = str(arg) 177 fp.write(arg) 178 179 import itertools 180 181 def decode_backslashreplace(text, encoding): 182 try: 183 return text.decode(encoding) 184 except UnicodeDecodeError: 185 # regular "backslashreplace" for an incompatible encoding raises: 186 # "TypeError: don't know how to handle UnicodeDecodeError in 187 # error callback" 188 return repr(text)[1:-1].decode() 189 190 itertools_filterfalse = itertools.ifilterfalse 191 itertools_filter = itertools.ifilter 192 itertools_imap = itertools.imap 193 from itertools import izip_longest as zip_longest # noqa 194 195if py35: 196 from inspect import formatannotation 197 198 def inspect_formatargspec( 199 args, 200 varargs=None, 201 varkw=None, 202 defaults=None, 203 kwonlyargs=(), 204 kwonlydefaults={}, 205 annotations={}, 206 formatarg=str, 207 formatvarargs=lambda name: "*" + name, 208 formatvarkw=lambda name: "**" + name, 209 formatvalue=lambda value: "=" + repr(value), 210 formatreturns=lambda text: " -> " + text, 211 formatannotation=formatannotation, 212 ): 213 """Copy formatargspec from python 3.7 standard library. 214 215 Python 3 has deprecated formatargspec and requested that Signature 216 be used instead, however this requires a full reimplementation 217 of formatargspec() in terms of creating Parameter objects and such. 218 Instead of introducing all the object-creation overhead and having 219 to reinvent from scratch, just copy their compatibility routine. 220 221 Utimately we would need to rewrite our "decorator" routine completely 222 which is not really worth it right now, until all Python 2.x support 223 is dropped. 224 225 """ 226 227 def formatargandannotation(arg): 228 result = formatarg(arg) 229 if arg in annotations: 230 result += ": " + formatannotation(annotations[arg]) 231 return result 232 233 specs = [] 234 if defaults: 235 firstdefault = len(args) - len(defaults) 236 for i, arg in enumerate(args): 237 spec = formatargandannotation(arg) 238 if defaults and i >= firstdefault: 239 spec = spec + formatvalue(defaults[i - firstdefault]) 240 specs.append(spec) 241 if varargs is not None: 242 specs.append(formatvarargs(formatargandannotation(varargs))) 243 else: 244 if kwonlyargs: 245 specs.append("*") 246 if kwonlyargs: 247 for kwonlyarg in kwonlyargs: 248 spec = formatargandannotation(kwonlyarg) 249 if kwonlydefaults and kwonlyarg in kwonlydefaults: 250 spec += formatvalue(kwonlydefaults[kwonlyarg]) 251 specs.append(spec) 252 if varkw is not None: 253 specs.append(formatvarkw(formatargandannotation(varkw))) 254 result = "(" + ", ".join(specs) + ")" 255 if "return" in annotations: 256 result += formatreturns(formatannotation(annotations["return"])) 257 return result 258 259 260else: 261 from inspect import formatargspec as inspect_formatargspec # noqa 262 263if win32 or jython: 264 time_func = time.clock 265else: 266 time_func = time.time 267 268 269if py3k: 270 271 def reraise(tp, value, tb=None, cause=None): 272 if cause is not None: 273 assert cause is not value, "Same cause emitted" 274 value.__cause__ = cause 275 if value.__traceback__ is not tb: 276 raise value.with_traceback(tb) 277 raise value 278 279 280else: 281 # not as nice as that of Py3K, but at least preserves 282 # the code line where the issue occurred 283 exec( 284 "def reraise(tp, value, tb=None, cause=None):\n" 285 " if cause is not None:\n" 286 " assert cause is not value, 'Same cause emitted'\n" 287 " raise tp, value, tb\n" 288 ) 289 290 291def raise_from_cause(exception, exc_info=None): 292 if exc_info is None: 293 exc_info = sys.exc_info() 294 exc_type, exc_value, exc_tb = exc_info 295 cause = exc_value if exc_value is not exception else None 296 reraise(type(exception), exception, tb=exc_tb, cause=cause) 297 298 299if py3k: 300 exec_ = getattr(builtins, "exec") 301else: 302 303 def exec_(func_text, globals_, lcl=None): 304 if lcl is None: 305 exec("exec func_text in globals_") 306 else: 307 exec("exec func_text in globals_, lcl") 308 309 310def with_metaclass(meta, *bases): 311 """Create a base class with a metaclass. 312 313 Drops the middle class upon creation. 314 315 Source: http://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/ 316 317 """ 318 319 class metaclass(meta): 320 __call__ = type.__call__ 321 __init__ = type.__init__ 322 323 def __new__(cls, name, this_bases, d): 324 if this_bases is None: 325 return type.__new__(cls, name, (), d) 326 return meta(name, bases, d) 327 328 return metaclass("temporary_class", None, {}) 329 330 331@contextmanager 332def nested(*managers): 333 """Implement contextlib.nested, mostly for unit tests. 334 335 As tests still need to run on py2.6 we can't use multiple-with yet. 336 337 Function is removed in py3k but also emits deprecation warning in 2.7 338 so just roll it here for everyone. 339 340 """ 341 342 exits = [] 343 vars_ = [] 344 exc = (None, None, None) 345 try: 346 for mgr in managers: 347 exit_ = mgr.__exit__ 348 enter = mgr.__enter__ 349 vars_.append(enter()) 350 exits.append(exit_) 351 yield vars_ 352 except: 353 exc = sys.exc_info() 354 finally: 355 while exits: 356 exit_ = exits.pop() 357 try: 358 if exit_(*exc): 359 exc = (None, None, None) 360 except: 361 exc = sys.exc_info() 362 if exc != (None, None, None): 363 reraise(exc[0], exc[1], exc[2]) 364 365 366# Fix deprecation of accessing ABCs straight from collections module 367# (which will stop working in 3.8). 368if py33: 369 import collections.abc as collections_abc 370else: 371 import collections as collections_abc # noqa 372