retracesoftware-proxy 0.0.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.
Files changed (41) hide show
  1. retracesoftware/__init__.py +0 -0
  2. retracesoftware/__main__.py +266 -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 +242 -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 +384 -0
  22. retracesoftware/proxy/__init__.py +3 -0
  23. retracesoftware/proxy/gateway.py +49 -0
  24. retracesoftware/proxy/globalref.py +31 -0
  25. retracesoftware/proxy/messagestream.py +204 -0
  26. retracesoftware/proxy/proxyfactory.py +357 -0
  27. retracesoftware/proxy/proxysystem.py +454 -0
  28. retracesoftware/proxy/proxytype.py +424 -0
  29. retracesoftware/proxy/record.py +211 -0
  30. retracesoftware/proxy/replay.py +138 -0
  31. retracesoftware/proxy/serializer.py +28 -0
  32. retracesoftware/proxy/startthread.py +40 -0
  33. retracesoftware/proxy/stubfactory.py +195 -0
  34. retracesoftware/proxy/thread.py +106 -0
  35. retracesoftware/replay.py +104 -0
  36. retracesoftware/run.py +373 -0
  37. retracesoftware/stackdifference.py +133 -0
  38. retracesoftware_proxy-0.0.0.dist-info/METADATA +8 -0
  39. retracesoftware_proxy-0.0.0.dist-info/RECORD +41 -0
  40. retracesoftware_proxy-0.0.0.dist-info/WHEEL +5 -0
  41. retracesoftware_proxy-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,138 @@
