retracesoftware-proxy 0.1.0__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,92 @@
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))
@@ -0,0 +1,128 @@
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
+
10
+ import os
11
+ import sys
12
+ from datetime import datetime
13
+ import json
14
+ from pathlib import Path
15
+
16
+ def write_files(recording_path):
17
+ with open(recording_path / 'env', 'w') as f:
18
+ json.dump(dict(os.environ), f, indent=2)
19
+
20
+ with open(recording_path / 'exe', 'w') as f:
21
+ f.write(sys.executable)
22
+
23
+ with open(recording_path / 'cwd', 'w') as f:
24
+ f.write(os.getcwd())
25
+
26
+ with open(recording_path / 'cmd', 'w') as f:
27
+ json.dump(sys.orig_argv, f, indent=2)
28
+
29
+ def create_recording_path(path):
30
+ expanded = datetime.now().strftime(path.format(pid = os.getpid()))
31
+ os.environ['RETRACE_RECORDING_PATH'] = expanded
32
+ return Path(expanded)
33
+
34
+ def tracing_level(config):
35
+ return os.environ.get('RETRACE_DEBUG', config['default_tracing_level'])
36
+
37
+ # def tracing_config(config):
38
+ # level = os.environ.get('RETRACE_DEBUG', config['default_tracing_level'])
39
+ # return config['tracing_levels'].get(level, {})
40
+
41
+ def thread_aware_writer(writer):
42
+ on_thread_switch = functional.sequence(utils.thread_id(), writer.handle('THREAD_SWITCH'))
43
+ return utils.threadawareproxy(on_thread_switch = on_thread_switch, target = writer)
44
+
45
+ def record_system(thread_state, immutable_types, config):
46
+
47
+ recording_path = create_recording_path(config['recording_path'])
48
+
49
+ recording_path.mkdir(parents=True, exist_ok=True)
50
+
51
+ globals.recording_path = globals.RecordingPath(recording_path)
52
+
53
+ write_files(recording_path)
54
+
55
+ tracing_config = config['tracing_levels'].get(tracing_level(config), {})
56
+
57
+ with open(recording_path / 'tracing_config.json', 'w') as f:
58
+ json.dump(tracing_config, f, indent=2)
59
+
60
+ writer = thread_aware_writer(stream.writer(path = recording_path / 'trace.bin'))
61
+
62
+ # os.register_at_fork(
63
+ # # before = self.thread_state.wrap('disabled', self.before_fork),
64
+ # before = before,
65
+ # after_in_parent = self.thread_state.wrap('disabled', self.after_fork_in_parent),
66
+ # after_in_child = self.thread_state.wrap('disabled', self.after_fork_in_child))
67
+
68
+ # self.writer = thread_state.wrap(
69
+ # desired_state = 'disabled',
70
+ # sticky = True,
71
+ # function = VerboseWriter(writer)) if verbose else writer
72
+
73
+ # def gc_start(self):
74
+ # self.before_gc = self.thread_state.value
75
+ # self.thread_state.value = 'external'
76
+
77
+ # def gc_end(self):
78
+ # self.thread_state.value = self.before_gc
79
+ # del self.before_gc
80
+
81
+ # def gc_hook(self, phase, info):
82
+ # if phase == 'start':
83
+ # self.gc_start()
84
+
85
+ # elif phase == 'stop':
86
+ # self.gc_end()
87
+ # gc.callbacks.append(self.gc_hook)
88
+
89
+ w = writer.handle('TRACE')
90
+ def trace_writer(*args):
91
+ print(f'Trace: {args}')
92
+ w(*args)
93
+
94
+ # print(f'Tracing config: {tracing_config(config)}')
95
+
96
+ tracer = Tracer(tracing_config, writer = trace_writer)
97
+ # tracer = Tracer(config = tracing_config(config), writer = writer.handle('TRACE'))
98
+
99
+ factory = RecordProxySystem(thread_state = thread_state,
100
+ immutable_types = immutable_types,
101
+ tracer = tracer,
102
+ writer = writer)
103
+
104
+ # factory.proxy_type = compose(factory.proxy_type, side_effect(...))
105
+
106
+ def on_patched(module_name, updates):
107
+ ...
108
+ # for name, value in updates.items():
109
+ # if isinstance(value, type) and \
110
+ # issubclass(value, ExtendingProxy) and \
111
+ # not issubclass(value, InternalProxy):
112
+
113
+ # ref = writer.handle(ExtendingProxySpec(module = module_name, name = name))
114
+ # try:
115
+ # writer.add_type_serializer(cls = value, serializer = functional.constantly(ref))
116
+ # except:
117
+ # pass
118
+
119
+ factory.on_patched = on_patched
120
+
121
+ factory.sync = lambda function: functional.observer(on_call = functional.always(writer.handle('SYNC')), function = function)
122
+
123
+ # factory.set_thread_number = writer.thread_number
124
+ factory.tracer = tracer
125
+
126
+ factory.log = functional.partial(writer, 'LOG')
127
+
128
+ return factory
@@ -0,0 +1,65 @@
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 value is old:
35
+ container[key] = new
36
+
37
+ elif isinstance(container, list):
38
+ for i,value in enumerate(container):
39
+ if value is old:
40
+ container[i] = new
41
+
42
+ elif isinstance(container, set):
43
+ container.remove(old)
44
+ container.add(new)
45
+
46
+ # elif isinstance(container, tuple):
47
+ # pdb.set_trace()
48
+ # else:
49
+ # pdb.set_trace()
50
+ # raise Exception('TODO')
51
+
52
+ def update(f, obj):
53
+ new = f(obj)
54
+ if new is not obj:
55
+ refs = references(obj)
56
+ for ref in refs:
57
+ replace(ref, obj, new)
58
+
59
+ return new
60
+
61
+ # import math
62
+ # import os
63
+ # print(references(math.sqrt))
64
+ # print(references(os.nice))
65
+ # print(references(os.open))
@@ -0,0 +1,151 @@
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
+ from retracesoftware.install.tracer import Tracer
7
+ from retracesoftware.install import globals
8
+
9
+ import os, json, re, glob, weakref
10
+ from pathlib import Path
11
+ from datetime import datetime
12
+
13
+ def latest_from_pattern(pattern: str) -> str | None:
14
+ """
15
+ Given a strftime-style filename pattern (e.g. "recordings/%Y%m%d_%H%M%S_%f"),
16
+ return the path to the most recent matching file, or None if no files exist.
17
+ """
18
+ # Turn strftime placeholders into '*' for globbing
19
+ # (very simple replacement: %... -> *)
20
+ glob_pattern = re.sub(r"%[a-zA-Z]", "*", pattern)
21
+
22
+ # Find all matching files
23
+ candidates = glob.glob(glob_pattern)
24
+ if not candidates:
25
+ return None
26
+
27
+ # Derive the datetime format from the pattern (basename only)
28
+ base_pattern = os.path.basename(pattern)
29
+
30
+ def parse_time(path: str):
31
+ name = os.path.basename(path)
32
+ return datetime.strptime(name, base_pattern)
33
+
34
+ # Find the latest by parsed timestamp
35
+ latest = max(candidates, key=parse_time)
36
+ return latest
37
+
38
+
39
+
40
+ def thread_aware_reader(reader):
41
+ def on_thread_switch():
42
+ ...
43
+
44
+ return utils.threadawareproxy(on_thread_switch = on_thread_switch, target = reader)
45
+
46
+
47
+ def replay_system(thread_state, immutable_types, config):
48
+
49
+ recording_path = Path(latest_from_pattern(config['recording_path']))
50
+
51
+ print(f"replay running against path: {recording_path}")
52
+
53
+ globals.recording_path = globals.RecordingPath(recording_path)
54
+
55
+ assert recording_path.exists()
56
+ assert recording_path.is_dir()
57
+
58
+ with open(recording_path / "env", "r", encoding="utf-8") as f:
59
+ os.environ.update(json.load(f))
60
+
61
+ with open(recording_path / "tracing_config.json", "r", encoding="utf-8") as f:
62
+ tracing_config = json.load(f)
63
+
64
+ handler = None
65
+
66
+ # def create_stub_type(proxyspec):
67
+ # if isinstance(proxyspec, ExtendingProxySpec):
68
+ # mod = sys.modules[proxyspec.module]
69
+ # resolved = getattr(mod, proxyspec.name)
70
+ # assert issubclass(resolved, ExtendingProxy)
71
+
72
+ # return resolved
73
+ # # unproxied = resolved.__base__
74
+ # # print(f'base: {unproxied} has_generic_new: {utils.has_generic_new(unproxied)}')
75
+ # # print(f'base: {unproxied} has_generic_alloc: {utils.has_generic_alloc(unproxied)}')
76
+ # # utils.create_stub_object(resolved)
77
+ # # os._exit(1)
78
+ # # return resolved
79
+
80
+ # elif isinstance(proxyspec, WrappingProxySpec):
81
+ # nonlocal handler
82
+ # return proxyspec.create_type(handler)
83
+ # else:
84
+ # print(f'In create_stub_type!!!! {proxyspec}')
85
+ # os._exit(1)
86
+
87
+ # deserializer = functional.compose(pickle.loads, functional.when_instanceof(ProxySpec, create_stub_type))
88
+
89
+ reader = thread_aware_reader(stream.reader(path = recording_path / 'trace.bin'))
90
+
91
+ # reader = utils.threadawareproxy(on_thread_switch = ..., target = reader)
92
+
93
+ def readnext():
94
+ return reader()
95
+ # print(f'read: {obj}')
96
+ # return obj
97
+
98
+ lookup = weakref.WeakKeyDictionary()
99
+
100
+ # debug = debug_level(config)
101
+
102
+ # int_refs = {}
103
+
104
+ def checkpoint(replay):
105
+ ...
106
+
107
+ def read_required(required):
108
+ obj = readnext()
109
+ if obj != required:
110
+ print(f'Expected: {required} but got: {obj}')
111
+ for i in range(5):
112
+ readnext()
113
+
114
+ utils.sigtrap(None)
115
+ os._exit(1)
116
+ raise Exception(f'Expected: {required} but got: {obj}')
117
+
118
+ def trace_writer(name, *args):
119
+ print(f'Trace: {name} {args}')
120
+
121
+ read_required('TRACE')
122
+ read_required(name)
123
+
124
+ for arg in args:
125
+ read_required(arg)
126
+
127
+ tracer = Tracer(tracing_config, writer = trace_writer)
128
+
129
+ factory = ReplayProxySystem(thread_state = thread_state,
130
+ immutable_types = immutable_types,
131
+ tracer = tracer,
132
+ reader = reader)
133
+
134
+ # factory = replaying_proxy_factory(thread_state = thread_state,
135
+ # is_immutable_type = is_immutable_type,
136
+ # tracer = tracer,
137
+ # bind = reader.supply,
138
+ # next = next_result,
139
+ # checkpoint = checkpoint)
140
+
141
+ factory.tracer = tracer
142
+
143
+ def read_sync(): read_required('SYNC')
144
+
145
+ factory.sync = lambda function: functional.observer(on_call = functional.always(read_sync), function = function)
146
+
147
+ # factory.set_thread_number = writer.thread_number
148
+
149
+ factory.log = lambda message: checkpoint({'type': 'log_message', 'message': message})
150
+
151
+ return factory
@@ -0,0 +1,144 @@
1
+ import retracesoftware.functional as functional
2
+ import retracesoftware_utils as utils
3
+ from retracesoftware.proxy.proxytype import Proxy
4
+
5
+ import types
6
+ import os
7
+
8
+ def format_kwargs(kwargs):
9
+ result = []
10
+ for k, v in kwargs.items():
11
+ result.extend([f"{k} = ", v])
12
+
13
+ return tuple(result)
14
+
15
+ class Tracer:
16
+ def __init__(self, config, writer):
17
+ self.config = config
18
+ serialize = functional.walker(self.serialize)
19
+ self.writer = functional.mapargs(transform = serialize, function = writer)
20
+
21
+ def serialize(self, obj):
22
+ try:
23
+ if obj is None: return None
24
+
25
+ cls = functional.typeof(obj)
26
+
27
+ if issubclass(cls, (int, str)):
28
+ return obj
29
+
30
+ else:
31
+ return 'FOO!!!!'
32
+
33
+ if issubclass(cls, Proxy):
34
+ return f'<Proxy>'
35
+
36
+ if issubclass(cls, types.TracebackType):
37
+ return '<traceback>'
38
+
39
+ elif issubclass(cls, utils.wrapped_function):
40
+ return utils.unwrap(obj).__name__
41
+
42
+ elif hasattr(obj, '__module__') and hasattr(obj, '__name__'):
43
+ return f'{obj.__module__}.{obj.__name__}'
44
+ # elif isinstance(obj, type):
45
+ # return f'{obj.__module__}.{obj.__name__}'
46
+ else:
47
+ return '<other>'
48
+ # return f'instance type: {str(self.serialize(type(obj)))}'
49
+ except:
50
+ print("ERROR in tracer serialize!!!!")
51
+ os._exit(1)
52
+
53
+ def log(self, name, message):
54
+ if name in self.config:
55
+ self.writer(name, message)
56
+
57
+ def __call__(self, name, func = None):
58
+ if name in self.config:
59
+ if name.endswith('.call'):
60
+ def write_call(*args, **kwargs):
61
+ self.writer(name, args[0].__name__, args[1:], kwargs)
62
+
63
+ return functional.firstof(write_call, func) if func else write_call
64
+
65
+ elif name.endswith('.result'):
66
+ def write_result(obj):
67
+ self.writer(name, obj)
68
+ return obj
69
+
70
+ return functional.sequence(func, write_result) if func else write_result
71
+
72
+ elif name.endswith('.error'):
73
+ def write_error(*args):
74
+ self.writer(name, args)
75
+
76
+ return functional.firstof(write_error, func) if func else write_error
77
+
78
+ elif name.endswith('.event'):
79
+ def write_event(*args, **kwargs):
80
+ self.writer(name)
81
+
82
+ return functional.firstof(write_event, func) if func else write_event
83
+
84
+ elif name.endswith('.wrap'):
85
+ def wrapper(*args, **kwargs):
86
+ self.writer(name, 'enter')
87
+ try:
88
+ return func(*args, **kwargs)
89
+ finally:
90
+ self.writer(name, 'exit')
91
+
92
+ return wrapper
93
+
94
+ elif name.endswith('.stack'):
95
+ def write_event(*args, **kwargs):
96
+ self.writer(name, utils.stacktrace())
97
+
98
+ return functional.firstof(write_event, func) if func else write_event
99
+
100
+
101
+ return func
102
+
103
+
104
+ def write(self, name):
105
+ if name in self.config:
106
+ return functional.partial(self.writer, name)
107
+
108
+ def write_call(self, name, func = None):
109
+
110
+ def writer(*args, **kwargs):
111
+ self.writer(name, *(args + format_kwargs(kwargs)))
112
+
113
+ if name in self.config:
114
+ return functional.firstof(writer, func) if func else writer
115
+ else:
116
+ return func
117
+
118
+ def write_result(self, name, func):
119
+
120
+ writer = functional.partial(self.writer, name)
121
+
122
+ if name in self.config:
123
+ return functional.compose(func, functional.side_effect(writer))
124
+ else:
125
+ return func
126
+
127
+ def event(self, name):
128
+ return functional.always(lambda: self.writer(name))
129
+
130
+ def event_before(self, name, func = None):
131
+ if name in self.config:
132
+ # def foo(*args, **kwargs):
133
+ # print("GRRRRR!!!!!")
134
+
135
+ # return functional.firstof(foo, func)
136
+ return functional.firstof(self.event(name), func)
137
+ else:
138
+ return func
139
+
140
+ def event_after(self, name, func):
141
+ if name in self.config:
142
+ return functional.compose(func, functional.side_effect(self.event(name)))
143
+ else:
144
+ return func
@@ -0,0 +1,72 @@
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
+ def add_flag(cls : type, flag):
39
+ if isinstance(flag, str):
40
+ flag = utils.TypeFlags[flag]
41
+
42
+ flags = utils.type_flags(cls)
43
+ utils.set_type_flags(cls, flags | flag)
44
+
45
+ def modify(cls):
46
+ return WithoutFlags(cls, "Py_TPFLAGS_IMMUTABLETYPE")
47
+
48
+ def type_has_feature(cls, flag):
49
+ if isinstance(flag, str):
50
+ flag = utils.TypeFlags[flag]
51
+ return (utils.type_flags(cls) & flag) != 0
52
+
53
+ def type_disallow_instantiation(cls : type):
54
+ return utils.type_flags(cls) & utils.Py_TPFLAGS_DISALLOW_INSTANTIATION
55
+
56
+ def is_method_descriptor(cls : type):
57
+ return (utils.type_flags(cls) & utils.TypeFlags["Py_TPFLAGS_METHOD_DESCRIPTOR"]) != 0
58
+
59
+ def extend_type(cls : type):
60
+ subclasses = list(cls.__subclasses__())
61
+
62
+ utils.make_extensible(cls)
63
+
64
+ print(f'extending: {cls}')
65
+
66
+ extended = utils.extend_type(cls)
67
+
68
+ for subclass in subclasses:
69
+ print(f"updating subclass: {subclass}")
70
+ subclass.__bases__ = tuple(map(lambda x: extended if x is cls else x, subclass.__bases__))
71
+
72
+ return extended
@@ -0,0 +1,3 @@
1
+ from retracesoftware.proxy.record import RecordProxySystem
2
+ from retracesoftware.proxy.replay import ReplayProxySystem
3
+