Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wrapt/patches.py: 17%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Utilities for monkey patching and wrapping object attributes."""
3import inspect
4import sys
6from .__wrapt__ import FunctionWrapper
7from .importer import register_post_import_hook
9# Helper functions for applying wrappers to existing functions.
12def resolve_path(target, name):
13 """
14 Resolves the dotted path supplied as `name` to an attribute on a target
15 object. The `target` can be a module, class, or instance of a class. If the
16 `target` argument is a string, it is assumed to be the name of a module,
17 which will be imported if necessary and then used as the target object.
18 Returns a tuple containing the parent object holding the attribute lookup
19 resolved to, the attribute name (path prefix removed if present), and the
20 original attribute value.
21 """
23 if isinstance(target, str):
24 __import__(target)
25 target = sys.modules[target]
27 parent = target
29 path = name.split(".")
30 attribute = path[0]
32 # We can't just always use getattr() because in doing
33 # that on a class it will cause binding to occur which
34 # will complicate things later and cause some things not
35 # to work. For the case of a class we therefore access
36 # the __dict__ directly. To cope though with the wrong
37 # class being given to us, or a method being moved into
38 # a base class, we need to walk the class hierarchy to
39 # work out exactly which __dict__ the method was defined
40 # in, as accessing it from __dict__ will fail if it was
41 # not actually on the class given. Fallback to using
42 # getattr() if we can't find it. If it truly doesn't
43 # exist, then that will fail.
45 def lookup_attribute(parent, attribute):
46 if inspect.isclass(parent):
47 for cls in inspect.getmro(parent):
48 if attribute in vars(cls):
49 return vars(cls)[attribute]
50 else:
51 return getattr(parent, attribute)
52 else:
53 return getattr(parent, attribute)
55 original = lookup_attribute(parent, attribute)
57 for attribute in path[1:]:
58 parent = original
59 original = lookup_attribute(parent, attribute)
61 return (parent, attribute, original)
64def apply_patch(parent, attribute, replacement):
65 """
66 Convenience function for applying a patch to an attribute. This maps to
67 the standard setattr() function.
68 """
70 setattr(parent, attribute, replacement)
73def wrap_object(target, name, factory, args=(), kwargs=None):
74 """
75 Wraps an object which is the attribute of a target object with a wrapper
76 object created by the `factory` function. The `target` can be a module,
77 class, or instance of a class. In the special case of `target` being a
78 string, it is assumed to be the name of a module, with the module being
79 imported if necessary and then used as the target object. The `name` is a
80 string representing the dotted path to the attribute. The `factory` function
81 should accept the original object and may accept additional positional and
82 keyword arguments which will be set by unpacking input arguments using
83 `*args` and `**kwargs` calling conventions. The factory function should
84 return a new object that will replace the original object.
85 """
87 if kwargs is None:
88 kwargs = {}
90 (parent, attribute, original) = resolve_path(target, name)
91 wrapper = factory(original, *args, **kwargs)
92 apply_patch(parent, attribute, wrapper)
94 return wrapper
97# Function for applying a proxy object to an attribute of a class
98# instance. The wrapper works by defining an attribute of the same name
99# on the class which is a descriptor and which intercepts access to the
100# instance attribute. Note that this cannot be used on attributes which
101# are themselves defined by a property object.
104class AttributeWrapper:
105 """A descriptor that intercepts access to an instance attribute to apply
106 a wrapper factory."""
108 def __init__(self, attribute, factory, args, kwargs):
109 self.attribute = attribute
110 self.factory = factory
111 self.args = args
112 self.kwargs = kwargs
114 def __get__(self, instance, owner):
115 value = instance.__dict__[self.attribute]
116 return self.factory(value, *self.args, **self.kwargs)
118 def __set__(self, instance, value):
119 instance.__dict__[self.attribute] = value
121 def __delete__(self, instance):
122 del instance.__dict__[self.attribute]
125def wrap_object_attribute(module, name, factory, args=(), kwargs=None):
126 """
127 Wraps an object which is the attribute of a class instance with a wrapper
128 object created by the `factory` function. It does this by patching the
129 class, not the instance, with a descriptor that intercepts access to the
130 instance attribute. The `module` can be a module, class, or instance of a
131 class. In the special case of `module` being a string, it is assumed to be
132 the name of a module, with the module being imported if necessary and then
133 used as the target object. The `name` is a string representing the dotted
134 path to the attribute. The `factory` function should accept the original
135 object and may accept additional positional and keyword arguments which will
136 be set by unpacking input arguments using `*args` and `**kwargs` calling
137 conventions. The factory function should return a new object that will
138 replace the original object.
139 """
141 if kwargs is None:
142 kwargs = {}
144 path, attribute = name.rsplit(".", 1)
145 parent = resolve_path(module, path)[2]
146 wrapper = AttributeWrapper(attribute, factory, args, kwargs)
147 apply_patch(parent, attribute, wrapper)
148 return wrapper
151# Functions for creating a simple decorator using a FunctionWrapper,
152# plus short cut functions for applying wrappers to functions. These are
153# for use when doing monkey patching. For a more featured way of
154# creating decorators see the decorator decorator instead.
157def function_wrapper(wrapper):
158 """
159 Creates a decorator for wrapping a function with a `wrapper` function.
160 The decorator which is returned may also be applied to any other callable
161 objects such as lambda functions, methods, classmethods, and staticmethods,
162 or objects which implement the `__call__()` method. The `wrapper` function
163 should accept the `wrapped` function, `instance`, `args`, and `kwargs`,
164 arguments and return the result of calling the wrapped function or some
165 other appropriate value.
166 """
168 def _wrapper(wrapped, instance, args, kwargs):
169 target_wrapped = args[0]
170 if instance is None:
171 target_wrapper = wrapper
172 elif inspect.isclass(instance):
173 target_wrapper = wrapper.__get__(None, instance)
174 else:
175 target_wrapper = wrapper.__get__(instance, type(instance))
176 return FunctionWrapper(target_wrapped, target_wrapper)
178 return FunctionWrapper(wrapper, _wrapper)
181def wrap_function_wrapper(target, name, wrapper):
182 """
183 Wraps a function which is the attribute of a target object with a `wrapper`
184 function. The `target` can be a module, class, or instance of a class. In
185 the special case of `target` being a string, it is assumed to be the name
186 of a module, with the module being imported if necessary. If the `target`
187 is a string with a trailing ``?``, the wrapping will be deferred until the
188 module is imported. If the module is already imported, the wrapping will be
189 applied immediately. The `name` is a string representing the dotted path to
190 the attribute. The `wrapper` function should accept the `wrapped` function,
191 `instance`, `args`, and `kwargs` arguments, and would return the result of
192 calling the wrapped attribute or some other appropriate value. Returns the
193 wrapped target function if the wrapping was applied immediately, or ``None``
194 if the wrapping was deferred.
195 """
197 if isinstance(target, str) and target.endswith("?"):
198 target = target[:-1]
200 if target in sys.modules:
201 return wrap_object(sys.modules[target], name, FunctionWrapper, (wrapper,))
203 def callback(module):
204 wrap_object(module, name, FunctionWrapper, (wrapper,))
206 register_post_import_hook(callback, target)
207 return None
209 return wrap_object(target, name, FunctionWrapper, (wrapper,))
212def patch_function_wrapper(target, name, enabled=None):
213 """
214 Creates a decorator which can be applied to a wrapper function, where the
215 wrapper function will be used to wrap a function which is the attribute of
216 a target object. The decorator returns the original wrapper function. The
217 `target` can be a module, class, or instance of a class. In the special case
218 of `target` being a string, it is assumed to be the name of a module, with
219 the module being imported if necessary. If the `target` is a string with a
220 trailing ``?``, the wrapping will be deferred until the module is imported.
221 If the module is already imported, the wrapping will be applied immediately.
222 The `name` is a string representing the dotted path to the attribute. The
223 `enabled` argument can be a boolean or a callable that returns a boolean.
224 When a callable is provided, it will be called each time the wrapper is
225 invoked to determine if the wrapper function should be executed or whether
226 the wrapped function should be called directly. If `enabled` is not
227 provided, the wrapper is enabled by default.
228 """
230 def _wrapper(wrapper):
231 if isinstance(target, str) and target.endswith("?"):
232 _target = target[:-1]
234 if _target in sys.modules:
235 wrap_object(
236 sys.modules[_target], name, FunctionWrapper, (wrapper, enabled)
237 )
238 return wrapper
240 def callback(module):
241 wrap_object(module, name, FunctionWrapper, (wrapper, enabled))
243 register_post_import_hook(callback, _target)
244 return wrapper
246 wrap_object(target, name, FunctionWrapper, (wrapper, enabled))
247 return wrapper
249 return _wrapper
252def transient_function_wrapper(target, name):
253 """Creates a decorator that patches a target function with a wrapper
254 function, but only for the duration of the call that the decorator was
255 applied to. The `target` can be a module, class, or instance of a class.
256 In the special case of `target` being a string, it is assumed to be the name
257 of a module, with the module being imported if necessary. The `name` is a
258 string representing the dotted path to the attribute.
259 """
261 def _decorator(wrapper):
262 def _wrapper(wrapped, instance, args, kwargs):
263 target_wrapped = args[0]
264 if instance is None:
265 target_wrapper = wrapper
266 elif inspect.isclass(instance):
267 target_wrapper = wrapper.__get__(None, instance)
268 else:
269 target_wrapper = wrapper.__get__(instance, type(instance))
271 def _execute(wrapped, instance, args, kwargs):
272 (parent, attribute, original) = resolve_path(target, name)
273 replacement = FunctionWrapper(original, target_wrapper)
274 setattr(parent, attribute, replacement)
275 try:
276 return wrapped(*args, **kwargs)
277 finally:
278 setattr(parent, attribute, original)
280 return FunctionWrapper(target_wrapped, _execute)
282 return FunctionWrapper(wrapper, _wrapper)
284 return _decorator