wrapt 2.1.0rc1__cp313-cp313t-macosx_11_0_arm64.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.
- wrapt/__init__.py +64 -0
- wrapt/__init__.pyi +388 -0
- wrapt/__wrapt__.py +42 -0
- wrapt/_wrappers.c +4097 -0
- wrapt/_wrappers.cpython-313t-darwin.so +0 -0
- wrapt/arguments.py +59 -0
- wrapt/decorators.py +522 -0
- wrapt/importer.py +332 -0
- wrapt/patches.py +239 -0
- wrapt/proxies.py +351 -0
- wrapt/py.typed +1 -0
- wrapt/weakrefs.py +114 -0
- wrapt/wrappers.py +980 -0
- wrapt-2.1.0rc1.dist-info/METADATA +188 -0
- wrapt-2.1.0rc1.dist-info/RECORD +18 -0
- wrapt-2.1.0rc1.dist-info/WHEEL +6 -0
- wrapt-2.1.0rc1.dist-info/licenses/LICENSE +24 -0
- wrapt-2.1.0rc1.dist-info/top_level.txt +1 -0
wrapt/importer.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"""This module implements a post import hook mechanism styled after what is
|
|
2
|
+
described in PEP-369. Note that it doesn't cope with modules being reloaded.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import importlib.metadata
|
|
7
|
+
import sys
|
|
8
|
+
import threading
|
|
9
|
+
from importlib.util import find_spec
|
|
10
|
+
from typing import Callable, Dict, List
|
|
11
|
+
|
|
12
|
+
from .__wrapt__ import BaseObjectProxy
|
|
13
|
+
|
|
14
|
+
# The dictionary registering any post import hooks to be triggered once
|
|
15
|
+
# the target module has been imported. Once a module has been imported
|
|
16
|
+
# and the hooks fired, the list of hooks recorded against the target
|
|
17
|
+
# module will be truncated but the list left in the dictionary. This
|
|
18
|
+
# acts as a flag to indicate that the module had already been imported.
|
|
19
|
+
|
|
20
|
+
_post_import_hooks: Dict[str, List[Callable]] = {}
|
|
21
|
+
_post_import_hooks_init = False
|
|
22
|
+
_post_import_hooks_lock = threading.RLock()
|
|
23
|
+
|
|
24
|
+
# Register a new post import hook for the target module name. This
|
|
25
|
+
# differs from the PEP-369 implementation in that it also allows the
|
|
26
|
+
# hook function to be specified as a string consisting of the name of
|
|
27
|
+
# the callback in the form 'module:function'. This will result in a
|
|
28
|
+
# proxy callback being registered which will defer loading of the
|
|
29
|
+
# specified module containing the callback function until required.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _create_import_hook_from_string(name):
|
|
33
|
+
def import_hook(module):
|
|
34
|
+
module_name, function = name.split(":")
|
|
35
|
+
attrs = function.split(".")
|
|
36
|
+
__import__(module_name)
|
|
37
|
+
callback = sys.modules[module_name]
|
|
38
|
+
for attr in attrs:
|
|
39
|
+
callback = getattr(callback, attr)
|
|
40
|
+
return callback(module)
|
|
41
|
+
|
|
42
|
+
return import_hook
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def register_post_import_hook(hook, name):
|
|
46
|
+
"""
|
|
47
|
+
Register a post import hook for the target module `name`. The `hook`
|
|
48
|
+
function will be called once the module is imported and will be passed the
|
|
49
|
+
module as argument. If the module is already imported, the `hook` will be
|
|
50
|
+
called immediately. If you also want to defer loading of the module containing
|
|
51
|
+
the `hook` function until required, you can specify the `hook` as a string in
|
|
52
|
+
the form 'module:function'. This will result in a proxy hook function being
|
|
53
|
+
registered which will defer loading of the specified module containing the
|
|
54
|
+
callback function until required.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# Create a deferred import hook if hook is a string name rather than
|
|
58
|
+
# a callable function.
|
|
59
|
+
|
|
60
|
+
if isinstance(hook, str):
|
|
61
|
+
hook = _create_import_hook_from_string(hook)
|
|
62
|
+
|
|
63
|
+
with _post_import_hooks_lock:
|
|
64
|
+
# Automatically install the import hook finder if it has not already
|
|
65
|
+
# been installed.
|
|
66
|
+
|
|
67
|
+
global _post_import_hooks_init
|
|
68
|
+
|
|
69
|
+
if not _post_import_hooks_init:
|
|
70
|
+
_post_import_hooks_init = True
|
|
71
|
+
sys.meta_path.insert(0, ImportHookFinder())
|
|
72
|
+
|
|
73
|
+
# Check if the module is already imported. If not, register the hook
|
|
74
|
+
# to be called after import.
|
|
75
|
+
|
|
76
|
+
module = sys.modules.get(name, None)
|
|
77
|
+
|
|
78
|
+
if module is None:
|
|
79
|
+
_post_import_hooks.setdefault(name, []).append(hook)
|
|
80
|
+
|
|
81
|
+
# If the module is already imported, we fire the hook right away. Note that
|
|
82
|
+
# the hook is called outside of the lock to avoid deadlocks if code run as a
|
|
83
|
+
# consequence of calling the module import hook in turn triggers a separate
|
|
84
|
+
# thread which tries to register an import hook.
|
|
85
|
+
|
|
86
|
+
if module is not None:
|
|
87
|
+
hook(module)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# Register post import hooks defined as package entry points.
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _create_import_hook_from_entrypoint(entrypoint):
|
|
94
|
+
def import_hook(module):
|
|
95
|
+
entrypoint_value = entrypoint.value.split(":")
|
|
96
|
+
module_name = entrypoint_value[0]
|
|
97
|
+
__import__(module_name)
|
|
98
|
+
callback = sys.modules[module_name]
|
|
99
|
+
|
|
100
|
+
if len(entrypoint_value) > 1:
|
|
101
|
+
attrs = entrypoint_value[1].split(".")
|
|
102
|
+
for attr in attrs:
|
|
103
|
+
callback = getattr(callback, attr)
|
|
104
|
+
return callback(module)
|
|
105
|
+
|
|
106
|
+
return import_hook
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def discover_post_import_hooks(group):
|
|
110
|
+
"""
|
|
111
|
+
Discover and register post import hooks defined as package entry points
|
|
112
|
+
in the specified `group`. The group should be a string that matches the
|
|
113
|
+
entry point group name used in the package metadata.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
# Python 3.10+ style with select parameter
|
|
118
|
+
entrypoints = importlib.metadata.entry_points(group=group)
|
|
119
|
+
except TypeError:
|
|
120
|
+
# Python 3.8-3.9 style that returns a dict
|
|
121
|
+
entrypoints = importlib.metadata.entry_points().get(group, ())
|
|
122
|
+
|
|
123
|
+
for entrypoint in entrypoints:
|
|
124
|
+
callback = entrypoint.load() # Use the loaded callback directly
|
|
125
|
+
register_post_import_hook(callback, entrypoint.name)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Indicate that a module has been loaded. Any post import hooks which
|
|
129
|
+
# were registered against the target module will be invoked. If an
|
|
130
|
+
# exception is raised in any of the post import hooks, that will cause
|
|
131
|
+
# the import of the target module to fail.
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def notify_module_loaded(module):
|
|
135
|
+
"""
|
|
136
|
+
Notify that a `module` has been loaded and invoke any post import hooks
|
|
137
|
+
registered against the module. If the module is not registered, this
|
|
138
|
+
function does nothing.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
name = getattr(module, "__name__", None)
|
|
142
|
+
|
|
143
|
+
with _post_import_hooks_lock:
|
|
144
|
+
hooks = _post_import_hooks.pop(name, ())
|
|
145
|
+
|
|
146
|
+
# Note that the hook is called outside of the lock to avoid deadlocks if
|
|
147
|
+
# code run as a consequence of calling the module import hook in turn
|
|
148
|
+
# triggers a separate thread which tries to register an import hook.
|
|
149
|
+
|
|
150
|
+
for hook in hooks:
|
|
151
|
+
hook(module)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# A custom module import finder. This intercepts attempts to import
|
|
155
|
+
# modules and watches out for attempts to import target modules of
|
|
156
|
+
# interest. When a module of interest is imported, then any post import
|
|
157
|
+
# hooks which are registered will be invoked.
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class _ImportHookLoader:
|
|
161
|
+
|
|
162
|
+
def load_module(self, fullname):
|
|
163
|
+
module = sys.modules[fullname]
|
|
164
|
+
notify_module_loaded(module)
|
|
165
|
+
|
|
166
|
+
return module
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class _ImportHookChainedLoader(BaseObjectProxy):
|
|
170
|
+
|
|
171
|
+
def __init__(self, loader):
|
|
172
|
+
super(_ImportHookChainedLoader, self).__init__(loader)
|
|
173
|
+
|
|
174
|
+
if hasattr(loader, "load_module"):
|
|
175
|
+
self.__self_setattr__("load_module", self._self_load_module)
|
|
176
|
+
if hasattr(loader, "create_module"):
|
|
177
|
+
self.__self_setattr__("create_module", self._self_create_module)
|
|
178
|
+
if hasattr(loader, "exec_module"):
|
|
179
|
+
self.__self_setattr__("exec_module", self._self_exec_module)
|
|
180
|
+
|
|
181
|
+
def _self_set_loader(self, module):
|
|
182
|
+
# Set module's loader to self.__wrapped__ unless it's already set to
|
|
183
|
+
# something else. Import machinery will set it to spec.loader if it is
|
|
184
|
+
# None, so handle None as well. The module may not support attribute
|
|
185
|
+
# assignment, in which case we simply skip it. Note that we also deal
|
|
186
|
+
# with __loader__ not existing at all. This is to future proof things
|
|
187
|
+
# due to proposal to remove the attribute as described in the GitHub
|
|
188
|
+
# issue at https://github.com/python/cpython/issues/77458. Also prior
|
|
189
|
+
# to Python 3.3, the __loader__ attribute was only set if a custom
|
|
190
|
+
# module loader was used. It isn't clear whether the attribute still
|
|
191
|
+
# existed in that case or was set to None.
|
|
192
|
+
|
|
193
|
+
class UNDEFINED:
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
if getattr(module, "__loader__", UNDEFINED) in (None, self):
|
|
197
|
+
try:
|
|
198
|
+
module.__loader__ = self.__wrapped__
|
|
199
|
+
except AttributeError:
|
|
200
|
+
pass
|
|
201
|
+
|
|
202
|
+
if (
|
|
203
|
+
getattr(module, "__spec__", None) is not None
|
|
204
|
+
and getattr(module.__spec__, "loader", None) is self
|
|
205
|
+
):
|
|
206
|
+
module.__spec__.loader = self.__wrapped__
|
|
207
|
+
|
|
208
|
+
def _self_load_module(self, fullname):
|
|
209
|
+
module = self.__wrapped__.load_module(fullname)
|
|
210
|
+
self._self_set_loader(module)
|
|
211
|
+
notify_module_loaded(module)
|
|
212
|
+
|
|
213
|
+
return module
|
|
214
|
+
|
|
215
|
+
# Python 3.4 introduced create_module() and exec_module() instead of
|
|
216
|
+
# load_module() alone. Splitting the two steps.
|
|
217
|
+
|
|
218
|
+
def _self_create_module(self, spec):
|
|
219
|
+
return self.__wrapped__.create_module(spec)
|
|
220
|
+
|
|
221
|
+
def _self_exec_module(self, module):
|
|
222
|
+
self._self_set_loader(module)
|
|
223
|
+
self.__wrapped__.exec_module(module)
|
|
224
|
+
notify_module_loaded(module)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class ImportHookFinder:
|
|
228
|
+
|
|
229
|
+
def __init__(self):
|
|
230
|
+
self.in_progress = {}
|
|
231
|
+
|
|
232
|
+
def find_module(self, fullname, path=None):
|
|
233
|
+
# If the module being imported is not one we have registered
|
|
234
|
+
# post import hooks for, we can return immediately. We will
|
|
235
|
+
# take no further part in the importing of this module.
|
|
236
|
+
|
|
237
|
+
with _post_import_hooks_lock:
|
|
238
|
+
if fullname not in _post_import_hooks:
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
# When we are interested in a specific module, we will call back
|
|
242
|
+
# into the import system a second time to defer to the import
|
|
243
|
+
# finder that is supposed to handle the importing of the module.
|
|
244
|
+
# We set an in progress flag for the target module so that on
|
|
245
|
+
# the second time through we don't trigger another call back
|
|
246
|
+
# into the import system and cause a infinite loop.
|
|
247
|
+
|
|
248
|
+
if fullname in self.in_progress:
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
self.in_progress[fullname] = True
|
|
252
|
+
|
|
253
|
+
# Now call back into the import system again.
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
# For Python 3 we need to use find_spec().loader
|
|
257
|
+
# from the importlib.util module. It doesn't actually
|
|
258
|
+
# import the target module and only finds the
|
|
259
|
+
# loader. If a loader is found, we need to return
|
|
260
|
+
# our own loader which will then in turn call the
|
|
261
|
+
# real loader to import the module and invoke the
|
|
262
|
+
# post import hooks.
|
|
263
|
+
|
|
264
|
+
loader = getattr(find_spec(fullname), "loader", None)
|
|
265
|
+
|
|
266
|
+
if loader and not isinstance(loader, _ImportHookChainedLoader):
|
|
267
|
+
return _ImportHookChainedLoader(loader)
|
|
268
|
+
|
|
269
|
+
finally:
|
|
270
|
+
del self.in_progress[fullname]
|
|
271
|
+
|
|
272
|
+
def find_spec(self, fullname, path=None, target=None):
|
|
273
|
+
# Since Python 3.4, you are meant to implement find_spec() method
|
|
274
|
+
# instead of find_module() and since Python 3.10 you get deprecation
|
|
275
|
+
# warnings if you don't define find_spec().
|
|
276
|
+
|
|
277
|
+
# If the module being imported is not one we have registered
|
|
278
|
+
# post import hooks for, we can return immediately. We will
|
|
279
|
+
# take no further part in the importing of this module.
|
|
280
|
+
|
|
281
|
+
with _post_import_hooks_lock:
|
|
282
|
+
if fullname not in _post_import_hooks:
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
# When we are interested in a specific module, we will call back
|
|
286
|
+
# into the import system a second time to defer to the import
|
|
287
|
+
# finder that is supposed to handle the importing of the module.
|
|
288
|
+
# We set an in progress flag for the target module so that on
|
|
289
|
+
# the second time through we don't trigger another call back
|
|
290
|
+
# into the import system and cause a infinite loop.
|
|
291
|
+
|
|
292
|
+
if fullname in self.in_progress:
|
|
293
|
+
return None
|
|
294
|
+
|
|
295
|
+
self.in_progress[fullname] = True
|
|
296
|
+
|
|
297
|
+
# Now call back into the import system again.
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
# This should only be Python 3 so find_spec() should always
|
|
301
|
+
# exist so don't need to check.
|
|
302
|
+
|
|
303
|
+
spec = find_spec(fullname)
|
|
304
|
+
loader = getattr(spec, "loader", None)
|
|
305
|
+
|
|
306
|
+
if loader and not isinstance(loader, _ImportHookChainedLoader):
|
|
307
|
+
spec.loader = _ImportHookChainedLoader(loader)
|
|
308
|
+
|
|
309
|
+
return spec
|
|
310
|
+
|
|
311
|
+
finally:
|
|
312
|
+
del self.in_progress[fullname]
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# Decorator for marking that a function should be called as a post
|
|
316
|
+
# import hook when the target module is imported.
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def when_imported(name):
|
|
320
|
+
"""
|
|
321
|
+
Returns a decorator that registers the decorated function as a post import
|
|
322
|
+
hook for the module specified by `name`. The function will be called once
|
|
323
|
+
the module with the specified name is imported, and will be passed the
|
|
324
|
+
module as argument. If the module is already imported, the function will
|
|
325
|
+
be called immediately.
|
|
326
|
+
"""
|
|
327
|
+
|
|
328
|
+
def register(hook):
|
|
329
|
+
register_post_import_hook(hook, name)
|
|
330
|
+
return hook
|
|
331
|
+
|
|
332
|
+
return register
|
wrapt/patches.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from .__wrapt__ import FunctionWrapper
|
|
5
|
+
|
|
6
|
+
# Helper functions for applying wrappers to existing functions.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def resolve_path(target, name):
|
|
10
|
+
"""
|
|
11
|
+
Resolves the dotted path supplied as `name` to an attribute on a target
|
|
12
|
+
object. The `target` can be a module, class, or instance of a class. If the
|
|
13
|
+
`target` argument is a string, it is assumed to be the name of a module,
|
|
14
|
+
which will be imported if necessary and then used as the target object.
|
|
15
|
+
Returns a tuple containing the parent object holding the attribute lookup
|
|
16
|
+
resolved to, the attribute name (path prefix removed if present), and the
|
|
17
|
+
original attribute value.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
if isinstance(target, str):
|
|
21
|
+
__import__(target)
|
|
22
|
+
target = sys.modules[target]
|
|
23
|
+
|
|
24
|
+
parent = target
|
|
25
|
+
|
|
26
|
+
path = name.split(".")
|
|
27
|
+
attribute = path[0]
|
|
28
|
+
|
|
29
|
+
# We can't just always use getattr() because in doing
|
|
30
|
+
# that on a class it will cause binding to occur which
|
|
31
|
+
# will complicate things later and cause some things not
|
|
32
|
+
# to work. For the case of a class we therefore access
|
|
33
|
+
# the __dict__ directly. To cope though with the wrong
|
|
34
|
+
# class being given to us, or a method being moved into
|
|
35
|
+
# a base class, we need to walk the class hierarchy to
|
|
36
|
+
# work out exactly which __dict__ the method was defined
|
|
37
|
+
# in, as accessing it from __dict__ will fail if it was
|
|
38
|
+
# not actually on the class given. Fallback to using
|
|
39
|
+
# getattr() if we can't find it. If it truly doesn't
|
|
40
|
+
# exist, then that will fail.
|
|
41
|
+
|
|
42
|
+
def lookup_attribute(parent, attribute):
|
|
43
|
+
if inspect.isclass(parent):
|
|
44
|
+
for cls in inspect.getmro(parent):
|
|
45
|
+
if attribute in vars(cls):
|
|
46
|
+
return vars(cls)[attribute]
|
|
47
|
+
else:
|
|
48
|
+
return getattr(parent, attribute)
|
|
49
|
+
else:
|
|
50
|
+
return getattr(parent, attribute)
|
|
51
|
+
|
|
52
|
+
original = lookup_attribute(parent, attribute)
|
|
53
|
+
|
|
54
|
+
for attribute in path[1:]:
|
|
55
|
+
parent = original
|
|
56
|
+
original = lookup_attribute(parent, attribute)
|
|
57
|
+
|
|
58
|
+
return (parent, attribute, original)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def apply_patch(parent, attribute, replacement):
|
|
62
|
+
"""
|
|
63
|
+
Convenience function for applying a patch to an attribute. Currently this
|
|
64
|
+
maps to the standard setattr() function, but in the future may be extended
|
|
65
|
+
to support more complex patching strategies.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
setattr(parent, attribute, replacement)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def wrap_object(target, name, factory, args=(), kwargs={}):
|
|
72
|
+
"""
|
|
73
|
+
Wraps an object which is the attribute of a target object with a wrapper
|
|
74
|
+
object created by the `factory` function. The `target` can be a module,
|
|
75
|
+
class, or instance of a class. In the special case of `target` being a
|
|
76
|
+
string, it is assumed to be the name of a module, with the module being
|
|
77
|
+
imported if necessary and then used as the target object. The `name` is a
|
|
78
|
+
string representing the dotted path to the attribute. The `factory` function
|
|
79
|
+
should accept the original object and may accept additional positional and
|
|
80
|
+
keyword arguments which will be set by unpacking input arguments using
|
|
81
|
+
`*args` and `**kwargs` calling conventions. The factory function should
|
|
82
|
+
return a new object that will replace the original object.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
(parent, attribute, original) = resolve_path(target, name)
|
|
86
|
+
wrapper = factory(original, *args, **kwargs)
|
|
87
|
+
apply_patch(parent, attribute, wrapper)
|
|
88
|
+
|
|
89
|
+
return wrapper
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Function for applying a proxy object to an attribute of a class
|
|
93
|
+
# instance. The wrapper works by defining an attribute of the same name
|
|
94
|
+
# on the class which is a descriptor and which intercepts access to the
|
|
95
|
+
# instance attribute. Note that this cannot be used on attributes which
|
|
96
|
+
# are themselves defined by a property object.
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class AttributeWrapper:
|
|
100
|
+
|
|
101
|
+
def __init__(self, attribute, factory, args, kwargs):
|
|
102
|
+
self.attribute = attribute
|
|
103
|
+
self.factory = factory
|
|
104
|
+
self.args = args
|
|
105
|
+
self.kwargs = kwargs
|
|
106
|
+
|
|
107
|
+
def __get__(self, instance, owner):
|
|
108
|
+
value = instance.__dict__[self.attribute]
|
|
109
|
+
return self.factory(value, *self.args, **self.kwargs)
|
|
110
|
+
|
|
111
|
+
def __set__(self, instance, value):
|
|
112
|
+
instance.__dict__[self.attribute] = value
|
|
113
|
+
|
|
114
|
+
def __delete__(self, instance):
|
|
115
|
+
del instance.__dict__[self.attribute]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def wrap_object_attribute(module, name, factory, args=(), kwargs={}):
|
|
119
|
+
"""
|
|
120
|
+
Wraps an object which is the attribute of a class instance with a wrapper
|
|
121
|
+
object created by the `factory` function. It does this by patching the
|
|
122
|
+
class, not the instance, with a descriptor that intercepts access to the
|
|
123
|
+
instance attribute. The `module` can be a module, class, or instance of a
|
|
124
|
+
class. In the special case of `module` being a string, it is assumed to be
|
|
125
|
+
the name of a module, with the module being imported if necessary and then
|
|
126
|
+
used as the target object. The `name` is a string representing the dotted
|
|
127
|
+
path to the attribute. The `factory` function should accept the original
|
|
128
|
+
object and may accept additional positional and keyword arguments which will
|
|
129
|
+
be set by unpacking input arguments using `*args` and `**kwargs` calling
|
|
130
|
+
conventions. The factory function should return a new object that will
|
|
131
|
+
replace the original object.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
path, attribute = name.rsplit(".", 1)
|
|
135
|
+
parent = resolve_path(module, path)[2]
|
|
136
|
+
wrapper = AttributeWrapper(attribute, factory, args, kwargs)
|
|
137
|
+
apply_patch(parent, attribute, wrapper)
|
|
138
|
+
return wrapper
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# Functions for creating a simple decorator using a FunctionWrapper,
|
|
142
|
+
# plus short cut functions for applying wrappers to functions. These are
|
|
143
|
+
# for use when doing monkey patching. For a more featured way of
|
|
144
|
+
# creating decorators see the decorator decorator instead.
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def function_wrapper(wrapper):
|
|
148
|
+
"""
|
|
149
|
+
Creates a decorator for wrapping a function with a `wrapper` function.
|
|
150
|
+
The decorator which is returned may also be applied to any other callable
|
|
151
|
+
objects such as lambda functions, methods, classmethods, and staticmethods,
|
|
152
|
+
or objects which implement the `__call__()` method. The `wrapper` function
|
|
153
|
+
should accept the `wrapped` function, `instance`, `args`, and `kwargs`,
|
|
154
|
+
arguments and return the result of calling the wrapped function or some
|
|
155
|
+
other appropriate value.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def _wrapper(wrapped, instance, args, kwargs):
|
|
159
|
+
target_wrapped = args[0]
|
|
160
|
+
if instance is None:
|
|
161
|
+
target_wrapper = wrapper
|
|
162
|
+
elif inspect.isclass(instance):
|
|
163
|
+
target_wrapper = wrapper.__get__(None, instance)
|
|
164
|
+
else:
|
|
165
|
+
target_wrapper = wrapper.__get__(instance, type(instance))
|
|
166
|
+
return FunctionWrapper(target_wrapped, target_wrapper)
|
|
167
|
+
|
|
168
|
+
return FunctionWrapper(wrapper, _wrapper)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def wrap_function_wrapper(target, name, wrapper):
|
|
172
|
+
"""
|
|
173
|
+
Wraps a function which is the attribute of a target object with a `wrapper`
|
|
174
|
+
function. The `target` can be a module, class, or instance of a class. In
|
|
175
|
+
the special case of `target` being a string, it is assumed to be the name
|
|
176
|
+
of a module, with the module being imported if necessary. The `name` is a
|
|
177
|
+
string representing the dotted path to the attribute. The `wrapper` function
|
|
178
|
+
should accept the `wrapped` function, `instance`, `args`, and `kwargs`
|
|
179
|
+
arguments, and would return the result of calling the wrapped attribute or
|
|
180
|
+
some other appropriate value.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
return wrap_object(target, name, FunctionWrapper, (wrapper,))
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def patch_function_wrapper(target, name, enabled=None):
|
|
187
|
+
"""
|
|
188
|
+
Creates a decorator which can be applied to a wrapper function, where the
|
|
189
|
+
wrapper function will be used to wrap a function which is the attribute of
|
|
190
|
+
a target object. The `target` can be a module, class, or instance of a class.
|
|
191
|
+
In the special case of `target` being a string, it is assumed to be the name
|
|
192
|
+
of a module, with the module being imported if necessary. The `name` is a
|
|
193
|
+
string representing the dotted path to the attribute. The `enabled`
|
|
194
|
+
argument can be a boolean or a callable that returns a boolean. When a
|
|
195
|
+
callable is provided, it will be called each time the wrapper is invoked to
|
|
196
|
+
determine if the wrapper function should be executed or whether the wrapped
|
|
197
|
+
function should be called directly. If `enabled` is not provided, the
|
|
198
|
+
wrapper is enabled by default.
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
def _wrapper(wrapper):
|
|
202
|
+
return wrap_object(target, name, FunctionWrapper, (wrapper, enabled))
|
|
203
|
+
|
|
204
|
+
return _wrapper
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def transient_function_wrapper(target, name):
|
|
208
|
+
"""Creates a decorator that patches a target function with a wrapper
|
|
209
|
+
function, but only for the duration of the call that the decorator was
|
|
210
|
+
applied to. The `target` can be a module, class, or instance of a class.
|
|
211
|
+
In the special case of `target` being a string, it is assumed to be the name
|
|
212
|
+
of a module, with the module being imported if necessary. The `name` is a
|
|
213
|
+
string representing the dotted path to the attribute.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
def _decorator(wrapper):
|
|
217
|
+
def _wrapper(wrapped, instance, args, kwargs):
|
|
218
|
+
target_wrapped = args[0]
|
|
219
|
+
if instance is None:
|
|
220
|
+
target_wrapper = wrapper
|
|
221
|
+
elif inspect.isclass(instance):
|
|
222
|
+
target_wrapper = wrapper.__get__(None, instance)
|
|
223
|
+
else:
|
|
224
|
+
target_wrapper = wrapper.__get__(instance, type(instance))
|
|
225
|
+
|
|
226
|
+
def _execute(wrapped, instance, args, kwargs):
|
|
227
|
+
(parent, attribute, original) = resolve_path(target, name)
|
|
228
|
+
replacement = FunctionWrapper(original, target_wrapper)
|
|
229
|
+
setattr(parent, attribute, replacement)
|
|
230
|
+
try:
|
|
231
|
+
return wrapped(*args, **kwargs)
|
|
232
|
+
finally:
|
|
233
|
+
setattr(parent, attribute, original)
|
|
234
|
+
|
|
235
|
+
return FunctionWrapper(target_wrapped, _execute)
|
|
236
|
+
|
|
237
|
+
return FunctionWrapper(wrapper, _wrapper)
|
|
238
|
+
|
|
239
|
+
return _decorator
|