Coverage for adhoc-cicd-odoo-odoo / odoo / tools / func.py: 61%

176 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-09 18:15 +0000

1# Part of Odoo. See LICENSE file for full copyright and licensing details. 

2from __future__ import annotations 

3 

4import functools 

5import typing 

6import warnings 

7from collections.abc import Callable # noqa: TC003 

8from inspect import Parameter, getsourcefile, signature 

9 

10__all__ = [ 

11 'classproperty', 

12 'conditional', 

13 'lazy', 

14 'lazy_classproperty', 

15 'lazy_property', 

16 'reset_cached_properties', 

17] 

18 

19T = typing.TypeVar("T") 

20P = typing.ParamSpec("P") 

21 

22 

23def reset_cached_properties(obj) -> None: 

24 """ Reset all cached properties on the instance `obj`. """ 

25 cls = type(obj) 

26 obj_dict = vars(obj) 

27 for name in list(obj_dict): 

28 if isinstance(getattr(cls, name, None), functools.cached_property): 

29 del obj_dict[name] 

30 

31 

32class lazy_property(functools.cached_property): 

33 def __init__(self, func): 

34 super().__init__(func) 

35 warnings.warn( 

36 "lazy_property is deprecated since Odoo 19, use `functools.cached_property`", 

37 category=DeprecationWarning, 

38 stacklevel=2, 

39 ) 

40 

41 @staticmethod 

42 def reset_all(instance): 

43 warnings.warn( 

44 "lazy_property is deprecated since Odoo 19, use `reset_cache_properties` directly", 

45 category=DeprecationWarning, 

46 ) 

47 reset_cached_properties(instance) 

48 

49 

50def conditional(condition: typing.Any, decorator: Callable[[T], T]) -> Callable[[T], T]: 

51 """ Decorator for a conditionally applied decorator. 

52 

53 Example:: 

54 

55 @conditional(get_config('use_cache'), ormcache) 

56 def fn(): 

57 pass 

58 """ 

59 if condition: 59 ↛ 62line 59 didn't jump to line 62 because the condition on line 59 was always true

60 return decorator 

61 else: 

62 return lambda fn: fn 

63 

64 

65def filter_kwargs(func: Callable, kwargs: dict[str, typing.Any]) -> dict[str, typing.Any]: 

66 """ Filter the given keyword arguments to only return the kwargs 

67 that binds to the function's signature. 

68 """ 

69 leftovers = set(kwargs) 

70 for p in signature(func).parameters.values(): 

71 if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY): 

72 leftovers.discard(p.name) 

73 elif p.kind == Parameter.VAR_KEYWORD: # **kwargs 

74 leftovers.clear() 

75 break 

76 

77 if not leftovers: 

78 return kwargs 

79 

80 return {key: kwargs[key] for key in kwargs if key not in leftovers} 

81 

82 

83def synchronized(lock_attr: str = '_lock') -> Callable[[Callable[P, T]], Callable[P, T]]: 

84 def synchronized_lock(func, /): 

85 @functools.wraps(func) 

86 def locked(inst, *args, **kwargs): 

87 with getattr(inst, lock_attr): 

88 return func(inst, *args, **kwargs) 

89 return locked 

90 return synchronized_lock 

91 

92 

93locked = synchronized() 

94 

95 

96def frame_codeinfo(fframe, back=0): 

97 """ Return a (filename, line) pair for a previous frame . 

98 @return (filename, lineno) where lineno is either int or string=='' 

99 """ 

100 try: 

101 if not fframe: 

102 return "<unknown>", '' 

103 for _i in range(back): 

104 fframe = fframe.f_back 

105 try: 

106 fname = getsourcefile(fframe) 

107 except TypeError: 

108 fname = '<builtin>' 

109 lineno = fframe.f_lineno or '' 

110 return fname, lineno 

111 except Exception: 

112 return "<unknown>", '' 

113 

114 

115class classproperty(typing.Generic[T]): 

116 def __init__(self, fget: Callable[[typing.Any], T]) -> None: 

117 self.fget = classmethod(fget) 

118 

119 def __get__(self, cls, owner: type | None = None, /) -> T: 

120 return self.fget.__get__(None, owner)() 

121 

122 @property 

123 def __doc__(self): 

124 return self.fget.__doc__ 

125 

126 

127class lazy_classproperty(classproperty[T], typing.Generic[T]): 

128 """ Similar to :class:`lazy_property`, but for classes. """ 

129 def __get__(self, cls, owner: type | None = None, /) -> T: 

130 val = super().__get__(cls, owner) 

131 setattr(owner, self.fget.__name__, val) 

132 return val 

133 

134 

135class lazy(object): 

