Conversation
e4b1e51 to
9df6297
Compare
|
Also need to add tests of this change since it seems none got broken. The following prints something that changed: import json
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
def get_stuff(x: str | None = None):
return {'x': x}
print(json.dumps(app.openapi(), indent=2))but obviously needs a real test |
|
I test this using |
|
Thanks for the work on this. This PR does resolve issues with removing the extra In the below example, (For reference: FastAPI before pydantic v2 with from fastapi import FastAPI
from pydantic import BaseModel, Field
from pydantic.json_schema import SkipJsonSchema
app = FastAPI()
class Model(BaseModel):
required: str
optional: str | None = None
optional_better: str | SkipJsonSchema[None] = Field(
default=None, json_schema_extra=lambda x: x.pop("default")
) # From https://github.com/pydantic/pydantic/pull/6653#issuecomment-1646211152
@app.get("/echo/{required}", response_model=Model, response_model_exclude_none=True)
def echo(required: str, optional: str | None = None, optional_better: str | None = None):
return Model(required=required, optional=optional, optional_better=optional_better) |
|
Finally found the issue here, I opened this discussion with the same issue. |
|
@tiangolo from what I can see, this doesn't seem like a new feature, since the schema generation with Pydantic v2 is not the same as v1, and is not correct from what I can see from the OpenAPI spec.
|
|
Any recent news on this much-needed feature? |
|
For anyone who is still encountering this problem that stumbles upon this stalled PR, the below snippet can be used for both FastAPI router parameters and Pydantic models. from typing import TYPE_CHECKING, Annotated, Generic, List, TypeVar, Union
from pydantic import BaseModel, Field
from pydantic.json_schema import SkipJsonSchema
if TYPE_CHECKING:
from typing import Optional as OptionalParam
else:
class OptionalParam:
"""Hold-over typing solution for cleaner Swagger UI rendering.
Accomplishes two things:
1) Optional typing within Annotated that appeases MyPy
while properly showing endpoint parameter types in the Swagger UI.
2) Eliminates the ugly [type, null] arrays shown for Schemas in the Swagger UI,
simply showing optional fields without the red asterisk.
Credit: https://github.com/pydantic/pydantic/issues/6647#issuecomment-1646227125
FastAPI Solution Pending: https://github.com/tiangolo/fastapi/pull/9873
Usage within a Pydantic model ::
class BetterOptional(BaseModel):
x: OptionalParam[str] = Field(
None, description="No array of `[str, null]` in UI"
)
y: OptionalParam[int]
Usage within FastAPI view function parameters ::
@api.get("/d", response_model=BetterOptional)
async def d(
x: Annotated[OptionalParam[str], Query()] = None,
y: Annotated[OptionalParam[int], Query()] = None,
z: Annotated[OptionalParam[bool], Query()] = False
):
'''Temporary fix.'''
"""
def __class_getitem__(cls, item):
return Annotated[
Union[item, SkipJsonSchema[None]],
Field(json_schema_extra=lambda x: x.pop("default", None))
] |
|
I am also interested in a solution to this problem |
|
A generic way for me to suppress this ugly anyof null that break openapi generator was to rewrite the fastapi scheme output like: def handle_anyof_nullable(schema: dict):
"""Recursively modifies the schema to handle anyOf with null for OpenAPI 3.0 compatibility."""
if isinstance(schema, dict):
for key, value in list(
schema.items()
): # Iterate over a copy to avoid modification errors
if key == "anyOf" and isinstance(value, list):
non_null_types = [item for item in value if item.get("type") != "null"]
if len(value) > len(non_null_types): # Found 'null' in anyOf
if len(non_null_types) == 1:
schema.update(non_null_types[0]) # Replace with non-null type
schema["nullable"] = True
del schema[key] # Remove anyOf
else:
logger.warning(
f"Complex anyOf with multiple non-null types at key '{key}'. Review manually."
)
else:
handle_anyof_nullable(value)
elif isinstance(schema, list):
for item in schema:
handle_anyof_nullable(item)
def downgrade_openapi_schema_to_3_0(schema: dict) -> dict:
"""Downgrades an OpenAPI schema from 3.1 to 3.0, handling anyOf with null."""
logger.info("Customizing OpenAPI schema for 3.0 compatibility...")
handle_anyof_nullable(schema)
return schema
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="my API",
openapi_version="3.0.1",
version="1.0.0",
description="my OpenAPI schema",
routes=app.routes,
)
# Here, modify the openapi_schema as needed to ensure 3.0.0 compatibility
# For example, adjust for `nullable` fields compatibility
openapi_schema = downgrade_openapi_schema_to_3_0(openapi_schema)
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi |
@matt7aylor This is a nice workaround. |
|
I just tried this PR with this example: and got: |
|
Also taking a look at this summary of related issues: |
maurerle
left a comment
There was a problem hiding this comment.
This looks good, except for the _mode access.
Can you fix this and rebase onto master? :)
| ) | ||
|
|
||
| for key, mode, schema in inputs: | ||
| self.mode = mode |
There was a problem hiding this comment.
| self.mode = mode | |
| self._mode = mode |
The mode itself does not have a setter
|
|
||
| json_schemas_map: dict[tuple[JsonSchemaKeyT, JsonSchemaMode], DefsRef] = {} | ||
| for key, mode, schema in inputs: | ||
| self.mode = mode |
There was a problem hiding this comment.
| self.mode = mode | |
| self._mode = mode |
The mode itself does not have a setter
|
Any progress on this? |
|
This pull request has a merge conflict that needs to be resolved. |
|
Thanks for the effort with this! I just added support for both Pydantic v2 and But as GenerateJsonSchema is just not compatible with v1 models (and there's no straightforward way to make it compatible, I really tried 😅), I had to write a lot of extra custom code, copying parts from v1 and making them compatible with v2 models, etc. So, having a custom GenerateJsonSchema is not gonna be feasible for now (and until v1 is removed here). So, for now, I'll close this one, but thanks @Kludex and everyone here! 🍰 |


I need to fix the tests... 👀