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
« 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
4import functools
5import typing
6import warnings
7from collections.abc import Callable # noqa: TC003
8from inspect import Parameter, getsourcefile, signature
10__all__ = [
11 'classproperty',
12 'conditional',
13 'lazy',
14 'lazy_classproperty',
15 'lazy_property',
16 'reset_cached_properties',
17]
19T = typing.TypeVar("T")
20P = typing.ParamSpec("P")
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]
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 )
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)
50def conditional(condition: typing.Any, decorator: Callable[[T], T]) -> Callable[[T], T]:
51 """ Decorator for a conditionally applied decorator.
53 Example::
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
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
77 if not leftovers:
78 return kwargs
80 return {key: kwargs[key] for key in kwargs if key not in leftovers}
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
93locked = synchronized()
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>", ''
115class classproperty(typing.Generic[T]):
116 def __init__(self, fget: Callable[[typing.Any], T]) -> None:
117 self.fget = classmethod(fget)
119 def __get__(self, cls, owner: type | None = None, /) -> T:
120 return self.fget.__get__(None, owner)()
122 @property
123 def __doc__(self):
124 return self.fget.__doc__
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
135class lazy(object):
136 """ A proxy to the (memoized) result of a lazy evaluation:
138 .. code-block::
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']
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)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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)
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)