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.
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env python
2
+ from pprint import pformat as pretty
3
+ from inspect import currentframe, signature, formatargvalues, getargvalues, Signature
4
+ from objwatch.wrappers.base_wrapper import BaseWrapper
5
+ from io import TextIOBase
6
+ from typing import Any
7
+ from types import NoneType, FrameType
8
+
9
+ from ..util import fix_locals_qualname
10
+ from ..monkeypatching import add_to_class
11
+
12
+ # Prevent fn_name from infinite recursion (for example if
13
+ # put in __init__, which wants to say what self is (which
14
+ # triggers an __init__ ...). That particular route is blocked
15
+ # in the actual code, but I'm betting I missed another way
16
+ # this problem arises
17
+ _fn_name_recursion_block_:int = 0
18
+ _fn_name_recursion_fn_:str = ""
19
+ def fn_name(frame: FrameType | None = None, caller:bool|str = False) -> str :
20
+ """
21
+ Returns a string listing the function signature and arguements
22
+ for the calling function (or if frame is provided, for the function
23
+ that frame represents). caller, if provided, should be True (to
24
+ provide info on the caller of the calling function) or 'both'
25
+ (to provide info on calling function AND the function that called it)
26
+ """
27
+ global _fn_name_recursion_block_
28
+ global _fn_name_recursion_fn_
29
+ if not frame :
30
+ frame = currentframe()
31
+ assert(isinstance(frame, FrameType))
32
+ frame = frame.f_back # frame of caller of fn_name
33
+ pass
34
+ if not isinstance(frame, FrameType) :
35
+ return ''
36
+ caller_frame = frame.f_back
37
+ assert(isinstance(caller_frame, FrameType))
38
+ if _fn_name_recursion_block_ :
39
+ from_name = caller_frame.f_code.co_qualname
40
+ return f'recursion {_fn_name_recursion_block_} called from {from_name} originally {_fn_name_recursion_fn_}'
41
+ else :
42
+ _fn_name_recursion_block_ += 1
43
+ _fn_name_recursion_fn_ = caller_frame.f_code.co_qualname
44
+ pass
45
+ if caller :
46
+ if type(caller) == str and caller == "both" :
47
+ do_caller = True
48
+ call = "Fn: "
49
+ else:
50
+ do_caller = False
51
+ frame = frame.f_back
52
+ assert(isinstance(frame, FrameType))
53
+ call = "Caller: "
54
+ pass
55
+ else:
56
+ do_caller = False
57
+ call = "Fn: "
58
+ pass
59
+ args, varargs, keywords, locals = getargvalues(frame)
60
+ arg_str = formatargvalues(args, varargs,keywords, locals)
61
+ fn_qname = fix_locals_qualname(frame.f_code.co_qualname)
62
+ result = f"{call}{fn_qname}{arg_str}"
63
+ _fn_name_recursion_block_ -= 1
64
+ _fn_name_recursion_fn_ = ''
65
+ if do_caller :
66
+ result += f"\nCaller: {fn_name(caller_frame)[4:]}" #trim "Fn: "
67
+ return result
68
+
69
+ def labelest(label:str) -> None :
70
+ """
71
+ Function to watch with ObjWatch, which just notes
72
+ what is going on in the log
73
+ """
74
+ return
75
+
76
+ class DetailWrapper(BaseWrapper) :
77
+ """
78
+ ObjWatch 'wrapper' which logs all inputs and output.
79
+
80
+ Can be subclassed as
81
+ class CustWrapper(DetailWrapper, special_classes = [C1, C2, C3])
82
+ pass
83
+
84
+ in which case C1, C2, and C3 have an _id_ property added which is
85
+ read only and unique for each instance of the same class. This _id_
86
+ is used in the wrap_return to note which instance is returning the value
87
+ in question.
88
+ """
89
+ special_classes:list[type] = []
90
+
91
+ def __init_subclass__(cls2, *, special_classes:list[type], **kwargs) :
92
+ cls2.special_classes = special_classes
93
+ for c in special_classes :
94
+ c._i_count_ = 0 # type: ignore[attr-defined]
95
+ @add_to_class(c) # type: ignore[misc]
96
+ @classmethod
97
+ def _inst_count_(cls) -> int :
98
+ cls._i_count_ += 1
99
+ return cls._i_count_
100
+ @add_to_class(c) # type: ignore[misc]
101
+ @property
102
+ def _id_(self) -> str :
103
+ if '_inst_id_' not in self.__dict__ :
104
+ self._inst_id_ = self.__class__._inst_count_()
105
+ pass
106
+ return f'{self._inst_id_:05d}'
107
+ pass
108
+ super().__init_subclass__(**kwargs)
109
+ return
110
+
111
+ def inst_id(self, slf:Any) -> str :
112
+ """
113
+ Find unique instance identifier for class we are monitoring
114
+ """
115
+ DW = self.__class__ # class of wrapper
116
+ if slf == None :
117
+ return ""
118
+ cls = slf.__class__ # class if instance we are logging
119
+ if cls in DW.special_classes :
120
+ return slf._id_ # we've made certain this property exists
121
+ return ""
122
+
123
+ def wrap_call(self, func_name:str, frame: FrameType) -> str:
124
+ if 'saved_frame' not in self.__dict__ :
125
+ self.saved_frame:list[FrameType] = []
126
+ pass
127
+ self.saved_frame.append(frame)
128
+ g = frame.f_globals
129
+ l = frame.f_locals
130
+ code = frame.f_code
131
+ qname = code.co_qualname # equals func_name I think ...
132
+ simple_report = False
133
+ if frame.f_back :
134
+ caller = f" called from {frame.f_back.f_code.co_qualname}"
135
+ else :
136
+ caller = ""
137
+ pass
138
+ if 'self' in l:
139
+ slf = l['self']
140
+ fn = getattr(slf, code.co_name)
141
+ elif code.co_qualname in g:
142
+ slf = None
143
+ fn = g[code.co_qualname]
144
+ else:
145
+ slf = None
146
+ simple_report = True
147
+ pass
148
+ if simple_report :
149
+ return f"Function {func_name}{caller}: Locals: {str(l)}"
150
+ sig = signature(fn)
151
+ args_str = ""
152
+ return_str = sig.return_annotation
153
+ if return_str == Signature.empty :
154
+ return_str = ":"
155
+ else:
156
+ return_str = f"-> {str(return_str)} :"
157
+ pass
158
+ for p,v in sig.parameters.items() :
159
+ if '=' in str(v) :
160
+ sv = str(v)[0:str(v).index('=')]
161
+ else :
162
+ sv = str(v)
163
+ pass
164
+ try :
165
+ s = f"{sv}={l[p].__repr__()}"
166
+ except :
167
+ s = f"{sv}={l[p]}"
168
+ pass
169
+ if not args_str :
170
+ args_str = s
171
+ else :
172
+ args_str += ", " + s
173
+ pass
174
+ pass
175
+ result = f"{func_name} ({args_str}) {return_str} ({caller})"
176
+ return result
177
+
178
+ def wrap_return(self, func_name:str, result: Any) -> str:
179
+ frame = self.saved_frame.pop()
180
+ l = frame.f_locals
181
+ if 'self' in l:
182
+ slf = l['self']
183
+ cls = slf.__class__.__qualname__ + ':'
184
+ inst_id = self.inst_id(slf)
185
+ if inst_id :
186
+ return_str = f"{cls}{func_name} -> {result.__repr__()} <{inst_id}>"
187
+ else :
188
+ return_str = f"{cls}{func_name} -> {result.__repr__()}"
189
+ pass
190
+ else :
191
+ return_str = f"{func_name} -> {result.__repr__()}"
192
+ pass
193
+ return return_str
194
+
195
+
196
+ def wrap_upd(self, old_value: Any, new_value: Any) -> tuple[str,str]:
197
+ return_strs = (f"{old_value.__repr__()}", f"{new_value.__repr__()}",)
198
+ return return_strs
199
+
200
+
201
+ def fn_entry(frame: FrameType | None = None, callers:int=0, file:TextIOBase|None = None) -> None:
202
+ '''
203
+ Prints entry info, including caller info if key word callers is set to the
204
+ number of callers to report. If key word file is not set, prints to stdout,
205
+ otherwise to the specified file.
206
+ '''
207
+ if not isinstance(frame, FrameType) :
208
+ frame = currentframe()
209
+ if isinstance(frame, FrameType) :
210
+ frame = frame.f_back
211
+ pass
212
+ pass
213
+ print(fn_name(frame), file=file)
214
+ for _ in range(callers) :
215
+ if isinstance(frame, FrameType) :
216
+ frame = frame.f_back
217
+ print(f'Called by{fn_name(frame)[2:]}', file=file)
218
+ pass
219
+ pass
220
+ return
221
+
222
+ def fn_exit(frame: FrameType | None = None, **kwargs) -> None :
223
+ '''
224
+ Prints exit info, including return value if key word result is defined. If key
225
+ word file is not set, prints to stdout, otherwise to the specified file.
226
+ '''
227
+ if not isinstance(frame, FrameType) :
228
+ frame = currentframe()
229
+ if isinstance(frame, FrameType) :
230
+ frame = frame.f_back
231
+ pass
232
+ pass
233
+ if 'file' in kwargs :
234
+ file = kwargs['file']
235
+ else:
236
+ file = None
237
+ pass
238
+ if 'result' in kwargs :
239
+ result = str(kwargs['result'])
240
+ print(f'{fn_name(frame)} -> {result}')
241
+ else:
242
+ print(f'{fn_name(frame)}')
243
+ pass
244
+ return
@@ -0,0 +1,23 @@
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
+ from .util import grab_all_namespaces, qualname_context, fix_locals_qualname, find_in_dicts
22
+
23
+ __all__ = ['grab_all_namespaces', 'qualname_context', 'fix_locals_qualname']
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env python
2
+
3
+ from inspect import currentframe, isclass
4
+ from typing import Any, Callable, overload, Literal
5
+ from types import FrameType, FunctionType, NoneType
6
+
7
+ _local_mark = "<locals>"
8
+ _local_mark2 = f".{_local_mark}."
9
+
10
+ def find_in_dicts(name:str, dicts:list|dict|None) -> Any :
11
+ """
12
+ searches a list of dictionaries for name, or returns None
13
+ (Note that a successfull lookup can also return None)
14
+ """
15
+ if isinstance(dicts, dict) :
16
+ ds = [dicts,]
17
+ elif isinstance(dicts, list):
18
+ ds = []
19
+ for item in dicts :
20
+ if isinstance(item, list):
21
+ for i2 in item :
22
+ assert(type(i2) == dict)
23
+ ds.append(i2)
24
+ pass
25
+ pass
26
+ elif isinstance(item, NoneType) :
27
+ pass
28
+ else :
29
+ if type(item) != dict :
30
+ print(f"Expecting dict, got {type(item)} = {str(item)}")
31
+ continue
32
+ assert(type(item) == dict)
33
+ ds.append(item)
34
+ pass
35
+ pass # end each item
36
+ pass # end flattening dicts if needed
37
+ for d in ds :
38
+ if d and name in d : # skip None's
39
+ return d[name]
40
+ pass
41
+ return None
42
+
43
+ def return_empty_list() -> list :
44
+ """
45
+ function to replace __dir__ when it isn't found
46
+ """
47
+ return []
48
+
49
+ @overload
50
+ def grab_all_namespaces(o: object, split:Literal[True]) -> list[dict[str,Any]]: ...
51
+ @overload
52
+ def grab_all_namespaces(o: object, split:Literal[False]) -> dict[str,Any]: ...
53
+ @overload
54
+ def grab_all_namespaces(o: object) -> dict[str,Any]: ...
55
+
56
+ def grab_all_namespaces(o: object, split=False) -> dict[str,Any] | list[dict[str,Any]]:
57
+ '''
58
+ find Any namespaces associated with o, and collect them
59
+ together. Should even survive cases where __dir__() and/or
60
+ __dict__ don't exist
61
+ '''
62
+ d = getattr(o, '__dict__', None)
63
+ if not d :
64
+ d = dict()
65
+ else:
66
+ d = dict(d) # incase it was something standing in for a dictionary
67
+ pass
68
+ l = d
69
+ g = dict()
70
+ try :
71
+ dirs = getattr(o,'__dir__', return_empty_list)()
72
+ except TypeError:
73
+ if isclass(o) :
74
+ try :
75
+ o = o() # only works if __init__ has no arguments, or defauts for all
76
+ dirs = getattr(o,'__dir__', return_empty_list)()
77
+ except TypeError:
78
+ dirs = []
79
+ pass
80
+ pass
81
+ else:
82
+ dirs = []
83
+ pass
84
+ pass
85
+ if not dirs :
86
+ pass
87
+ if 'globals' in d :
88
+ g.update(d['globals'])
89
+ pass
90
+ if '__globals__' in d :
91
+ g.update(d['__globals__'])
92
+ pass
93
+ if 'globals' in dirs :
94
+ g.update(getattr(o,'globals'))
95
+ pass
96
+ if '__globals__' in dirs :
97
+ g.update(getattr(o,'__globals__'))
98
+ pass
99
+ if 'locals' in dirs :
100
+ l.update(getattr(o,'locals'))
101
+ pass
102
+ if '__locals__' in dirs :
103
+ l.update(getattr(o,'__locals__'))
104
+ pass
105
+ if 'locals' in d :
106
+ l.update(d['locals'])
107
+ pass
108
+ if '__locals__' in d :
109
+ l.update(d['__locals__'])
110
+ pass
111
+ if split :
112
+ return [dict(g), dict(l)]
113
+ else :
114
+ d2 = dict(g)
115
+ d2.update(l)
116
+ return d2
117
+ pass # type: ignore[unreachable]
118
+
119
+ # We want to get global/local address spaces for function
120
+ # Handling these variations on qualname
121
+ #
122
+ # A) N2.N3.fn vs
123
+ # B) N0.N1.<locals>.N2.N3.fn <Figure out N2, then it is reduced to (A)
124
+ #
125
+ #
126
+ # 1) fn : Go back in stack until fn is in global or local
127
+ # 2) N1.fn : Go Back until N1 is in global or local
128
+ # 3) N1.N2.fn same
129
+ # To get actual function go forward using Ni.__dict__, until
130
+ # you finally find a dictionary you can look fn up in.
131
+
132
+ def qualname_context(item:str|Callable) -> tuple[str,list[dict[str,Any]],list[dict[str,Any]],dict[str,Any],dict[str,Any],FrameType|None] :
133
+ """
134
+ Given a string with the qualname, or a function (from which we get the
135
+ qualname) find:
136
+ 1) "qualname after <locals>"
137
+ 2) pair of global and local dictionaries where fn or containing package/class
138
+ are defined
139
+ 3) __dict__ of package/class where fn is defined
140
+ 4) frame where (non "<locals>") part of qualname was first found
141
+ """
142
+ if isinstance(item, str) :
143
+ name = item
144
+ else:
145
+ name = item.__qualname__
146
+ pass
147
+ split_name = name.split('.')
148
+ if _local_mark in split_name :
149
+ local_index = split_name.index(_local_mark)
150
+ else:
151
+ local_index = len(split_name) + 1
152
+ pass
153
+ top_name = split_name[0]
154
+ bottom_name = split_name[-1]
155
+ frame = currentframe()
156
+ found_index = -1
157
+ while (isinstance(frame, FrameType)
158
+ and frame.f_back
159
+ and found_index < 0) : # to continue if we find wrong one first
160
+ while (isinstance(frame, FrameType)
161
+ and frame.f_back # No earlier frames
162
+ and (not frame.f_code.co_name in split_name # code not in qualname
163
+ or (local_index # or is, but before local_index
164
+ and split_name.index(frame.f_code.co_name) > local_index)
165
+ )
166
+ and not split_name[0] in frame.f_locals # root of qualname in globals
167
+ and not split_name[0] in frame.f_globals # or locals
168
+ ):
169
+ frame = frame.f_back # Then go back a level
170
+ pass # end moving back
171
+ top_dicts = [dict(frame.f_locals), dict(frame.f_globals)]
172
+ obj = None
173
+ found_index = -1
174
+ local_dicts = top_dicts # default value for extra dictionaries returned
175
+ d2:dict[str,Any] = {}
176
+ d1:dict[str,Any] = {}
177
+ for i in range(len(split_name)) :
178
+ if split_name[i] == _local_mark :
179
+ local_dicts = grab_all_namespaces(obj, split=True) # return g and l separately
180
+ local_index = i
181
+ continue
182
+ obj = find_in_dicts(split_name[i], top_dicts)
183
+ if obj :
184
+ found_index = i
185
+ if i == len(split_name) - 3 :
186
+ d1 = grab_all_namespaces(obj, split=False)
187
+ elif i == len(split_name) - 2 :
188
+ d2 = grab_all_namespaces(obj, split=False)
189
+ pass
190
+ for j in range(i+1, len(split_name)) :
191
+ if split_name[j] == _local_mark :
192
+ local_dicts = grab_all_namespaces(obj, split=True)
193
+ local_index = j
194
+ continue
195
+ obj = find_in_dicts(split_name[j], grab_all_namespaces(obj, split=True))
196
+ if not obj :
197
+ found_index = -1
198
+ break
199
+ if j == len(split_name) - 3 :
200
+ d1 = grab_all_namespaces(obj, split=False)
201
+ elif j == len(split_name) - 2 :
202
+ d2 = grab_all_namespaces(obj)
203
+ pass
204
+ if found_index >= 0 :
205
+ break
206
+ pass #end if lookup worked
207
+ pass # end of searching in dicts for parts of name
208
+ if found_index < 0 :
209
+ frame = frame.f_back
210
+ pass
211
+ pass # end searching for frame
212
+ if found_index == len(split_name) - 1:
213
+ lname = split_name[-1]
214
+ elif (local_index < len(split_name)
215
+ and local_index == len(split_name) - 2) :
216
+ lname = split_name[-1]
217
+ elif local_index < len(split_name):
218
+ lname = '.'.join(split_name[max(found_index, local_index+1):])
219
+ else:
220
+ lname = '.'.join(split_name[found_index:])
221
+ pass
222
+ if found_index > len(split_name) :
223
+ raise ValueError("What The Hell?")
224
+ elif found_index == len(split_name)-1 :
225
+ if isinstance(frame, FrameType) and frame.f_back :
226
+ f = frame.f_back
227
+ d2 = dict(f.f_globals)
228
+ d2.update(f.f_locals)
229
+ if f.f_back :
230
+ f = f.f_back
231
+ d1 = dict(f.f_globals)
232
+ d1.update(f.f_locals)
233
+ else :
234
+ d1 = {}
235
+ pass
236
+ else:
237
+ d2 = {}
238
+ d1 = {}
239
+ pass
240
+ # Take care of case where we lack "context" right away
241
+ return (lname, top_dicts, [{}, {}], d2, d1, frame)
242
+ else:
243
+ return (lname, top_dicts, local_dicts, d2, d1, frame)
244
+ pass # type: ignore[unreachable]
245
+
246
+ def fix_locals_qualname(name:str) -> str :
247
+ """
248
+ returns the portion of qualname after '<locals>', or all
249
+ of the qualname if '<locals>' does not occur
250
+ """
251
+ if _local_mark in name :
252
+ name = name[name.index(_local_mark2)+len(_local_mark2):]
253
+ pass
254
+ return name
255
+
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: pusher_debug
3
+ Version: 1.0.0
4
+ Summary: tools to assist with debug
5
+ Project-URL: repository, https://codeberg.org/Pusher2531/debug_tools.git
6
+ Project-URL: issues, https://codeberg.org/Pusher2531/debug_tools/issues
7
+ Author-email: Doug Sojourner <doug@sojournings.org>
8
+ License: GNU General Public License v3 (GPLv3)
9
+ License-File: LICENSE.md
10
+ Keywords: class modification,debug,debuging,decorator,monkeypatching,typed
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
14
+ Classifier: Natural Language :: English
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Software Development
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: <4.0,>=3.14
24
+ Requires-Dist: objwatch
25
+ Requires-Dist: packaging
26
+ Requires-Dist: project-version-finder
27
+ Requires-Dist: regex
28
+ Description-Content-Type: text/markdown
29
+
30
+ ### `pusher_debug`
31
+ ## `pusher_debug.tracing`
32
+ Tools to aid in adding print statements to trace program flow:
33
+ * `ObjWatch` from the objwatch library
34
+ * `DetailWrapper`, a "wrapper" to use with Objwatch that provides more details on function calls and returns, and can be subclassed easily to provide distinct id strings for different instances of selected classes.
35
+ * `fn_name`, `fn_entry`, and `fn_exit`, to provide manual logging of details of function you enter (caller, parameters, return value, annotations)
36
+
37
+ ## `pusher_debug.monkeypatching`
38
+ Tools to aid in monkeypatching. Two decorators:
39
+ ```python
40
+ @add_new_class(ExistingClass)
41
+ class MyClass: ...
42
+ ```
43
+
44
+ will (in the module where `ExistingClass` is defined)
45
+ 1. Create `orig_ExistingClass`, as a backup of `ExistingClass`
46
+ 2. Create `new_ExistingClass`, as a backup of `MyClass`
47
+ 3. Make `ExistingClass` be `new_ExistingClass` (fixing `__name__` and `__qualname__` between `new_ExistingClass` and `orig_ExistingClass` as well)
48
+
49
+ Then `restore_class(ExistingClass)` will restore the original, and `restore_new_class(ExistingClass)` will put back the new version again.
50
+
51
+ ```python
52
+ @add_to_class(ExistingClass)
53
+ def fn():...
54
+ ```
55
+
56
+ will (in `ExistingClass`) add a method `fn`. If `fn` already exists, the the original is backed up as `orig_fn`. `new_fn` is created and names are fixed as for `@add_new_class`.
57
+
58
+ `@add_to_class` can be combined with one of `@classmethod`, `@staticmethod`, or `@property` (and if property, then `fn` in the local namespace will be the property, so you can then do
59
+ ```python
60
+ @add_to_class(ExistingClass)
61
+ @fn@setter
62
+ def fn():...
63
+ ```
64
+
65
+ You should know that monkeypatching will wreack havoc with mypy, since it does stuff you shouldn't do. (It's for __debugging__ !)
66
+
67
+ For examples, use help(...), and/or download the source package and look at the tests. Seeing all the # test: ignore[] stuff in the test program gives you an idea of how much mypy hates this.
68
+
69
+ The library can be built with `hatchling build`, tested with `tests/test_tools.py -v`, and should pass `mypy` (including having annotations for objwatch).
@@ -0,0 +1,12 @@
1
+ pusher_debug/__init__.py,sha256=1GCNPeim3YNLiAhQEGp9IeiWUrm-4IUmMTTuwZGa47A,2052
2
+ pusher_debug/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ pusher_debug/monkeypatching/__init__.py,sha256=79YSIMcQEg5OZLvmHhjnA46DgaiFmJPZzP6qCytKFgw,1225
4
+ pusher_debug/monkeypatching/monkeypatching.py,sha256=r7j71ITB8wOqbr7S1lGhxcaO3KMCATyHnEjMzrk7RwI,15754
5
+ pusher_debug/tracing/__init__.py,sha256=Ie5HwdQVrdqi6pmOYxttU5pOoAB0LLpMLb_AQ5ip2Yo,1049
6
+ pusher_debug/tracing/tracing.py,sha256=FhjQi0U70d72sQfsm09YyQbQq66j7Vh4U5MPpXpjlhI,8452
7
+ pusher_debug/util/__init__.py,sha256=mGyRpNPxneDM7b94UaoYMBBIPzZwzuOxWpHogSFi71U,959
8
+ pusher_debug/util/util.py,sha256=cwm6peTxaA2DHwtBVplOr-QPQAcAeWtup8Dx-pHe-Wk,8746
9
+ pusher_debug-1.0.0.dist-info/METADATA,sha256=p-zeagKl0RrXD8n3w3cYCcguGYn-yXfF0EoiNpXof9M,3319
10
+ pusher_debug-1.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
11
+ pusher_debug-1.0.0.dist-info/licenses/LICENSE.md,sha256=zFRw_u1mGSOH8GrpOu0L1P765aX9fB5UpKz06mTxAos,34893
12
+ pusher_debug-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any