136 """ A proxy to the (memoized) result of a lazy evaluation: 

137 

138 .. code-block:: 

139 

140 foo = lazy(func, arg) # func(arg) is not called yet 

141 bar = foo + 1 # eval func(arg) and add 1 

142 baz = foo + 2 # use result of func(arg) and add 2 

143 """ 

144 __slots__ = ['_func', '_args', '_kwargs', '_cached_value'] 

145 

146 def __init__(self, func, *args, **kwargs): 

147 # bypass own __setattr__ 

148 object.__setattr__(self, '_func', func) 

149 object.__setattr__(self, '_args', args) 

150 object.__setattr__(self, '_kwargs', kwargs) 

151 

152 @property 

153 def _value(self): 

154 if self._func is not None: 

155 value = self._func(*self._args, **self._kwargs) 

156 object.__setattr__(self, '_func', None) 

157 object.__setattr__(self, '_args', None) 

158 object.__setattr__(self, '_kwargs', None) 

159 object.__setattr__(self, '_cached_value', value) 

160 return self._cached_value 

161 

162 def __getattr__(self, name): return getattr(self._value, name) 162 ↛ exitline 162 didn't return from function '__getattr__' because the return on line 162 wasn't executed

163 def __setattr__(self, name, value): return setattr(self._value, name, value) 163 ↛ exitline 163 didn't return from function '__setattr__' because the return on line 163 wasn't executed

164 def __delattr__(self, name): return delattr(self._value, name) 164 ↛ exitline 164 didn't return from function '__delattr__' because the return on line 164 wasn't executed

165 

166 def __repr__(self): 

167 return repr(self._value) if self._func is None else object.__repr__(self) 

168 def __str__(self): return str(self._value) 168 ↛ exitline 168 didn't return from function '__str__' because the return on line 168 wasn't executed

169 def __bytes__(self): return bytes(self._value) 169 ↛ exitline 169 didn't return from function '__bytes__' because the return on line 169 wasn't executed

170 def __format__(self, format_spec): return format(self._value, format_spec) 170 ↛ exitline 170 didn't return from function '__format__' because the return on line 170 wasn't executed

171 

172 def __lt__(self, other): return other > self._value 172 ↛ exitline 172 didn't return from function '__lt__' because the return on line 172 wasn't executed

173 def __le__(self, other): return other >= self._value 173 ↛ exitline 173 didn't return from function '__le__' because the return on line 173 wasn't executed

174 def __eq__(self, other): return other == self._value 174 ↛ exitline 174 didn't return from function '__eq__' because the return on line 174 wasn't executed

175 def __ne__(self, other): return other != self._value 175 ↛ exitline 175 didn't return from function '__ne__' because the return on line 175 wasn't executed

176 def __gt__(self, other): return other < self._value 176 ↛ exitline 176 didn't return from function '__gt__' because the return on line 176 wasn't executed

177 def __ge__(self, other): return other <= self._value 177 ↛ exitline 177 didn't return from function '__ge__' because the return on line 177 wasn't executed

178 

179 def __hash__(self): return hash(self._value) 179 ↛ exitline 179 didn't return from function '__hash__' because the return on line 179 wasn't executed

180 def __bool__(self): return bool(self._value) 180 ↛ exitline 180 didn't return from function '__bool__' because the return on line 180 wasn't executed

181 

182 def __call__(self, *args, **kwargs): return self._value(*args, **kwargs) 182 ↛ exitline 182 didn't return from function '__call__' because the return on line 182 wasn't executed

183 

184 def __len__(self): return len(self._value) 184 ↛ exitline 184 didn't return from function '__len__' because the return on line 184 wasn't executed

185 def __getitem__(self, key): return self._value[key] 185 ↛ exitline 185 didn't return from function '__getitem__' because the return on line 185 wasn't executed

186 def __missing__(self, key): return self._value.__missing__(key) 186 ↛ exitline 186 didn't return from function '__missing__' because the return on line 186 wasn't executed

187 def __setitem__(self, key, value): self._value[key] = value 187 ↛ exitline 187 didn't return from function '__setitem__' because

188 def __delitem__(self, key): del self._value[key] 188 ↛ exitline 188 didn't return from function '__delitem__' because

189 def __iter__(self): return iter(self._value) 189 ↛ exitline 189 didn't return from function '__iter__' because the return on line 189 wasn't executed

190 def __reversed__(self): return reversed(self._value) 190 ↛ exitline 190 didn't return from function '__reversed__' because the return on line 190 wasn't executed

191 def __contains__(self, key): return key in self._value 191 ↛ exitline 191 didn't return from function '__contains__' because the return on line 191 wasn't executed

192 

193 def __add__(self, other): return self._value.__add__(other) 193 ↛ exitline 193 didn't return from function '__add__' because the return on line 193 wasn't executed

