retracesoftware-proxy 0.2.12__py3-none-any.whl → 0.2.15__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.
@@ -0,0 +1,20 @@
1
+ # Extend namespace to include retracesoftware.utils, retracesoftware.functional, etc.
2
+ __path__ = __import__('pkgutil').extend_path(__path__, __name__)
3
+
4
+ from retracesoftware.exceptions import (
5
+ RetraceError,
6
+ RecordError,
7
+ ReplayError,
8
+ ConfigurationError,
9
+ VersionMismatchError,
10
+ RecordingNotFoundError,
11
+ )
12
+
13
+ __all__ = [
14
+ 'RetraceError',
15
+ 'RecordError',
16
+ 'ReplayError',
17
+ 'ConfigurationError',
18
+ 'VersionMismatchError',
19
+ 'RecordingNotFoundError',
20
+ ]
@@ -20,6 +20,7 @@ import gc
20
20
  import hashlib
21
21
 
22
22
  from retracesoftware.run import install, run_with_retrace, ImmutableTypes, thread_states
23
+ from retracesoftware.exceptions import RecordingNotFoundError, VersionMismatchError, ConfigurationError
23
24
 
24
25
  def expand_recording_path(path):
25
26
  return datetime.datetime.now().strftime(path.format(pid = os.getpid()))
@@ -74,7 +75,7 @@ def checksum(path):
74
75
  return file_md5(path) if path.is_file() else {entry.name: checksum(entry) for entry in path.iterdir() if entry.name != '__pycache__'}
75
76
 
76
77
  def retrace_extension_paths():
77
- names = ['retracesoftware_utils', 'retracesoftware_functional', 'retracesoftware_stream']
78
+ names = ['retracesoftware_utils', '_retracesoftware_functional', 'retracesoftware_stream']
78
79
  return {name: Path(sys.modules[name].__file__) for name in names}
79
80
 
80
81
  def retrace_module_paths():
@@ -175,20 +176,20 @@ def replay(args):
175
176
  path = Path(args.recording)
176
177
 
177
178
  if not path.exists():
178
- raise Exception(f"Recording path: {path} does not exist")
179
+ raise RecordingNotFoundError(f"Recording path: {path} does not exist")
179
180
 
180
181
  settings = load_json(path / "settings.json")
181
182
 
182
183
  if settings['md5_checksums'] != checksums():
183
- raise Exception("Checksums for Retrace do not match, cannot run replay with different version of retrace to record")
184
+ raise VersionMismatchError("Checksums for Retrace do not match, cannot run replay with different version of retrace to record")
184
185
 
185
186
  if settings['python_version'] != sys.version:
186
- raise Exception("Python version does not match, cannot run replay with different version of Python to record")
187
+ raise VersionMismatchError("Python version does not match, cannot run replay with different version of Python to record")
187
188
 
188
189
  os.environ.update(settings['env'])
189
190
 
190
191
  if sys.executable != settings['executable']:
191
- raise Exception(f"Stopping replay as current python executable: {sys.executable} is not what was used for record: {settings['executable']}")
192
+ raise ConfigurationError(f"Stopping replay as current python executable: {sys.executable} is not what was used for record: {settings['executable']}")
192
193
 
193
194
  thread_state = utils.ThreadState(*thread_states)
194
195
 
@@ -1,12 +1,17 @@
1
1
  if __name__ == "__main__":
2
2
  import sysconfig
3
3
  import pathlib
4
+ import shutil
4
5
 
5
- # 'purelib' is the name of the directory where non-platform-specific modules are installed.
6
- file = pathlib.Path(sysconfig.get_paths()["purelib"]) / 'retrace.pth'
7
- file.write_text('import retracesoftware.autoenable;', encoding='utf-8')
6
+ # Source: retrace.pth bundled with this package
7
+ source = pathlib.Path(__file__).parent / 'retrace.pth'
8
+
9
+ # Target: site-packages root
10
+ target = pathlib.Path(sysconfig.get_paths()["purelib"]) / 'retrace.pth'
11
+
12
+ shutil.copy(source, target)
8
13
 
9
- print(f'Retrace autoinstall enabled by creating: {file}')
14
+ print(f'Retrace autoinstall enabled: {source} -> {target}')
10
15
  else:
11
16
  import os
12
17
 
@@ -0,0 +1,34 @@
1
+ """
2
+ Retrace exception hierarchy.
3
+
4
+ All retrace-specific exceptions inherit from RetraceError.
5
+ """
6
+
7
+ class RetraceError(Exception):
8
+ """Base exception for all retrace errors."""
9
+ pass
10
+
11
+
12
+ class RecordError(RetraceError):
13
+ """Error during recording phase."""
14
+ pass
15
+
16
+
17
+ class ReplayError(RetraceError):
18
+ """Error during replay phase."""
19
+ pass
20
+
21
+
22
+ class ConfigurationError(RetraceError):
23
+ """Error in retrace configuration."""
24
+ pass
25
+
26
+
27
+ class VersionMismatchError(RetraceError):
28
+ """Recording and replay versions don't match."""
29
+ pass
30
+
31
+
32
+ class RecordingNotFoundError(RetraceError):
33
+ """Recording path does not exist."""
34
+ pass
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import retracesoftware.functional as functional
4
- import retracesoftware_utils as utils
4
+ import retracesoftware.utils as utils
5
5
 
6
6
  # from retracesoftware_functional import mapargs, walker, first_arg, if_then_else, compose, observer, anyargs, memoize_one_arg, side_effect, partial, threadwatcher, firstof, notinstance_test, instance_test,typeof, isinstanceof, always
