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.
Files changed (42) hide show
  1. retracesoftware/__init__.py +0 -0
  2. retracesoftware/__main__.py +288 -0
  3. retracesoftware/autoenable.py +53 -0
  4. retracesoftware/config.json +175 -0
  5. retracesoftware/config.yaml +0 -0
  6. retracesoftware/install/__init__.py +0 -0
  7. retracesoftware/install/config.py +59 -0
  8. retracesoftware/install/edgecases.py +250 -0
  9. retracesoftware/install/globals.py +17 -0
  10. retracesoftware/install/install.py +142 -0
  11. retracesoftware/install/patcher.py +122 -0
  12. retracesoftware/install/patchfindspec.py +117 -0
  13. retracesoftware/install/phases.py +338 -0
  14. retracesoftware/install/predicate.py +92 -0
  15. retracesoftware/install/record.py +174 -0
  16. retracesoftware/install/references.py +66 -0
  17. retracesoftware/install/replace.py +28 -0
  18. retracesoftware/install/replay.py +102 -0
  19. retracesoftware/install/tracer.py +284 -0
  20. retracesoftware/install/typeutils.py +92 -0
  21. retracesoftware/modules.toml +388 -0
  22. retracesoftware/preload.txt +218 -0
  23. retracesoftware/proxy/__init__.py +3 -0
  24. retracesoftware/proxy/gateway.py +49 -0
  25. retracesoftware/proxy/globalref.py +31 -0
  26. retracesoftware/proxy/messagestream.py +204 -0
  27. retracesoftware/proxy/proxyfactory.py +357 -0
  28. retracesoftware/proxy/proxysystem.py +454 -0
  29. retracesoftware/proxy/proxytype.py +424 -0
  30. retracesoftware/proxy/record.py +211 -0
  31. retracesoftware/proxy/replay.py +138 -0
  32. retracesoftware/proxy/serializer.py +28 -0
  33. retracesoftware/proxy/startthread.py +40 -0
  34. retracesoftware/proxy/stubfactory.py +195 -0
  35. retracesoftware/proxy/thread.py +106 -0
  36. retracesoftware/replay.py +104 -0
  37. retracesoftware/run.py +378 -0
  38. retracesoftware/stackdifference.py +133 -0
  39. retracesoftware_proxy-0.2.11.dist-info/METADATA +8 -0
  40. retracesoftware_proxy-0.2.11.dist-info/RECORD +42 -0
  41. retracesoftware_proxy-0.2.11.dist-info/WHEEL +5 -0
  42. retracesoftware_proxy-0.2.11.dist-info/top_level.txt +1 -0
