openapi-generator icon indicating copy to clipboard operation
openapi-generator copied to clipboard

[python-fastapi] Fix additionalProperties support & support pydantic v2

Open mgoltzsche opened this issue 1 year ago • 14 comments

  • Don't let generated model code extend the type specified within the additionalProperties schema (or the array item schema) but the pydantic BaseModel.
  • Support additionalProperties the pydantic v2 way within __dict__: Previously, the generated additional_properties field showed up within the response of the generated API as opposed marshaling the model so that its fields are added to/embedded into the root object. Apparently that is because pydantic v2 does not honour the generated to_dict methods (which would have mapped the object to the correct representation) but, instead, supports additional properties natively by specifying extra=allow within the model_config. Correspondingly, the following changes have been applied:
    • To allow additional fields, specify extra=allow within the model_config.
    • Don't generate the additional_properties field but use __dict__ - users can use pydantic's built-in model.model_extra instead.
    • ~~Don't generate the {to|from}_{dict|json} methods since pydantic is taking care of the model mapping based on the declared fields and model_config - users can use pydantic's model.model_dump[_json] instead.~~
    • Let the generated {to|from}_{dict|json} methods delegate to Pydantic's model_dump[_json] methods.
    • Let the generated API endpoints enable the exclude_unset response marshalling option in order to omit fields from the response that weren't explicitly set by the code. This is so that non-required fields don't show up with null values within the response (which would be invalid according to the OpenAPI spec, unless those fields are explicitly marked as nullable within the OpenAPI schema).
  • Support oneOf and anyOf schemas the Pydantic v2 way:
    • Let the generated model extend the pydantic RootModel.
    • Generate (discriminated) Unions and leave it to Pydantic to validate and figure out the type.
    • Generate model constructors that forcefully set the discriminator field in order to ensure it is included in the marshalled representation (otherwise it would be ignored when not explicitly set due to the exclude_unset marshalling option being enabled).

Closes #19311 Relates to #17703 (might also close that one) Relates to #19454

cc @cbornet @tomplus @krjakbrjak @fa0311 @multani