7
7
  # from retracesoftware.proxy.proxy import ProxyFactory, InternalProxy, ProxySpec, WrappingProxySpec, ExtendingProxySpec, ExtendingProxy
@@ -0,0 +1,506 @@
1
+ import retracesoftware.utils as utils
2
+ import retracesoftware.functional as functional
3
+ from functools import wraps
4
+ import inspect
5
+ import gc
6
+ import importlib
7
+ import types
8
+ import sys
9
+
10
+ from retracesoftware.proxy.thread import start_new_thread_wrapper, counters
11
+ from retracesoftware.install.typeutils import modify
12
+
13
+ def find_attr(mro, name):
14
+ for cls in mro:
15
+ if name in cls.__dict__:
16
+ return cls.__dict__[name]
17
+
18
+ def is_descriptor(obj):
19
+ return hasattr(obj, '__get__') or hasattr(obj, '__set__') or hasattr(obj, '__delete__')
20
+
21
+ def resolve(path):
22
+ module, sep, name = path.rpartition('.')
23
+ if module == None: module = 'builtins'
24
+
25
+ return getattr(importlib.import_module(module), name)
26
+
27
+ def phase(func):
28
+ func.is_phase = True # add marker attribute
29
+ return func
30
+
31
+ def replace(replacements, coll):
32
+ return map(lambda x: replacements.get(x, x), coll)
33
+
34
+ def container_replace(container, old, new):
35
+ if isinstance(container, dict):
36
+ if old in container:
37
+ elem = container.pop(old)
38
+ container[new] = elem
39
+ container_replace(container, old, new)
40
+ else:
41
+ for key,value in container.items():
42
+ if key != '__retrace_unproxied__' and value is old:
43
+ container[key] = new
44
+ return True
45
+ elif isinstance(container, list):
46
+ for i,value in enumerate(container):
47
+ if value is old:
48
+ container[i] = new
49
+ return True
50
+ elif isinstance(container, set):
51
+ container.remove(old)
52
+ container.add(new)
53
+ return True
54
+ else:
55
+ return False
56
+
57
+ def select_keys(keys, dict):
58
+ return {key: dict[key] for key in keys if key in dict}
59
+
60
+ def map_values(f, dict):
61
+ return {key: f(value) for key,value in dict.items()}
62
+
63
+ def common_keys(dict, *dicts):
64
+ common_keys = utils.set(dict)
65
+ for d in dicts:
66
+ common_keys &= d.keys()
67
+
68
+ assert isinstance(common_keys, utils.set)
69
+
70
+ return common_keys
71
+
72
+ def intersection(*dicts):
73
+ return { key: tuple(d[key] for d in dicts) for key in common_keys(*dicts) }
74
+
75
+ def intersection_apply(f, *dicts):
76
+ return map_values(lambda vals: f(*vals), intersection(*dicts))
77
+
78
+ def patch(func):
79
+ @wraps(func)
80
+ def wrapper(self, spec, mod_dict):
81
+ if isinstance(spec, str):
82
+ return wrapper(self, [spec], mod_dict)
83
+ elif isinstance(spec, list):
84
+ res = {}
85
+ for name in spec:
86
+ if name in mod_dict:
87
+ value = func(self, mod_dict[name])
88
+ if value is not None:
89
+ res[name] = value
90
+ return res
91
+ elif isinstance(spec, dict):
92
+ # return {name: func(self, mod_dict[name], value) for name, value in spec.items() if name in mod_dict}
93
+ res = {}
94
+ for name,value in spec.items():
95
+ if name in mod_dict:
96
+ value = func(self, mod_dict[name], value)
97
+ if value is not None:
98
+ res[name] = value
99
+ return res
100
+ else:
101
+ raise Exception('TODO')
102
+
103
+ wrapper.is_phase = True
104
+ return wrapper
105
+
106
+ def is_special(name):
107
+ return len(name) > 4 and name.startswith('__') and name.endswith('__')
108
+
109
+ def superdict(cls):
110
+ result = {}
111
+ for cls in list(reversed(cls.__mro__))[1:]:
112
+ result.update(cls.__dict__)
113
+
114
+ return result
115
+
116
+ def wrap_method_descriptors(wrapper, prefix, base):
117
+ slots = {"__slots__": () }
118
+
119
+ extended = type(f'{prefix}.{base.__module__}.{base.__name__}', (base,), {"__slots__": () })
120
+
121
+ blacklist = ['__getattribute__', '__hash__', '__del__']
122
+
123
+ for name,value in superdict(base).items():
124
+ if name not in blacklist:
125
+ if utils.is_method_descriptor(value):
126
+ setattr(extended, name, wrapper(value))
127
+
128
+ return extended
129
+
130
+ class ElementPatcher:
131
+
132
+ def __init__(self, config, phases):
133
+ funcs = {}
134
+
135
+ for phase in phases:
136
+ if phase.name in config:
137
+ for key,func in phase(config[phase.name]).items():
138
+ val = funcs.get(key, [])
139
+ val.append(func)
140
+ funcs[key] = val
141
+
142
+ self.funcs = utils.map_values(lambda funcs: functional.sequence(*funcs), funcs)
143
+ self.fallback = None
144
+
145
+ if 'default' in config:
146
+ default_name = config['default']
147
+ for phase in phases:
148
+ if phase.name == default_name:
149
+ self.fallback = phase.patch
150
+
151
+ def __call__(self, name, value):
152
+ if name in self.funcs:
153
+ return self.funcs[name](value)
154
+ elif not is_special(name) and self.fallback:
155
+ return self.fallback(value)
156
+ else:
157
+ return value
158
+
159
+ # class Patcher:
160
+
161
+ # def __init__(self, config):
162
+
163
+ # self.config = config
164
+ # self.phases = []
165
+ # self.patched = {}
166
+
167
+ # # system.set_thread_id(0)
168
+ # # self.thread_counter = system.sync(utils.counter(1))
169
+ # # self.module_config = module_config
170
+
171
+ # # self.thread_state = thread_state
172
+ # # self.debug_level = debug_level
173
+ # # self.on_function_proxy = on_function_proxy
174
+ # # self.modules = config['modules']
175
+ # # self.immutable_types_set = immutable_types
176
+ # # self.predicate = PredicateBuilder()
177
+ # # self.system = system
178
+ # # self.type_attribute_filter = self.predicate(config['type_attribute_filter'])
179
+ # # self.post_commit = post_commit
180
+ # # self.exclude_paths = [re.compile(s) for s in config.get('exclude_paths', [])]
181
+ # # self.typepatcher = {}
182
+ # # self.originals = {}
183
+
184
+ # # def is_phase(name): return getattr(getattr(self, name, None), "is_phase", False)
185
+
186
+ # # self.phases = [(name, getattr(self, name)) for name in Patcher.__dict__.keys() if is_phase(name)]
187
+
188
+ # def add_phase(self, phase):
189
+ # self.phases.append(phase)
190
+
191
+ # # def log(self, *args):
192
+ # # self.system.tracer.log(*args)
193
+
194
+ # def path_predicate(self, path):
195
+ # for exclude in self.exclude_paths:
196
+ # if exclude.match(str(path)) is not None:
197
+ # # print(f'in path_predicate, excluding {path}')
198
+ # return False
199
+ # return True
200
+
201
+ # # def on_proxytype(self, cls):
202
+
203
+ # # def patch(spec):
204
+ # # for method, transform in spec.items():
205
+ # # setattr(cls, method, resolve(transform)(getattr(cls, method)))
206
+
207
+ # # if cls.__module__ in self.modules:
208
+ # # spec = self.modules[cls.__module__]
209
+
210
+ # # if 'patchtype' in spec:
211
+ # # patchtype = spec['patchtype']
212
+ # # if cls.__name__ in patchtype:
213
+ # # patch(patchtype[cls.__name__])
214
+
215
+ # @property
216
+ # def disable(self):
217
+ # return self.thread_state.select('disabled')
218
+
219
+ # def proxyable(self, name, obj):
220
+ # if name.startswith('__') and name.endswith('__'):
221
+ # return False
222
+
223
+ # if isinstance(obj, (str, int, dict, list, tuple)):
224
+ # return False
225
+
226
+ # if isinstance(obj, type):
227
+ # return not issubclass(obj, BaseException) and obj not in self.immutable_types_set
228
+
229
+ class Patcher:
230
+
231
+ def __init__(self, config):
232
+
233
+ self.config = config
234
+ self.phases = []
235
+ self.patched = {}
236
+
237
+ def add_phase(self, phase):
238
+ self.phases.append(phase)
239
+
240
+ def path_predicate(self, path):
241
+ for exclude in self.exclude_paths:
242
+ if exclude.match(str(path)) is not None:
243
+ # print(f'in path_predicate, excluding {path}')
244
+ return False
245
+ return True
246
+
247
+ @property
248
+ def disable(self):
249
+ return self.thread_state.select('disabled')
250
+
251
+ def proxyable(self, name, obj):
252
+ if name.startswith('__') and name.endswith('__'):
253
+ return False
254
+
255
+ if isinstance(obj, (str, int, dict, list, tuple)):
256
+ return False
257
+
258
+ if isinstance(obj, type):
259
+ return not issubclass(obj, BaseException) and obj not in self.immutable_types_set
260
+ else:
261
+ return type(obj) not in self.immutable_types_set
262
+
263
+ @phase
264
+ def proxy_type_attributes(self, spec, mod_dict):
265
+ for classname, attributes in spec.items():
266
+ if classname in mod_dict:
267
+ cls = mod_dict[classname]
268
+ if isinstance(cls, type):
269
+ for name in attributes:
270
+ attr = find_attr(cls.__mro__, name)
271
+ if attr is not None and (callable(attr) or is_descriptor(attr)):
272
+ proxied = self.system(attr)
273
+ # proxied = self.proxy(attr)
274
+
275
+ with modify(cls):
276
+ setattr(cls, name, proxied)
277
+ else:
278
+ raise Exception(f"Cannot patch attributes for {cls.__module__}.{cls.__name__} as object is: {cls} and not a type")
279
+
280
+ @phase
281
+ def replace(self, spec, mod_dict):
282
+ return {key: resolve(value) for key,value in spec.items()}
283
+
284
+ @patch
285
+ def patch_start_new_thread(self, value):
286
+ return start_new_thread_wrapper(thread_state = self.thread_state,
287
+ on_exit = self.system.on_thread_exit,
288
+ start_new_thread = value)
289
+
290
+ # def start_new_thread(function, *args):
291
+ # # synchronized, replay shoudl yield correct number
292
+ # thread_id = self.thread_counter()
293
+
294
+ # def threadrunner(*args, **kwargs):
295
+ # nonlocal thread_id
296
+ # self.system.set_thread_id(thread_id)
297
+
298
+ # with self.thread_state.select('internal'):
299
+ # try:
300
+ # # if self.tracing:
301
+ # # FrameTracer.install(self.thread_state.dispatch(noop, internal = self.checkpoint))
302
+ # return function(*args, **kwargs)
303
+ # finally:
304
+ # print(f'exiting: {thread_id}')
305
+
306
+ # return value(threadrunner, *args)
307
+
308
+ # return self.thread_state.dispatch(value, internal = start_new_thread)
309
+
310
+ @phase
311
+ def wrappers(self, spec, mod_dict):
312
+ return intersection_apply(lambda path, value: resolve(path)(value), spec, mod_dict)
313
+
314
+ @patch
315
+ def patch_exec(self, exec):
316
+
317
+ def is_module(source, *args):
318
+ return isinstance(source, types.CodeType) and source.co_name == '<module>'
319
+
320
+ def after_exec(source, globals = None, locals = None):
321
+ if isinstance(source, types.CodeType) and source.co_name == '<module>' and '__name__' in globals:
322
+ self(sys.modules[globals['__name__']])
323
+
324
+ def first(x): return x[0]
325
+
326
+ def disable(func): return self.thread_state.wrap('disabled', func)
327
+
328
+ return self.thread_state.dispatch(
329
+ exec,
330
+ internal = functional.sequence(
331
+ functional.juxt(exec, functional.when(is_module, disable(after_exec))), first))
332
+
333
+ # self.thread_state.wrap(desired_state = 'disabled', function = exec_wrapper)
334
+
335
+ @patch
336
+ def sync_types(self, value):
337
+ return wrap_method_descriptors(self.system.sync, "retrace", value)
338
+
339
+ @phase
340
+ def with_state_recursive(self, spec, mod_dict):
341
+
342
+ updates = {}
343
+
344
+ for state,elems in spec.items():
345
+
346
+ def wrap(obj):
347
+ return functional.recurive_wrap_function(
348
+ functional.partial(self.thread_state.wrap, state),
349
+ obj)
350
+
351
+ updates.update(map_values(wrap, select_keys(elems, mod_dict)))
352
+
353
+ return updates
354
+
355
+ @phase
356
+ def methods_with_state(self, spec, mod_dict):
357
+
358
+ # updates = {}
359
+
360
+ def update(cls, name, f):
361
+ setattr(cls, name, f(getattr(cls, name)))
362
+
363
+ for state,cls_methods in spec.items():
364
+ def wrap(obj):
365
+ assert callable(obj)
366
+ return self.thread_state.wrap(desired_state = state, function = obj)
367
+
368
+ for typename,methodnames in cls_methods.items():
369
+ cls = mod_dict[typename]
370
+
371
+ for methodname in methodnames:
372
+ update(cls, methodname, wrap)
373
+
374
+ return {}
375
+
376
+ @phase
377
+ def with_state(self, spec, mod_dict):
378
+
379
+ updates = {}
380
+
381
+ for state,elems in spec.items():
382
+
383
+ def wrap(obj):
384
+ return self.thread_state.wrap(desired_state = state, function = obj)
385
+
386
+ updates.update(map_values(wrap, select_keys(elems, mod_dict)))
387
+
388
+ return updates
389
+
390
+ @patch
391
+ def patch_extension_exec(self, exec):
392
+
393
+ def first(x): return x[0]
394
+
395
+ def disable(func): return self.thread_state.wrap('disabled', func)
396
+
397
+ return self.thread_state.dispatch(exec,
398
+ internal = functional.sequence(functional.juxt(exec, disable(self)), first))
399
+
400
+ # def wrapper(module):
401
+ # with self.thread_state.select('internal'):
402
+ # res = exec(module)
403
+
404
+ # self(module)
405
+ # return res
406
+
407
+ # return wrapper
408
+
409
+ @patch
410
+ def path_predicates(self, func, param):
411
+ signature = inspect.signature(func).parameters
412
+
413
+ try:
414
+ index = list(signature.keys()).index(param)
415
+ except ValueError:
416
+ print(f'parameter {param} not in: {signature.keys()} {type(func)} {func}')
417
+ raise
418
+
419
+ param = functional.param(name = param, index = index)
420
+
421
+ assert callable(param)
422
+
423
+ return functional.if_then_else(
424
+ test = functional.sequence(param, self.path_predicate),
425
+ then = func,
426
+ otherwise = self.thread_state.wrap('disabled', func))
427
+
428
+ @phase
429
+ def wrap(self, spec, mod_dict):
430
+ updates = {}
431
+
432
+ for path, wrapper_name in spec.items():
433
+
434
+ parts = path.split('.')
435
+ name = parts[0]
436
+ if name in mod_dict:
437
+ value = mod_dict[name]
438
+ assert not isinstance(value, utils.wrapped_function), \
439
+ f"value for key: {name} is already wrapped"
440
+
441
+ if len(parts) == 1:
442
+ updates[name] = resolve(wrapper_name)(value)
443
+ elif len(parts) == 2:
444
+ member = getattr(value, parts[1], None)
445
+ if member:
446
+ new_value = resolve(wrapper_name)(member)
447
+ setattr(value, parts[1], new_value)
448
+ else:
449
+ raise Exception('TODO')
450
+
451
+ return updates
452
+
453
+ def find_phase(self, name):
454
+ for phase in self.phases:
455
+ if phase.name == name:
456
+ return phase
457
+
458
+ raise Exception(f'Phase: {name} not found')
459
+
460
+ # def run_transforms(self, config, obj):
461
+ # if isinstance(config, str):
462
+ # return find_phase(config).patch(obj)
463
+ # else:
464
+ # for name in config
465
+
466
+ def element_patcher(self, name):
467
+ if name in self.config:
468
+ return ElementPatcher(config = self.config[name],
469
+ phases = self.phases)
470
+
471
+ def wrap_namespace(self, ns):
472
+ name = ns.get('__name__', None)
473
+ patcher = self.element_patcher(name)
474
+ print(f'wrap_namespace {name} {patcher}')
475
+ return utils.InterceptDict(ns, patcher) if patcher else ns
476
+
477
+ def update(self, old, new):
478
+ if isinstance(new, type) and isinstance(old, type) and issubclass(new, old):
479
+ for subclass in old.__subclasses__():
480
+ if subclass is not new:
481
+ subclass.__bases__ = tuple(replace({old: new}, subclass.__bases__))
482
+
483
+ for ref in gc.get_referrers(old):
484
+ container_replace(container = ref, old = old, new = new)
485
+
486
+ def patch_loaded_module(self, mod_dict):
487
+ modname = mod_dict.get('__name__', None)
488
+
489
+ print(f'Patching loaded module: {modname}')
490
+
491
+ if modname in self.config:
492
+
493
+
494
+ originals = {}
495
+ self.patched[modname] = originals
496
+
497
+ patcher = self.element_patcher(modname)
498
+
499
+ for key in list(mod_dict.keys()):
500
+ original = mod_dict[key]
501
+ patched = patcher(key, original)
502
+
503
+ if patched is not original:
504
+ self.update(old = original, new = patched)
505
+ originals[key] = original
506
+ assert mod_dict[key] is patched
@@ -94,8 +94,13 @@ def patch_namespace(patcher, config, namespace, update_refs):
94
94
  # print(f"patching: {name}")
