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

107 statements  

1"""Utilities for monkey patching and wrapping object attributes.""" 

2 

3import inspect 

4import sys 

5 

6from .__wrapt__ import FunctionWrapper 

7from .importer import register_post_import_hook 

8 

9# Helper functions for applying wrappers to existing functions. 

10 

11 

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 """ 

22 

23 if isinstance(target, str): 

24 __import__(target) 

25 target = sys.modules[target] 

26 

27 parent = target 

28 

29 path = name.split(".") 

30 attribute = path[0] 

31 

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. 

44 

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) 

54 

55 original = lookup_attribute(parent, attribute) 

56 

57 for attribute in path[1:]: 

58 parent = original 

59 original = lookup_attribute(parent, attribute) 

60 

61 return (parent, attribute, original) 

62 

63 

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 """ 

69 

70 setattr(parent, attribute, replacement) 

71 

72 

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 """ 

86 

87 if kwargs is None: 

88 kwargs = {} 

89 

90 (parent, attribute, original) = resolve_path(target, name) 

91 wrapper = factory(original, *args, **kwargs) 

92 apply_patch(parent, attribute, wrapper) 

93 

94 return wrapper 

95 

96 

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. 

102 

103 

104class AttributeWrapper: 

105 """A descriptor that intercepts access to an instance attribute to apply 

106 a wrapper factory.""" 

107 

108 def __init__(self, attribute, factory, args, kwargs): 

109 self.attribute = attribute 

110 self.factory = factory 

111 self.args = args 

112 self.kwargs = kwargs 

113 

114 def __get__(self, instance, owner): 

115 value = instance.__dict__[self.attribute] 

116 return self.factory(value, *self.args, **self.kwargs) 

117 

118 def __set__(self, instance, value): 

119 instance.__dict__[self.attribute] = value 

120 

121 def __delete__(self, instance): 

122 del instance.__dict__[self.attribute] 

123 

124 

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 """ 

140 

141 if kwargs is None: 

142 kwargs = {} 

143 

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 

149 

150 

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. 

155 

156 

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 """ 

167 

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) 

177 

178 return FunctionWrapper(wrapper, _wrapper) 

179 

180 

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 """ 

196 

197 if isinstance(target, str) and target.endswith("?"): 

198 target = target[:-1] 

199 

200 if target in sys.modules: 

201 return wrap_object(sys.modules[target], name, FunctionWrapper, (wrapper,)) 

202 

203 def callback(module): 

204 wrap_object(module, name, FunctionWrapper, (wrapper,)) 

205 

206 register_post_import_hook(callback, target) 

207 return None 

208 

209 return wrap_object(target, name, FunctionWrapper, (wrapper,)) 

210 

211 

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 """ 

229 

230 def _wrapper(wrapper): 

231 if isinstance(target, str) and target.endswith("?"): 

232 _target = target[:-1] 

233 

234 if _target in sys.modules: 

235 wrap_object( 

236 sys.modules[_target], name, FunctionWrapper, (wrapper, enabled) 

237 ) 

238 return wrapper 

239 

240 def callback(module): 

241 wrap_object(module, name, FunctionWrapper, (wrapper, enabled)) 

242 

243 register_post_import_hook(callback, _target) 

244 return wrapper 

245 

246 wrap_object(target, name, FunctionWrapper, (wrapper, enabled)) 

247 return wrapper 

248 

249 return _wrapper 

250 

251 

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 """ 

260 

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)) 

270 

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) 

279 

280 return FunctionWrapper(target_wrapped, _execute) 

281 

282 return FunctionWrapper(wrapper, _wrapper) 

283 

284 return _decorator