retracesoftware-proxy 0.2.18__py3-none-any.whl → 0.2.19__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.
@@ -1,338 +0,0 @@
1
- import retracesoftware.functional as functional
2
- import retracesoftware.utils as utils
3
- from retracesoftware.proxy.thread import start_new_thread_wrapper
4
- import threading
5
- import importlib
6
- from retracesoftware.install.typeutils import modify, WithFlags, WithoutFlags
7
- import enum
8
-
9
- from retracesoftware.install.typeutils import modify
10
- from abc import ABC, abstractmethod
11
- from typing import List, Dict, Any, Callable
12
- import functools
13
-
14
- def simple_phase(names, func):
15
- """
16
- Simplest phase type
17
- """
18
- return {name: func for name in names}
19
-
20
- def simple_patch_phase(names, func):
21
- """
22
- Simplest phase type
23
- """
24
- def side_effect(obj):
25
- func(obj)
26
- return obj
27
-
28
- return {name: side_effect for name in names}
29
-
30
- Transform = Callable[[Any], Any]
31
- Config = Any
32
-
33
- Updater = Dict[str, Transform]
34
-
35
- Phase = Callable[[Config], Updater]
36
-
37
- Patcher = Dict[str, Phase]
38
-
39
- def update_class(cls : type, actions : Updater):
40
- ...
41
-
42
- def type_attributes(patcher, config):
43
- result = {}
44
-
45
- for typename, action_attrs in config.items():
46
-
47
- def patch_type(cls):
48
- with modify(cls):
49
- for action, attrs in action_attrs.items():
50
- update_class(cls, patcher[action](attrs))
51
- return cls
52
-
53
- result[typename] = patch_type
54
-
55
- return result
56
-
57
- def create_patcher(system) -> Patcher:
58
- patcher = {}
59
-
60
- def simple_patcher(func): lambda config: {name: func for name in config}
61
-
62
- patcher.update({
63
- 'type_attributes': functools.partial(type_attributes, patcher),
64
- 'disable': simple_patcher(system.disable),
65
- 'patch_types': simple_patcher(system.patch_type),
66
-
67
- })
68
- return patcher
69
-
70
- class TypeAttributesPhase(Phase):
71
-
72
- def __init__(self, patcher):
73
- super().__init__('type_attributes')
74
- self.patcher = patcher
75
-
76
- def patch_with_config(self, config, cls):
77
- # print(f'TypeAttributesPhase: {config} {cls}')
78
- if not isinstance(cls, type):
79
- raise Exception("TODO")
80
-
81
- with modify(cls):
82
- for phase_name,values in config.items():
83
- for attribute_name,func in self.patcher.find_phase(phase_name)(values).items():
84
- utils.update(cls, attribute_name, func)
85
-
86
- return cls
87
-
88
- class Phase:
89
- def __init__(self, system):
90
- self.system = system
91
-
92
- def simple_phase(func): return lambda names: {name: func for name in names}
93
-
94
- self.phases = {
95
- 'disable': simple_phase(system.disable),
96
- 'type_attributes': lambda config: simple_phase(config, system.disable)
97
- }
98
-
99
- def type_attributes(self, config, cls):
100
- with modify(cls):
101
- for action_name, attribute_names in config.items():
102
- for attribute_name, func in self(action_name, attribute_names).items():
103
- utils.update(cls, attribute_name, func)
104
-
105
- return cls
106
-
107
- def __call__(self, name, config):
108
- match name:
109
- case 'disable':
110
- return {name: self.system.disable for name in config}
111
- case 'type_attributes':
112
- return {cls: partial(self.type_attributes, actions) for cls, actions in config.items()}
113
-
114
-
115
- phases = {
116
- 'disable': lambda system, config: simple_phase(config, system.disable),
117
- 'type_attributes': lambda system, config: simple_phase(config, system.disable)
118
- }
119
-
120
- class Phase(ABC):
121
- """
122
- A phase is function when called with system and config yields map of name->func
123
- A patch phase
124
- A phase has... keys... and action
125
- """
126
- def __init__(self, name):
127
- self.name = name
128
-
129
- def patch(self, obj):
130
- return obj
131
-
132
- def patch_with_config(self, config, obj):
133
- return obj
134
-
135
- @abstractmethod
136
- def apply(self, name : str, value : Any) -> Any:
137
- pass
138
-
139
- @abstractmethod
140
- def __call__(self, namespace : Dict[str, Any]) -> None:
141
- for name, value in namespace.items():
142
- if
143
-
144
- def __call__(self, config):
145
- if isinstance(config, str):
146
- return {config: self.patch}
147
- elif isinstance(config, list):
148
- return {key: self.patch for key in config}
149
- elif isinstance(config, dict):
150
- return {key: lambda obj: self.patch_with_config(value, obj) for key,value in config.items()}
151
- else:
152
- raise Exception(f'Unhandled config type: {config}')
153
-
154
-
155
-
156
- class Proxy(Phase):
157
-
158
- def proxy()
159
- def __call__(self, name : str, value : Any) -> None:
160
- if name in self.targets
161
-
162
-
163
- class Patch(Phase):
164
-
165
- def __init__(self, targets, action):
166
- self.targets = targets
167
-
168
- def select(self, name : str) -> bool:
169
- pass
170
-
171
- def __call__(self, name : str, value : Any) -> None:
172
- if name in self.targets
173
-
174
- class SimplePhase(Phase):
175
- def __init__(self, name, patch):
176
- super().__init__(name)
177
- self.patch = patch
178
-
179
- class DisablePhase(Phase):
180
- def __init__(self, thread_state):
181
- super().__init__('disable')
182
- self.thread_state = thread_state
183
-
184
- def patch(self, obj):
185
- print(f'Disabling: {obj}')
186
- return self.thread_state.wrap('disabled', obj)
187
-
188
- class TryPatchPhase(Phase):
189
- def __init__(self, patcher):
190
- super().__init__('try_patch')
191
- self.patcher = patcher
192
-
193
- def patch(self, obj):
194
- try:
195
- return self.patcher(obj)
196
- except:
197
- return obj
198
-
199
- class PatchTypesPhase(Phase):
200
- def __init__(self, patcher):
201
- super().__init__('patch_types')
202
- self.patcher = patcher
203
-
204
- def patch(self, obj):
205
- if not isinstance(obj, type):
206
- raise Exception("TODO")
207
-
208
- self.patcher(obj)
209
- return obj
210
-
211
- class BindPhase(Phase):
212
- def __init__(self, bind):
213
- super().__init__('bind')
214
- self.bind = bind
215
-
216
- def patch(self, obj):
217
- # print(f'In BindPhase: {obj} {isinstance(obj, type)}')
218
- if issubclass(obj, enum.Enum):
219
- for member in obj:
220
- # print(f'binding member: {member}')
221
- # utils.sigtrap(member)
222
- self.bind(member)
223
- else:
224
- self.bind(obj)
225
-
226
- return obj
227
-
228
- class ImmutableTypePhase(Phase):
229
-
230
- def __init__(self, types):
231
- super().__init__('immutable')
232
- self.types = types
233
-
234
- def patch(self, obj):
235
- if not isinstance(obj, type):
236
- raise Exception("TODO")
237
-
238
- self.types.add(obj)
239
- return obj
240
-
241
- class PatchThreadPhase(Phase):
242
- def __init__(self, thread_state, on_exit):
243
- super().__init__('patch_start_new_thread')
244
- self.thread_state = thread_state
245
- self.on_exit = on_exit
246
-
247
- def patch(self, obj):
248
- return start_new_thread_wrapper(thread_state = self.thread_state,
249
- on_exit = self.on_exit,
250
- start_new_thread = obj)
251
-
252
- def resolve(path):
253
- module, sep, name = path.rpartition('.')
254
- if module == None: module = 'builtins'
255
-
256
- return getattr(importlib.import_module(module), name)
257
-
258
- class WrapPhase(Phase):
259
- def __init__(self):
260
- super().__init__('wrap')
261
-
262
- def patch_with_config(self, wrapper, obj):
263
- return resolve(wrapper)(obj)
264
-
265
- class PatchClassPhase(Phase):
266
- def __init__(self):
267
- super().__init__('patch_class')
268
-
269
- def patch_with_config(self, config, cls):
270
-
271
- patchers = utils.map_values(resolve, config)
272
-
273
- assert cls is not None
274
-
275
- with WithoutFlags(cls, "Py_TPFLAGS_IMMUTABLETYPE"):
276
- for name,func in patchers.items():
277
- utils.update(cls, name, func)
278
-
279
- return cls
280
-
281
- class ProxyWrapPhase(Phase):
282
- def __init__(self):
283
- super().__init__('wrap_proxy')
284
-
285
- def patch_with_config(self, config, cls):
286
-
287
- patchers = utils.map_values(resolve, config)
288
-
289
- def patch(proxytype):
290
- for name,func in patchers.items():
291
- utils.update(proxytype, name, func)
292
-
293
- cls.__retrace_patch_proxy__ = patch
294
-
295
- return cls
296
- # return resolve(wrapper)(obj)
297
-
298
- class TypeAttributesPhase(Phase):
299
-
300
- def __init__(self, patcher):
301
- super().__init__('type_attributes')
302
- self.patcher = patcher
303
-
304
- def patch_with_config(self, config, cls):
305
- # print(f'TypeAttributesPhase: {config} {cls}')
306
- if not isinstance(cls, type):
307
- raise Exception("TODO")
308
-
309
- with modify(cls):
310
- for phase_name,values in config.items():
311
- for attribute_name,func in self.patcher.find_phase(phase_name)(values).items():
312
- utils.update(cls, attribute_name, func)
313
-
314
- return cls
315
-
316
- class PerThread(threading.local):
317
- def __init__(self):
318
- self.internal = utils.counter()
319
- self.external = utils.counter()
320
-
321
- class PatchHashPhase(Phase):
322
-
323
- def __init__(self, thread_state):
324
- super().__init__('patch_hash')
325
-
326
- per_thread = PerThread()
327
-
328
- self.hashfunc = thread_state.dispatch(
329
- functional.constantly(None),
330
- internal = functional.repeatedly(functional.partial(getattr, per_thread, 'internal')),
331
- external = functional.repeatedly(functional.partial(getattr, per_thread, 'external')))
332
-
333
- def patch(self, obj):
334
- if not isinstance(obj, type):
335
- raise Exception("TODO")
336
-
337
- utils.patch_hash(cls = obj, hashfunc = self.hashfunc)
338
- return obj
@@ -1,92 +0,0 @@
1
- from retracesoftware_functional import *
2
- import re
3
- import pdb
4
- import weakref
5
- from types import MappingProxyType
6
-
7
- class RegexPredicate:
8
- __slots__ = ['regex']
9
-
10
- def __init__(self, regex):
11
- self.regex = re.compile(regex)
12
-
13
- def __hash__(self):
14
- return hash(self.regex)
15
-
16
- def __eq__(self, other):
17
- if not isinstance(other, RegexPredicate):
18
- return NotImplemented # Allows Python to attempt `other.__eq__(self)`
19
- return self.regex == other.regex
20
-
21
- def __str__(self):
22
- return repr(self.regex.pattern)
23
-
24
- def __call__(self, obj):
25
- return bool(re.fullmatch(self.regex, str(obj)))
26
-
27
- def canonical(spec):
28
- if isinstance(spec, str):
29
- return ('regex', spec)
30
- elif isinstance(spec, list):
31
- return ('or',) + tuple(map(canonical, spec))
32
- elif 'not' in spec:
33
- return ('not', canonical(spec['not']))
34
- elif 'and' in spec:
35
- return ('and',) + tuple(map(canonical, spec['and']))
36
- elif 'or' in spec:
37
- return ('or',) + tuple(map(canonical, spec['or']))
38
- else:
39
- pdb.set_trace()
40
- raise Exception()
41
-
42
- class PredicateBuilder:
43
- __slots__ = ['cache']
44
-
45
- def __init__(self):
46
- # self.cache = weakref.WeakKeyDictionary()
47
- self.cache = {}
48
-
49
- def build(self, spec):
50
- if spec in self.cache:
51
- return self.cache[spec]
52
-
53
- if spec[0] == 'regex':
54
- pred = RegexPredicate(spec[1])
55
- elif spec[0] == 'not':
56
- pred = not_predicate(self.build(spec[1]))
57
- elif spec[0] == 'and':
58
- pred = and_predicate(*map(self.build, spec[1:]))
59
- elif spec[0] == 'or':
60
- pred = or_predicate(*map(self.build, spec[1:]))
61
- else:
62
- raise Exception("TODO")
63
-
64
- self.cache[spec] = pred
65
-
66
- return pred
67
-
68
- def __call__(self, spec):
69
- return self.build(canonical(spec))
70
-
71
- class ParamPredicate:
72
- __slots__ = ['pred', 'index', 'param']
73
-
74
- def __init__(self, signature, param, pred):
75
- # params = inspect.signature(target).parameters
76
- self.index = list(signature.keys()).index(param)
77
- self.param = param
78
- self.pred = pred
79
-
80
- def __str__(self):
81
- return f'ParamPredicate(pred = {self.pred}, index = {self.index}, param = {self.param})'
82
-
83
- def find_arg(self, *args, **kwargs):
84
- if self.param in kwargs:
85
- return kwargs[self.param]
86
- elif self.index < len(args):
87
- return args[self.index]
88
- else:
89
- raise Exception(f'Cant get arg: {self.param}')
90
-
91
- def __call__(self, *args, **kwargs):
92
- return self.pred(self.find_arg(*args, **kwargs))
@@ -1,174 +0,0 @@
1
- from retracesoftware.proxy import *
2
-
3
- import retracesoftware.functional as functional
4
- import retracesoftware.utils as utils
5
- import retracesoftware.stream as stream
6
-
7
- from retracesoftware.install.tracer import Tracer
8
- from retracesoftware.install import globals
9
- from retracesoftware.install.config import env_truthy
10
- from retracesoftware.install.patchfindspec import patch_find_spec
11
-
12
- import os
13
- import sys
14
- from datetime import datetime
15
- import json
16
- from pathlib import Path
17
- import shutil
18
-
19
- # class ThreadSwitch:
20
- # def __init__(self, id):
21
- # self.id = id
22
-
23
- # def __repr__(self):
24
- # return f'ThreadSwitch<{self.id}>'
25
-
26
- # def __str__(self):
27
- # return f'ThreadSwitch<{self.id}>'
28
-
29
- def code_workspace():
30
- return {
31
- 'folders': [
32
- {'path': '../..', 'name': 'Application'},
33
- {'path': '.', 'name': 'Recording'}
34
- ]
35
- }
36
-
37
- def write_files(recording_path):
38
- with open(recording_path / 'env', 'w') as f:
39
- json.dump(dict(os.environ), f, indent=2)
40
-
41
- with open(recording_path / 'exe', 'w') as f:
42
- f.write(sys.executable)
43
-
44
- with open(recording_path / 'cwd', 'w') as f:
45
- f.write(os.getcwd())
46
-
47
- with open(recording_path / 'cmd', 'w') as f:
48
- json.dump(sys.orig_argv, f, indent=2)
49
-
50
- with open(recording_path / 'replay.code-workspace', 'w') as f:
51
- json.dump(code_workspace(), f, indent=2)
52
-
53
- def create_recording_path(path):
54
- expanded = datetime.now().strftime(path.format(pid = os.getpid()))
55
- os.environ['RETRACE_RECORDING_PATH'] = expanded
56
- return Path(expanded)
57
-
58
- def tracing_level(config):
59
- return os.environ.get('RETRACE_DEBUG', config['default_tracing_level'])
60
-
61
- # def tracing_config(config):
62
- # level = os.environ.get('RETRACE_DEBUG', config['default_tracing_level'])
63
- # return config['tracing_levels'].get(level, {})
64
-
65
- def merge_config(base, override):
66
- if isinstance(base, dict) and isinstance(override, dict):
67
- ...
68
- else:
69
- return override
70
-
71
-
72
-
73
- def dump_as_json(path, obj):
74
- with open(path, 'w') as f:
75
- json.dump(obj, f, indent=2)
76
-
77
- def record_system(thread_state, immutable_types, config):
78
-
79
- recording_path = create_recording_path(config['recording_path'])
80
- recording_path.mkdir(parents=True, exist_ok=True)
81
-
82
- globals.recording_path = globals.RecordingPath(recording_path)
83
-
84
- write_files(recording_path)
85
-
86
- tracing_config = config['tracing_levels'].get(tracing_level(config), {})
87
-
88
- dump_as_json(path = recording_path / 'tracing_config.json', obj = tracing_config)
89
-
90
- def write_main_path(path):
91
- with open(recording_path / 'mainscript', 'w') as f:
92
- f.write(path)
93
-
94
- run_path = recording_path / 'run'
95
- run_path.mkdir(parents=True, exist_ok=False)
96
-
97
- shutil.copy2(sys.argv[0], run_path)
98
-
99
- python_path = recording_path / 'pythonpath'
100
- python_path.mkdir(parents=True, exist_ok=False)
101
-
102
- vscode = recording_path / '.vscode'
103
- vscode.mkdir(parents=True, exist_ok=False)
104
-
105
- # launch_json = {
106
- # # // Use IntelliSense to learn about possible attributes.
107
- # # // Hover to view descriptions of existing attributes.
108
- # # // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
109
- # "version": "0.2.0",
110
- # "configurations": [
111
- # {
112
- # "name": "Python Debugger: Python File",
113
- # "type": "debugpy",
114
- # "request": "launch",
115
- # "cwd": "${workspaceFolder}/run",
116
- # "env": {
117
- # "RETRACE_MODE": "replay"
118
- # },
119
- # "python": sys.executable,
120
- # "program": sys.argv[0]
121
- # }
122
- # ]
123
- # }
124
-
125
- # dump_as_json(vscode / 'launch.json', launch_json)
126
-
127
- workspace = {
128
- "folders": [{ 'path': '.' }],
129
- "settings": {
130
- # This is the one that works reliably in .code-workspace files
131
- # "python.defaultInterpreterPath":
132
- "python.defaultInterpreterPath": sys.executable,
133
- # "python.interpreterInfo": {
134
- # "run": {"path": sys.executable}
135
- # }
136
- },
137
- "launch": {
138
- "version": "0.2.0",
139
- "configurations": [
140
- {
141
- "justMyCode": False,
142
- "name": "Python Debugger: Python File",
143
- "type": "debugpy",
144
- "request": "launch",
145
- "cwd": "${workspaceFolder}/run",
146
- "env": {
147
- "RETRACE_RECORDING_PATH": "..",
148
- "RETRACE_MODE": "replay"
149
- },
150
- # "python": ["${command:python.interpreterPath}"],
151
- "python": sys.executable,
152
- # "python": sys.executable,
153
- "program": sys.argv[0]
154
- }
155
- ]
156
- },
157
- }
158
-
159
- dump_as_json(recording_path / 'replay.code-workspace', workspace)
160
-
161
- copy_source = thread_state.wrap('disabled', patch_find_spec(cwd = Path(os.getcwd()), run_path = run_path, python_path = python_path))
162
-
163
- for finder in sys.meta_path:
164
- finder.find_spec = functional.sequence(finder.find_spec, functional.side_effect(copy_source))
165
-
166
- return RecordProxySystem(thread_state = thread_state,
167
- immutable_types = immutable_types,
168
- tracing_config = tracing_config,
169
- write_main_path = write_main_path,
170
- path = recording_path / 'trace.bin',
171
- tracecalls = env_truthy('RETRACE_ALL', False),
172
- verbose = env_truthy('RETRACE_VERBOSE', False),
173
- stacktraces = env_truthy('RETRACE_STACKTRACES', False),
174
- magic_markers = env_truthy('RETRACE_MAGIC_MARKERS', False))
@@ -1,66 +0,0 @@
1
- import gc
2
- import sys
3
- import pdb
4
-
5
- def references(obj):
6
- refcnt = sys.getrefcount(obj) - 2
7
-
8
- if refcnt == 1:
9
- moddict = sys.modules[obj.__module__].__dict__
10
-
11
- if obj.__name__ in moddict and moddict[obj.__name__] is obj:
12
- return [moddict]
13
- else:
14
- refs = []
15
-
16
- for mod in list(sys.modules.values()):
17
- moddict = mod.__dict__
18
- if obj.__name__ in moddict and moddict[obj.__name__] is obj:
19
- refs.append(moddict)
20
-
21
- if len(refs) == refcnt:
22
- return refs
23
-
24
- return gc.get_referrers(obj)
25
-
26
- def replace(container, old, new):
27
- if isinstance(container, dict):
28
- if old in container:
29
- elem = container.pop(old)
30
- container[new] = elem
31
- replace(container, old, new)
32
- else:
33
- for key,value in container.items():
34
- if key != '__retrace_unproxied__' and value is old:
35
- print(f'FOO replacing: {key} {old} {new}')
36
- container[key] = new
37
-
38
- elif isinstance(container, list):
39
- for i,value in enumerate(container):
40
- if value is old:
41
- container[i] = new
42
-
43
- elif isinstance(container, set):
44
- container.remove(old)
45
- container.add(new)
46
-
47
- # elif isinstance(container, tuple):
48
- # pdb.set_trace()
49
- # else:
50
- # pdb.set_trace()
51
- # raise Exception('TODO')
52
-
53
- def update(f, obj):
54
- new = f(obj)
55
- if new is not obj:
56
- refs = references(obj)
57
- for ref in refs:
58
- replace(ref, obj, new)
59
-
60
- return new
61
-
62
- # import math
63
- # import os
64
- # print(references(math.sqrt))
65
- # print(references(os.nice))
66
- # print(references(os.open))