1
+ import retracesoftware.functional as functional
2
+ import retracesoftware_utils as utils
3
+
4
+ from retracesoftware.install.tracer import Tracer
5
+ from retracesoftware.proxy.thread import per_thread_messages, thread_id
6
+ from retracesoftware.proxy.messagestream import *
7
+ from retracesoftware.proxy.proxytype import *
8
+ # from retracesoftware.proxy.gateway import gateway_pair
9
+ from retracesoftware.proxy.record import StubRef
10
+ from retracesoftware.proxy.proxysystem import ProxySystem
11
+ from retracesoftware.proxy.stubfactory import StubFactory
12
+ from retracesoftware.proxy.globalref import GlobalRef
13
+
14
+ import os
15
+
16
+ class ReplayProxySystem(ProxySystem):
17
+
18
+ def after_fork_in_child(self):
19
+ self.reader.path = self.new_child_path(self.reader.path)
20
+ super().after_fork_in_child()
21
+
22
+ # def dynamic_ext_proxytype(self, cls):
23
+ # raise Exception('dynamic_ext_proxytype should not be called in replay')
24
+
25
+ @property
26
+ def ext_apply(self):
27
+ return functional.repeatedly(self.next_result)
28
+
29
+ def proxy__new__(self, __new__, *args, **kwargs):
30
+ func = functional.repeatedly(self.next_result)
31
+ func.__name__ = '__new__'
32
+ return super().proxy__new__(func, *args, **kwargs)
33
+
34
+ def basetype(self, cls):
35
+ return self.stub_factory.create_stubtype(StubRef(cls))
36
+
37
+ def trace_writer(self, name, *args):
38
+ with self.thread_state.select('disabled'):
39
+ # read = self.messages_read
40
+
41
+ self.read_required('TRACE')
42
+ # self.read_required(read)
43
+ self.read_required(name)
44
+
45
+ if name == 'stacktrace':
46
+ print('FOOO!!!')
47
+ os._exit(1)
48
+ record = self.readnext()
49
+ if args[0] == record:
50
+ self.last_matching_stack = args[0]
51
+ else:
52
+ on_stack_mismatch(
53
+ last_matching = self.last_matching_stack,
54
+ record = record,
55
+ replay = args[0])
56
+ os._exit(1)
57
+ else:
58
+ # print(f'Trace: {self.reader.messages_read} {name} {args}')
59
+ for arg in args:
60
+ self.read_required(arg)
61
+
62
+ def on_thread_exit(self, thread_id):
63
+ # print(f'on_thread_exit!!!!')
64
+ self.reader.wake_pending()
65
+
66
+ def __init__(self,
67
+ reader,
68
+ thread_state,
69
+ immutable_types,
70
+ tracing_config,
71
+ traceargs,
72
+ verbose = False,
73
+ fork_path = [],
74
+ skip_weakref_callbacks = False):
75
+
76
+ self.reader = reader
77
+ # self.skip_weakref_callbacks = skip_weakref_callbacks
78
+
79
+ self.fork_path = fork_path
80
+
81
+ self.messages = MessageStream(
82
+ thread_state = thread_state,
83
+ source = reader,
84
+ skip_weakref_callbacks = skip_weakref_callbacks,
85
+ verbose = verbose)
86
+
87
+ self.checkpoint = self.messages.checkpoint
88
+ self.bind = self.messages.bind
89
+ self.next_result = self.messages.result
90
+ self.exclude_from_stacktrace = self.messages.excludes.add
91
+
92
+ self.stub_factory = StubFactory(thread_state = thread_state, next_result = self.next_result)
93
+
94
+ self.last_matching_stack = None
95
+
96
+ def run_ref(ref):
97
+ print(f'run_ref!!!! {ref}')
98
+ return ref()
99
+
100
+ self.reader.type_deserializer[StubRef] = self.stub_factory
101
+ # self.reader.type_deserializer[GlobalRef] = lambda ref: ref()
102
+ self.reader.type_deserializer[GlobalRef] = run_ref
103
+
104
+ excludes = [ReplayProxySystem.trace_writer]
105
+
106
+ for exclude in excludes:
107
+ self.messages.excludes.add(exclude)
108
+
109
+ sync = functional.lazy(self.messages.read_required, 'SYNC')
110
+
111
+ read_sync = thread_state.dispatch(utils.noop, internal = sync)
112
+
113
+ self.on_ext_call = sync
114
+
115
+ self.sync = lambda function: utils.observer(on_call = read_sync, function = function)
116
+
117
+ self.create_from_external = utils.noop
118
+
119
+ if skip_weakref_callbacks:
120
+ self.wrap_weakref_callback = \
121
+ lambda callback: \
122
+ thread_state.dispatch(
123
+ callback, internal = self.disable_for(callback))
124
+ else:
125
+ self.on_weakref_callback_start = functional.lazy(self.messages.read_required, 'ON_WEAKREF_CALLBACK_START')
126
+ self.on_weakref_callback_end = functional.lazy(self.messages.read_required, 'ON_WEAKREF_CALLBACK_END')
127
+
128
+ super().__init__(thread_state = thread_state,
129
+ tracer = Tracer(tracing_config, writer = self.trace_writer),
130
+ immutable_types = immutable_types,
131
+ traceargs = traceargs)
132
+
133
+ def write_trace(self, obj):
134
+ if 'TRACER' != self.messages():
135
+ utils.sigtrap(obj)
136
+
137
+ # self.read_required ('TRACER')
138
+ self.read_required(obj)
@@ -0,0 +1,28 @@
1
+ def serializer(obj):
2
+ cls = type(obj)
3
+
4
+ try:
5
+ if issubclass(cls, Enum):
6
+ return str(obj)
7
+
8
+ if issubclass(cls, (tuple, list)):
9
+ return list(map(serializer, obj))
10
+ elif issubclass(cls, dict):
11
+ return {str(serializer(k)):serializer(v) for k,v in obj.items()}
12
+ elif issubclass(cls, int):
13
+ if obj > 1000000 or obj < -1000000:
14
+ return "XXXX"
15
+ else:
16
+ return int(obj)
17
+ elif issubclass(cls, (bool, str, types.NoneType)):
18
+ return obj
19
+ elif issubclass(cls, types.FunctionType):
20
+ return obj.__qualname__
21
+ elif issubclass(cls, types.CellType):
22
+ return getattr(obj, '__qualname__', 'CellType')
23
+ elif issubclass(cls, types.GeneratorType):
24
+ return obj.__qualname__
25
+ else:
26
+ return cleanse(str(obj))
27
+ except:
28
+ return f'Unserializable object of type: {cls}'
@@ -0,0 +1,40 @@
1
+ from retracesoftware import functional
2
+ from retracesoftware import utils
3
+
4
+ import _thread
5
+
6
+ # _thread.start_new_thread(function, args[, kwargs])
7
+
8
+ # push a per thread executor function, as a context manager
9
+
10
+ counters = _thread._local()
11
+ counters.id = ()
12
+ counters.counter = 0
13
+
14
+ def with_thread_id(thread_id, function, *args, **kwargs):
15
+ counters.id = thread_id
16
+ counters.counter = 0
17
+ return function(*args, **kwargs)
18
+
19
+ thread_id = functional.lazy(getattr, counters, 'id')
20
+
21
+ def start_new_thread(original, wrapper, function, *args):
22
+ return original(wrapper(function), *args)
23
+
24
+ def wrap_thread_function(thread_state, function):
25
+ if thread_state.value == 'internal':
26
+ next_id = counters.id + (counters.counter,)
27
+ counters.counter += 1
28
+ return functional.partial(with_thread_id, next_id, thread_state.wrap('internal', function))
29
+ else:
30
+ return function
31
+
32
+ def patch_thread_start(thread_state):
33
+
34
+ wrapper = functional.partial(wrap_thread_function, thread_state)
35
+
36
+ _thread.start_new = functional.partial(start_new_thread, _thread.start_new, wrapper)
37
+ _thread.start_new_thread = functional.partial(start_new_thread, _thread.start_new_thread, wrapper)
38
+
39
+ import threading
40
+ threading._start_new_thread = _thread.start_new_thread
@@ -0,0 +1,195 @@
1
+ import sys
2
+ import os
3
+
4
+ import retracesoftware.functional as functional
5
+ import retracesoftware.utils as utils
6
+
7
+ class Stub:
8
+ __slots__ = []
9
+
10
+ class ExtendedRef:
11
+ def __init__(self, module, name):
12
+ self.type = type
13
+ self.module = module
14
+
15
+ class StubRef:
16
+ def __init__(self, cls):
17
+ blacklist = ['__class__', '__dict__', '__module__', '__doc__', '__new__']
18
+
19
+ self.methods = []
20
+ self.static_methods = []
21
+ self.class_methods = []
22
+
23
+ for key,value in cls.__dict__.items():
24
+ if key not in blacklist:
25
+ if isinstance(value, classmethod):
26
+ self.class_methods.append(key)
27
+ elif isinstance(value, staticmethod):
28
+ self.class_methods.append(key)
29
+ elif utils.is_method_descriptor(value):
30
+ self.methods.append(key)
31
+
32
+ self.name = cls.__name__
33
+ self.module = cls.__module__
34
+
35
+ # def __init__(self, module, name, methods, members):
36
+ # self.name = name
37
+ # self.module = module
38
+ # self.methods = methods
39
+ # self.members = members
40
+
41
+ def __str__(self):
42
+ return f'StubRef(module = {self.module}, name = {self.name}, methods = {self.methods})'
43
+
44
+ def resolve(module, name):
45
+ try:
46
+ return getattr(sys.modules[module], name)
47
+ except:
48
+ return None
49
+
50
+ class StubMethodDescriptor(functional.repeatedly):
51
+ def __init__(self, name, next_result):
52
+ super().__init__(next_result)
53
+ self.__name__ = name
54
+
55
+ # @utils.striptraceback
56
+ # def __call__(self, *args, **kwargs):
57
+ # super().__call__(*args, **kwargs)
58
+
59
+ #@utils.striptraceback
60
+ def __str__(self):
61
+ return f"stub - {__name__}"
62
+
63
+ class StubMemberDescriptor:
64
+ def __init__(self, name, next_result):
65
+ self.next_result = next_result
66
+ self.__name__ = name
67
+
68
+ #@utils.striptraceback
69
+ def __get__(self, instance, owner):
70
+ if instance is None:
71
+ return self
72
+
73
+ return self.next_result()
74
+
75
+ #@utils.striptraceback
76
+ def __set__(self, instance, value):
77
+ return self.next_result()
78
+
79
+ #@utils.striptraceback
80
+ def __delete__(self, instance):
81
+ return self.next_result()
82
+
83
+ #@utils.striptraceback
84
+ def __str__(self):
85
+ return f"stub member - {__name__}"
86
+
87
+ class StubFactory:
88
+
89
+ __slots__ = ['next_result', 'thread_state', 'cache']
90
+
91
+ def __init__(self, thread_state, next_result):
92
+ self.next_result = next_result
93
+ self.thread_state = thread_state
94
+ self.cache = {}
95
+
96
+ def create_member(self, name):
97
+ def disabled(*args, **kwargs):
98
+ if self.thread_state.value == 'disabled' and name == '__repr__':
99
+ return f"stub member - {name}"
100
+ else:
101
+ print(f'Error trying to call member descriptor: {name} {args} {kwargs}, retrace mode: {self.thread_state.value}')
102
+ utils.sigtrap(None)
103
+ os._exit(1)
104
+
105
+ next_result = self.thread_state.dispatch(disabled, external = self.next_result)
106
+
107
+ return StubMemberDescriptor(name = name, next_result = next_result)
108
+
109
+ def create_method(self, name):
110
+
111
+ def disabled(*args, **kwargs):
112
+ if self.thread_state.value == 'disabled' and name == '__repr__':
113
+ return f"stub - {name}"
114
+ else:
115
+ return None
116
+ # print(f'Error trying to call descriptor: {name} {args} {kwargs}, retrace mode: {self.thread_state.value}')
117
+
118
+ # import traceback
119
+ # traceback.print_stack()
120
+
121
+ # utils.sigtrap(None)
122
+ # os._exit(1)
123
+
124
+ next_result = self.thread_state.dispatch(disabled, external = self.next_result)
125
+
126
+ func = functional.repeatedly(next_result)
127
+ func.__name__ = name
128
+
129
+ return func
130
+
131
+ def create_stubtype(self, spec):
132
+
133
+ assert self.thread_state.value == 'disabled'
134
+
135
+ slots = {
136
+ '__module__': spec.module,
137
+ '__qualname__': spec.name,
138
+ '__name__': spec.name,
139
+ }
140
+
141
+ for method in spec.methods:
142
+ slots[method] = self.create_method(method)
143
+ assert utils.is_method_descriptor(slots[method])
144
+
145
+ def on_disabled(name):
146
+ print(f'Error trying to get/set attribute: {name}, when retrace mode: {self.thread_state.value} was not external')
147
+ utils.sigtrap(None)
148
+ os._exit(1)
149
+
150
+ #@utils.striptraceback
151
+ def getattr(instance, name):
152
+ print('In stub getattr!!!')
153
+ if self.thread_state.value == 'external':
154
+ return self.next_result()
155
+ else:
156
+ print(f'Error trying to get attribute: {name}, when retrace mode: {self.thread_state.value} was not external')
157
+ utils.sigtrap(None)
158
+ os._exit(1)
159
+
160
+ #@utils.striptraceback
161
+ def setattr(instance, name, value):
162
+ if self.thread_state.value == 'external':
163
+ return self.next_result()
164
+ else:
165
+ print(f'Error trying to set attribute: {name}, to: {value} when retrace mode: {self.thread_state.value} was not external')
166
+ utils.sigtrap(None)
167
+ os._exit(1)
168
+
169
+ slots['__getattr__'] = self.thread_state.method_dispatch(on_disabled, external = self.next_result)
170
+ # slots['__getattr__'] = getattr
171
+ slots['__setattr__'] = self.thread_state.method_dispatch(on_disabled, external = self.next_result)
172
+
173
+ resolved = resolve(spec.module, spec.name)
174
+
175
+ # if isinstance(resolved, type):
176
+ # slots['__class__'] = property(functional.repeatedly(resolved))
177
+
178
+ # else:
179
+ # utils.sigtrap(f'{spec.module}.{spec.name}')
180
+
181
+ stubtype = type(spec.name, (Stub, ), slots)
182
+
183
+ for method in spec.methods:
184
+ slots[method].__objclass__ = stubtype
185
+
186
+ stubtype.__retrace_target_type__ = resolved
187
+
188
+ return stubtype
189
+
190
+ def __call__(self, spec):
191
+ if spec not in self.cache:
192
+ self.cache[spec] = self.create_stubtype(spec)
193
+
194
+ stubtype = self.cache[spec]
195
+ return stubtype.__new__(stubtype)
@@ -0,0 +1,106 @@
1
+ import retracesoftware.functional as functional
2
+ import retracesoftware_utils as utils
3
+
4
+ import os
5
+ import _thread
6
+
7
+ # def thread_aware_writer(writer):
8
+ # on_thread_switch = functional.sequence(utils.thread_id(), writer.handle('THREAD_SWITCH'))
9
+ # return utils.threadawareproxy(on_thread_switch = on_thread_switch, target = writer)
10
+
11
+ class ThreadSwitch:
12
+ __slots__ = ['id']
13
+
14
+ def __init__(self, id):
15
+ self.id = id
16
+
17
+ def __repr__(self):
18
+ return f'ThreadSwitch<{self.id}>'
19
+
20
+ def __str__(self):
21
+ return f'ThreadSwitch<{self.id}>'
22
+
23
+ # def set_thread_id(writer, id):
24
+ # utils.sigtrap(id)
25
+ # utils.set_thread_id(writer.handle(ThreadSwitch(id)))
26
+
27
+ def write_thread_switch(writer):
28
+ on_thread_switch = functional.repeatedly(functional.sequence(utils.thread_id, writer))
29
+
30
+ return lambda f: utils.thread_aware_proxy(target = f, on_thread_switch = on_thread_switch, sticky = False)
31
+
32
+ def prefix_with_thread_id(f, thread_id):
33
+ current = None
34
+
35
+ def next():
36
+ nonlocal current, f
37
+ if current is None: current = thread_id()
38
+
39
+ obj = f()
40
+
41
+ while issubclass(type(obj), ThreadSwitch):
42
+ current = obj.id
43
+ obj = f()
44
+
45
+ # print(f'prefix_with_thread_id: {(current, obj)}')
46
+ return (current, obj)
47
+
48
+ return next
49
+
50
+ def per_thread_messages(messages):
51
+ thread_id = utils.thread_id
52
+ # thread_id = lambda: 'FOOOOO!!!'
53
+
54
+ def on_timeout(demux, key):
55
+ print(f'ON TIMEOUT!!!! {key} pending: {demux.pending} {demux.pending_keys}')
56
+ utils.sigtrap(demux)
57
+ os._exit(1)
58
+
59
+ demux = utils.demux(source = prefix_with_thread_id(messages, thread_id),
60
+ key_function = lambda obj: obj[0],
61
+ timeout_seconds = 60,
62
+ on_timeout = on_timeout)
63
+
64
+ # def next():
65
+ # thread,message = demux(thread_id())
66
+ # return message
67
+
68
+ # return next
69
+ return functional.repeatedly(lambda: demux(thread_id())[1])
70
+
71
+
72
+ # _thread.start_new_thread(function, args[, kwargs])
73
+ counters = _thread._local()
74
+ counters.id = ()
75
+ counters.counter = 0
76
+
77
+ def with_thread_id(thread_id, on_exit, function):
78
+ def on_call(*args, **kwargs):
79
+ counters.id = thread_id
80
+ counters.counter = 0
81
+
82
+ def on_result(res):
83
+ on_exit(thread_id)
84
+
85
+ def on_error(*args):
86
+ on_exit(thread_id)
87
+
88
+ return utils.observer(on_call = on_call, on_result = on_result, on_error = on_error, function = function)
89
+
90
+ thread_id = functional.lazy(getattr, counters, 'id')
91
+
92
+ def start_new_thread_wrapper(thread_state, on_exit, start_new_thread):
93
+
94
+ def wrapper(function, *args):
95
+
96
+ next_id = counters.id + (counters.counter,)
97
+ counters.counter += 1
98
+
99
+ wrapped_function = with_thread_id(thread_id = next_id,
100
+ on_exit = on_exit,
101
+ function = thread_state.wrap('internal', function))
102
+
103
+ return start_new_thread(wrapped_function, *args)
104
+
105
+ return thread_state.dispatch(start_new_thread, internal = wrapper)
106
+
@@ -0,0 +1,104 @@
1
+ import retracesoftware.stream as stream
2
+ from retracesoftware.run import install, run_with_retrace, ImmutableTypes, thread_states
3
+ import argparse
4
+ import sys
5
+ from pathlib import Path
6
+ from retracesoftware.proxy.thread import thread_id
7
+ from retracesoftware.proxy.replay import ReplayProxySystem
8
+ import retracesoftware.utils as utils
9
+ import json
10
+ from retracesoftware.stackdifference import on_stack_difference
11
+
12
+ def parse_args():
13
+ parser = argparse.ArgumentParser(
14
+ prog=f"python -m {sys.argv[0]}",
15
+ description="Run a Python module with debugging, logging, etc."
16
+ )
17
+
18
+ parser.add_argument(
19
+ '--verbose',
20
+ action='store_true',
21
+ help='Enable verbose output'
22
+ )
23
+
24
+ parser.add_argument(
25
+ '--timeout', # or '-r'
26
+ type = int, # ensures it's a string (optional, but safe)
27
+ default = 60, # default value if not provided
28
+ help = 'the directory to place the recording files'
29
+ )
30
+
31
+ parser.add_argument(
32
+ '--recording', # or '-r'
33
+ type = str, # ensures it's a string (optional, but safe)
34
+ default = '.', # default value if not provided
35
+ help = 'the directory to place the recording files'
36
+ )
37
+
38
+ return parser.parse_args()
39
+
40
+ def load_json(file):
41
+ with open(file, "r", encoding="utf-8") as f:
42
+ return json.load(f)
43
+
44
+ def main():
45
+ # import debugpy
46
+
47
+ # port = 1977
48
+ # debugpy.listen(("127.0.0.1", port))
49
+ # debugpy.wait_for_client()
50
+
51
+ # self.reader = stream.reader(path,
52
+ # thread = thread_id,
53
+ # timeout_seconds = 60,
54
+ # verbose = verbose,
55
+ # on_stack_difference = thread_state.wrap('disabled', on_stack_difference),
56
+ # magic_markers = magic_markers)
57
+
58
+ # return ReplayProxySystem(thread_state = thread_state,
59
+ # immutable_types = immutable_types,
60
+ # tracing_config = tracing_config,
61
+ # mainscript = mainscript,
62
+ # path = recording_path / 'trace.bin',
63
+ # tracecalls = env_truthy('RETRACE_ALL', False),
64
+ # verbose = verbose,
65
+ # magic_markers = env_truthy('RETRACE_MAGIC_MARKERS', False))
66
+
67
+ args = parse_args()
68
+ path = Path(args.recording)
69
+
70
+ if not path.exists():
71
+ raise Exception(f"Recording path: {path} does not exist")
72
+
73
+ settings = load_json(path / "settings.json")
74
+
75
+ thread_state = utils.ThreadState(*thread_states)
76
+
77
+ with stream.reader(path = path / 'trace.bin',
78
+ thread = thread_id,
79
+ timeout_seconds = args.timeout,
80
+ verbose = args.verbose,
81
+ on_stack_difference = thread_state.wrap('disabled', on_stack_difference),
82
+ magic_markers = settings['magic_markers']) as reader:
83
+
84
+ tracing_config = {}
85
+
86
+ system = ReplayProxySystem(
87
+ reader = reader,
88
+ thread_state = thread_state,
89
+ immutable_types = ImmutableTypes(),
90
+ tracing_config = tracing_config,
91
+ tracecalls = settings['trace_inputs'])
92
+
93
+ install(system)
94
+
95
+ run_with_retrace(system, settings['argv'])
96
+
97
+ # install(system)
98
+
99
+ # run_with_retrace(system, args.rest[1:])
100
+
101
+ # runpy.run_module('foo', run_name="__main__", alter_sys=False)
102
+
103
+ if __name__ == "__main__":
104
+ main()