@@ -0,0 +1,250 @@
1
+ # from .proxytype import *
2
+
3
+ import functools
4
+ import os
5
+
6
+ from retracesoftware.install import globals
7
+
8
+ def recvfrom_into(target):
9
+ @functools.wraps(target)
10
+ def wrapper(self, buffer, nbytes = 0, flags = 0):
11
+ data, address = self.recvfrom(len(buffer) if nbytes == 0 else nbytes, flags)
12
+ buffer[0:len(data)] = data
13
+ return len(data), address
14
+ return wrapper
15
+
16
+ def recv_into(target):
17
+ @functools.wraps(target)
18
+ def wrapper(self, buffer, nbytes = 0, flags = 0):
19
+ data = self.recv(len(buffer) if nbytes == 0 else nbytes, flags)
20
+ buffer[0:len(data)] = data
21
+ return len(data)
22
+ return wrapper
23
+
24
+ def recvmsg_into(target):
25
+ @functools.wraps(target)
26
+ def wrapper(self, buffers, ancbufsize = 0, flags = 0):
27
+ raise NotImplementedError('TODO')
28
+ return wrapper
29
+
30
+ def read(target):
31
+ @functools.wraps(target)
32
+ def wrapper(self, *args):
33
+ # super_type = super(type(self), self)
34
+
35
+ if len(args) == 0:
36
+ return target(self)
37
+ else:
38
+ buflen = args[0]
39
+
40
+ # pdb.set_trace()
41
+
42
+ data = target(self, buflen)
43
+
44
+ if len(args) == 1:
45
+ return data
46
+ else:
47
+ buffer = args[1]
48
+
49
+ buffer[0:len(data)] = data
50
+
51
+ return len(data)
52
+ return wrapper
53
+
54
+ def write(target):
55
+ @functools.wraps(target)
56
+ def wrapper(self, byteslike):
57
+ return target(byteslike.tobytes())
58
+
59
+ return wrapper
60
+
61
+ def readinto(target):
62
+ @functools.wraps(target)
63
+ def wrapper(self, buffer):
64
+ bytes = self.read(buffer.nbytes)
65
+ buffer[:len(bytes)] = bytes
66
+ return len(bytes)
67
+ return wrapper
68
+
69
+ typewrappers = {
70
+ '_socket': {
71
+ 'socket': {
72
+ 'recvfrom_into': recvfrom_into,
73
+ 'recv_into': recv_into,
74
+ 'recvmsg_into': recvmsg_into
75
+ }
76
+ },
77
+ '_ssl': {
78
+ '_SSLSocket': {
79
+ 'read': read,
80
+ # 'write': write
81
+ }
82
+ },
83
+ 'io': {
84
+ 'FileIO': {
85
+ 'readinto': readinto
86
+ },
87
+ 'BufferedReader': {
88
+ 'readinto': readinto
89
+ },
90
+ 'BufferedRandom': {
91
+ 'readinto': readinto
92
+ }
93
+ }
94
+ }
95
+
96
+ def patchtype(module, name, cls : type):
97
+ if module in typewrappers:
98
+ if name in typewrappers[module]:
99
+ for method,patcher in typewrappers[module][name].items():
100
+ setattr(cls, method, patcher(getattr(cls, method)))
101
+
102
+ def transform_argument(target : callable, position : int, name : str, transform : callable, default = None):
103
+ assert callable(transform)
104
+ assert callable(target)
105
+
106
+ @functools.wraps(target)
107
+ def wrapper(*args, **kwargs):
108
+ if name in kwargs:
109
+ kwargs[name] = transform(kwargs[name])
110
+ elif len(args) > position:
111
+ args = list(args)
112
+ args[position] = transform(args[position])
113
+ else:
114
+ kwargs[name] = transform(default)
115
+
116
+ # print(f'Running: {args} {kwargs}')
117
+ return target(*args, **kwargs)
118
+
119
+ return wrapper
120
+
121
+ subprocess_counter = 0
122
+ recording_path = None
123
+
124
+ def next_subprocess_path():
125
+ global subprocess_counter
126
+ path = f'subprocess-{subprocess_counter}'
127
+ subprocess_counter += 1
128
+ return recording_path / path
129
+
130
+ def retrace_env():
131
+
132
+ env = {
133
+ 'RETRACE_RECORDING_PATH': str(next_subprocess_path()),
134
+ # 'RETRACE_PARENT_THREAD_ID': threading.current_thread().__retrace_thread_id__,
135
+ # 'RETRACE_PARENT_EXEC_COUNTER': str(next_exec_counter()
136
+ 'RETRACE': 'true'
137
+ # 'RETRACE_MODE': 'proxy' if os.getenv('RETRACE_MODE') == 'proxy' else 'record'
138
+ }
139
+ # a timestamped directory off current directory
140
+ # controlled by config
141
+ # 1. if recording_dir is set use that
142
+ # 2. if recording_dir is not set look for
143
+
144
+ # env['RETRACE_EXECUTION_ID'] = hashlib.md5(json.dumps(env).encode('UTF8')).hexdigest()
145
+
146
+ return env
147
+
148
+ def fork_exec(target):
149
+
150
+ def transform(env):
151
+ r = [f'{k}={v}'.encode('utf-8') for k,v in retrace_env().items()]
152
+ return env + r if env else r
153
+
154
+ return transform_argument(target = target,
155
+ position = 5,
156
+ name = 'env',
157
+ transform = transform, default = os.environ)
158
+
159
+ # import threading
160
+ # thread_state = threading.current_thread().__retrace__.thread_state
161
+
162
+ # patched = transform_argument(target = target,
163
+ # position = 5,
164
+ # name = 'env',
165
+ # transform = transform, default = os.environ)
166
+
167
+ # return thread_state.dispatch(target, internal = patched)
168
+
169
+ def posix_spawn(target):
170
+
171
+ def transform(env):
172
+ r = retrace_env()
173
+ return {**env, **r}
174
+
175
+ return transform_argument(target = target,
176
+ position = 2,
177
+ name = 'env',
178
+ transform = transform, default = os.environ)
179
+
180
+ # import threading
181
+ # thread_state = threading.current_thread().__retrace__.thread_state
182
+
183
+ # patched = transform_argument(target = target,
184
+ # position = 2,
185
+ # name = 'env',
186
+ # transform = transform, default = os.environ)
187
+
188
+ # return thread_state.dispatch(target, internal = patched)
189
+
190
+ function_patchers = {
191
+ '_posixsubprocess': {
192
+ 'fork_exec': fork_exec
193
+ },
194
+ 'posix': {
195
+ 'posix_spawn': posix_spawn
196
+ }
197
+ }
198
+
199
+ # import traceback
200
+
201
+ def typepatcher(cls : type):
202
+
203
+ # print(f'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! type: {cls} created')
204
+ # traceback.print_stack()
205
+
206
+ return typewrappers.get(cls.__module__, {}).get(cls.__name__, {})
207
+
208
+ # if cls.__module__ in typewrappers:
209
+ # # print(f'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TYPEWRAPPER for {cls}')
210
+
211
+ # mod = typewrappers[cls.__module__]
212
+
213
+ # return mod.get(cls.__name__, {})
214
+
215
+ # if cls.__name__ in mod:
216
+ # # if cls.__name__ == '_SSLSocket':
217
+ # # breakpoint()
218
+
219
+ # log.info("Applying specialized typewrapper to %s, updated slots: %s", cls, list(mod[cls.__name__].keys()))
220
+
221
+ # for name,value in mod[cls.__name__].items():
222
+ # setattr(cls, name, value(getattr(cls, name)))
223
+
224
+ # # slots = {'__module__': cls.__module__, '__slots__': ()}
225
+ # # slots.update(mod[cls.__name__])
226
+ # # return type(cls.__name__, (cls, ), slots)
227
+
228
+ # return cls
229
+
230
+ def typewrapper(cls : type):
231
+
232
+ # print(f'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! type: {cls} created')
233
+ # traceback.print_stack()
234
+
235
+ if cls.__module__ in typewrappers:
236
+ # print(f'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TYPEWRAPPER for {cls}')
237
+
238
+ mod = typewrappers[cls.__module__]
239
+
240
+ if cls.__name__ in mod:
241
+ # if cls.__name__ == '_SSLSocket':
242
+ # breakpoint()
243
+
244
+ log.info("Applying specialized typewrapper to %s, updated slots: %s", classname(cls), list(mod[cls.__name__].keys()))
245
+
246
+ slots = {'__module__': cls.__module__, '__slots__': ()}
247
+ slots.update(mod[cls.__name__])
248
+ return type(cls.__name__, (cls, ), slots)
249
+
250
+ return cls
@@ -0,0 +1,17 @@
1
+ class RecordingPath:
2
+ def __init__(self, path):
3
+ self.path = path
4
+ self.subprocess_counter = 0
5
+ self.fork_counter = 0
6
+
7
+ def next_subprocess_path(self):
8
+ path = self.path / f'subprocess-{self.subprocess_counter}'
9
+ self.subprocess_counter = self.subprocess_counter + 1
10
+ return path
11
+
12
+ def next_fork_path(self):
13
+ path = self.path / f'fork-{self.fork_counter}'
14
+ self.fork_counter = self.fork_counter + 1
15
+ return path
16
+
17
+ recording_path = None
@@ -0,0 +1,142 @@
1
+ from __future__ import annotations
2
+
3
+ import retracesoftware.functional as functional
4
+ import retracesoftware_utils as utils
5
+
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
+ # from retracesoftware.proxy.proxy import ProxyFactory, InternalProxy, ProxySpec, WrappingProxySpec, ExtendingProxySpec, ExtendingProxy
8
+ from retracesoftware_proxy import thread_id
9
+ # from retracesoftware_stream import ObjectWriter, ObjectReader
10
+ import retracesoftware.stream as stream
11
+
12
+ # from retracesoftware_utils import visitor
13
+ from retracesoftware.install.tracer import Tracer
14
+ from retracesoftware.proxy.record import RecordProxySystem
15
+ from retracesoftware.proxy.replay import ReplayProxySystem
16
+ from datetime import datetime
17
+
18
+ import os
19
+ import sys
20
+ import json
21
+ import enum
22
+ import _thread
23
+ import pickle
24
+ import weakref
25
+ import types
26
+ from pathlib import Path
27
+ import glob, os
28
+ import re
29
+
30
+ # from retracesoftware_proxy import *
31
+ # from retracesoftware_utils import *
32
+ # from retracesoftware.proxy import references
33
+ from retracesoftware.install import patcher
34
+ from retracesoftware.install.config import load_config
35
+ # from retracesoftware.proxy.immutabletypes import ImmutableTypes
36
+ # from retracesoftware.proxy import edgecases
37
+ from retracesoftware.install import globals
38
+ # from retracesoftware.proxy.record import RecordProxyFactory
39
+
40
+
41
+ class DebugWriter:
42
+ __slot__ = ['checkpoint']
43
+
44
+ def __init__(self, checkpoint):
45
+ self.checkpoint = checkpoint
46
+
47
+ def write_call(self, func, *args, **kwargs):
48
+ self.checkpoint({'type': 'call', 'func': ...})
49
+
50
+ def write_result(self, res):
51
+ self.checkpoint({'type': 'result', 'result': res})
52
+
53
+ def write_error(self, *args):
54
+ self.checkpoint({'type': 'error', 'error': tuple(args)})
55
+
56
+ class MethodDescriptor:
57
+ def __init__(self, descriptor):
58
+ self.cls = descriptor.__objclass__
59
+ self.name = descriptor.name
60
+
61
+ def once(*args): return functional.memoize_one_arg(functional.sequence(*args))
62
+
63
+ any = functional.firstof
64
+
65
+
66
+ def compose(*args):
67
+ new_args = [item for item in args if item is not None]
68
+ if len(new_args) == 0:
69
+ raise Exception('TODO')
70
+ elif len(new_args) == 1:
71
+ return new_args[0]
72
+ else:
73
+ return functional.compose(*new_args)
74
+
75
+ class SerializedWrappedFunction:
76
+ def __init__(self, func):
77
+ if hasattr(func, '__objclass__'):
78
+ self.cls = func.__objclass__
79
+ elif hasattr(func, '__module__'):
80
+ self.module = func.__module__
81
+
82
+ if hasattr(func, '__name__'):
83
+ self.name = func.__name__
84
+
85
+
86
+ def replaying_proxy_factory(thread_state, is_immutable_type, tracer, next, bind, checkpoint):
87
+
88
+ # def on_new_ext_proxytype(proxytype):
89
+ # assert not issubclass(proxytype, DynamicProxy)
90
+ # bind(proxytype)
91
+ # writer.add_type_serializer(cls = proxytype, serializer = functional.typeof)
92
+
93
+ # bind_new_int_proxy = functional.if_then_else(functional.isinstanceof(InternalProxy), functional.memoize_one_arg(bind), None)
94
+
95
+ # on_ext_call = utils.visitor(from_arg = 1, function = bind_new_int_proxy)
96
+
97
+ def wrap_int_call(handler):
98
+ return functional.observer(
99
+ on_call = tracer('proxy.int.call'),
100
+ on_result = tracer('proxy.int.result'),
101
+ on_error = tracer('proxy.int.error'),
102
+ function = handler)
103
+
104
+ # def is_stub_type(obj):
105
+ # return type(obj) == type and issubclass(obj, (WrappingProxy, ExtendingProxy))
106
+
107
+ def is_stub_type(obj):
108
+ return type(obj) == type
109
+
110
+ create_stubs = functional.walker(functional.when(is_stub_type, utils.create_stub_object))
111
+ # create_stubs = functional.walker(functional.when(is_stub_type, utils.create_stub_object))
112
+
113
+ def wrap_ext_call(handler):
114
+ return functional.observer(
115
+ on_call = tracer('proxy.ext.call'),
116
+ on_result = tracer('proxy.ext.result'),
117
+ on_error = tracer('proxy.ext.error'),
118
+ function = functional.sequence(functional.always(next), create_stubs))
119
+
120
+ return ProxyFactory(thread_state = thread_state,
121
+ is_immutable_type = is_immutable_type,
122
+ tracer = tracer,
123
+ on_new_int_proxy = bind,
124
+ # on_new_ext_proxytype = on_new_ext_proxytype,
125
+ wrap_int_call = wrap_int_call,
126
+ wrap_ext_call = wrap_ext_call)
127
+
128
+
129
+ # class Reader:
130
+
131
+ # def __init__(self, objectreader):
132
+ # self.objectreader = objectreader
133
+
134
+ # self.objectreader()
135
+
136
+ def tracing_config(config):
137
+ level = os.environ.get('RETRACE_DEBUG', config['default_tracing_level'])
138
+ return config['tracing_levels'].get(level, {})
139
+
140
+
141
+ def install(create_system):
142
+ return patcher.install(config = load_config('config.json'), create_system = create_system)
@@ -0,0 +1,122 @@
1
+ import enum
2
+ from retracesoftware.install.typeutils import modify
3
+ from retracesoftware.install.replace import update
4
+ import threading
5
+ import sys
6
+ import retracesoftware.utils as utils
7
+ import retracesoftware.functional as functional
8
+ import importlib
9
+
10
+ def resolve(path):
11
+ module, sep, name = path.rpartition('.')
12
+
13
+ if module is None:
14
+ module = 'builtins'
15
+
16
+ return getattr(importlib.import_module(module), name)
17
+
18
+ def replace(replacements, coll):
19
+ return map(lambda x: replacements.get(x, x), coll)
20
+
21
+ def patch_class(transforms, cls):
22
+ with modify(cls):
23
+ for attr,transform in transforms.items():
24
+ utils.update(cls, attr, resolve(transform))
25
+
26
+ return cls
27
+
28
+ class PerThread(threading.local):
29
+ def __init__(self):
30
+ self.internal = utils.counter()
31
+ self.external = utils.counter()
32
+
33
+ def create_patcher(system):
34
+
35
+ patcher = {}
36
+
37
+ def foreach(func): return lambda config: {name: func for name in config}
38
+ def selector(func): return lambda config: {name: functional.partial(func, value) for name, value in config.items()}
39
+
40
+ def simple_patcher(func): return foreach(functional.side_effect(func))
41
+
42
+ def type_attributes(transforms, cls):
43
+ with modify(cls):
44
+ for action, attrs in transforms.items():
45
+ for attr,func in patcher[action](attrs).items():
46
+ utils.update(cls, attr, func)
47
+ return cls
48
+
49
+ def bind(obj):
50
+ if issubclass(obj, enum.Enum):
51
+ for member in obj:
52
+ system.bind(member)
53
+ else:
54
+ system.bind(obj)
55
+
56
+ def add_immutable_type(obj):
57
+ if not isinstance(obj, type):
58
+ raise Exception("TODO")
59
+ system.immutable_types.add(obj)
60
+ return obj
61
+
62
+ per_thread = PerThread()
63
+
64
+ hashfunc = system.thread_state.dispatch(
65
+ functional.constantly(None),
66
+ internal = functional.repeatedly(functional.partial(getattr, per_thread, 'internal')),
67
+ external = functional.repeatedly(functional.partial(getattr, per_thread, 'external')))
68
+
69
+ def patch_hash(obj):
70
+ if not isinstance(obj, type):
71
+ raise Exception("TODO")
72
+
73
+ utils.patch_hash(cls = obj, hashfunc = hashfunc)
74
+ return obj
75
+
76
+ patcher.update({
77
+ 'type_attributes': selector(type_attributes),
78
+ 'patch_class': selector(patch_class),
79
+ 'disable': foreach(system.disable_for),
80
+ 'patch_types': simple_patcher(system.patch_type),
81
+ 'proxy': foreach(system),
82
+ 'bind': simple_patcher(bind),
83
+ 'wrap': lambda config: {name: resolve(action) for name,action in config.items() },
84
+ 'immutable': simple_patcher(add_immutable_type),
85
+ 'patch_hash': simple_patcher(patch_hash),
86
+ })
87
+ return patcher
88
+
89
+ def patch_namespace(patcher, config, namespace, update_refs):
90
+ for phase_name, phase_config in config.items():
91
+ if phase_name in patcher:
92
+ for name,func in patcher[phase_name](phase_config).items():
93
+ if name in namespace:
94
+ # print(f"patching: {name}")
95
+
96
+ value = namespace[name]
97
+ new_value = func(value)
98
+
99
+ if value is not new_value:
100
+ namespace[name] = new_value
101
+
102
+ if update_refs:
103
+ update(value, new_value)
104
+ else:
105
+ print(phase_name)
106
+ utils.sigtrap('FOO1')
107
+
108
+ def patch_module(patcher, config, namespace, update_refs):
109
+ if '__name__' in namespace:
110
+ name = namespace['__name__']
111
+ if name in config:
112
+ patch_namespace(patcher, config = config[name], namespace=namespace, update_refs=update_refs)
113
+
114
+ def patch_imported_module(patcher, checkpoint, config, namespace, update_refs):
115
+ if '__name__' in namespace:
116
+ name = namespace['__name__']
117
+ checkpoint(f"importing module: {name}")
118
+
119
+ if name in config:
120
+ # print(f"Patching imported module: {name}")
121
+ # checkpoint(f"Patching imported module: {name}")
122
+ patch_namespace(patcher, config = config[name], namespace=namespace, update_refs=update_refs)
@@ -0,0 +1,117 @@
1
+ from functools import wraps
2
+ import os
3
+ from pathlib import Path
4
+ import shutil
5
+ import sys
6
+
7
+ # Set to track copied modules
8
+ copied_modules = set()
9
+
10
+ def get_relative_path(module_path: Path, sys_path: list) -> Path:
11
+ """Compute the relative path of module_path relative to sys.path entries."""
12
+ module_path = module_path.resolve()
13
+ for base in sys_path:
14
+ base_path = Path(base).resolve()
15
+ try:
16
+ if module_path.is_relative_to(base_path):
17
+ return module_path.relative_to(base_path)
18
+ except ValueError:
19
+ continue
20
+ return Path(module_path.name)
21
+
22
+ class patch_find_spec:
23
+ def __init__(self, cwd, run_path, python_path):
24
+ self.cwd = cwd
25
+ self.run_path = run_path
26
+ self.python_path = python_path
27
+
28
+ def __call__(self, spec):
29
+ if spec is not None and spec.origin and spec.origin != "built-in" and os.path.isfile(spec.origin):
30
+ module_name = spec.name
31
+ if module_name not in copied_modules:
32
+ module_path = Path(spec.origin)
33
+
34
+ dest_path = None
35
+
36
+ if module_path.is_relative_to(self.cwd):
37
+ dest_path = self.run_path / module_path.relative_to(self.cwd)
38
+ elif self.python_path:
39
+ relative_path = get_relative_path(module_path, sys.path)
40
+ dest_path = self.python_path / relative_path
41
+
42
+ if dest_path:
43
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
44
+ shutil.copy2(module_path, dest_path)
45
+ copied_modules.add(module_name)
46
+
47
+ # def __call__(self, fullname, path, target=None):
48
+ # spec = self.original_find_spec(fullname, path, target)
49
+
50
+ # if spec is not None and spec.origin and spec.origin != "built-in" and os.path.isfile(spec.origin):
51
+ # module_name = spec.name
52
+ # if module_name not in copied_modules:
53
+ # module_path = Path(spec.origin)
54
+
55
+ # dest_path = None
56
+
57
+ # if module_path.is_relative_to(self.cwd):
58
+ # dest_path = self.run_path / module_path.relative_to(self.cwd)
59
+ # elif self.python_path:
60
+ # relative_path = get_relative_path(module_path, sys.path)
61
+ # dest_path = self.python_path / relative_path
62
+
63
+ # if dest_path:
64
+ # dest_path.parent.mkdir(parents=True, exist_ok=True)
65
+ # shutil.copy2(module_path, dest_path)
66
+ # copied_modules.add(module_name)
67
+
68
+ # return spec
69
+
70
+ # def patch_find_spec(cwd, run_path, python_path, original_find_spec):
71
+ # """Create a patched version of find_spec that copies .py files."""
72
+ # @wraps(original_find_spec)
73
+ # def patched_find_spec(fullname, path, target=None):
74
+ # spec = original_find_spec(fullname, path, target)
75
+
76
+ # if spec is not None and spec.origin and spec.origin != "built-in" and os.path.isfile(spec.origin):
77
+ # module_name = spec.name
78
+ # if module_name not in copied_modules:
79
+ # module_path = Path(spec.origin)
80
+
81
+ # dest_path = None
82
+
83
+ # if module_path.is_relative_to(cwd):
84
+ # dest_path = run_path / module_path.relative_to(cwd)
85
+ # elif python_path:
86
+ # relative_path = get_relative_path(module_path, sys.path)
87
+ # dest_path = python_path / relative_path
88
+
89
+ # if dest_path:
90
+ # dest_path.parent.mkdir(parents=True, exist_ok=True)
91
+ # shutil.copy2(module_path, dest_path)
92
+ # copied_modules.add(module_name)
93
+
94
+ # # elif python_path:
95
+ # # relative_path = get_relative_path(module_path, sys.path)
96
+ # # dest_path = python_path / relative_path
97
+ # # dest_path.parent.mkdir(parents=True, exist_ok=True)
98
+
99
+ # # shutil.copy2(module_path, dest_path)
100
+ # # copied_modules.add(module_name)
101
+
102
+ # # if module_path.suffix == '.py':
103
+ # # elif module_path.suffix == '.py':
104
+ # # relative_path = get_relative_path(module_path, sys.path)
105
+ # # dest_path = path / relative_path
106
+ # # dest_path.parent.mkdir(parents=True, exist_ok=True)
107
+ # # try:
108
+ # # shutil.copy2(module_path, dest_path)
109
+ # # print(f"Copied {module_path} to {dest_path} (relative: {relative_path})")
110
+ # # copied_modules.add(module_name)
111
+ # # except Exception as e:
112
+ # # print(f"Failed to copy {module_path}: {e}")
113
+ # # else:
114
+ # # print(f"Skipped copying {module_path} (not a .py file)")
115
+
116
+ # return spec
117
+ # return patched_find_spec