PR checklist

  • [x] Read the contribution guidelines.
  • [x] Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
  • [x] Run the following to build the project and update samples:
    ./mvnw clean package 
    ./bin/generate-samples.sh ./bin/configs/*.yaml
    ./bin/utils/export_docs_generators.sh
    
    (For Windows users, please run the script in Git BASH) Commit all changed files. This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master. These must match the expectations made by your contribution. You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*. IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • [x] File the PR against the correct branch: master (upcoming 7.6.0 minor release - breaking changes with fallbacks), 8.0.x (breaking changes without fallbacks)
  • [x] If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

mgoltzsche avatar Aug 06 '24 23:08 mgoltzsche

Thanks for the PR! I'll need something like this PR to move off python-pydantic-v1.

I have a question about this bit:

Don't generate the {to|from}_{dict|json} methods since pydantic is taking care of the model mapping based on the declared fields and model_config - users can use pydantic's model.model_dump[_json] instead.

Why remove {to|from}_{dict|json} (and to_str) rather than replace the body of those methods with the right pydantic calls? I imagine this could be a substantial breaking change for some folks.

denosaurtrain avatar Aug 30 '24 03:08 denosaurtrain

Why remove {to|from}_{dict|json} (and to_str) rather than replace the body of those methods with the right pydantic calls? I imagine this could be a substantial breaking change for some folks.

The idea is to let the generated code fully leverage the fastapi/pydantic platform idioms, which include letting the route methods return typed objects that pydantic then maps and validates based on the type annotation. I didn't want to bloat the generated code unnecessarily and I was thinking the python-fastapi generator is young enough so that I could still make such a breaking change. However, to make it backward-compatible, I'll adjust the PR to let it generate those methods again but letting them delegate to pydantic's methods...

mgoltzsche avatar Sep 01 '24 22:09 mgoltzsche

I updated the PR correspondingly but unfortunately this is still a breaking change since

  • nullable field semantics are not honoured anymore - would need to be generated into the type annotation like this maybe or applied via the exclude_unset marshalling option.
  • Discriminator logic for oneOf schemas is not generated - would need to be generated into the type annotation like this.

I can look into making the adjustments some other evening.

mgoltzsche avatar Sep 01 '24 23:09 mgoltzsche

I like it! I anticipate using this as a client to replace python-pydantic-v1. Hopefully a maintainer can chime in/approve when you are ready!

denosaurtrain avatar Sep 05 '24 01:09 denosaurtrain

I also made the generator generate oneOf and anyOf schemas as (discriminated) Unions the Pydantic v2 way and enabled the exclude_unset response option for generated API routes/endpoints (PR description updated correspondingly). The PR is ready for review now.

mgoltzsche avatar Oct 20 '24 00:10 mgoltzsche

Epic! I hope a maintainer can review it for you soon.

denosaurtrain avatar Oct 20 '24 13:10 denosaurtrain

ping @cbornet @tomplus @krjakbrjak @fa0311 @multani

mgoltzsche avatar Nov 14 '24 21:11 mgoltzsche

Came across this while looking to convert an OpenAPI spec to a Python client. Would really like to try this out, but not going to bother until Pydantic V2 support is released. Fingers crossed this PR gets merged soon.

will-uai avatar Jan 07 '25 12:01 will-uai

Same here !I'd also be interested by the support of pydantic v2 :-)

npfp avatar Jan 31 '25 17:01 npfp

I just merged the master branch into this one in order to resolve the merge conflict (an import within a Java test class). However, now the Python test of the generated pydantic v1 client fails due to the DELETE /v2/pet/{petId} endpoint (delete_pet) returning a 404 response when being called during the test data setup of the test_404_error test (prior to the actual 404 response test):

 =========================== short test summary info ============================
FAILED tests/test_api_exception.py::ApiExceptionTests::test_404_error - petstore_api.exceptions.NotFoundException: (404)
Reason: Not Found
HTTP response headers: HTTPHeaderDict({'Date': 'Sat, 08 Mar 2025 16:39:45 GMT', 'Content-Length': '0', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, DELETE, PUT', 'Access-Control-Allow-Headers': 'Content-Type, api_key, Authorization', 'Server': 'Jetty(9.2.9.v20150224)'})
============ 1 failed, 265 passed, 2 skipped, 18 warnings in 5.69s =============

That doesn't seem to be caused by the changes introduced within this PR as the build of #20830 confirms which fails with the same error.

mgoltzsche avatar Mar 08 '25 18:03 mgoltzsche

Turns out the failed python client test was fixed here now. Correspondingly, I applied the fix also to the python-pydantic-v1 client test which failed for the same reason. Also, I rebased the PR.

Now that the merge conflicts are resolved and the CI build succeeds again, this PR is ready for review again.

mgoltzsche avatar Mar 17 '25 21:03 mgoltzsche

Thanks for your efforts with this MR, it would be really great to see this get merged (along with a few of the other python-fastapi fixes).

ampedandwired avatar Mar 21 '25 03:03 ampedandwired

Thank you for your work - as many I probably found this PR because I ran into similar problems with the python-fastapi generator. It would be really nice if the python-fastapi fix PRs could be merged.

srnbckr avatar Jun 11 '25 13:06 srnbckr

I rebased the PR now again to resolve the merge conflicts (which dropped my last commit that fixed the python-pydantic-v1 test since the fix is on the master branch now).

However, now the Python client deserialization test fails with the following error:

tests/test_deserialization.py:301: error: Item "None" of "Union[Cat, Dog, None]" has no attribute "class_name"  [union-attr]
tests/test_deserialization.py:302: error: Item "Dog" of "Union[Cat, Dog, None]" has no attribute "declawed"  [union-attr]
tests/test_deserialization.py:302: error: Item "None" of "Union[Cat, Dog, None]" has no attribute "declawed"  [union-attr]
tests/test_deserialization.py:303: error: Item "None" of "Union[Cat, Dog, None]" has no attribute "to_json"  [union-attr]

Though, that problem is not related to this PR which the master branch build of #21415 shows which also fails with the same error! (The same problem also makes the build of PR #21402 fail.)

Thus, this PR is ready for review again!

mgoltzsche avatar Jun 14 '25 19:06 mgoltzsche

Rebased the PR again to include the python unit test fix of #21423. Now the docs and samples are reported to be outdated but that has nothing to do with this PR but is the case within the master branch.

This PR is ready for review again!

mgoltzsche avatar Jun 21 '25 21:06 mgoltzsche

I rebased the PR again to include the fixes from the main branch.

Please review!

mgoltzsche avatar Jun 25 '25 20:06 mgoltzsche

@wing328 can you review the PR by any chance?

mgoltzsche avatar Jul 01 '25 19:07 mgoltzsche

@mgoltzsche thanks for the PR 🙏

it's a huge change. do you mind pinging me via Slack (https://app.slack.com/client/TLQFRCNJZ/dms) when you've time tomorrow or Friday for a few quick questions on this PR?

wing328 avatar Jul 02 '25 08:07 wing328

Hey guys bumping the pull request would be great to get it in the mainstream branch

unkindypie avatar Nov 03 '25 16:11 unkindypie

Unfortunately it doesn't look like this PR will be merged. @wing328 told me via Slack that it is too big to be reviewed and that the actual Pydantic v2 support should be implemented in the client generator first in form of multiple small incremental PRs since there are more tests for that. Afterwards the changes could be applied to the FastAPI server generator similarly. However, I don't have time for that any time soon.

mgoltzsche avatar Nov 03 '25 23:11 mgoltzsche

thanks for your effort so far!

I can confirm that anyOf and oneOf schemas are also not supported for the client generator, which generates erroneous code for those.

MoritzProg avatar Nov 06 '25 10:11 MoritzProg