Since the @route
decorator has to register the view with the current callback given to the decorator, it has to be the outermost decorator to receive the correct function to invoke when handling a request.
This creates a possible situation where a view has been decorated, but since the decorators are in the wrong order, the decorated function is not invoked. If used for decorating views that require the user to be logged in, have a certain role or have a specific flag, the check will be left out silently.
Our current fix is to have the standard action be to deny access to the resource, then requiring a decorator to allow access. In that case, if the decorator isn't invoked when the request is being handled, the request will fail.
But there are use-cases where this becomes cumbersome since it requires you to decorate all views, except for those few that should be exempt. For a pure hierarchical layout this may work, but for checking single flags the structure can get complicated.
Is there a proper way to detect that we're being invoked in a useful place in the decoratory hierarchy? I.e. can we detect that there hasn't already been a route
decorator applied to function we get to wrap?
# wrapped in wrong order - @require_administrator should be after @app.route
@require_administrator
@app.route('/users', methods=['GET'])
Implemented as:
def require_administrator(func):
@functools.wraps(func)
def has_administrator(*args, **kwargs):
if not getattr(g, 'user') or not g.user.is_administrator:
abort(403)
return func(*args, **kwargs)
return has_administrator
Here I'd like to detect if my custom decorator is being wrapped after @app.route
, and thus, never will be invoked when the request is handled.
Using functools.wraps
replaces the wrapped function with the new one in all ways, so looking at __name__
of the function to be wrapped will fail. This also happens at each step of the decorator wrapping process.
I've tried looking at both traceback
and inspect
, but haven't found any decent way of determining if the sequence is correct.
Update
My currently best solution is to check the called function name against the set of registered endpoints. But since a Route()
decorator can change the name of the endpoint, I'll have to support that for my decorator as well in that case, and it'll silently pass if a different function has used the same endpoint name as the current function.
It also have to iterate the set of registered endpoints since I weren't able to find a simple way to check if just the endpoint name exists (possibly more efficient by attempting to build an URL with it and catch the exception).
def require_administrator_checked(func):
for rule in app.url_map.iter_rules():
if func.__name__ == rule.endpoint:
raise DecoratorOrderError(f"Wrapped endpoint '{rule.endpoint}' has already been registered - wrong order of decorators?")
# as above ..