flask-openapi3
flask-openapi3 copied to clipboard
Issues using flask-openapi3 when its views are decorated
Environment:
- Python version: 312
- Operating system:
- Flask version:
- flask-openapi3 version:
This affects all versions. Flask-openapi3 removes all arguments passed to the view by any previous callers -- such as Dependency Injection frameworks, breaking things in the meanwhile.
In request._validate_request all arguments kwargs are removed as they are treated as path args. This is however not always a correct assumption as views from flask-openapi3 may be decorated again before being passed onto flask.
In this case flask calls the views decorated elsewhere which supply some arguments to flask-openapi3 which then simply removes them instead of passing them down. There are a few flask extensions which do this as well.
In the below example the call chain is as follows flask -> inject_services -> flask_openapi3.
Example:
@app.get("/greet")
def view(greeter: GreeterService, query: GreetQuery):
return greeter.greet(query.name)
...
# Automatically decorate all flask views so that known services are passed down.
app.view_functions = {k: inject_services(v) for k, v in app.view_functions.items()}
...
Not an entirely similar situation, but something that we run into.
We use decorators to provide authentication checks. A similar approach is flask-login.
@app.route("/settings")
@login_required
def settings():
pass
Using this sort of set-up together with openapi3 means:
- flask handles incoming request (routing)
- flask-openapi3 checks incoming query-string, body, etc via the
_validate_request(validation) - decorators come into play (authorization)
Meaning our authentication decorator goes off AFTER the payload, query-string and all that has been checked. So we get validation before authorization! Which is in my opinion a bad pattern.
Personally, I think it's better to first check;
- Route existence
- Authorization
- Validation
I don't see a easy solution to this, since the current set-up with _validate_request is on a high level.
Perhaps move the _validate_request logic into a decorator? Then we control the flow by the order of decorators.
Then you could remove all the validation_error_callback logic and just document how to catch ValidationError in a errorhandler on app level. Now you're trying to do everything, which isn't needed IMO.
@luolingchun This is quite a big issue we face using your project. The validation before authentication is very undesirable.
I'm working on a PR with these ergonomics. What do you think? @maldoinc @puittenbroek
@api_view.route("/protected")
class ProtectedAPIView:
decorators = [require_auth_decorator]
@api_view.doc(summary="get protected resource")
def get(self, query: BookQuery):
return "protected data"
@api_view.route("/profile")
class ProfileAPIViewWithInjection:
@api_view.doc(summary="get user profile", decorators=[inject_user_decorator])
def get(self, query: BookQuery, user=None):
"""View receives user object injected by decorator"""
return {"message": f"Profile for {user['name']}", "user_id": user["id"], "role": user["role"]}
@app.post("/books", summary="Create book", decorators=[inject_user_decorator])
def create_book(body: BookBody, user=None):
return {"title": body.title, "created_by": user["name"], "user_id": user["id"]}
Hi @odie5533 check the documentation, in the meantime we've added a decorator which will delayed the validation.
https://github.com/luolingchun/flask-openapi3/blob/3e9b220d2f98867ab7da3a184aca24e99de96485/docs/Usage/Request.md#validate_request
This solves the issues for my team.
No longer using this so I'll close, however I still stand by the original request(shown in the example above) in that this package should not blindly remove all passed arguments.