194 def __sub__(self, other): return self._value.__sub__(other) 194 ↛ exitline 194 didn't return from function '__sub__' because the return on line 194 wasn't executed

195 def __mul__(self, other): return self._value.__mul__(other) 195 ↛ exitline 195 didn't return from function '__mul__' because the return on line 195 wasn't executed

196 def __matmul__(self, other): return self._value.__matmul__(other) 196 ↛ exitline 196 didn't return from function '__matmul__' because the return on line 196 wasn't executed

197 def __truediv__(self, other): return self._value.__truediv__(other) 197 ↛ exitline 197 didn't return from function '__truediv__' because the return on line 197 wasn't executed

198 def __floordiv__(self, other): return self._value.__floordiv__(other) 198 ↛ exitline 198 didn't return from function '__floordiv__' because the return on line 198 wasn't executed

199 def __mod__(self, other): return self._value.__mod__(other) 199 ↛ exitline 199 didn't return from function '__mod__' because the return on line 199 wasn't executed

200 def __divmod__(self, other): return self._value.__divmod__(other) 200 ↛ exitline 200 didn't return from function '__divmod__' because the return on line 200 wasn't executed

201 def __pow__(self, other): return self._value.__pow__(other) 201 ↛ exitline 201 didn't return from function '__pow__' because the return on line 201 wasn't executed

202 def __lshift__(self, other): return self._value.__lshift__(other) 202 ↛ exitline 202 didn't return from function '__lshift__' because the return on line 202 wasn't executed

203 def __rshift__(self, other): return self._value.__rshift__(other) 203 ↛ exitline 203 didn't return from function '__rshift__' because the return on line 203 wasn't executed

204 def __and__(self, other): return self._value.__and__(other) 204 ↛ exitline 204 didn't return from function '__and__' because the return on line 204 wasn't executed

205 def __xor__(self, other): return self._value.__xor__(other) 205 ↛ exitline 205 didn't return from function '__xor__' because the return on line 205 wasn't executed

206 def __or__(self, other): return self._value.__or__(other) 206 ↛ exitline 206 didn't return from function '__or__' because the return on line 206 wasn't executed

207 

208 def __radd__(self, other): return self._value.__radd__(other) 208 ↛ exitline 208 didn't return from function '__radd__' because the return on line 208 wasn't executed

209 def __rsub__(self, other): return self._value.__rsub__(other) 209 ↛ exitline 209 didn't return from function '__rsub__' because the return on line 209 wasn't executed

210 def __rmul__(self, other): return self._value.__rmul__(other) 210 ↛ exitline 210 didn't return from function '__rmul__' because the return on line 210 wasn't executed

211 def __rmatmul__(self, other): return self._value.__rmatmul__(other) 211 ↛ exitline 211 didn't return from function '__rmatmul__' because the return on line 211 wasn't executed

212 def __rtruediv__(self, other): return self._value.__rtruediv__(other) 212 ↛ exitline 212 didn't return from function '__rtruediv__' because the return on line 212 wasn't executed

213 def __rfloordiv__(self, other): return self._value.__rfloordiv__(other) 213 ↛ exitline 213 didn't return from function '__rfloordiv__' because the return on line 213 wasn't executed

214 def __rmod__(self, other): return self._value.__rmod__(other) 214 ↛ exitline 214 didn't return from function '__rmod__' because the return on line 214 wasn't executed

215 def __rdivmod__(self, other): return self._value.__rdivmod__(other) 215 ↛ exitline 215 didn't return from function '__rdivmod__' because the return on line 215 wasn't executed

216 def __rpow__(self, other): return self._value.__rpow__(other) 216 ↛ exitline 216 didn't return from function '__rpow__' because the return on line 216 wasn't executed

217 def __rlshift__(self, other): return self._value.__rlshift__(other) 217 ↛ exitline 217 didn't return from function '__rlshift__' because the return on line 217 wasn't executed

218 def __rrshift__(self, other): return self._value.__rrshift__(other) 218 ↛ exitline 218 didn't return from function '__rrshift__' because the return on line 218 wasn't executed

219 def __rand__(self, other): return self._value.__rand__(other) 219 ↛ exitline 219 didn't return from function '__rand__' because the return on line 219 wasn't executed

220 def __rxor__(self, other): return self._value.__rxor__(other) 220 ↛ exitline 220 didn't return from function '__rxor__' because the return on line 220 wasn't executed

221 def __ror__(self, other): return self._value.__ror__(other) 221 ↛ exitline 221 didn't return from function '__ror__' because the return on line 221 wasn't executed

222 

223 def __iadd__(self, other): return self._value.__iadd__(other) 223 ↛ exitline 223 didn't return from function '__iadd__' because the return on line 223 wasn't executed

224 def __isub__(self, other): return self._value.__isub__(other) 224 ↛ exitline 224 didn't return from function '__isub__' because the return on line 224 wasn't executed