95
95
 
96
96
  value = namespace[name]
97
- new_value = func(value)
98
97
 
98
+ try:
99
+ new_value = func(value)
100
+ except Exception as e:
101
+ print(f"Error patching {name}, phase: {phase_name}: {e}")
102
+ raise e
103
+
99
104
  if value is not new_value:
100
105
  namespace[name] = new_value
101
106
 
@@ -1,7 +1,7 @@
1
1
  from retracesoftware.proxy import *
2
2
 
3
3
  import retracesoftware.functional as functional
4
- import retracesoftware_utils as utils
4
+ import retracesoftware.utils as utils
5
5
  import retracesoftware.stream as stream
6
6
 
7
7
  from retracesoftware.install.tracer import Tracer
@@ -1,5 +1,5 @@
1
1
  import retracesoftware.functional as functional
2
- import retracesoftware_utils as utils
2
+ import retracesoftware.utils as utils
3
3
  from retracesoftware.proxy.proxytype import Proxy
4
4
 
5
5
  from retracesoftware.proxy.serializer import serializer
@@ -1,5 +1,5 @@
1
1
  # from retrace_utils import _intercept
2
- import retracesoftware_utils as utils
2
+ import retracesoftware.utils as utils
3
3
 
4
4
  def flags(cls : type):
