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

[Feature] - Support Pydantic 2 and remove support for Pydantic V1 #70

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

cgearing
Copy link
Contributor

@cgearing cgearing commented Aug 11, 2024

This PR updates Flask-Pydantic-Spec to only support Pydantic V2. After this PR is merged, only Pydantic V2 will be supported by Flask-Pydantic-Spec.

As part of this upgrade, we have decided to fully target OpenAPI 3.1 as the output of the library. As such, there are a few changes to keep in mind.

  1. It's not valid in OpenAPI 3.1 for a path to not return some kind of response, and if it does, it should be a successful response. As such, Flask-Pydantic-Spec will now raise an error if there is no response defined for a view.
  2. [ and ] is not allowed in the name of the schema, so we will replace these character with _.
  3. Expands testing to also run against Python 3.12
  4. Updates Flake8 dependency to latest

@cgearing cgearing force-pushed the feature/support-pydantic-2 branch from 664b414 to a8c6f69 Compare August 11, 2024 14:13
@cgearing cgearing force-pushed the feature/support-pydantic-2 branch from ab6158f to a749a3a Compare August 11, 2024 14:16
@cgearing cgearing force-pushed the feature/support-pydantic-2 branch from 36187dc to c51a3f2 Compare August 11, 2024 18:51
pyproject.toml Outdated Show resolved Hide resolved
@FrankTub
Copy link

This would be really awesome! Running into the issue that I can't use this package without downgrading Pydantic to <2

@FrankTub
Copy link

@robyoung , is it realistic to expect this to be merged any time soon and result in a new version?

@robyoung
Copy link
Contributor

Sorry, this totally dropped off my radar.

@FrankTub
Copy link

@robyoung , any idea when this will be available on pypi?

@FrankTub
Copy link

@robyoung , what is needed for this PR to be merged and deployed to pypi?

@@ -18,10 +15,9 @@ class Config:
def __init__(self, **kwargs: Dict[str, Any]) -> None:
self.PATH: str = "apidoc"
self.FILENAME: str = "openapi.json"
self.OPENAPI_VERSION: str = "3.0.3"
self.OPENAPI_VERSION: str = "3.1.0"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to run this locally with a forked repo of this change but this results in an error:

image

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverting it to self.OPENAPI_VERSION: str = "3.0.3" resolves the issue for me.

@eyalruby
Copy link

Please merge

@DonaldNgai
Copy link

@cgearing Could you please merge this?

@FrankTub @robyoung , anyway to push this if @cgearing doesn't respond?

@kedvall
Copy link

kedvall commented Feb 18, 2025

Hello, when trying to test this branch I found a couple of issues.

  1. When sending a request that causes a validation error, Flask crashes.
Steps to reproduce...

Add the following dummy route to examples/flask_demo.py:

@app.route('/api/test-error', methods=['POST'])
@api.validate(body=Data, resp=Response('HTTP_200'))
def test_error():
    return "PASS"

Call the route without passing a Data object in the request body:
http POST localhost:8000/api/test-error

Resulting exception:

 * Serving Flask app 'flask_demo'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:8000
Press CTRL+C to quit
[2025-02-18 17:36:50,449] ERROR in app: Exception on /api/test-error [POST]
Traceback (most recent call last):
  File "/Users/kedvall/dev/flask-pydantic-spec/.venv/lib/python3.10/site-packages/flask/app.py", line 1511, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/kedvall/dev/flask-pydantic-spec/.venv/lib/python3.10/site-packages/flask/app.py", line 919, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/kedvall/dev/flask-pydantic-spec/.venv/lib/python3.10/site-packages/flask/app.py", line 917, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/kedvall/dev/flask-pydantic-spec/.venv/lib/python3.10/site-packages/flask/app.py", line 902, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
  File "/Users/kedvall/dev/flask-pydantic-spec/flask_pydantic_spec/spec.py", line 129, in sync_validate
    return self.backend.validate(
  File "/Users/kedvall/dev/flask-pydantic-spec/flask_pydantic_spec/flask_backend.py", line 200, in validate
    before(request, response, req_validation_error, None)
  File "/Users/kedvall/dev/flask-pydantic-spec/flask_pydantic_spec/utils.py", line 186, in default_before_handler
    "spectree_model": req_validation_error.model.__name__,
AttributeError: 'pydantic_core._pydantic_core.ValidationError' object has no attribute 'model'
127.0.0.1 - - [18/Feb/2025 17:36:50] "POST /api/test-error HTTP/1.1" 500 -

I think this can be fixed via changing .model to .__class__ in default_before_handler and default_after_handler. This should give ValidationError and fixes the exception:

def default_before_handler(
    req: Request, resp: Response, req_validation_error: Any, instance: BaseModel
) -> None:
    """
    default handler called before the endpoint function after the request validation

    :param req: request provided by the web framework
    :param resp: response generated by Flask_Pydantic_Spec that will be returned
        if the validation error is not None
    :param req_validation_error: request validation error
    :param instance: class instance if the endpoint function is a class method
    """
    if req_validation_error:
        logger.info(
            "Validation Error",
            extra={
                "spectree_model": req_validation_error.__class__.__name__,        # .model --> .__class__
                "spectree_validation": req_validation_error.errors(),
            },
        )


def default_after_handler(
    req: Request, resp: Response, resp_validation_error: Any, instance: BaseModel
) -> None:
    """
    default handler called after the response validation

    :param req: request provided by the web framework
    :param resp: response from the endpoint function (if there is no validation error)
        or response validation error
    :param resp_validation_error: response validation error
    :param instance: class instance if the endpoint function is a class method
    """
    if resp_validation_error:
        logger.info(
            "500 Response Validation Error",
            extra={
                "spectree_model": resp_validation_error.__class__.__name__,      # .model --> .__class__
                "spectree_validation": resp_validation_error.errors(),
            },
        )


  1. The model config key schema_extra has been renamed to json_schema_extra and should be updated in examples/flask_demo.py:
class Data(BaseModel):
    uid: str
    limit: int = 5
    vip: bool

    class Config:
        json_schema_extra = {                            # schema_extra --> json_schema_extra
            'example': {
                'uid': 'very_important_user',
                'limit': 10,
                'vip': True,
            }
        }

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

Successfully merging this pull request may close these issues.

6 participants