1# util/compat.py 2# Copyright (C) 2005-2021 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: https://www.opensource.org/licenses/mit-license.php 7 8"""Handle Python version/platform incompatibilities.""" 9 10import collections 11import contextlib 12import inspect 13import operator 14import platform 15import sys 16 17py39 = sys.version_info >= (3, 9) 18py38 = sys.version_info >= (3, 8) 19py37 = sys.version_info >= (3, 7) 20py3k = sys.version_info >= (3, 0) 21py2k = sys.version_info < (3, 0) 22pypy = platform.python_implementation() == "PyPy" 23 24 25cpython = platform.python_implementation() == "CPython" 26win32 = sys.platform.startswith("win") 27osx = sys.platform.startswith("darwin") 28arm = "aarch" in platform.machine().lower() 29 30has_refcount_gc = bool(cpython) 31 32contextmanager = contextlib.contextmanager 33dottedgetter = operator.attrgetter 34namedtuple = collections.namedtuple 35next = next # noqa 36 37FullArgSpec = collections.namedtuple( 38 "FullArgSpec", 39 [ 40 "args", 41 "varargs", 42 "varkw", 43 "defaults", 44 "kwonlyargs", 45 "kwonlydefaults", 46 "annotations", 47 ], 48) 49 50 51class nullcontext(object): 52 """Context manager that does no additional processing. 53 54 Vendored from Python 3.7. 55 56 """ 57 58 def __init__(self, enter_result=None): 59 self.enter_result = enter_result 60 61 def __enter__(self): 62 return self.enter_result 63 64 def __exit__(self, *excinfo): 65 pass 66 67 68try: 69 import threading 70except ImportError: 71 import dummy_threading as threading # noqa 72 73 74def inspect_getfullargspec(func): 75 """Fully vendored version of getfullargspec from Python 3.3.""" 76 77 if inspect.ismethod(func): 78 func = func.__func__ 79 if not inspect.isfunction(func): 80 raise TypeError("{!r} is not a Python function".format(func)) 81 82 co = func.__code__ 83 if not inspect.iscode(co): 84 raise TypeError("{!r} is not a code object".format(co)) 85 86 nargs = co.co_argcount 87 names = co.co_varnames 88 nkwargs = co.co_kwonlyargcount if py3k else 0 89 args = list(names[:nargs]) 90 kwonlyargs = list(names[nargs : nargs + nkwargs]) 91 92 nargs += nkwargs 93 varargs = None 94 if co.co_flags & inspect.CO_VARARGS: 95 varargs = co.co_varnames[nargs] 96 nargs = nargs + 1 97 varkw = None 98 if co.co_flags & inspect.CO_VARKEYWORDS: 99 varkw = co.co_varnames[nargs] 100 101 return FullArgSpec( 102 args, 103 varargs, 104 varkw, 105 func.__defaults__, 106 kwonlyargs, 107 func.__kwdefaults__ if py3k else None, 108 func.__annotations__ if py3k else {}, 109 ) 110 111 112if py38: 113 from importlib import metadata as importlib_metadata 114else: 115 import importlib_metadata # noqa 116 117 118def importlib_metadata_get(group): 119 ep = importlib_metadata.entry_points() 120 if hasattr(ep, "select"): 121 return ep.select(group=group) 122 else: 123 return ep.get(group, ()) 124 125 126if py3k: 127 import base64 128 import builtins 129 import configparser 130 import itertools 131 import pickle 132 133 from functools import reduce 134 from io import BytesIO as byte_buffer 135 from io import StringIO 136 from itertools import zip_longest 137 from time import perf_counter 138 from urllib.parse import ( 139 quote_plus, 140 unquote_plus, 141 parse_qsl, 142 quote, 143 unquote, 144 ) 145 146 string_types = (str,) 147 binary_types = (bytes,) 148 binary_type = bytes 149 text_type = str 150 int_types = (int,) 151 iterbytes = iter 152 long_type = int 153 154 itertools_filterfalse = itertools.filterfalse 155 itertools_filter = filter 156 itertools_imap = map 157 158 exec_ = getattr(builtins, "exec") 159 import_ = getattr(builtins, "__import__") 160 print_ = getattr(builtins, "print") 161 162 def b(s): 163 return s.encode("latin-1") 164 165 def b64decode(x): 166 return base64.b64decode(x.encode("ascii")) 167 168 def b64encode(x): 169 return base64.b64encode(x).decode("ascii") 170 171 def decode_backslashreplace(text, encoding): 172 return text.decode(encoding, errors="backslashreplace") 173 174 def cmp(a, b): 175 return (a > b) - (a < b) 176 177 def raise_( 178 exception, with_traceback=None, replace_context=None, from_=False 179 ): 180 r"""implement "raise" with cause support. 181 182 :param exception: exception to raise 183 :param with_traceback: will call exception.with_traceback() 184 :param replace_context: an as-yet-unsupported feature. This is 185 an exception object which we are "replacing", e.g., it's our 186 "cause" but we don't want it printed. Basically just what 187 ``__suppress_context__`` does but we don't want to suppress 188 the enclosing context, if any. So for now we make it the 189 cause. 190 :param from\_: the cause. this actually sets the cause and doesn't 191 hope to hide it someday. 192 193 """ 194 if with_traceback is not None: 195 exception = exception.with_traceback(with_traceback) 196 197 if from_ is not False: 198 exception.__cause__ = from_ 199 elif replace_context is not None: 200 # no good solution here, we would like to have the exception 201 # have only the context of replace_context.__context__ so that the 202 # intermediary exception does not change, but we can't figure 203 # that out. 204 exception.__cause__ = replace_context 205 206 try: 207 raise exception 208 finally: 209 # credit to 210 # https://cosmicpercolator.com/2016/01/13/exception-leaks-in-python-2-and-3/ 211 # as the __traceback__ object creates a cycle 212 del exception, replace_context, from_, with_traceback 213 214 def u(s): 215 return s 216 217 def ue(s): 218 return s 219 220 from typing import TYPE_CHECKING 221 222 # Unused. Kept for backwards compatibility. 223 callable = callable # noqa 224 225 from abc import ABC 226 227 def _qualname(fn): 228 return fn.__qualname__ 229 230 231else: 232 import base64 233 import ConfigParser as configparser # noqa 234 import itertools 235 236 from StringIO import StringIO # noqa 237 from cStringIO import StringIO as byte_buffer # noqa 238 from itertools import izip_longest as zip_longest # noqa 239 from time import clock as perf_counter # noqa 240 from urllib import quote # noqa 241 from urllib import quote_plus # noqa 242 from urllib import unquote # noqa 243 from urllib import unquote_plus # noqa 244 from urlparse import parse_qsl # noqa 245 246 from abc import ABCMeta 247 248 class ABC(object): 249 __metaclass__ = ABCMeta 250 251 try: 252 import cPickle as pickle 253 except ImportError: 254 import pickle # noqa 255 256 string_types = (basestring,) # noqa 257 binary_types = (bytes,) 258 binary_type = str 259 text_type = unicode # noqa 260 int_types = int, long # noqa 261 long_type = long # noqa 262 263 callable = callable # noqa 264 cmp = cmp # noqa 265 reduce = reduce # noqa 266 267 b64encode = base64.b64encode 268 b64decode = base64.b64decode 269 270 itertools_filterfalse = itertools.ifilterfalse 271 itertools_filter = itertools.ifilter 272 itertools_imap = itertools.imap 273 274 def b(s): 275 return s 276 277 def exec_(func_text, globals_, lcl=None): 278 if lcl is None: 279 exec("exec func_text in globals_") 280 else: 281 exec("exec func_text in globals_, lcl") 282 283 def iterbytes(buf): 284 return (ord(byte) for byte in buf) 285 286 def import_(*args): 287 if len(args) == 4: 288 args = args[0:3] + ([str(arg) for arg in args[3]],) 289 return __import__(*args) 290 291 def print_(*args, **kwargs): 292 fp = kwargs.pop("file", sys.stdout) 293 if fp is None: 294 return 295 for arg in enumerate(args): 296 if not isinstance(arg, basestring): # noqa 297 arg = str(arg) 298 fp.write(arg) 299 300 def u(s): 301 # this differs from what six does, which doesn't support non-ASCII 302 # strings - we only use u() with 303 # literal source strings, and all our source files with non-ascii 304 # in them (all are tests) are utf-8 encoded. 305 return unicode(s, "utf-8") # noqa 306 307 def ue(s): 308 return unicode(s, "unicode_escape") # noqa 309 310 def decode_backslashreplace(text, encoding): 311 try: 312 return text.decode(encoding) 313 except UnicodeDecodeError: 314 # regular "backslashreplace" for an incompatible encoding raises: 315 # "TypeError: don't know how to handle UnicodeDecodeError in 316 # error callback" 317 return repr(text)[1:-1].decode() 318 319 def safe_bytestring(text): 320 # py2k only 321 if not isinstance(text, string_types): 322 return unicode(text).encode( # noqa: F821 323 "ascii", errors="backslashreplace" 324 ) 325 elif isinstance(text, unicode): # noqa: F821 326 return text.encode("ascii", errors="backslashreplace") 327 else: 328 return text 329 330 exec( 331 "def raise_(exception, with_traceback=None, replace_context=None, " 332 "from_=False):\n" 333 " if with_traceback:\n" 334 " raise type(exception), exception, with_traceback\n" 335 " else:\n" 336 " raise exception\n" 337 ) 338 339 TYPE_CHECKING = False 340 341 def _qualname(meth): 342 """return __qualname__ equivalent for a method on a class""" 343 344 for cls in meth.im_class.__mro__: 345 if meth.__name__ in cls.__dict__: 346 break 347 else: 348 return meth.__name__ 349 350 return "%s.%s" % (cls.__name__, meth.__name__) 351 352 353if py3k: 354 355 def _formatannotation(annotation, base_module=None): 356 """vendored from python 3.7""" 357 358 if getattr(annotation, "__module__", None) == "typing": 359 return repr(annotation).replace("typing.", "") 360 if isinstance(annotation, type): 361 if annotation.__module__ in ("builtins", base_module): 362 return annotation.__qualname__ 363 return annotation.__module__ + "." + annotation.__qualname__ 364 return repr(annotation) 365 366 def inspect_formatargspec( 367 args, 368 varargs=None, 369 varkw=None, 370 defaults=None, 371 kwonlyargs=(), 372 kwonlydefaults={}, 373 annotations={}, 374 formatarg=str, 375 formatvarargs=lambda name: "*" + name, 376 formatvarkw=lambda name: "**" + name, 377 formatvalue=lambda value: "=" + repr(value), 378 formatreturns=lambda text: " -> " + text, 379 formatannotation=_formatannotation, 380 ): 381 """Copy formatargspec from python 3.7 standard library. 382 383 Python 3 has deprecated formatargspec and requested that Signature 384 be used instead, however this requires a full reimplementation 385 of formatargspec() in terms of creating Parameter objects and such. 386 Instead of introducing all the object-creation overhead and having 387 to reinvent from scratch, just copy their compatibility routine. 388 389 Ultimately we would need to rewrite our "decorator" routine completely 390 which is not really worth it right now, until all Python 2.x support 391 is dropped. 392 393 """ 394 395 kwonlydefaults = kwonlydefaults or {} 396 annotations = annotations or {} 397 398 def formatargandannotation(arg): 399 result = formatarg(arg) 400 if arg in annotations: 401 result += ": " + formatannotation(annotations[arg]) 402 return result 403 404 specs = [] 405 if defaults: 406 firstdefault = len(args) - len(defaults) 407 for i, arg in enumerate(args): 408 spec = formatargandannotation(arg) 409 if defaults and i >= firstdefault: 410 spec = spec + formatvalue(defaults[i - firstdefault]) 411 specs.append(spec) 412 413 if varargs is not None: 414 specs.append(formatvarargs(formatargandannotation(varargs))) 415 else: 416 if kwonlyargs: 417 specs.append("*") 418 419 if kwonlyargs: 420 for kwonlyarg in kwonlyargs: 421 spec = formatargandannotation(kwonlyarg) 422 if kwonlydefaults and kwonlyarg in kwonlydefaults: 423 spec += formatvalue(kwonlydefaults[kwonlyarg]) 424 specs.append(spec) 425 426 if varkw is not None: 427 specs.append(formatvarkw(formatargandannotation(varkw))) 428 429 result = "(" + ", ".join(specs) + ")" 430 if "return" in annotations: 431 result += formatreturns(formatannotation(annotations["return"])) 432 return result 433 434 435else: 436 from inspect import formatargspec as _inspect_formatargspec 437 438 def inspect_formatargspec(*spec, **kw): 439 # convert for a potential FullArgSpec from compat.getfullargspec() 440 return _inspect_formatargspec(*spec[0:4], **kw) # noqa 441 442 443# Fix deprecation of accessing ABCs straight from collections module 444# (which will stop working in 3.8). 445if py3k: 446 import collections.abc as collections_abc 447else: 448 import collections as collections_abc # noqa 449 450 451if py37: 452 import dataclasses 453 454 def dataclass_fields(cls): 455 """Return a sequence of all dataclasses.Field objects associated 456 with a class.""" 457 458 if dataclasses.is_dataclass(cls): 459 return dataclasses.fields(cls) 460 else: 461 return [] 462 463 def local_dataclass_fields(cls): 464 """Return a sequence of all dataclasses.Field objects associated with 465 a class, excluding those that originate from a superclass.""" 466 467 if dataclasses.is_dataclass(cls): 468 super_fields = set() 469 for sup in cls.__bases__: 470 super_fields.update(dataclass_fields(sup)) 471 return [ 472 f for f in dataclasses.fields(cls) if f not in super_fields 473 ] 474 else: 475 return [] 476 477 478else: 479 480 def dataclass_fields(cls): 481 return [] 482 483 def local_dataclass_fields(cls): 484 return [] 485 486 487def raise_from_cause(exception, exc_info=None): 488 r"""legacy. use raise\_()""" 489 490 if exc_info is None: 491 exc_info = sys.exc_info() 492 exc_type, exc_value, exc_tb = exc_info 493 cause = exc_value if exc_value is not exception else None 494 reraise(type(exception), exception, tb=exc_tb, cause=cause) 495 496 497def reraise(tp, value, tb=None, cause=None): 498 r"""legacy. use raise\_()""" 499 500 raise_(value, with_traceback=tb, from_=cause) 501 502 503def with_metaclass(meta, *bases, **kw): 504 """Create a base class with a metaclass. 505 506 Drops the middle class upon creation. 507 508 Source: https://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/ 509 510 """ 511 512 class metaclass(meta): 513 __call__ = type.__call__ 514 __init__ = type.__init__ 515 516 def __new__(cls, name, this_bases, d): 517 if this_bases is None: 518 cls = type.__new__(cls, name, (), d) 519 else: 520 cls = meta(name, bases, d) 521 522 if hasattr(cls, "__init_subclass__") and hasattr( 523 cls.__init_subclass__, "__func__" 524 ): 525 cls.__init_subclass__.__func__(cls, **kw) 526 return cls 527 528 return metaclass("temporary_class", None, {}) 529 530 531if py3k: 532 from datetime import timezone 533else: 534 from datetime import datetime 535 from datetime import timedelta 536 from datetime import tzinfo 537 538 class timezone(tzinfo): 539 """Minimal port of python 3 timezone object""" 540 541 __slots__ = "_offset" 542 543 def __init__(self, offset): 544 if not isinstance(offset, timedelta): 545 raise TypeError("offset must be a timedelta") 546 if not self._minoffset <= offset <= self._maxoffset: 547 raise ValueError( 548 "offset must be a timedelta " 549 "strictly between -timedelta(hours=24) and " 550 "timedelta(hours=24)." 551 ) 552 self._offset = offset 553 554 def __eq__(self, other): 555 if type(other) != timezone: 556 return False 557 return self._offset == other._offset 558 559 def __hash__(self): 560 return hash(self._offset) 561 562 def __repr__(self): 563 return "sqlalchemy.util.%s(%r)" % ( 564 self.__class__.__name__, 565 self._offset, 566 ) 567 568 def __str__(self): 569 return self.tzname(None) 570 571 def utcoffset(self, dt): 572 return self._offset 573 574 def tzname(self, dt): 575 return self._name_from_offset(self._offset) 576 577 def dst(self, dt): 578 return None 579 580 def fromutc(self, dt): 581 if isinstance(dt, datetime): 582 if dt.tzinfo is not self: 583 raise ValueError("fromutc: dt.tzinfo " "is not self") 584 return dt + self._offset 585 raise TypeError( 586 "fromutc() argument must be a datetime instance" " or None" 587 ) 588 589 @staticmethod 590 def _timedelta_to_microseconds(timedelta): 591 """backport of timedelta._to_microseconds()""" 592 return ( 593 timedelta.days * (24 * 3600) + timedelta.seconds 594 ) * 1000000 + timedelta.microseconds 595 596 @staticmethod 597 def _divmod_timedeltas(a, b): 598 """backport of timedelta.__divmod__""" 599 600 q, r = divmod( 601 timezone._timedelta_to_microseconds(a), 602 timezone._timedelta_to_microseconds(b), 603 ) 604 return q, timedelta(0, 0, r) 605 606 @staticmethod 607 def _name_from_offset(delta): 608 if not delta: 609 return "UTC" 610 if delta < timedelta(0): 611 sign = "-" 612 delta = -delta 613 else: 614 sign = "+" 615 hours, rest = timezone._divmod_timedeltas( 616 delta, timedelta(hours=1) 617 ) 618 minutes, rest = timezone._divmod_timedeltas( 619 rest, timedelta(minutes=1) 620 ) 621 result = "UTC%s%02d:%02d" % (sign, hours, minutes) 622 if rest.seconds: 623 result += ":%02d" % (rest.seconds,) 624 if rest.microseconds: 625 result += ".%06d" % (rest.microseconds,) 626 return result 627 628 _maxoffset = timedelta(hours=23, minutes=59) 629 _minoffset = -_maxoffset 630 631 timezone.utc = timezone(timedelta(0)) 632