5
5
  f = utils.type_flags(cls)
@@ -366,9 +366,8 @@ proxy = ["SemLock", "sem_unlink"]
366
366
  # "map_buffer": "ignore",
367
367
  # "*": "proxy_if_function"
368
368
  # },
369
- # "psycopg2._psycopg": {
370
- # "proxy_all_except": ["Error"]
371
- # },
369
+ ["psycopg2._psycopg"]
370
+ proxy = ["connect", "cursor", "connection"]
372
371
 
373
372
  # "_weakrefset": {
374
373
  # "WeakSet": "sync_type",
@@ -1,5 +1,5 @@
1
1
  import retracesoftware.functional as functional
2
- import retracesoftware_utils as utils
2
+ import retracesoftware.utils as utils
3
3
 
4
4
  from retracesoftware.proxy.proxytype import ExtendingProxy
5
5
 
@@ -1,7 +1,7 @@
1
1
  from ast import dump
2
2
  from math import e
3
3
  import retracesoftware.stream as stream
4
- import retracesoftware_utils as utils
4
+ import retracesoftware.utils as utils
5
5
  from retracesoftware.proxy.startthread import thread_id
6
6
  import gc
7
7
  import sys
@@ -1,5 +1,5 @@
1
- from retracesoftware_utils import counter
2
- from retracesoftware_functional import *
1
+ from retracesoftware.utils import counter
2
+ from retracesoftware.functional import *
3
3
 
