luolingchun / flask-openapi3

Generate REST API and OpenAPI documentation for your Flask project.
https://luolingchun.github.io/flask-openapi3/
MIT License
204 stars 34 forks source link

Body throws exception when receiving str instead of dict #26

Closed nor3th closed 2 years ago

nor3th commented 2 years ago

First off thank you very much for your great library. Exactly what I was looking for!!!

Environment:

Using for example this model

class MyModel(BaseModel):
   text: str

and the flask server is looking for the model as body

@page.post("/path/")
def post(body: MyModel):
   pass

, but the user sends the model json formated (thus as str instead of a dict)

response = requests.post(url="localhost:5000/path/", json=mymodel.json())

The wrapper throws an exception, since a str can't be mapped to the MyModel like a dict

ERROR:app:Exception on /path/ [POST]
Traceback (most recent call last):
  File "/home/user/project/venv/lib/python3.10/site-packages/flask/app.py", line 2073, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/user/project/venv/lib/python3.10/site-packages/flask/app.py", line 1518, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/home/user/project/venv/lib/python3.10/site-packages/flask/app.py", line 1516, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/user/project/venv/lib/python3.10/site-packages/flask/app.py", line 1502, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "/home/user/project/venv/lib/python3.10/site-packages/flask_openapi3/api_blueprint.py", line 231, in wrapper
    resp = _do_wrapper(
  File "/home/user/project/venv/lib/python3.10/site-packages/flask_openapi3/do_wrapper.py", line 96, in _do_wrapper
    body_ = body(**request.get_json(silent=True) or {})
TypeError: MyModel() argument after ** must be a mapping, not str

An alternative would be to either do attempt a json.loads() before doing a body(**value) or to return 422?

mkdir700 commented 2 years ago

this is wrong. json shoud be A JSON serializable Python object.

response = requests.post(url="localhost:5000/path/", json=mymodel.json())

i think.

  1. response = requests.post(url="localhost:5000/path/", json=mymodel.dict())
  2. resp = requests.post("http://localhost:5000/", data=mymodel.json(), headers={
    "content-type": "application/json"
    })
nor3th commented 2 years ago

Hey @mkdir700

I agree that the approach with response = requests.post(url="localhost:5000/path/", json=mymodel.json()) is wrong. My point is though that ideally flask-opencti3 would handle a type mismatch better, than throwing an unhandled exception where it is not very obvious for the developer to see what the issue is.

One alternative would be to either check the value type before using the value like here https://github.com/luolingchun/flask-openapi3/blob/master/flask_openapi3/do_wrapper.py#L73-L87 So a solution could be to do (here https://github.com/luolingchun/flask-openapi3/blob/master/flask_openapi3/do_wrapper.py#L92-L96):

value = request.get_json(silent=True, force=True) or {}
if body.__custom_root_type__:
    # https://pydantic-docs.helpmanual.io/usage/models/#custom-root-types
    body_ = body(__root__=value)
else:
    body_ = body(**value)

or to set value to

value = request.get_json(silent=True, force=True) or {}
if not isinstance(value, dict):
   raise ValidationError(f"Unable to process content {value} of type {type(value)}!")
if body.__custom_root_type__:
    # https://pydantic-docs.helpmanual.io/usage/models/#custom-root-types
    body_ = body(__root__=value)
else:
    body_ = body(**value)

Regards

luolingchun commented 2 years ago

To improve the robustness, I will fix it. Support for converting str to dict, which will raise 422 exceptions if the conversion is wrong.

luolingchun commented 2 years ago

release v2.0.0rc2