diff --git a/swh/core/api/__init__.py b/swh/core/api/__init__.py --- a/swh/core/api/__init__.py +++ b/swh/core/api/__init__.py @@ -436,6 +436,11 @@ A function with no argument that returns an instance of `backend_class`. If unset, defaults to calling `backend_class` constructor directly. + + For each method 'do_x()' of the ``backend_factory``, subclasses may implement + two methods: ``pre_do_x(self, kw)`` and ``post_do_x(self, ret, kw)`` that will + be called respectively before and after ``do_x(**kw)``. ``kw`` is the dict + of request parameters, and ``ret`` is the return value of ``do_x(**kw)``. """ request_class = BytesRequest @@ -447,6 +452,9 @@ """Value of `extra_decoders` passed to `json_loads` or `msgpack_loads` to be able to deserialize more object types.""" + method_decorators: List[Callable[[Callable], Callable]] = [] + """List of decorators to all methods generated from the ``backend_class``.""" + def __init__(self, *args, backend_class=None, backend_factory=None, **kwargs): super().__init__(*args, **kwargs) self.add_backend_class(backend_class, backend_factory) @@ -466,12 +474,27 @@ def __add_endpoint(self, meth_name, meth, backend_factory): from flask import request - @self.route("/" + meth._endpoint_path, methods=["POST"]) @negotiate(MsgpackFormatter, extra_encoders=self.extra_type_encoders) @negotiate(JSONFormatter, extra_encoders=self.extra_type_encoders) @functools.wraps(meth) # Copy signature and doc - def _f(): + def f(): # Call the actual code + pre_hook = getattr(self, f"pre_{meth_name}", None) + post_hook = getattr(self, f"post_{meth_name}", None) obj_meth = getattr(backend_factory(), meth_name) kw = decode_request(request, extra_decoders=self.extra_type_decoders) - return obj_meth(**kw) + + if pre_hook is not None: + pre_hook(kw) + + ret = obj_meth(**kw) + + if post_hook is not None: + post_hook(ret, kw) + + return ret + + for decorator in self.method_decorators: + f = decorator(f) + + self.route("/" + meth._endpoint_path, methods=["POST"])(f)