4
4
  import types
5
5
  import pickle
@@ -1,5 +1,5 @@
1
1
  import retracesoftware.functional as functional
2
- import retracesoftware_utils as utils
2
+ import retracesoftware.utils as utils
3
3
  import types
4
4
  from retracesoftware.proxy.gateway import adapter_pair
5
5
  from types import SimpleNamespace
@@ -16,8 +16,7 @@ import enum
16
16
  import functools
17
17
  import re
18
18
 
19
- class RetraceError(Exception):
20
- pass
19
+ from retracesoftware.exceptions import RetraceError
21
20
 
22
21
  def proxy(proxytype):
23
22
  return functional.spread(
@@ -403,8 +402,9 @@ class ProxySystem:
403
402
  cls.__init_subclass__ = classmethod(init_subclass)
404
403
 
405
404
  for subtype in get_all_subtypes(cls):
406
- init_subclass(subtype)
407
- utils.set_on_alloc(subtype, on_alloc)
405
+ with WithoutFlags(subtype, "Py_TPFLAGS_IMMUTABLETYPE"):
406
+ init_subclass(subtype)
407
+ utils.set_on_alloc(subtype, on_alloc)
408
408
 
409
409
  cls.__retrace__ = self
410
410
 
@@ -162,6 +162,21 @@ class RecordProxySystem(ProxySystem):
162
162
 
163
163
  def write_error(cls, val, traceback):
164
164
  assert isinstance(val, BaseException)
165
+ # if not isinstance(val, BaseException):
166
+ # # Debug: something is passing wrong value type
167
+ # import sys
168
+ # print(f"DEBUG write_error called with:")
169
+ # print(f" cls={cls}, type={type(cls)}")
170
+ # print(f" val={val!r}, type={type(val)}")
171
+ # print(f" traceback={traceback}")
172
+ # print(f" sys.exc_info()={sys.exc_info()}")
173
+
174
+ # # Some C extensions (e.g. psycopg2) return plain strings as errors
175
+ # # Wrap them in the exception class if possible
176
+ # if isinstance(val, str) and isinstance(cls, type) and issubclass(cls, BaseException):
177
+ # val = cls(val)
178
+ # else:
179
+ # val = RuntimeError(f"Non-exception error: {val}")
165
180
  error(cls, val)
166
181
 
167
182
  tracer = Tracer(tracing_config, writer = self.writer.handle('TRACE'))
@@ -1,5 +1,5 @@
1
1
  import retracesoftware.functional as functional
2
- import retracesoftware_utils as utils
2
+ import retracesoftware.utils as utils
3
3
 
4
4
  import os
5
5
  import _thread
retracesoftware/replay.py CHANGED
@@ -8,6 +8,7 @@ from retracesoftware.proxy.replay import ReplayProxySystem
8
8
  import retracesoftware.utils as utils
9
9
  import json
10
10
  from retracesoftware.stackdifference import on_stack_difference
11
+ from retracesoftware.exceptions import RecordingNotFoundError
11
12
 
12
13
  def parse_args():
13
14
  parser = argparse.ArgumentParser(
@@ -68,7 +69,7 @@ def main():
68
69
  path = Path(args.recording)
69
70
 
70
71
  if not path.exists():
71
- raise Exception(f"Recording path: {path} does not exist")
72
+ raise RecordingNotFoundError(f"Recording path: {path} does not exist")
72
73
 
73
74
  settings = load_json(path / "settings.json")
74
75
 
@@ -0,0 +1 @@
1
+ import retracesoftware.autoenable;
retracesoftware/run.py CHANGED
@@ -145,9 +145,9 @@ def run_python_command(argv):
145
145
  except ModuleNotFoundError:
146
146
  print(f"Error: No module named '{argv[1]}'", file=sys.stderr)
147
147
  return 1
148
- except Exception as e:
149
- print(f"Error: {e}", file=sys.stderr)
150
- return 1
148
+ # except Exception as e:
149
+ # print(f"Error1234: {e}", file=sys.stderr)
150
+ # raise e
151
151
  finally:
152
152
  sys.argv = original_argv
153
153
  os.chdir(original_cwd)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: retracesoftware_proxy
3
- Version: 0.2.12
3
+ Version: 0.2.15
4
4
  License: Apache-2.0
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: retracesoftware_utils
@@ -0,0 +1,45 @@
1
+ retracesoftware/__init__.py,sha256=jQw15XBREFZX93BHpvREfc-hQmMUHYtO6gO-qZmdW1o,482
2
+ retracesoftware/__main__.py,sha256=DGCvchaF_wW1j_cirC1tu1QM1gBHE3i-69okn3LKEXM,9582
3
+ retracesoftware/autoenable.py,sha256=6KxpaPpeleSN7-qmekA4w2HJQaet_9Flt9WikQvyN04,1808
4
+ retracesoftware/config.json,sha256=Cw5YZCfTo8GLmtbTH2LYwST7wNe7925DAwsTtRIGhBE,3817
5
+ retracesoftware/config.yaml,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ retracesoftware/exceptions.py,sha256=8jT4tJrRRRh3nRERrsqaKgIJ3itb-VYumpa9UcoxF9o,647
7
+ retracesoftware/modules.toml,sha256=Xu8B6H9HMM4ZaxiEPy9FGcMHBHsyBD1qR0weUXgau4Y,14613
8
+ retracesoftware/preload.txt,sha256=8Wm8OWnDfEp9X4FN-yNQYLfZk_h36kMgHA_QUw_7Hbs,2370
9
+ retracesoftware/replay.py,sha256=FEEenvRUeULmv-Zzb6g7Ub85oGonmvMT4ATE4q0yWqI,3676
10
+ retracesoftware/retrace.pth,sha256=umCRQITpnqAp1N-svTCi4lJxOo7GihDrLtO3pA2wwgM,35
11
+ retracesoftware/run.py,sha256=Gi1TpMF30QLmiziCU7hu3zR_8Hr1R7Qx0TsiQOjLmzA,12216
12
+ retracesoftware/stackdifference.py,sha256=nM9r5YHMCNQcrWozMjUX4ZTrZL5U0rQChSGzpiXy_DU,4466
13
+ retracesoftware/install/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ retracesoftware/install/config.py,sha256=AnSlHI3lWQrPUg0fOS26PqXiq1hJSy56SwrNL8mbAG8,1546
15
+ retracesoftware/install/edgecases.py,sha256=Ry3BETJ4Hmqpw9xXTlNeUgBL9q-sumaazqjmzZBRtD4,7265
16
+ retracesoftware/install/globals.py,sha256=F8XvIoZQQ10gSRalk30dvdKllxlwxkaggYY6FogLDxY,510
17
+ retracesoftware/install/install.py,sha256=fP7E9OWiQd1Dgi4jL8rL9T5SXW5AL62BM3NEW61fisw,4969
18
+ retracesoftware/install/modulepatcher.py,sha256=yBXrbfr5J9JQXCVbcNpDm8H17bsPbL6DY54bImFjBKM,16715
19
+ retracesoftware/install/patcher.py,sha256=DL8GKQzxCSVdTXeexgRuPaKL51SsymcPpP6lUJ1Ezo8,4364
20
+ retracesoftware/install/patchfindspec.py,sha256=uQkfdOQOY20hFAcwDAqx7m8rbtGM2rBKOfC3Szb1zhw,4942
21
+ retracesoftware/install/phases.py,sha256=OWaw86xzC924tdIA9C-12Qdr3sCx67hEOn-Ih9FM--4,9359
22
+ retracesoftware/install/predicate.py,sha256=tX7NQc0rGkyyHYO3mduYHcJHbw1wczT53m_Dpkzo6do,2679
23
+ retracesoftware/install/record.py,sha256=0iV9zMnO7aMUx8HDkni1eeopC3lWR5J6sDwxY6bgeX0,5861
24
+ retracesoftware/install/references.py,sha256=A-G651IDOfuo00MkbAdpbIQh_15ChvJ7uAVTSmE6zd4,1721
25
+ retracesoftware/install/replace.py,sha256=FYiSJtNrXEhl-H6M5tJm0kbliBA0sZdxE0306pr-YQg,872
26
+ retracesoftware/install/replay.py,sha256=bB2eMpIP2vXZzeJ98jtlkGSE1DEYWk3olUbiBQcdVj0,3539
27
+ retracesoftware/install/tracer.py,sha256=VfjghYepk2M2DLy1nEYgHcaCXo0616OVMxxx3A3dNhM,9363
28
+ retracesoftware/install/typeutils.py,sha256=GiYDPsKGBtp0_1E71AJm16u03L8eSoR1piz3yvOgAhk,2253
29
+ retracesoftware/proxy/__init__.py,sha256=ntIyqKhBRkKEkcW_oOPodikh-mxYl8OXRnSaj-9-Xwc,178
30
+ retracesoftware/proxy/gateway.py,sha256=-U0WobF8oFku5ncgO9avXfqTV4t8urMtIQ1FUaHD2Wg,1590
31
+ retracesoftware/proxy/globalref.py,sha256=yXtJsOeBHN9xoEgJWA3MJco-jD2SQUef_fDatA4A6rg,803
32
+ retracesoftware/proxy/messagestream.py,sha256=lp0_Stqn1Ju3tgHrt1E5pcH02RXjyd6rvkuHQnhGRlk,5890
33
+ retracesoftware/proxy/proxyfactory.py,sha256=xssL3rPtou9QurBrQkQNXfJszXkF90V8w6OuKdd02o8,12272
34
+ retracesoftware/proxy/proxysystem.py,sha256=F-NWRMIww1KNw_Bk0GTr2DBVxl0D5sb3EKSf3Zap3LU,15172
35
+ retracesoftware/proxy/proxytype.py,sha256=LZ1rNMsxXRUMdI4uTzDvGkUyPOYxCFroT_ZAtOGTbBk,14012
36
+ retracesoftware/proxy/record.py,sha256=BfZap06nz2cr6eGgf3baMyyilqWipTXCjMB5_1ZIlSA,7522
37
+ retracesoftware/proxy/replay.py,sha256=Kx4XrsP0T35IvTzf3pwhR24Lotoz43p7E8LBevEQHpE,4948
38
+ retracesoftware/proxy/serializer.py,sha256=S5yhHoP2iYaXBY7Jr8Ei630Z31521x0IO6DeuClOD9s,958
39
+ retracesoftware/proxy/startthread.py,sha256=YKUcOSjA-9_5ZsDHhyP0fXJSP3RWDKV-ajX-I0SI-bU,1270
40
+ retracesoftware/proxy/stubfactory.py,sha256=fyQtCZnkXvLaTlGcxE0Lx8od09ZOgnPWPn0YvNFfVeQ,6219
41
+ retracesoftware/proxy/thread.py,sha256=AAj6BL4kw-KMHX4fV66LuePQbTYvPmI3IY8ZwV069nI,3073
42
+ retracesoftware_proxy-0.2.15.dist-info/METADATA,sha256=KjgPEEtawqKuayhjhSq_s0lFFWw4qZrb4zHZurIB4X0,227
43
+ retracesoftware_proxy-0.2.15.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
44
+ retracesoftware_proxy-0.2.15.dist-info/top_level.txt,sha256=hYHsR6txLidmqvjBMITpIHvmJJbmoCAgr76-IpZPRz8,16
45
+ retracesoftware_proxy-0.2.15.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,42 +0,0 @@
1
- retracesoftware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- retracesoftware/__main__.py,sha256=ckCG50cNKujXnQSNezgzSXnY0Ke-xi9v1-6NM3SSIWE,9433
3
- retracesoftware/autoenable.py,sha256=s7dSykM0y3xVqRzrWouGgoed4-lMNRhdA7Gnm1f04NA,1772
4
- retracesoftware/config.json,sha256=Cw5YZCfTo8GLmtbTH2LYwST7wNe7925DAwsTtRIGhBE,3817
5
- retracesoftware/config.yaml,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- retracesoftware/modules.toml,sha256=rQkTbb8eU88JxHdht1sDV3vZvXQAUYBOuyG5xrIaFJw,14623
7
- retracesoftware/preload.txt,sha256=8Wm8OWnDfEp9X4FN-yNQYLfZk_h36kMgHA_QUw_7Hbs,2370
8
- retracesoftware/replay.py,sha256=rKuhen_MDDi_5WVDmQgchxJ0ZrCbYL1yZZboZsu_q54,3601
9
- retracesoftware/run.py,sha256=dQ_yM5f7Mlb0NyZf0CysBX6d_j_IHtIB_NG1W6FGIuY,12207
10
- retracesoftware/stackdifference.py,sha256=nM9r5YHMCNQcrWozMjUX4ZTrZL5U0rQChSGzpiXy_DU,4466
11
- retracesoftware/install/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- retracesoftware/install/config.py,sha256=AnSlHI3lWQrPUg0fOS26PqXiq1hJSy56SwrNL8mbAG8,1546
13
- retracesoftware/install/edgecases.py,sha256=Ry3BETJ4Hmqpw9xXTlNeUgBL9q-sumaazqjmzZBRtD4,7265
14
- retracesoftware/install/globals.py,sha256=F8XvIoZQQ10gSRalk30dvdKllxlwxkaggYY6FogLDxY,510
15
- retracesoftware/install/install.py,sha256=HCD_ji8XCr96b5fNzNdL_8qcEp0Jf05Em7T6GA6u8HU,4969
16
- retracesoftware/install/patcher.py,sha256=sHgsKvdvjMJSVl8lOa3YiCjGf6zYhxgkWp67-ptJvlk,4157
17
- retracesoftware/install/patchfindspec.py,sha256=uQkfdOQOY20hFAcwDAqx7m8rbtGM2rBKOfC3Szb1zhw,4942
18
- retracesoftware/install/phases.py,sha256=OWaw86xzC924tdIA9C-12Qdr3sCx67hEOn-Ih9FM--4,9359
19
- retracesoftware/install/predicate.py,sha256=tX7NQc0rGkyyHYO3mduYHcJHbw1wczT53m_Dpkzo6do,2679
20
- retracesoftware/install/record.py,sha256=08q7zc9cfyCyhLsh15q1d_5vFIhKbs9ln6VvL6kED5o,5861
21
- retracesoftware/install/references.py,sha256=A-G651IDOfuo00MkbAdpbIQh_15ChvJ7uAVTSmE6zd4,1721
22
- retracesoftware/install/replace.py,sha256=FYiSJtNrXEhl-H6M5tJm0kbliBA0sZdxE0306pr-YQg,872
23
- retracesoftware/install/replay.py,sha256=bB2eMpIP2vXZzeJ98jtlkGSE1DEYWk3olUbiBQcdVj0,3539
24
- retracesoftware/install/tracer.py,sha256=GO3Nnzd0ylxM7-A7RN0LzLCFdouJIiHtIcJ2zjFx0Q0,9363
25
- retracesoftware/install/typeutils.py,sha256=-oFzgUfq_nHeOkj3YKZiMLlMzQhCedg3qymLiEJNkVE,2253
26
- retracesoftware/proxy/__init__.py,sha256=ntIyqKhBRkKEkcW_oOPodikh-mxYl8OXRnSaj-9-Xwc,178
27
- retracesoftware/proxy/gateway.py,sha256=xESohWXkiNm4ZutU0RgWUwxjxcBWRQ4rQyxIGQXv_F4,1590
28
- retracesoftware/proxy/globalref.py,sha256=yXtJsOeBHN9xoEgJWA3MJco-jD2SQUef_fDatA4A6rg,803
29
- retracesoftware/proxy/messagestream.py,sha256=kjxIugCSr7YSJbIqsqqXeW7dyE2SzWow2E-UJnkAflk,5890
30
- retracesoftware/proxy/proxyfactory.py,sha256=qhOqDfMJnLDNkQs26JqDB431MwjjRhGQi8xupJ45asg,12272
31
- retracesoftware/proxy/proxysystem.py,sha256=Pj9sk5FE3Mfyf4-PAtXSz2Pvr_GwXcpfhUSJvkz00dg,15076
32
- retracesoftware/proxy/proxytype.py,sha256=LZ1rNMsxXRUMdI4uTzDvGkUyPOYxCFroT_ZAtOGTbBk,14012
33
- retracesoftware/proxy/record.py,sha256=JTmeZ13ISbsVal1WOtae425DP0iA-QiAv0C6azPpV8k,6680
34
- retracesoftware/proxy/replay.py,sha256=Kx4XrsP0T35IvTzf3pwhR24Lotoz43p7E8LBevEQHpE,4948
35
- retracesoftware/proxy/serializer.py,sha256=S5yhHoP2iYaXBY7Jr8Ei630Z31521x0IO6DeuClOD9s,958
36
- retracesoftware/proxy/startthread.py,sha256=YKUcOSjA-9_5ZsDHhyP0fXJSP3RWDKV-ajX-I0SI-bU,1270
37
- retracesoftware/proxy/stubfactory.py,sha256=fyQtCZnkXvLaTlGcxE0Lx8od09ZOgnPWPn0YvNFfVeQ,6219
38
- retracesoftware/proxy/thread.py,sha256=T1ME6DHB8O0xVnX3Rt1lMl7oCJ2Y0aoFT91D76yNICk,3073
39
- retracesoftware_proxy-0.2.12.dist-info/METADATA,sha256=zCFbVvn3SSkfMEVfReu6UG8lmz0lVRQGblAQ1RKz8BE,227
40
- retracesoftware_proxy-0.2.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
41
- retracesoftware_proxy-0.2.12.dist-info/top_level.txt,sha256=hYHsR6txLidmqvjBMITpIHvmJJbmoCAgr76-IpZPRz8,16
42
- retracesoftware_proxy-0.2.12.dist-info/RECORD,,