retracesoftware-proxy 0.2.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- retracesoftware/__init__.py +0 -0
- retracesoftware/__main__.py +288 -0
- retracesoftware/autoenable.py +53 -0
- retracesoftware/config.json +175 -0
- retracesoftware/config.yaml +0 -0
- retracesoftware/install/__init__.py +0 -0
- retracesoftware/install/config.py +59 -0
- retracesoftware/install/edgecases.py +250 -0
- retracesoftware/install/globals.py +17 -0
- retracesoftware/install/install.py +142 -0
- retracesoftware/install/patcher.py +122 -0
- retracesoftware/install/patchfindspec.py +117 -0
- retracesoftware/install/phases.py +338 -0
- retracesoftware/install/predicate.py +92 -0
- retracesoftware/install/record.py +174 -0
- retracesoftware/install/references.py +66 -0
- retracesoftware/install/replace.py +28 -0
- retracesoftware/install/replay.py +102 -0
- retracesoftware/install/tracer.py +284 -0
- retracesoftware/install/typeutils.py +92 -0
- retracesoftware/modules.toml +388 -0
- retracesoftware/preload.txt +218 -0
- retracesoftware/proxy/__init__.py +3 -0
- retracesoftware/proxy/gateway.py +49 -0
- retracesoftware/proxy/globalref.py +31 -0
- retracesoftware/proxy/messagestream.py +204 -0
- retracesoftware/proxy/proxyfactory.py +357 -0
- retracesoftware/proxy/proxysystem.py +454 -0
- retracesoftware/proxy/proxytype.py +424 -0
- retracesoftware/proxy/record.py +211 -0
- retracesoftware/proxy/replay.py +138 -0
- retracesoftware/proxy/serializer.py +28 -0
- retracesoftware/proxy/startthread.py +40 -0
- retracesoftware/proxy/stubfactory.py +195 -0
- retracesoftware/proxy/thread.py +106 -0
- retracesoftware/replay.py +104 -0
- retracesoftware/run.py +378 -0
- retracesoftware/stackdifference.py +133 -0
- retracesoftware_proxy-0.2.11.dist-info/METADATA +8 -0
- retracesoftware_proxy-0.2.11.dist-info/RECORD +42 -0
- retracesoftware_proxy-0.2.11.dist-info/WHEEL +5 -0
- retracesoftware_proxy-0.2.11.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from retracesoftware.proxy import *
|
|
2
|
+
|
|
3
|
+
from retracesoftware.install import globals
|
|
4
|
+
|
|
5
|
+
import os, json, re, glob
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from retracesoftware.install.config import env_truthy
|
|
9
|
+
|
|
10
|
+
def recordings(pattern):
|
|
11
|
+
# Turn strftime placeholders into '*' for globbing
|
|
12
|
+
# (very simple replacement: %... -> *)
|
|
13
|
+
glob_pattern = re.sub(r"%[a-zA-Z]", "*", pattern)
|
|
14
|
+
|
|
15
|
+
base_pattern = os.path.basename(pattern)
|
|
16
|
+
|
|
17
|
+
# Find all matching files
|
|
18
|
+
for path in glob.glob(glob_pattern):
|
|
19
|
+
try:
|
|
20
|
+
name = os.path.basename(path)
|
|
21
|
+
datetime.strptime(name, base_pattern)
|
|
22
|
+
yield path
|
|
23
|
+
except:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def latest_from_pattern(pattern: str) -> str | None:
|
|
27
|
+
"""
|
|
28
|
+
Given a strftime-style filename pattern (e.g. "recordings/%Y%m%d_%H%M%S_%f"),
|
|
29
|
+
return the path to the most recent matching file, or None if no files exist.
|
|
30
|
+
"""
|
|
31
|
+
# Derive the datetime format from the pattern (basename only)
|
|
32
|
+
base_pattern = os.path.basename(pattern)
|
|
33
|
+
|
|
34
|
+
def parse_time(path: str):
|
|
35
|
+
name = os.path.basename(path)
|
|
36
|
+
return datetime.strptime(name, base_pattern)
|
|
37
|
+
|
|
38
|
+
# Find the latest by parsed timestamp
|
|
39
|
+
return max(recordings(pattern), key=parse_time)
|
|
40
|
+
|
|
41
|
+
def replay_system(thread_state, immutable_types, config):
|
|
42
|
+
|
|
43
|
+
verbose = env_truthy('RETRACE_VERBOSE', False)
|
|
44
|
+
recording_path = Path(latest_from_pattern(config['recording_path']))
|
|
45
|
+
|
|
46
|
+
# print(f"replay running against path: {recording_path}")
|
|
47
|
+
|
|
48
|
+
globals.recording_path = globals.RecordingPath(recording_path)
|
|
49
|
+
|
|
50
|
+
assert recording_path.exists()
|
|
51
|
+
assert recording_path.is_dir()
|
|
52
|
+
|
|
53
|
+
with open(recording_path / "env", "r", encoding="utf-8") as f:
|
|
54
|
+
os.environ.update(json.load(f))
|
|
55
|
+
|
|
56
|
+
with open(recording_path / "tracing_config.json", "r", encoding="utf-8") as f:
|
|
57
|
+
tracing_config = json.load(f)
|
|
58
|
+
|
|
59
|
+
with open(recording_path / "mainscript", "r", encoding="utf-8") as f:
|
|
60
|
+
mainscript = f.read()
|
|
61
|
+
|
|
62
|
+
call_trace_file = recording_path / "call_trace.txt"
|
|
63
|
+
|
|
64
|
+
if call_trace_file.exists():
|
|
65
|
+
class CallTracer:
|
|
66
|
+
def __init__(self):
|
|
67
|
+
self.file = open(call_trace_file, 'r')
|
|
68
|
+
|
|
69
|
+
def __call__(self, obj):
|
|
70
|
+
recording = json.loads(self.file.readline())
|
|
71
|
+
if obj != recording:
|
|
72
|
+
breakpoint()
|
|
73
|
+
|
|
74
|
+
def close(self):
|
|
75
|
+
self.file.close()
|
|
76
|
+
|
|
77
|
+
# def on_call(self, function_name):
|
|
78
|
+
# recording = self.file.readline()
|
|
79
|
+
# if f'call: {function_name}\n' != recording:
|
|
80
|
+
# breakpoint()
|
|
81
|
+
|
|
82
|
+
# assert f'call: {function_name}\n' == recording
|
|
83
|
+
|
|
84
|
+
# def on_return(self, function_name):
|
|
85
|
+
# recording = self.file.readline()
|
|
86
|
+
# if f'return: {function_name}\n' != recording:
|
|
87
|
+
# breakpoint()
|
|
88
|
+
|
|
89
|
+
# assert f'return: {function_name}\n' == recording
|
|
90
|
+
|
|
91
|
+
call_tracer = CallTracer()
|
|
92
|
+
else:
|
|
93
|
+
call_tracer = None
|
|
94
|
+
|
|
95
|
+
return ReplayProxySystem(thread_state = thread_state,
|
|
96
|
+
immutable_types = immutable_types,
|
|
97
|
+
tracing_config = tracing_config,
|
|
98
|
+
mainscript = mainscript,
|
|
99
|
+
path = recording_path / 'trace.bin',
|
|
100
|
+
tracecalls = env_truthy('RETRACE_ALL', False),
|
|
101
|
+
verbose = verbose,
|
|
102
|
+
magic_markers = env_truthy('RETRACE_MAGIC_MARKERS', False))
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import retracesoftware.functional as functional
|
|
2
|
+
import retracesoftware_utils as utils
|
|
3
|
+
from retracesoftware.proxy.proxytype import Proxy
|
|
4
|
+
|
|
5
|
+
from retracesoftware.proxy.serializer import serializer
|
|
6
|
+
|
|
7
|
+
import types
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import functools
|
|
11
|
+
|
|
12
|
+
def format_kwargs(kwargs):
|
|
13
|
+
result = []
|
|
14
|
+
for k, v in kwargs.items():
|
|
15
|
+
result.extend([f"{k} = ", v])
|
|
16
|
+
|
|
17
|
+
return tuple(result)
|
|
18
|
+
|
|
19
|
+
class CallTracer:
|
|
20
|
+
|
|
21
|
+
ignore = set([('abc', '__subclasscheck__'),
|
|
22
|
+
('threading', '_shutdown'),
|
|
23
|
+
('typing', 'inner')])
|
|
24
|
+
|
|
25
|
+
def __init__(self, writer, thread_state):
|
|
26
|
+
self.writer = writer
|
|
27
|
+
self.thread_state = thread_state
|
|
28
|
+
|
|
29
|
+
def on_result(self, result):
|
|
30
|
+
if type(result) in [str]:
|
|
31
|
+
self.writer(result)
|
|
32
|
+
else:
|
|
33
|
+
self.writer(str(type(result)))
|
|
34
|
+
|
|
35
|
+
def on_error(self, exc_type, exc_value, exc_traceback):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def __call__(self, frame):
|
|
39
|
+
if self.thread_state.value == 'internal':
|
|
40
|
+
with self.thread_state.select('disabled'):
|
|
41
|
+
func = frame.function
|
|
42
|
+
|
|
43
|
+
key = (func.__module__, func.__name__)
|
|
44
|
+
|
|
45
|
+
if key not in CallTracer.ignore and func.__name__ != '__hash__':
|
|
46
|
+
self.writer(f'{func.__module__}.{func.__name__}')
|
|
47
|
+
return self
|
|
48
|
+
else:
|
|
49
|
+
return lambda *args, **kwargs: self(*args, **kwargs)
|
|
50
|
+
|
|
51
|
+
class Tracer:
|
|
52
|
+
|
|
53
|
+
def __init__(self, config, writer):
|
|
54
|
+
self.config = config
|
|
55
|
+
serialize = serializer
|
|
56
|
+
# functional.walker(self.serialize)
|
|
57
|
+
self.writer = functional.mapargs(transform = serialize, function = writer)
|
|
58
|
+
|
|
59
|
+
def systrace(self, frame, event, arg):
|
|
60
|
+
try:
|
|
61
|
+
if event in ['line', 'call']:
|
|
62
|
+
print(f'In systrace: {event} {frame.f_code.co_filename}:{frame.f_lineno}')
|
|
63
|
+
self.writer(event, frame.f_code.co_filename, frame.f_code.co_name, frame.f_lineno)
|
|
64
|
+
|
|
65
|
+
elif event == 'return':
|
|
66
|
+
print(f'In systrace: {event}')
|
|
67
|
+
self.writer(event)
|
|
68
|
+
# self.writer(event, self.serialize(arg))
|
|
69
|
+
|
|
70
|
+
elif event == 'exception':
|
|
71
|
+
print(f'In systrace: {event}')
|
|
72
|
+
self.writer(event)
|
|
73
|
+
else:
|
|
74
|
+
print(f'In systrace: {event}')
|
|
75
|
+
|
|
76
|
+
return self.systrace
|
|
77
|
+
except:
|
|
78
|
+
print(f'systrace RAISED ERROR!')
|
|
79
|
+
raise
|
|
80
|
+
|
|
81
|
+
# def trace_calls(self, thread_state):
|
|
82
|
+
# if 'tracecalls' in self.config:
|
|
83
|
+
# # def on_call(frame):
|
|
84
|
+
|
|
85
|
+
# # func = frame.function
|
|
86
|
+
|
|
87
|
+
# # key = (func.__module__, func.__name__)
|
|
88
|
+
|
|
89
|
+
# # ignore = set([('abc', '__subclasscheck__'),
|
|
90
|
+
# # ('threading', '_shutdown')])
|
|
91
|
+
|
|
92
|
+
# # if key in ignore:
|
|
93
|
+
# # print('About to throw!!!')
|
|
94
|
+
# # raise Exception()
|
|
95
|
+
|
|
96
|
+
# # objtype = None
|
|
97
|
+
|
|
98
|
+
# # if 'self' in frame.locals:
|
|
99
|
+
# # this = frame.locals['self']
|
|
100
|
+
# # objtype = type(this)
|
|
101
|
+
|
|
102
|
+
# # # if objtype:
|
|
103
|
+
# # # print(f'Called!!!!: {func.__module__}.{objtype.__name__}.{func.__name__}')
|
|
104
|
+
# # # else:
|
|
105
|
+
# # # print(f'Called!!!!: {func.__module__}.{func.__name__}')
|
|
106
|
+
|
|
107
|
+
# # qualname = (func.__module__, objtype, func.__name__)
|
|
108
|
+
|
|
109
|
+
# # self.writer(f'{func.__module__}.{func.__name__}')
|
|
110
|
+
|
|
111
|
+
# # # if func.__name__ == '_path_stat':
|
|
112
|
+
# # # utils.sigtrap('_path_stat start')
|
|
113
|
+
|
|
114
|
+
# # return qualname
|
|
115
|
+
|
|
116
|
+
# # def on_result(qualname, res):
|
|
117
|
+
# # mod, obj, func = qualname
|
|
118
|
+
|
|
119
|
+
# # # if obj:
|
|
120
|
+
# # # print(f'Returning!!!!: {mod}.{obj}.{func}')
|
|
121
|
+
# # # else:
|
|
122
|
+
# # # print(f'Returning!!!!: {mod}.{func}')
|
|
123
|
+
|
|
124
|
+
# # # if func == '_path_stat':
|
|
125
|
+
# # # utils.sigtrap('_path_stat res')
|
|
126
|
+
|
|
127
|
+
# # if func != '__hash__':
|
|
128
|
+
# # ignore = set(('typing', 'inner'))
|
|
129
|
+
|
|
130
|
+
# # if qualname not in ignore:
|
|
131
|
+
# # if type(res) in [str, int]:
|
|
132
|
+
# # self.writer(res)
|
|
133
|
+
# # else:
|
|
134
|
+
# # self.writer(str(type(res)))
|
|
135
|
+
|
|
136
|
+
# # def on_call_disabled(frame):
|
|
137
|
+
# # func = frame.function
|
|
138
|
+
# # print(f'disabled: {func.__module__}.{func.__name__}')
|
|
139
|
+
|
|
140
|
+
# callback = CallTracer(writer = self.writer, thread_state = thread_state)
|
|
141
|
+
# utils.intercept_frame_eval(callback)
|
|
142
|
+
|
|
143
|
+
# def serialize(self, obj):
|
|
144
|
+
# try:
|
|
145
|
+
# if obj is None: return None
|
|
146
|
+
|
|
147
|
+
# cls = functional.typeof(obj)
|
|
148
|
+
|
|
149
|
+
# if issubclass(cls, Proxy):
|
|
150
|
+
# return str(cls)
|
|
151
|
+
|
|
152
|
+
# if issubclass(cls, (int, str)):
|
|
153
|
+
# return obj
|
|
154
|
+
|
|
155
|
+
# return str(cls)
|
|
156
|
+
|
|
157
|
+
# # if issubclass(cls, Proxy):
|
|
158
|
+
# # return f'<Proxy>'
|
|
159
|
+
|
|
160
|
+
# # if issubclass(cls, types.TracebackType):
|
|
161
|
+
# # return '<traceback>'
|
|
162
|
+
|
|
163
|
+
# # elif issubclass(cls, utils.wrapped_function):
|
|
164
|
+
# # return utils.unwrap(obj).__name__
|
|
165
|
+
|
|
166
|
+
# # elif hasattr(obj, '__module__') and hasattr(obj, '__name__'):
|
|
167
|
+
# # return f'{obj.__module__}.{obj.__name__}'
|
|
168
|
+
# # # elif isinstance(obj, type):
|
|
169
|
+
# # # return f'{obj.__module__}.{obj.__name__}'
|
|
170
|
+
# # else:
|
|
171
|
+
# # return '<other>'
|
|
172
|
+
# # # return f'instance type: {str(self.serialize(type(obj)))}'
|
|
173
|
+
# except:
|
|
174
|
+
# print("ERROR in tracer serialize!!!!")
|
|
175
|
+
# os._exit(1)
|
|
176
|
+
|
|
177
|
+
def log(self, name, message):
|
|
178
|
+
if name in self.config:
|
|
179
|
+
self.writer(name, message)
|
|
180
|
+
|
|
181
|
+
def checkpoint(self, obj):
|
|
182
|
+
self.writer(obj)
|
|
183
|
+
|
|
184
|
+
# def stacktrace(self):
|
|
185
|
+
# self.writer('stacktrace', utils.stacktrace())
|
|
186
|
+
|
|
187
|
+
def _write_call(self, name, *args, **kwargs):
|
|
188
|
+
# self.stacktrace()
|
|
189
|
+
if len(args) > 0:
|
|
190
|
+
funcname = args[0].__name__
|
|
191
|
+
# if funcname == '__del__':
|
|
192
|
+
# utils.sigtrap('FOO')
|
|
193
|
+
|
|
194
|
+
self.writer(name, funcname, args[1:], kwargs)
|
|
195
|
+
else:
|
|
196
|
+
utils.sigtrap('FOO')
|
|
197
|
+
breakpoint()
|
|
198
|
+
|
|
199
|
+
def __call__(self, name, func = None):
|
|
200
|
+
if name in self.config:
|
|
201
|
+
if name.endswith('.call'):
|
|
202
|
+
f = functools.partial(self._write_call, name)
|
|
203
|
+
return functional.firstof(f, func) if func else f
|
|
204
|
+
|
|
205
|
+
elif name.endswith('.result'):
|
|
206
|
+
def write_result(obj):
|
|
207
|
+
self.writer(name, obj)
|
|
208
|
+
return obj
|
|
209
|
+
|
|
210
|
+
return functional.sequence(func, write_result) if func else write_result
|
|
211
|
+
|
|
212
|
+
elif name.endswith('.error'):
|
|
213
|
+
def write_error(*args):
|
|
214
|
+
self.writer(name, args)
|
|
215
|
+
|
|
216
|
+
return functional.firstof(write_error, func) if func else write_error
|
|
217
|
+
|
|
218
|
+
elif name.endswith('.event'):
|
|
219
|
+
def write_event(*args, **kwargs):
|
|
220
|
+
self.writer(name)
|
|
221
|
+
|
|
222
|
+
return functional.firstof(write_event, func) if func else write_event
|
|
223
|
+
|
|
224
|
+
elif name.endswith('.wrap'):
|
|
225
|
+
def wrapper(*args, **kwargs):
|
|
226
|
+
self.writer(name, 'enter')
|
|
227
|
+
try:
|
|
228
|
+
return func(*args, **kwargs)
|
|
229
|
+
finally:
|
|
230
|
+
self.writer(name, 'exit')
|
|
231
|
+
|
|
232
|
+
return wrapper
|
|
233
|
+
|
|
234
|
+
# elif name.endswith('.stack'):
|
|
235
|
+
# def write_event(*args, **kwargs):
|
|
236
|
+
# self.writer(name, utils.stacktrace())
|
|
237
|
+
|
|
238
|
+
# return functional.firstof(write_event, func) if func else write_event
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
return func
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def write(self, name):
|
|
245
|
+
if name in self.config:
|
|
246
|
+
return functional.partial(self.writer, name)
|
|
247
|
+
|
|
248
|
+
def write_call(self, name, func = None):
|
|
249
|
+
|
|
250
|
+
def writer(*args, **kwargs):
|
|
251
|
+
self.writer(name, *(args + format_kwargs(kwargs)))
|
|
252
|
+
|
|
253
|
+
if name in self.config:
|
|
254
|
+
return functional.firstof(writer, func) if func else writer
|
|
255
|
+
else:
|
|
256
|
+
return func
|
|
257
|
+
|
|
258
|
+
def write_result(self, name, func):
|
|
259
|
+
|
|
260
|
+
writer = functional.partial(self.writer, name)
|
|
261
|
+
|
|
262
|
+
if name in self.config:
|
|
263
|
+
return functional.sequence(func, functional.side_effect(writer))
|
|
264
|
+
else:
|
|
265
|
+
return func
|
|
266
|
+
|
|
267
|
+
def event(self, name):
|
|
268
|
+
return functional.always(lambda: self.writer(name))
|
|
269
|
+
|
|
270
|
+
def event_before(self, name, func = None):
|
|
271
|
+
if name in self.config:
|
|
272
|
+
# def foo(*args, **kwargs):
|
|
273
|
+
# print("GRRRRR!!!!!")
|
|
274
|
+
|
|
275
|
+
# return functional.firstof(foo, func)
|
|
276
|
+
return functional.firstof(self.event(name), func)
|
|
277
|
+
else:
|
|
278
|
+
return func
|
|
279
|
+
|
|
280
|
+
def event_after(self, name, func):
|
|
281
|
+
if name in self.config:
|
|
282
|
+
return functional.sequence(func, functional.side_effect(self.event(name)))
|
|
283
|
+
else:
|
|
284
|
+
return func
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# from retrace_utils import _intercept
|
|
2
|
+
import retracesoftware_utils as utils
|
|
3
|
+
|
|
4
|
+
def flags(cls : type):
|
|
5
|
+
f = utils.type_flags(cls)
|
|
6
|
+
|
|
7
|
+
s = set()
|
|
8
|
+
|
|
9
|
+
for name,value in utils.TypeFlags.items():
|
|
10
|
+
if (f & value) != 0:
|
|
11
|
+
s.add(name)
|
|
12
|
+
f = f & ~value
|
|
13
|
+
|
|
14
|
+
if f != 0:
|
|
15
|
+
s.add(f)
|
|
16
|
+
|
|
17
|
+
return s
|
|
18
|
+
|
|
19
|
+
class WithoutFlags:
|
|
20
|
+
|
|
21
|
+
def __init__(self, cls , *flags):
|
|
22
|
+
self.cls = cls
|
|
23
|
+
self.flags = flags
|
|
24
|
+
|
|
25
|
+
def __enter__(self):
|
|
26
|
+
self.saved = utils.type_flags(self.cls)
|
|
27
|
+
flags = self.saved
|
|
28
|
+
|
|
29
|
+
for flag in self.flags:
|
|
30
|
+
flags = flags & ~utils.TypeFlags[flag]
|
|
31
|
+
|
|
32
|
+
utils.set_type_flags(self.cls, flags)
|
|
33
|
+
return self.cls
|
|
34
|
+
|
|
35
|
+
def __exit__(self, *args):
|
|
36
|
+
utils.set_type_flags(self.cls, self.saved)
|
|
37
|
+
|
|
38
|
+
class WithFlags:
|
|
39
|
+
|
|
40
|
+
def __init__(self, cls , *flags):
|
|
41
|
+
self.cls = cls
|
|
42
|
+
self.flags = flags
|
|
43
|
+
|
|
44
|
+
def __enter__(self):
|
|
45
|
+
self.saved = utils.type_flags(self.cls)
|
|
46
|
+
flags = self.saved
|
|
47
|
+
|
|
48
|
+
for flag in self.flags:
|
|
49
|
+
flags |= utils.TypeFlags[flag]
|
|
50
|
+
|
|
51
|
+
utils.set_type_flags(self.cls, flags)
|
|
52
|
+
return self.cls
|
|
53
|
+
|
|
54
|
+
def __exit__(self, *args):
|
|
55
|
+
utils.set_type_flags(self.cls, self.saved)
|
|
56
|
+
|
|
57
|
+
def add_flag(cls : type, flag):
|
|
58
|
+
if isinstance(flag, str):
|
|
59
|
+
flag = utils.TypeFlags[flag]
|
|
60
|
+
|
|
61
|
+
flags = utils.type_flags(cls)
|
|
62
|
+
utils.set_type_flags(cls, flags | flag)
|
|
63
|
+
|
|
64
|
+
def modify(cls):
|
|
65
|
+
return WithoutFlags(cls, "Py_TPFLAGS_IMMUTABLETYPE")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def type_has_feature(cls, flag):
|
|
69
|
+
if isinstance(flag, str):
|
|
70
|
+
flag = utils.TypeFlags[flag]
|
|
71
|
+
return (utils.type_flags(cls) & flag) != 0
|
|
72
|
+
|
|
73
|
+
def type_disallow_instantiation(cls : type):
|
|
74
|
+
return utils.type_flags(cls) & utils.Py_TPFLAGS_DISALLOW_INSTANTIATION
|
|
75
|
+
|
|
76
|
+
def is_method_descriptor(cls : type):
|
|
77
|
+
return (utils.type_flags(cls) & utils.TypeFlags["Py_TPFLAGS_METHOD_DESCRIPTOR"]) != 0
|
|
78
|
+
|
|
79
|
+
def extend_type(cls : type):
|
|
80
|
+
subclasses = list(cls.__subclasses__())
|
|
81
|
+
|
|
82
|
+
utils.make_extensible(cls)
|
|
83
|
+
|
|
84
|
+
print(f'extending: {cls}')
|
|
85
|
+
|
|
86
|
+
extended = utils.extend_type(cls)
|
|
87
|
+
|
|
88
|
+
for subclass in subclasses:
|
|
89
|
+
print(f"updating subclass: {subclass}")
|
|
90
|
+
subclass.__bases__ = tuple(map(lambda x: extended if x is cls else x, subclass.__bases__))
|
|
91
|
+
|
|
92
|
+
return extended
|