This commit is the 4th commit of a comprehensive refactor of our HTTP framework. See odoo/odoo#78857 for complete historic, discussions and rationnals. Complete refactor of the "registry" of controllers. The `ControllerType` metaclass have been replaced by an abstract class with a py3.7 `__init_subclass__`. The `Endpoint` class is gone too, replaced by a clever usage of `functools.partial`. This refactor is "pure", it doesn't add any new feature, it only merely adds a few warnings. The four `@route`, `Controller`, `_generate_routing_rule`, `routing_map` work as follow: 1. A reference to each immediate child class of `Controller` (not grand- children) is registered in a global list indexed by module (thus a dictionnary) everytime the server starts. Remember that every first- child (not grand-children) of `Controller` is the primary controller, the one that can later be extended by other controllers (the grand- children) in other modules. Remember that it is possible to get each class's children via the `__subclasses__` dunder method. 2. Every controller method that is decorated with `@route` is granted an attribute: `original_routing`, a dictionnary containing the `@route` arguments. When a controller method has the `original_routing` attribute, this method is called an `endpoint`. 3. `_generate_routing_rules` receives the list of installed module names, this list is topologicaly sorted according to the modules dependencies. That is `base` comes before `web` in this list. The objectif of this function is to pair each route to an endpoint whoose class's MRO respect the above-mentioned order. Whe achieve this by carefully crafting classes at runtime, classes inheritating from the correct "source code" controllers in accordance to the topology. This method is also responsible of merging each method's `original_routing` into one `routing` dictionnary, the very `rule.endpoint.routing` dict that is used through the rest of the http framework. 4. Each route-endpoint pair is saved into a `routing_map`, an object that bind each route to its endpoint. This object exposes a `match` method used to find back the endpoint given its route (=http path). In addition to this refactor, we added some new helpers in our tools, among them `submap` that implement a kind of `dict() - set() -> dict()` operator. Filtering a dict on a set of keys is a common operation but the python standard library lacks a dedicated operator. PR: odoo#78857 Task: 2571224
261 lines
10 KiB
Python
261 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
__all__ = ['synchronized', 'lazy_classproperty', 'lazy_property',
|
|
'classproperty', 'conditional', 'lazy']
|
|
|
|
from inspect import getsourcefile, Parameter, signature
|
|
from functools import wraps
|
|
from json import JSONEncoder
|
|
|
|
from decorator import decorator
|
|
|
|
class lazy_property(object):
|
|
""" Decorator for a lazy property of an object, i.e., an object attribute
|
|
that is determined by the result of a method call evaluated once. To
|
|
reevaluate the property, simply delete the attribute on the object, and
|
|
get it again.
|
|
"""
|
|
def __init__(self, fget):
|
|
assert not fget.__name__.startswith('__'),\
|
|
"lazy_property does not support mangled names"
|
|
self.fget = fget
|
|
|
|
def __get__(self, obj, cls):
|
|
if obj is None:
|
|
return self
|
|
value = self.fget(obj)
|
|
setattr(obj, self.fget.__name__, value)
|
|
return value
|
|
|
|
@property
|
|
def __doc__(self):
|
|
return self.fget.__doc__
|
|
|
|
@staticmethod
|
|
def reset_all(obj):
|
|
""" Reset all lazy properties on the instance `obj`. """
|
|
cls = type(obj)
|
|
obj_dict = vars(obj)
|
|
for name in list(obj_dict):
|
|
if isinstance(getattr(cls, name, None), lazy_property):
|
|
obj_dict.pop(name)
|
|
|
|
class lazy_classproperty(lazy_property):
|
|
""" Similar to :class:`lazy_property`, but for classes. """
|
|
def __get__(self, obj, cls):
|
|
val = self.fget(cls)
|
|
setattr(cls, self.fget.__name__, val)
|
|
return val
|
|
|
|
def conditional(condition, decorator):
|
|
""" Decorator for a conditionally applied decorator.
|
|
|
|
Example::
|
|
|
|
@conditional(get_config('use_cache'), ormcache)
|
|
def fn():
|
|
pass
|
|
"""
|
|
if condition:
|
|
return decorator
|
|
else:
|
|
return lambda fn: fn
|
|
|
|
def filter_kwargs(func, kwargs):
|
|
""" Filter the given keyword arguments to only return the kwargs
|
|
that binds to the function's signature.
|
|
"""
|
|
leftovers = set(kwargs)
|
|
for p in signature(func).parameters.values():
|
|
if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
|
|
leftovers.discard(p.name)
|
|
elif p.kind == Parameter.VAR_KEYWORD: # **kwargs
|
|
leftovers.clear()
|
|
break
|
|
|
|
if not leftovers:
|
|
return kwargs
|
|
|
|
return {key: kwargs[key] for key in kwargs if key not in leftovers}
|
|
|
|
def synchronized(lock_attr='_lock'):
|
|
@decorator
|
|
def locked(func, inst, *args, **kwargs):
|
|
with getattr(inst, lock_attr):
|
|
return func(inst, *args, **kwargs)
|
|
return locked
|
|
locked = synchronized()
|
|
|
|
def frame_codeinfo(fframe, back=0):
|
|
""" Return a (filename, line) pair for a previous frame .
|
|
@return (filename, lineno) where lineno is either int or string==''
|
|
"""
|
|
|
|
try:
|
|
if not fframe:
|
|
return "<unknown>", ''
|
|
for i in range(back):
|
|
fframe = fframe.f_back
|
|
try:
|
|
fname = getsourcefile(fframe)
|
|
except TypeError:
|
|
fname = '<builtin>'
|
|
lineno = fframe.f_lineno or ''
|
|
return fname, lineno
|
|
except Exception:
|
|
return "<unknown>", ''
|
|
|
|
def compose(a, b):
|
|
""" Composes the callables ``a`` and ``b``. ``compose(a, b)(*args)`` is
|
|
equivalent to ``a(b(*args))``.
|
|
|
|
Can be used as a decorator by partially applying ``a``::
|
|
|
|
@partial(compose, a)
|
|
def b():
|
|
...
|
|
"""
|
|
@wraps(b)
|
|
def wrapper(*args, **kwargs):
|
|
return a(b(*args, **kwargs))
|
|
return wrapper
|
|
|
|
|
|
class _ClassProperty(property):
|
|
def __get__(self, cls, owner):
|
|
return self.fget.__get__(None, owner)()
|
|
|
|
def classproperty(func):
|
|
return _ClassProperty(classmethod(func))
|
|
|
|
|
|
class lazy(object):
|
|
""" A proxy to the (memoized) result of a lazy evaluation:
|
|
|
|
.. code-block::
|
|
|
|
foo = lazy(func, arg) # func(arg) is not called yet
|
|
bar = foo + 1 # eval func(arg) and add 1
|
|
baz = foo + 2 # use result of func(arg) and add 2
|
|
"""
|
|
__slots__ = ['_func', '_args', '_kwargs', '_cached_value']
|
|
|
|
def __init__(self, func, *args, **kwargs):
|
|
# bypass own __setattr__
|
|
object.__setattr__(self, '_func', func)
|
|
object.__setattr__(self, '_args', args)
|
|
object.__setattr__(self, '_kwargs', kwargs)
|
|
|
|
@property
|
|
def _value(self):
|
|
if self._func is not None:
|
|
value = self._func(*self._args, **self._kwargs)
|
|
object.__setattr__(self, '_func', None)
|
|
object.__setattr__(self, '_args', None)
|
|
object.__setattr__(self, '_kwargs', None)
|
|
object.__setattr__(self, '_cached_value', value)
|
|
return self._cached_value
|
|
|
|
def __getattr__(self, name): return getattr(self._value, name)
|
|
def __setattr__(self, name, value): return setattr(self._value, name, value)
|
|
def __delattr__(self, name): return delattr(self._value, name)
|
|
|
|
def __repr__(self):
|
|
return repr(self._value) if self._func is None else object.__repr__(self)
|
|
def __str__(self): return str(self._value)
|
|
def __bytes__(self): return bytes(self._value)
|
|
def __format__(self, format_spec): return format(self._value, format_spec)
|
|
|
|
def __lt__(self, other): return self._value < other
|
|
def __le__(self, other): return self._value <= other
|
|
def __eq__(self, other): return self._value == other
|
|
def __ne__(self, other): return self._value != other
|
|
def __gt__(self, other): return self._value > other
|
|
def __ge__(self, other): return self._value >= other
|
|
|
|
def __hash__(self): return hash(self._value)
|
|
def __bool__(self): return bool(self._value)
|
|
|
|
def __call__(self, *args, **kwargs): return self._value(*args, **kwargs)
|
|
|
|
def __len__(self): return len(self._value)
|
|
def __getitem__(self, key): return self._value[key]
|
|
def __missing__(self, key): return self._value.__missing__(key)
|
|
def __setitem__(self, key, value): self._value[key] = value
|
|
def __delitem__(self, key): del self._value[key]
|
|
def __iter__(self): return iter(self._value)
|
|
def __reversed__(self): return reversed(self._value)
|
|
def __contains__(self, key): return key in self._value
|
|
|
|
def __add__(self, other): return self._value.__add__(other)
|
|
def __sub__(self, other): return self._value.__sub__(other)
|
|
def __mul__(self, other): return self._value.__mul__(other)
|
|
def __matmul__(self, other): return self._value.__matmul__(other)
|
|
def __truediv__(self, other): return self._value.__truediv__(other)
|
|
def __floordiv__(self, other): return self._value.__floordiv__(other)
|
|
def __mod__(self, other): return self._value.__mod__(other)
|
|
def __divmod__(self, other): return self._value.__divmod__(other)
|
|
def __pow__(self, other): return self._value.__pow__(other)
|
|
def __lshift__(self, other): return self._value.__lshift__(other)
|
|
def __rshift__(self, other): return self._value.__rshift__(other)
|
|
def __and__(self, other): return self._value.__and__(other)
|
|
def __xor__(self, other): return self._value.__xor__(other)
|
|
def __or__(self, other): return self._value.__or__(other)
|
|
|
|
def __radd__(self, other): return self._value.__radd__(other)
|
|
def __rsub__(self, other): return self._value.__rsub__(other)
|
|
def __rmul__(self, other): return self._value.__rmul__(other)
|
|
def __rmatmul__(self, other): return self._value.__rmatmul__(other)
|
|
def __rtruediv__(self, other): return self._value.__rtruediv__(other)
|
|
def __rfloordiv__(self, other): return self._value.__rfloordiv__(other)
|
|
def __rmod__(self, other): return self._value.__rmod__(other)
|
|
def __rdivmod__(self, other): return self._value.__rdivmod__(other)
|
|
def __rpow__(self, other): return self._value.__rpow__(other)
|
|
def __rlshift__(self, other): return self._value.__rlshift__(other)
|
|
def __rrshift__(self, other): return self._value.__rrshift__(other)
|
|
def __rand__(self, other): return self._value.__rand__(other)
|
|
def __rxor__(self, other): return self._value.__rxor__(other)
|
|
def __ror__(self, other): return self._value.__ror__(other)
|
|
|
|
def __iadd__(self, other): return self._value.__iadd__(other)
|
|
def __isub__(self, other): return self._value.__isub__(other)
|
|
def __imul__(self, other): return self._value.__imul__(other)
|
|
def __imatmul__(self, other): return self._value.__imatmul__(other)
|
|
def __itruediv__(self, other): return self._value.__itruediv__(other)
|
|
def __ifloordiv__(self, other): return self._value.__ifloordiv__(other)
|
|
def __imod__(self, other): return self._value.__imod__(other)
|
|
def __ipow__(self, other): return self._value.__ipow__(other)
|
|
def __ilshift__(self, other): return self._value.__ilshift__(other)
|
|
def __irshift__(self, other): return self._value.__irshift__(other)
|
|
def __iand__(self, other): return self._value.__iand__(other)
|
|
def __ixor__(self, other): return self._value.__ixor__(other)
|
|
def __ior__(self, other): return self._value.__ior__(other)
|
|
|
|
def __neg__(self): return self._value.__neg__()
|
|
def __pos__(self): return self._value.__pos__()
|
|
def __abs__(self): return self._value.__abs__()
|
|
def __invert__(self): return self._value.__invert__()
|
|
|
|
def __complex__(self): return complex(self._value)
|
|
def __int__(self): return int(self._value)
|
|
def __float__(self): return float(self._value)
|
|
|
|
def __index__(self): return self._value.__index__()
|
|
|
|
def __round__(self): return self._value.__round__()
|
|
def __trunc__(self): return self._value.__trunc__()
|
|
def __floor__(self): return self._value.__floor__()
|
|
def __ceil__(self): return self._value.__ceil__()
|
|
|
|
def __enter__(self): return self._value.__enter__()
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
return self._value.__exit__(exc_type, exc_value, traceback)
|
|
|
|
def __await__(self): return self._value.__await__()
|
|
def __aiter__(self): return self._value.__aiter__()
|
|
def __anext__(self): return self._value.__anext__()
|
|
def __aenter__(self): return self._value.__aenter__()
|
|
def __aexit__(self, exc_type, exc_value, traceback):
|
|
return self._value.__aexit__(exc_type, exc_value, traceback)
|