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