Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error if validation error contains enum.Enum #54

Open
bruno-robert opened this issue Jul 29, 2022 · 4 comments
Open

Error if validation error contains enum.Enum #54

bruno-robert opened this issue Jul 29, 2022 · 4 comments

Comments

@bruno-robert
Copy link

This error is occurring for me when the body of an endpoint receives a pydantic.BaseModel that contains a field of type Enum and the validation fail, the error message returned is a JSON serialization error instead of a ValidationError.

Example validation that fail correctly:

from flask import Flask
from flask_pydantic import validate
from pydantic import BaseModel, validator

app = Flask(__name__)


class RequestBody(BaseModel):
    format: str



@app.route("/", methods=["POST"])
@validate()
def export(body: RequestBody):
    print(body.format)
    return f"{body.format}"


if __name__ == '__main__':
    app.config["TESTING"] = True
    client = app.test_client()

    valid_data = {"format": "csv"}
    invalid_data = {"format": [123,123]}

    valid_response = client.post("/", json=valid_data)
    print(valid_response.json)

    invalid_response = client.post("/", json=invalid_data)
    print(invalid_response.json)

response:

csv
None
{'validation_error': {'body_params': [{'loc': ['format'], 'msg': 'str type expected', 'type': 'type_error.str'}]}}

the response details why the validation fails

Example validation that fails with a JSON serialization error because of an Enum:

from enum import Enum

from flask import Flask
from flask_pydantic import validate
from pydantic import BaseModel

app = Flask(__name__)


class Formats(Enum):
    CSV = "csv"
    HTML = "html"


class RequestBody(BaseModel):
    format: Formats = Formats.CSV


@app.route("/", methods=["POST"])
@validate()
def export(body: RequestBody):
    return f"{body.format}"


if __name__ == '__main__':
    app.config["TESTING"] = True
    client = app.test_client()

    valid_data = {"format": "csv"}
    invalid_data = {"format": "notcsv"}

    valid_response = client.post("/", json=valid_data)
    print(valid_response.json)
    invalid_response = client.post("/", json=invalid_data)
    print(invalid_response.json)

response (with stack trace):

Traceback (most recent call last):
  File "/Users/bruno/Developer/flask_pydantic_error/main.py", line 34, in <module>
    invalid_response = client.post("/", json=invalid_data)
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/werkzeug/test.py", line 1140, in post
    return self.open(*args, **kw)
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/flask/testing.py", line 217, in open
    return super().open(
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/werkzeug/test.py", line 1089, in open
    response = self.run_wsgi_app(request.environ, buffered=buffered)
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/werkzeug/test.py", line 956, in run_wsgi_app
    rv = run_wsgi_app(self.application, environ, buffered=buffered)
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/werkzeug/test.py", line 1237, in run_wsgi_app
    app_rv = app(environ, start_response)
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/flask/app.py", line 2091, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/flask/app.py", line 2076, in wsgi_app
    response = self.handle_exception(e)
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/flask/app.py", line 2073, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/flask/app.py", line 1519, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/flask/app.py", line 1517, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/flask/app.py", line 1503, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/flask_pydantic/core.py", line 212, in wrapper
    return make_response(jsonify({"validation_error": err}), status_code)
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/flask/json/__init__.py", line 302, in jsonify
    f"{dumps(data, indent=indent, separators=separators)}\n",
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/flask/json/__init__.py", line 132, in dumps
    return _json.dumps(obj, **kwargs)
  File "/Users/bruno/.pyenv/versions/3.10.3/lib/python3.10/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/Users/bruno/.pyenv/versions/3.10.3/lib/python3.10/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/Users/bruno/.pyenv/versions/3.10.3/lib/python3.10/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/Users/bruno/Developer/flask_pydantic_error/venv/lib/python3.10/site-packages/flask/json/__init__.py", line 51, in default
    return super().default(o)
  File "/Users/bruno/.pyenv/versions/3.10.3/lib/python3.10/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Formats is not JSON serializable
None

When I was expecting an response of type:

csv
None
{'validation_error': {'body_params': [{'ctx': {'enum_values': ['csv', 'html']}, 'loc': ['format'], 'msg': "value is not a valid enumeration member; permitted: 'csv', 'html'", 'type': 'type_error.enum'}]}}

It seems like the flask's JSONEncoder is used instead of std json. If I modify flask.json.JSONEncoder's default method in order to add:

if isinstance(o, Enum):
    return o.value

the program functions as expected.

@cardoe
Copy link
Contributor

cardoe commented Aug 2, 2022

Have you tried class Formats(str, Enum):

@bruno-robert
Copy link
Author

Sorry for the delayed reply.
First off, thank you for the response!
I have been very busy lately and haven't had time to try this. I'll update this thread when I do ;)

@aberaut
Copy link

aberaut commented Jan 18, 2023

I'm running into the same issue. @cardoe is the ask here to have all enum classes extend str? This seems like it would have adverse effects on type safety

@cardoe
Copy link
Contributor

cardoe commented Mar 15, 2023

I'm running into the same issue. @cardoe is the ask here to have all enum classes extend str? This seems like it would have adverse effects on type safety

No. You need to extend it with the actual type you are serializing your enum data out with. JSON cannot encode enum's natively so you use another type. So in OPs example they are using str values. So they need to do that. If you use numbers then you should use int. Does that make sense?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants