_compat.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. # SPDX-License-Identifier: MIT
  2. from __future__ import absolute_import, division, print_function
  3. import platform
  4. import sys
  5. import threading
  6. import types
  7. import warnings
  8. PY2 = sys.version_info[0] == 2
  9. PYPY = platform.python_implementation() == "PyPy"
  10. PY36 = sys.version_info[:2] >= (3, 6)
  11. HAS_F_STRINGS = PY36
  12. PY310 = sys.version_info[:2] >= (3, 10)
  13. if PYPY or PY36:
  14. ordered_dict = dict
  15. else:
  16. from collections import OrderedDict
  17. ordered_dict = OrderedDict
  18. if PY2:
  19. from collections import Mapping, Sequence
  20. from UserDict import IterableUserDict
  21. # We 'bundle' isclass instead of using inspect as importing inspect is
  22. # fairly expensive (order of 10-15 ms for a modern machine in 2016)
  23. def isclass(klass):
  24. return isinstance(klass, (type, types.ClassType))
  25. def new_class(name, bases, kwds, exec_body):
  26. """
  27. A minimal stub of types.new_class that we need for make_class.
  28. """
  29. ns = {}
  30. exec_body(ns)
  31. return type(name, bases, ns)
  32. # TYPE is used in exceptions, repr(int) is different on Python 2 and 3.
  33. TYPE = "type"
  34. def iteritems(d):
  35. return d.iteritems()
  36. # Python 2 is bereft of a read-only dict proxy, so we make one!
  37. class ReadOnlyDict(IterableUserDict):
  38. """
  39. Best-effort read-only dict wrapper.
  40. """
  41. def __setitem__(self, key, val):
  42. # We gently pretend we're a Python 3 mappingproxy.
  43. raise TypeError(
  44. "'mappingproxy' object does not support item assignment"
  45. )
  46. def update(self, _):
  47. # We gently pretend we're a Python 3 mappingproxy.
  48. raise AttributeError(
  49. "'mappingproxy' object has no attribute 'update'"
  50. )
  51. def __delitem__(self, _):
  52. # We gently pretend we're a Python 3 mappingproxy.
  53. raise TypeError(
  54. "'mappingproxy' object does not support item deletion"
  55. )
  56. def clear(self):
  57. # We gently pretend we're a Python 3 mappingproxy.
  58. raise AttributeError(
  59. "'mappingproxy' object has no attribute 'clear'"
  60. )
  61. def pop(self, key, default=None):
  62. # We gently pretend we're a Python 3 mappingproxy.
  63. raise AttributeError(
  64. "'mappingproxy' object has no attribute 'pop'"
  65. )
  66. def popitem(self):
  67. # We gently pretend we're a Python 3 mappingproxy.
  68. raise AttributeError(
  69. "'mappingproxy' object has no attribute 'popitem'"
  70. )
  71. def setdefault(self, key, default=None):
  72. # We gently pretend we're a Python 3 mappingproxy.
  73. raise AttributeError(
  74. "'mappingproxy' object has no attribute 'setdefault'"
  75. )
  76. def __repr__(self):
  77. # Override to be identical to the Python 3 version.
  78. return "mappingproxy(" + repr(self.data) + ")"
  79. def metadata_proxy(d):
  80. res = ReadOnlyDict()
  81. res.data.update(d) # We blocked update, so we have to do it like this.
  82. return res
  83. def just_warn(*args, **kw): # pragma: no cover
  84. """
  85. We only warn on Python 3 because we are not aware of any concrete
  86. consequences of not setting the cell on Python 2.
  87. """
  88. else: # Python 3 and later.
  89. from collections.abc import Mapping, Sequence # noqa
  90. def just_warn(*args, **kw):
  91. """
  92. We only warn on Python 3 because we are not aware of any concrete
  93. consequences of not setting the cell on Python 2.
  94. """
  95. warnings.warn(
  96. "Running interpreter doesn't sufficiently support code object "
  97. "introspection. Some features like bare super() or accessing "
  98. "__class__ will not work with slotted classes.",
  99. RuntimeWarning,
  100. stacklevel=2,
  101. )
  102. def isclass(klass):
  103. return isinstance(klass, type)
  104. TYPE = "class"
  105. def iteritems(d):
  106. return d.items()
  107. new_class = types.new_class
  108. def metadata_proxy(d):
  109. return types.MappingProxyType(dict(d))
  110. def make_set_closure_cell():
  111. """Return a function of two arguments (cell, value) which sets
  112. the value stored in the closure cell `cell` to `value`.
  113. """
  114. # pypy makes this easy. (It also supports the logic below, but
  115. # why not do the easy/fast thing?)
  116. if PYPY:
  117. def set_closure_cell(cell, value):
  118. cell.__setstate__((value,))
  119. return set_closure_cell
  120. # Otherwise gotta do it the hard way.
  121. # Create a function that will set its first cellvar to `value`.
  122. def set_first_cellvar_to(value):
  123. x = value
  124. return
  125. # This function will be eliminated as dead code, but
  126. # not before its reference to `x` forces `x` to be
  127. # represented as a closure cell rather than a local.
  128. def force_x_to_be_a_cell(): # pragma: no cover
  129. return x
  130. try:
  131. # Extract the code object and make sure our assumptions about
  132. # the closure behavior are correct.
  133. if PY2:
  134. co = set_first_cellvar_to.func_code
  135. else:
  136. co = set_first_cellvar_to.__code__
  137. if co.co_cellvars != ("x",) or co.co_freevars != ():
  138. raise AssertionError # pragma: no cover
  139. # Convert this code object to a code object that sets the
  140. # function's first _freevar_ (not cellvar) to the argument.
  141. if sys.version_info >= (3, 8):
  142. # CPython 3.8+ has an incompatible CodeType signature
  143. # (added a posonlyargcount argument) but also added
  144. # CodeType.replace() to do this without counting parameters.
  145. set_first_freevar_code = co.replace(
  146. co_cellvars=co.co_freevars, co_freevars=co.co_cellvars
  147. )
  148. else:
  149. args = [co.co_argcount]
  150. if not PY2:
  151. args.append(co.co_kwonlyargcount)
  152. args.extend(
  153. [
  154. co.co_nlocals,
  155. co.co_stacksize,
  156. co.co_flags,
  157. co.co_code,
  158. co.co_consts,
  159. co.co_names,
  160. co.co_varnames,
  161. co.co_filename,
  162. co.co_name,
  163. co.co_firstlineno,
  164. co.co_lnotab,
  165. # These two arguments are reversed:
  166. co.co_cellvars,
  167. co.co_freevars,
  168. ]
  169. )
  170. set_first_freevar_code = types.CodeType(*args)
  171. def set_closure_cell(cell, value):
  172. # Create a function using the set_first_freevar_code,
  173. # whose first closure cell is `cell`. Calling it will
  174. # change the value of that cell.
  175. setter = types.FunctionType(
  176. set_first_freevar_code, {}, "setter", (), (cell,)
  177. )
  178. # And call it to set the cell.
  179. setter(value)
  180. # Make sure it works on this interpreter:
  181. def make_func_with_cell():
  182. x = None
  183. def func():
  184. return x # pragma: no cover
  185. return func
  186. if PY2:
  187. cell = make_func_with_cell().func_closure[0]
  188. else:
  189. cell = make_func_with_cell().__closure__[0]
  190. set_closure_cell(cell, 100)
  191. if cell.cell_contents != 100:
  192. raise AssertionError # pragma: no cover
  193. except Exception:
  194. return just_warn
  195. else:
  196. return set_closure_cell
  197. set_closure_cell = make_set_closure_cell()
  198. # Thread-local global to track attrs instances which are already being repr'd.
  199. # This is needed because there is no other (thread-safe) way to pass info
  200. # about the instances that are already being repr'd through the call stack
  201. # in order to ensure we don't perform infinite recursion.
  202. #
  203. # For instance, if an instance contains a dict which contains that instance,
  204. # we need to know that we're already repr'ing the outside instance from within
  205. # the dict's repr() call.
  206. #
  207. # This lives here rather than in _make.py so that the functions in _make.py
  208. # don't have a direct reference to the thread-local in their globals dict.
  209. # If they have such a reference, it breaks cloudpickle.
  210. repr_context = threading.local()