pusher-debug 1.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.
- pusher_debug/__init__.py +61 -0
- pusher_debug/monkeypatching/__init__.py +32 -0
- pusher_debug/monkeypatching/monkeypatching.py +418 -0
- pusher_debug/py.typed +0 -0
- pusher_debug/tracing/__init__.py +30 -0
- pusher_debug/tracing/tracing.py +244 -0
- pusher_debug/util/__init__.py +23 -0
- pusher_debug/util/util.py +255 -0
- pusher_debug-1.0.0.dist-info/METADATA +69 -0
- pusher_debug-1.0.0.dist-info/RECORD +12 -0
- pusher_debug-1.0.0.dist-info/WHEEL +4 -0
- pusher_debug-1.0.0.dist-info/licenses/LICENSE.md +675 -0
pusher_debug/__init__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
author = "Douglas Sojourner <doug@sojournings.org>"
|
|
3
|
+
|
|
4
|
+
copyright = f"""
|
|
5
|
+
debug-tools, a few tools to make debugging easier
|
|
6
|
+
Copyright (C) 2026 {author}
|
|
7
|
+
|
|
8
|
+
This program is free software: you can redistribute it and/or modify
|
|
9
|
+
it under the terms of the GNU General Public License as published by
|
|
10
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
(at your option) any later version.
|
|
12
|
+
|
|
13
|
+
This program is distributed in the hope that it will be useful,
|
|
14
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
GNU General Public License for more details.
|
|
17
|
+
|
|
18
|
+
You should have received a copy of the GNU General Public License
|
|
19
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .monkeypatching import *
|
|
23
|
+
from .tracing import *
|
|
24
|
+
from project_version_finder import get_version
|
|
25
|
+
from packaging.version import Version
|
|
26
|
+
|
|
27
|
+
_full_version_ = Version(get_version('pusher_debug'))
|
|
28
|
+
|
|
29
|
+
__version__ = str(_full_version_)
|
|
30
|
+
|
|
31
|
+
__doc__ = """
|
|
32
|
+
Functions to facillitate debugging:
|
|
33
|
+
|
|
34
|
+
Monkeypatching functions (add_to_class and add_new_class are decorators)
|
|
35
|
+
add_to_class, backup_member, restore_member, backup_new_member, restore_new_member
|
|
36
|
+
monkeypatch member functions and move between original and new versions
|
|
37
|
+
|
|
38
|
+
add_new_class, backup_class, restore_class, backup_new_class, restore_new_class
|
|
39
|
+
do the same for entire classes
|
|
40
|
+
|
|
41
|
+
Tracing functions
|
|
42
|
+
ObjWatch (from objwatch library) and DetailWrapper permit detailled monitoring
|
|
43
|
+
of calls/returns from a class or set of classes
|
|
44
|
+
|
|
45
|
+
fn_name, fn_entry, and fn_exit
|
|
46
|
+
make easy documentation of function enter/exit with more focus that ObjWatch
|
|
47
|
+
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
# Class member function manipulation
|
|
52
|
+
'add_to_class', 'backup_member', 'restore_member',
|
|
53
|
+
'restore_new_member',
|
|
54
|
+
# Class manipulation
|
|
55
|
+
'add_new_class', 'backup_class', 'restore_class',
|
|
56
|
+
'restore_new_class',
|
|
57
|
+
# ObjWatch functions and classes:
|
|
58
|
+
'ObjWatch', 'DetailWrapper', 'labelest',
|
|
59
|
+
# Debug logging
|
|
60
|
+
'fn_name', 'fn_entry', 'fn_exit',
|
|
61
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
author = "Douglas Sojourner <doug@sojournings.org>"
|
|
3
|
+
|
|
4
|
+
copyright = f"""
|
|
5
|
+
debug-tools, a few tools to make debugging easier
|
|
6
|
+
Copyright (C) 2026 {author}
|
|
7
|
+
|
|
8
|
+
This program is free software: you can redistribute it and/or modify
|
|
9
|
+
it under the terms of the GNU General Public License as published by
|
|
10
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
(at your option) any later version.
|
|
12
|
+
|
|
13
|
+
This program is distributed in the hope that it will be useful,
|
|
14
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
GNU General Public License for more details.
|
|
17
|
+
|
|
18
|
+
You should have received a copy of the GNU General Public License
|
|
19
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .monkeypatching import add_to_class, backup_member, restore_member, restore_new_member, add_new_class, backup_class, restore_class, restore_new_class, backup_new_member, backup_new_member
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
# Class member function manipulation
|
|
27
|
+
'add_to_class', 'backup_member', 'restore_member',
|
|
28
|
+
'restore_new_member',
|
|
29
|
+
# Class manipulation
|
|
30
|
+
'add_new_class', 'backup_class', 'restore_class',
|
|
31
|
+
'restore_new_class',
|
|
32
|
+
]
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from inspect import currentframe, signature, isclass, isfunction, unwrap, getmodule
|
|
3
|
+
import regex as re
|
|
4
|
+
from typing import Callable, overload, Any, Literal, Final
|
|
5
|
+
from types import FrameType, NoneType
|
|
6
|
+
|
|
7
|
+
from ..util import qualname_context, fix_locals_qualname, find_in_dicts
|
|
8
|
+
|
|
9
|
+
def add_to_class(cls:type) -> Any :
|
|
10
|
+
"""
|
|
11
|
+
Decorator w/ 1 argument:
|
|
12
|
+
Takes a class (cls), and modifies the following function to
|
|
13
|
+
(1) have an appropriate __qualname__ to be in class cls, and
|
|
14
|
+
(2) add it as a member to cls
|
|
15
|
+
|
|
16
|
+
Works with @classmethod, @staticmethod and @property (and if you
|
|
17
|
+
add a property p, works with @p to add other access functions)
|
|
18
|
+
@add_to_class(MyClass)
|
|
19
|
+
@classmethod
|
|
20
|
+
def MyFn(cls, arg1, arg2):
|
|
21
|
+
# do stuff
|
|
22
|
+
return result
|
|
23
|
+
|
|
24
|
+
will add a new class method to MyClass:
|
|
25
|
+
MyClass.MyFn() which takes two (other) arguments.
|
|
26
|
+
"""
|
|
27
|
+
assert(isclass(cls))
|
|
28
|
+
@overload
|
|
29
|
+
def decorator(_fn_: Callable[...,Any|None]) -> Callable[...,Any|None]: ...
|
|
30
|
+
@overload
|
|
31
|
+
def decorator(_fn_: property) -> property: ...
|
|
32
|
+
def decorator(_fn_: Callable[...,Any|None]|property) -> Callable[...,Any|None]|property:
|
|
33
|
+
"""
|
|
34
|
+
internal decorator function to actually decorate _fn_
|
|
35
|
+
"""
|
|
36
|
+
#
|
|
37
|
+
# Can probaby add properties since we now use setattr
|
|
38
|
+
#
|
|
39
|
+
# sanity checks
|
|
40
|
+
cleanup = True
|
|
41
|
+
frame = currentframe()
|
|
42
|
+
assert(isinstance(frame, FrameType) and isinstance(frame.f_back, FrameType))
|
|
43
|
+
local = frame.f_back.f_locals
|
|
44
|
+
cls_qualname = fix_locals_qualname(cls.__qualname__)
|
|
45
|
+
if isinstance(_fn_, property) :
|
|
46
|
+
cleanup = False
|
|
47
|
+
check_fns = [_fn_.fget, _fn_.fset, _fn_.fdel]
|
|
48
|
+
is_property = True
|
|
49
|
+
for f in check_fns :
|
|
50
|
+
if f :
|
|
51
|
+
fn_name = fix_locals_qualname(f.__name__)
|
|
52
|
+
break
|
|
53
|
+
pass
|
|
54
|
+
for f in check_fns :
|
|
55
|
+
if f :
|
|
56
|
+
f.__qualname__ = f'{cls_qualname}.{fn_name}'
|
|
57
|
+
f.__name__ = f'{fn_name}'
|
|
58
|
+
pass
|
|
59
|
+
pass
|
|
60
|
+
if fn_name in cls.__dict__ : # are we changing or replacing or new
|
|
61
|
+
c_prop = cls.__dict__[fn_name]
|
|
62
|
+
same = True
|
|
63
|
+
for f_old, f in zip([c_prop.fget, c_prop.fset, c_prop.fdel], check_fns) :
|
|
64
|
+
if f_old and f_old != f :
|
|
65
|
+
same = False
|
|
66
|
+
break
|
|
67
|
+
pass
|
|
68
|
+
if same :
|
|
69
|
+
save = False # adding an access function
|
|
70
|
+
else:
|
|
71
|
+
save = True # different property in local
|
|
72
|
+
pass
|
|
73
|
+
else :
|
|
74
|
+
save = False # nothing to save
|
|
75
|
+
pass
|
|
76
|
+
else :
|
|
77
|
+
check_fns = [_fn_]
|
|
78
|
+
fn_name = fix_locals_qualname(_fn_.__name__)
|
|
79
|
+
is_property = False
|
|
80
|
+
pass
|
|
81
|
+
if isinstance(_fn_, classmethod) :
|
|
82
|
+
for fn in check_fns : # type: ignore[unreachable]
|
|
83
|
+
if fn :
|
|
84
|
+
_fn_unwrap_ = unwrap(fn)
|
|
85
|
+
assert(list(signature(_fn_unwrap_).parameters.keys())[0] == 'cls')
|
|
86
|
+
assert(isfunction(_fn_unwrap_))
|
|
87
|
+
pass
|
|
88
|
+
pass
|
|
89
|
+
pass
|
|
90
|
+
elif isinstance(_fn_, staticmethod):
|
|
91
|
+
for fn in check_fns :
|
|
92
|
+
if fn :
|
|
93
|
+
_fn_unwrap_ = unwrap(fn)
|
|
94
|
+
assert(isfunction(_fn_unwrap_))
|
|
95
|
+
cleanup = False
|
|
96
|
+
pass
|
|
97
|
+
pass
|
|
98
|
+
pass
|
|
99
|
+
else :
|
|
100
|
+
for fn in check_fns :
|
|
101
|
+
if fn :
|
|
102
|
+
assert(list(signature(fn).parameters.keys())[0] == 'self')
|
|
103
|
+
pass
|
|
104
|
+
pass
|
|
105
|
+
pass
|
|
106
|
+
for fn in check_fns :
|
|
107
|
+
if fn :
|
|
108
|
+
fn.__qualname__ = f'{cls_qualname}.{fn_name}'
|
|
109
|
+
pass
|
|
110
|
+
pass
|
|
111
|
+
orig_name = "orig_"+fn_name
|
|
112
|
+
new_name = "new_"+fn_name
|
|
113
|
+
base_name = fn_name
|
|
114
|
+
if fn_name[0:4] == 'new_' :
|
|
115
|
+
base_name = fn_name[4:]
|
|
116
|
+
orig_name = 'orig_' + base_name
|
|
117
|
+
new_name = fn_name
|
|
118
|
+
pass
|
|
119
|
+
if (base_name in cls.__dict__
|
|
120
|
+
and (orig_name not in cls.__dict__
|
|
121
|
+
or (is_property and save))):
|
|
122
|
+
setattr(cls, orig_name, cls.__dict__[base_name])
|
|
123
|
+
pass
|
|
124
|
+
setattr(cls, new_name, _fn_)
|
|
125
|
+
setattr(cls, base_name, _fn_)
|
|
126
|
+
if _fn_.__doc__ :
|
|
127
|
+
if not re.search('monkeypatched',_fn_.__doc__) :
|
|
128
|
+
_fn_.__doc__ += f'\nmonkeypatched into {cls.__name__}'
|
|
129
|
+
pass
|
|
130
|
+
elif (orig_name in cls.__dict__
|
|
131
|
+
and cls.__dict__[orig_name].__doc__) :
|
|
132
|
+
_fn_.__doc__ = cls.__dict__[orig_name].__doc__ + '\nmonkeypatched'
|
|
133
|
+
else :
|
|
134
|
+
_fn_.__doc__ = f'monkeypatched into {cls.__name__}'
|
|
135
|
+
pass
|
|
136
|
+
if orig_name in cls.__dict__ :
|
|
137
|
+
# No longer the same as fn, so fix names
|
|
138
|
+
if is_property:
|
|
139
|
+
cls.__dict__[orig_name].__set_name__(cls,orig_name)
|
|
140
|
+
pass
|
|
141
|
+
else:
|
|
142
|
+
cls.__dict__[orig_name].__qualname__ = f"{cls_qualname}.{orig_name}"
|
|
143
|
+
cls.__dict__[orig_name].__name__ = orig_name
|
|
144
|
+
pass
|
|
145
|
+
pass
|
|
146
|
+
if is_property :
|
|
147
|
+
# for property, add property w/ expected name to locals
|
|
148
|
+
local[fn_name] = _fn_
|
|
149
|
+
pass
|
|
150
|
+
return _fn_
|
|
151
|
+
|
|
152
|
+
return decorator
|
|
153
|
+
|
|
154
|
+
# @overload
|
|
155
|
+
# def mv_cls(cls:type, *, saving:bool=False, saving_new:bool=False, restoring:bool=False, restoring_new:bool=False, new_class:Literal[True | False]) -> Callable: ...
|
|
156
|
+
# @overload
|
|
157
|
+
# def mv_cls(cls:type, *, saving:bool=False, saving_new:bool=False, restoring:bool=False, restoring_new:bool=False, new_class:None) -> None: ...
|
|
158
|
+
# @overload
|
|
159
|
+
# def mv_cls(cls:type, *, saving:bool=False, saving_new:bool=False, restoring:bool=False, restoring_new:bool=False, new_class:type) -> type: ...
|
|
160
|
+
def mv_cls(cls:type, *, saving:bool=False, saving_new:bool=False, restoring:bool=False, restoring_new:bool=False, new_class:type|None|bool = None) -> Any:
|
|
161
|
+
"""
|
|
162
|
+
Function which does the work of class monkey patching
|
|
163
|
+
We have (at most) two versions of classes: new and orig
|
|
164
|
+
The base function will be one of these, but we adjust the cls.__name__
|
|
165
|
+
as appropriate
|
|
166
|
+
"""
|
|
167
|
+
if (int(saving) + int(saving_new) + int(restoring) + int(restoring_new) + bool(new_class)) != 1 :
|
|
168
|
+
raise ValueError("You must set exactly 1 of saving, saving_new, restoring, "
|
|
169
|
+
"restoring_new, new_class to True (or a class in the last case)")
|
|
170
|
+
if (not isinstance(new_class, NoneType)
|
|
171
|
+
and not isinstance(new_class, bool)
|
|
172
|
+
and not isclass(new_class)) :
|
|
173
|
+
raise ValueError("new_class needs to be a class")
|
|
174
|
+
|
|
175
|
+
assert(isclass(cls))
|
|
176
|
+
cls_name = cls.__name__
|
|
177
|
+
if cls_name[0:4] == 'new_' :
|
|
178
|
+
base_name = cls_name[4:]
|
|
179
|
+
new_name = cls_name
|
|
180
|
+
orig_name = 'orig_' + base_name
|
|
181
|
+
elif cls_name[0:5] == 'orig_' :
|
|
182
|
+
base_name = cls_name[5:]
|
|
183
|
+
new_name = 'new_' + base_name
|
|
184
|
+
orig_name = cls_name
|
|
185
|
+
else:
|
|
186
|
+
base_name = cls_name
|
|
187
|
+
new_name = 'new_' + base_name
|
|
188
|
+
orig_name = 'orig_' + base_name
|
|
189
|
+
pass
|
|
190
|
+
module = getmodule(cls)
|
|
191
|
+
qualpart = '.'.join(cls.__qualname__.split('.')[:-1])
|
|
192
|
+
d = module.__dict__
|
|
193
|
+
if saving and orig_name not in d :
|
|
194
|
+
d[orig_name] = cls
|
|
195
|
+
if new_name in d :
|
|
196
|
+
d[new_name].__name__ = new_name
|
|
197
|
+
d[new_name].__qualname__ = qualpart + '.' + new_name
|
|
198
|
+
pass
|
|
199
|
+
return None
|
|
200
|
+
elif saving_new and new_name not in d :
|
|
201
|
+
d[new_name] = cls
|
|
202
|
+
if orig_name in d :
|
|
203
|
+
d[orig_name].__name__ = orig_name
|
|
204
|
+
d[orig_name].__qualname__ = qualpart + '.' + orig_name
|
|
205
|
+
pass
|
|
206
|
+
return None
|
|
207
|
+
elif restoring and orig_name in d :
|
|
208
|
+
d[base_name] = d[orig_name]
|
|
209
|
+
d[base_name].__name__ = base_name
|
|
210
|
+
d[base_name].__qualname__ = qualpart + '.' + base_name
|
|
211
|
+
if new_name in d :
|
|
212
|
+
d[new_name].__name__ = new_name
|
|
213
|
+
d[new_name].__qualname__ = qualpart + '.' + new_name
|
|
214
|
+
pass
|
|
215
|
+
return None
|
|
216
|
+
elif restoring and new_name in d :
|
|
217
|
+
d[base_name] = d[new_name]
|
|
218
|
+
d[base_name].__name__ = base_name
|
|
219
|
+
d[base_name].__qualname__ = qualpart + '.' + base_name
|
|
220
|
+
if orig_name in d :
|
|
221
|
+
d[orig_name].__name__ = orig_name
|
|
222
|
+
d[orig_name].__qualname__ = qualpart + '.' + orig_name
|
|
223
|
+
pass
|
|
224
|
+
return None
|
|
225
|
+
elif new_class :
|
|
226
|
+
def decorator(new_cls:type) -> type :
|
|
227
|
+
if new_cls.__doc__ :
|
|
228
|
+
new_cls.__doc__ += f'\nmonkeypatched into {module}'
|
|
229
|
+
elif base_name in d and d[base_name].__doc__:
|
|
230
|
+
new_cls.__doc__ = d[base_name].__doc__ + f'\nmonkeypatched into {module}'
|
|
231
|
+
elif orig_name in d and d[orig_name].__doc__:
|
|
232
|
+
new_cls.__doc__ = d[orig_name].__doc__ + f'\nmonkeypatched into {module}'
|
|
233
|
+
else:
|
|
234
|
+
new_cls.__doc__ = f'monkeypatched into {module}'
|
|
235
|
+
pass
|
|
236
|
+
if base_name in d :
|
|
237
|
+
mv_cls(d[base_name], saving=True)
|
|
238
|
+
pass
|
|
239
|
+
#b_name = name
|
|
240
|
+
d[orig_name] = d[base_name]
|
|
241
|
+
d[orig_name].__name__ = orig_name
|
|
242
|
+
d[orig_name].__qualname__ = qualpart + '.' + orig_name
|
|
243
|
+
d[new_name] = new_cls
|
|
244
|
+
d[new_name].__name__ = base_name
|
|
245
|
+
d[new_name].__qualname__ = qualpart + '.' + base_name
|
|
246
|
+
d[base_name] = d[new_name]
|
|
247
|
+
return new_cls
|
|
248
|
+
if isinstance(new_class, bool) and new_class :
|
|
249
|
+
return decorator
|
|
250
|
+
else :
|
|
251
|
+
return decorator(new_class)
|
|
252
|
+
else :
|
|
253
|
+
pass # exactly 1 previous case known to be true
|
|
254
|
+
pass # This branch has returned by now
|
|
255
|
+
|
|
256
|
+
def backup_class(cls:type) -> None :
|
|
257
|
+
"""
|
|
258
|
+
cls should be class
|
|
259
|
+
this will make orig_class = class
|
|
260
|
+
(checks first if orig_class already exists)
|
|
261
|
+
"""
|
|
262
|
+
mv_cls(cls, saving=True)
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
def backup_new_class(cls:type) -> None :
|
|
266
|
+
"""
|
|
267
|
+
cls should be class
|
|
268
|
+
this will make new_class = class
|
|
269
|
+
(checks first if new_class already exists)
|
|
270
|
+
"""
|
|
271
|
+
mv_cls(cls, saving_new=True)
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
def restore_class(cls:type) -> None :
|
|
275
|
+
"""
|
|
276
|
+
cls should be class
|
|
277
|
+
Checks first if orig_class exists, and if so
|
|
278
|
+
this will make class = orig_class
|
|
279
|
+
"""
|
|
280
|
+
mv_cls(cls, restoring=True)
|
|
281
|
+
return
|
|
282
|
+
|
|
283
|
+
def restore_new_class(cls:type) -> None :
|
|
284
|
+
"""
|
|
285
|
+
cls should be class
|
|
286
|
+
Checks first if new_class exists, and if so
|
|
287
|
+
this will make class = new_class
|
|
288
|
+
"""
|
|
289
|
+
mv_cls(cls, restoring_new=True)
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
def add_new_class(cls:type, new_cls:type|None=None) -> type|Callable :
|
|
293
|
+
"""
|
|
294
|
+
as a function:
|
|
295
|
+
cls should be class, as should new_cls
|
|
296
|
+
this will make new_class = new_cls (backing up class
|
|
297
|
+
first as orig_class if it already exists), and then
|
|
298
|
+
copy new_class to class.
|
|
299
|
+
|
|
300
|
+
With only one argument, it can be a decorator for
|
|
301
|
+
a class delcaration
|
|
302
|
+
"""
|
|
303
|
+
if not new_cls :
|
|
304
|
+
return mv_cls(cls, new_class=True)
|
|
305
|
+
else :
|
|
306
|
+
return mv_cls(cls, new_class=new_cls)
|
|
307
|
+
|
|
308
|
+
def fix_names_mv_fn(fn:Callable|property, *, saving:bool=False, saving_new:bool=False, restoring:bool=False, restoring_new:bool=False) -> None:
|
|
309
|
+
"""
|
|
310
|
+
Function which does the work of the member function monkeypatching
|
|
311
|
+
We have (at most) two versions of functions: new and orig
|
|
312
|
+
The base function will be one of these, but we adjust the fn.__name__
|
|
313
|
+
to be correct. E.g.
|
|
314
|
+
if we have original
|
|
315
|
+
C.__init__ == C.orig___init__
|
|
316
|
+
and C.__init__.__name__ == '__init__'
|
|
317
|
+
and C.new___init__.__name__ == 'new___init__'
|
|
318
|
+
OR
|
|
319
|
+
C.__init__ == C.new___init__
|
|
320
|
+
and C.__init__.__name__ == '__init__'
|
|
321
|
+
and C.orig___init__.__name__ == 'orig___init__'
|
|
322
|
+
"""
|
|
323
|
+
if (int(saving) + int(saving_new) + int(restoring) + int(restoring_new)) != 1 :
|
|
324
|
+
raise ValueError("You must set exactly 1 of saving, saving_new, restoring, "
|
|
325
|
+
"restoring_new to True")
|
|
326
|
+
if isinstance(fn, property) :
|
|
327
|
+
for f in [fn.fget, fn.fset, fn.fdel]:
|
|
328
|
+
if f :
|
|
329
|
+
base_name = f.__name__
|
|
330
|
+
fn_qname = f.__qualname__
|
|
331
|
+
break
|
|
332
|
+
pass
|
|
333
|
+
pass
|
|
334
|
+
else :
|
|
335
|
+
base_name = fn.__name__
|
|
336
|
+
fn_qname = fn.__qualname__
|
|
337
|
+
pass
|
|
338
|
+
orig_name = 'orig_' + base_name
|
|
339
|
+
new_name = 'new_' + base_name
|
|
340
|
+
split_name = fn_qname.split('.')
|
|
341
|
+
cls_qname = '.'.join(split_name[:-1])
|
|
342
|
+
# find out where cls may be defined
|
|
343
|
+
_, [g, l], [lg, ll], d1, d2, frm = qualname_context(fn_qname)
|
|
344
|
+
# prefer local, just up stack frame, and lastly global
|
|
345
|
+
cls = find_in_dicts(split_name[-2], [ll, l, d2, d1, lg, g])
|
|
346
|
+
# cls.__qualname__ differs from cls_qname in that the first
|
|
347
|
+
# may have blah.blah.<locals>. prepended
|
|
348
|
+
if saving : # base -> orig
|
|
349
|
+
if not orig_name in cls.__dict__ :
|
|
350
|
+
setattr(cls, orig_name, fn)
|
|
351
|
+
# don't change names in fn, as it is still base fn
|
|
352
|
+
else:
|
|
353
|
+
pass # nothing to do
|
|
354
|
+
elif saving_new : # base -> orig
|
|
355
|
+
if not new_name in cls.__dict__ :
|
|
356
|
+
setattr(cls, new_name, fn)
|
|
357
|
+
# don't change names in fn, as it is still base fn
|
|
358
|
+
else:
|
|
359
|
+
pass # nothing to do
|
|
360
|
+
elif restoring and orig_name in cls.__dict__ : # orig -> base
|
|
361
|
+
if (new_name in cls.__dict__
|
|
362
|
+
and cls.__dict__[base_name] == cls.__dict__[new_name]) :
|
|
363
|
+
# Do change names of new, since we replace
|
|
364
|
+
new_fn = cls.__dict__[new_name]
|
|
365
|
+
new_fn.__name__ = new_name
|
|
366
|
+
new_fn.__qualname__ = f"{cls_qname}.{new_name}"
|
|
367
|
+
pass
|
|
368
|
+
setattr(cls, base_name, cls.__dict__[orig_name])
|
|
369
|
+
base_fn = cls.__dict__[base_name]
|
|
370
|
+
base_fn.__name__ = base_name
|
|
371
|
+
base_fn.__qualname__ = f"{cls_qname}.{base_name}"
|
|
372
|
+
elif restoring_new and new_name in cls.__dict__ : # new -> base
|
|
373
|
+
if (orig_name in cls.__dict__
|
|
374
|
+
and cls.__dict__[base_name] == cls.__dict__[orig_name]) :
|
|
375
|
+
# Do change names of orig, since we replace
|
|
376
|
+
orig_fn = cls.__dict__[orig_name]
|
|
377
|
+
orig_fn.__name__ = orig_name
|
|
378
|
+
orig_fn.__qualname__ = f"{cls_qname}.{orig_name}"
|
|
379
|
+
pass
|
|
380
|
+
setattr(cls, base_name, cls.__dict__[new_name])
|
|
381
|
+
base_fn = cls.__dict__[base_name]
|
|
382
|
+
base_fn.__name__ = base_name
|
|
383
|
+
base_fn.__qualname__ = f"{cls_qname}.{base_name}"
|
|
384
|
+
else:
|
|
385
|
+
pass # all cases enumerated above or we raised ValueError
|
|
386
|
+
return
|
|
387
|
+
|
|
388
|
+
def backup_member(member:Any) -> None :
|
|
389
|
+
"""
|
|
390
|
+
member should be Class.Function
|
|
391
|
+
this will make Class.orig_Function = Class.Function
|
|
392
|
+
(checks first if orig_Function already exists)
|
|
393
|
+
"""
|
|
394
|
+
return fix_names_mv_fn(member, saving=True)
|
|
395
|
+
|
|
396
|
+
def backup_new_member(member:Any) -> None :
|
|
397
|
+
"""
|
|
398
|
+
member should be Class.Function
|
|
399
|
+
this will make Class.new_Function = Class.Function
|
|
400
|
+
(checks first if new_Function already exists)
|
|
401
|
+
"""
|
|
402
|
+
return fix_names_mv_fn(member, saving_new=True)
|
|
403
|
+
|
|
404
|
+
def restore_member(member:Any) -> None :
|
|
405
|
+
"""
|
|
406
|
+
member should be Class.Function
|
|
407
|
+
Checks first if orig_Function exists, and if so
|
|
408
|
+
this will make Class.Function = Class.orig_Function
|
|
409
|
+
"""
|
|
410
|
+
return fix_names_mv_fn(member, restoring=True)
|
|
411
|
+
|
|
412
|
+
def restore_new_member(member:Any) -> None :
|
|
413
|
+
"""
|
|
414
|
+
member should be Class.Function
|
|
415
|
+
Checks first if new_Function exists, and if so
|
|
416
|
+
this will make Class.Function = Class.new_Function
|
|
417
|
+
"""
|
|
418
|
+
return fix_names_mv_fn(member, restoring_new=True)
|
pusher_debug/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
author = "Douglas Sojourner <doug@sojournings.org>"
|
|
3
|
+
|
|
4
|
+
copyright = f"""
|
|
5
|
+
debug-tools, a few tools to make debugging easier
|
|
6
|
+
Copyright (C) 2026 {author}
|
|
7
|
+
|
|
8
|
+
This program is free software: you can redistribute it and/or modify
|
|
9
|
+
it under the terms of the GNU General Public License as published by
|
|
10
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
(at your option) any later version.
|
|
12
|
+
|
|
13
|
+
This program is distributed in the hope that it will be useful,
|
|
14
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
GNU General Public License for more details.
|
|
17
|
+
|
|
18
|
+
You should have received a copy of the GNU General Public License
|
|
19
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from objwatch import ObjWatch
|
|
23
|
+
from .tracing import DetailWrapper, labelest, fn_name, fn_entry, fn_exit
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
# ObjWatch functions and classes:
|
|
27
|
+
'ObjWatch', 'DetailWrapper', 'labelest',
|
|
28
|
+
# Debug logging
|
|
29
|
+
'fn_name', 'fn_entry', 'fn_exit',
|
|
30
|
+
]
|