wrapt 2.0.1rc1__cp38-cp38-musllinux_1_2_x86_64.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.

Potentially problematic release.


This version of wrapt might be problematic. Click here for more details.

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