225 def __imul__(self, other): return self._value.__imul__(other) 225 ↛ exitline 225 didn't return from function '__imul__' because the return on line 225 wasn't executed

226 def __imatmul__(self, other): return self._value.__imatmul__(other) 226 ↛ exitline 226 didn't return from function '__imatmul__' because the return on line 226 wasn't executed

227 def __itruediv__(self, other): return self._value.__itruediv__(other) 227 ↛ exitline 227 didn't return from function '__itruediv__' because the return on line 227 wasn't executed

228 def __ifloordiv__(self, other): return self._value.__ifloordiv__(other) 228 ↛ exitline 228 didn't return from function '__ifloordiv__' because the return on line 228 wasn't executed

229 def __imod__(self, other): return self._value.__imod__(other) 229 ↛ exitline 229 didn't return from function '__imod__' because the return on line 229 wasn't executed

230 def __ipow__(self, other): return self._value.__ipow__(other) 230 ↛ exitline 230 didn't return from function '__ipow__' because the return on line 230 wasn't executed

231 def __ilshift__(self, other): return self._value.__ilshift__(other) 231 ↛ exitline 231 didn't return from function '__ilshift__' because the return on line 231 wasn't executed

232 def __irshift__(self, other): return self._value.__irshift__(other) 232 ↛ exitline 232 didn't return from function '__irshift__' because the return on line 232 wasn't executed

233 def __iand__(self, other): return self._value.__iand__(other) 233 ↛ exitline 233 didn't return from function '__iand__' because the return on line 233 wasn't executed

234 def __ixor__(self, other): return self._value.__ixor__(other) 234 ↛ exitline 234 didn't return from function '__ixor__' because the return on line 234 wasn't executed

235 def __ior__(self, other): return self._value.__ior__(other) 235 ↛ exitline 235 didn't return from function '__ior__' because the return on line 235 wasn't executed

236 

237 def __neg__(self): return self._value.__neg__() 237 ↛ exitline 237 didn't return from function '__neg__' because the return on line 237 wasn't executed

238 def __pos__(self): return self._value.__pos__() 238 ↛ exitline 238 didn't return from function '__pos__' because the return on line 238 wasn't executed

239 def __abs__(self): return self._value.__abs__() 239 ↛ exitline 239 didn't return from function '__abs__' because the return on line 239 wasn't executed

240 def __invert__(self): return self._value.__invert__() 240 ↛ exitline 240 didn't return from function '__invert__' because the return on line 240 wasn't executed

241 

242 def __complex__(self): return complex(self._value) 242 ↛ exitline 242 didn't return from function '__complex__' because the return on line 242 wasn't executed

243 def __int__(self): return int(self._value) 243 ↛ exitline 243 didn't return from function '__int__' because the return on line 243 wasn't executed

244 def __float__(self): return float(self._value) 244 ↛ exitline 244 didn't return from function '__float__' because the return on line 244 wasn't executed

245 

246 def __index__(self): return self._value.__index__() 246 ↛ exitline 246 didn't return from function '__index__' because the return on line 246 wasn't executed

247 

248 def __round__(self): return self._value.__round__() 248 ↛ exitline 248 didn't return from function '__round__' because the return on line 248 wasn't executed

249 def __trunc__(self): return self._value.__trunc__() 249 ↛ exitline 249 didn't return from function '__trunc__' because the return on line 249 wasn't executed

250 def __floor__(self): return self._value.__floor__() 250 ↛ exitline 250 didn't return from function '__floor__' because the return on line 250 wasn't executed

251 def __ceil__(self): return self._value.__ceil__() 251 ↛ exitline 251 didn't return from function '__ceil__' because the return on line 251 wasn't executed

252 

253 def __enter__(self): return self._value.__enter__() 253 ↛ exitline 253 didn't return from function '__enter__' because the return on line 253 wasn't executed

254 def __exit__(self, exc_type, exc_value, traceback): 

255 return self._value.__exit__(exc_type, exc_value, traceback) 

256 

257 def __await__(self): return self._value.__await__() 257 ↛ exitline 257 didn't return from function '__await__' because the return on line 257 wasn't executed

258 def __aiter__(self): return self._value.__aiter__() 258 ↛ exitline 258 didn't return from function '__aiter__' because the return on line 258 wasn't executed

259 def __anext__(self): return self._value.__anext__() 259 ↛ exitline 259 didn't return from function '__anext__' because the return on line 259 wasn't executed

260 def __aenter__(self): return self._value.__aenter__() 260 ↛ exitline 260 didn't return from function '__aenter__' because the return on line 260 wasn't executed

261 def __aexit__(self, exc_type, exc_value, traceback): 

262 return self._value.__aexit__(exc_type, exc_value, traceback)