Describe the bug
FastAPI version 0.30.0
Pydantic version 0.28
I recently upgraded to these versions, and the server is now (sometimes) hitting a ValidationError while trying to generate the OpenAPI spec (rendering the swagger docs broken).
I haven't yet been able to produce a reproducible example (I think because the behavior is non-deterministic and is more likely to occur when there are many models present), but I dug into it a little and I think I have pinpointed the source of the problem: fastapi.utils.get_model_definitions is occasionally returning some of its values as None. (Strangely, this is non-deterministic; on each refresh of the docs page, a different subset of the keys have value None; I'm guessing this has to do with the fact that the relevant function iterates over a set, in a non-deterministic order.)
If I comment the following line, all the issues go away:
def get_model_definitions(
*, flat_models: Set[Type[BaseModel]], model_name_map: Dict[Type[BaseModel], str]
) -> Dict[str, Any]:
definitions: Dict[str, Dict] = {}
for model in flat_models:
m_schema, m_definitions = model_process_schema(
model, model_name_map=model_name_map, ref_prefix=REF_PREFIX
)
# definitions.update(m_definitions) # THIS LINE COMMENTED
model_name = model_name_map[model]
definitions[model_name] = m_schema
return definitions
(I am confident this is not introducing other problems, at least for _my_ entire app, because with this change I am able to use the openapi.json to generate precisely the same openapi client I was generating before I upgraded.)
I dug into the model_process_schema function to see how m_definitions was getting None as a value in some cases, and I believe this is coming from pydantic.schema.field_type_schema or pydantic.schema.field_singleton_schema (and probably the singleton one; that's the only place I saw None ever being set as a value).
It looks like None only gets set if the field is not a subclass of BaseModel, but I can confirm that all of the models involved are subclasses of BaseModel. (Besides, if this were the problem, I wouldn't expect commenting the above mentioned line to fix the problem).
If I figure out more I'll update this issue, but any guesses about what's going wrong would be appreciated!
(This problem doesn't seem to have had any effect on the handling of any requests besides the one for generating the OpenAPI spec.)
Environment:
Okay, I think I've figured this out.
In pydantic 0.28, it looks like the field_singleton_schema function will set the definition to None if the model has already been encountered:
# pydantic/schema.py
def field_singleton_schema(
...
if issubclass(field_type, pydantic.BaseModel):
model_name = model_name_map[field_type]
if field_type not in known_models:
sub_schema, sub_definitions = model_process_schema(
field_type,
by_alias=by_alias,
model_name_map=model_name_map,
ref_prefix=ref_prefix,
known_models=known_models,
)
definitions.update(sub_definitions)
definitions[model_name] = sub_schema
else:
definitions[model_name] = None # critical line
...
I assume this is related to handling self-referencing schemas.
The problem occurs (or can occur) when the same model is referenced multiple times in the schema after it has already "had its turn" in the get_model_definitions function. Then it could get set to None in m_definitions, and then it will be updated in definitions, and since it already had its turn in the iteration over flat_models, it may end up not being replaced with a non-None value again.
If I understand correctly, it is not necessary to update definitions with the contents of m_definitions; or at the very least, it should only be done for items whose value is not None. I explicitly tested this change with both self-referencing and multi-same-sub-referencing models, and the generated schemas were correct.
I'll try to put together a simple reproducible example where it fails.
Here's a self contained example -- this fails for me about 50% of the time:
from pydantic import BaseModel
from fastapi import FastAPI
app = FastAPI()
class Model(BaseModel):
pass
class Model2(BaseModel):
a: Model
b: Model
class Model3(BaseModel):
c: Model
d: Model2
@app.get("/", response_model=Model3)
def f():
pass
print(app.openapi())
Sometimes it passes, sometimes it raises the following error:
File "tests/test_get_openapi.py", line 27, in <module>
print(app.openapi())
File "/Users/dmontague/.local/share/virtualenvs/fastapi-GEv7EKkN/lib/python3.6/site-packages/fastapi/applications.py", line 79, in openapi
openapi_prefix=self.openapi_prefix,
File "/Users/dmontague/.local/share/virtualenvs/fastapi-GEv7EKkN/lib/python3.6/site-packages/fastapi/openapi/utils.py", line 270, in get_openapi
return jsonable_encoder(OpenAPI(**output), by_alias=True, include_none=False)
File "/Users/dmontague/.local/share/virtualenvs/fastapi-GEv7EKkN/lib/python3.6/site-packages/pydantic/main.py", line 246, in __init__
values, fields_set, _ = validate_model(self, data)
File "/Users/dmontague/.local/share/virtualenvs/fastapi-GEv7EKkN/lib/python3.6/site-packages/pydantic/main.py", line 665, in validate_model
raise ValidationError(errors)
pydantic.error_wrappers.ValidationError: 4 validation errors
schemas -> Model
value is not a valid dict (type=type_error.dict)
schemas -> Model
value is not a valid dict (type=type_error.dict)
components -> schemas
value is not none (type=type_error.none.allowed)
components
value is not none (type=type_error.none.allowed)
does it fail the same on 3.7 ?
On Tue, Jun 25, 2019 at 11:40 AM dmontagu notifications@github.com wrote:
Here's a self contained example -- this fails for me about 50% of the time:
from pydantic import BaseModel
from fastapi import FastAPIapp = FastAPI()
class Model(BaseModel):
passclass Model2(BaseModel):
a: Model
b: Modelclass Model3(BaseModel):
c: Model
d: Model2@app.get("/", response_model=Model3)def f():
passprint(app.openapi())
Sometimes it passes, sometimes it raises the following error:
File "tests/test_get_openapi.py", line 27, in
print(app.openapi())
File "/Users/dmontague/.local/share/virtualenvs/fastapi-GEv7EKkN/lib/python3.6/site-packages/fastapi/applications.py", line 79, in openapi
openapi_prefix=self.openapi_prefix,
File "/Users/dmontague/.local/share/virtualenvs/fastapi-GEv7EKkN/lib/python3.6/site-packages/fastapi/openapi/utils.py", line 270, in get_openapi
return jsonable_encoder(OpenAPI(**output), by_alias=True, include_none=False)
File "/Users/dmontague/.local/share/virtualenvs/fastapi-GEv7EKkN/lib/python3.6/site-packages/pydantic/main.py", line 246, in __init__
values, fields_set, _ = validate_model(self, data)
File "/Users/dmontague/.local/share/virtualenvs/fastapi-GEv7EKkN/lib/python3.6/site-packages/pydantic/main.py", line 665, in validate_model
raise ValidationError(errors)
pydantic.error_wrappers.ValidationError: 4 validation errors
schemas -> Model
value is not a valid dict (type=type_error.dict)
schemas -> Model
value is not a valid dict (type=type_error.dict)
components -> schemas
value is not none (type=type_error.none.allowed)
components
value is not none (type=type_error.none.allowed)—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/tiangolo/fastapi/issues/332?email_source=notifications&email_token=AAINSPVE5Q2MOIAELPVNBEDP4HRZ7A5CNFSM4H3DGGE2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODYPVDIY#issuecomment-505368995,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAINSPW4TWQEMTHDTKHMJ6DP4HRZ7ANCNFSM4H3DGGEQ
.
--
benoit barthelet
http://pgp.mit.edu/pks/lookup?op=get&search=0xF150E01A72F6D2EE
@euri10 see https://github.com/tiangolo/fastapi/pull/333 -- that pull request now has a test that fails for me 100% of the time without the above change.
I believe this may also address the issue from https://github.com/tiangolo/fastapi/pull/326 and https://github.com/samuelcolvin/pydantic/pull/613 as well, but I couldn't tell if it was exactly the same problem. In any case, I did confirm that with this change (possibly also without; I didn't test that) I was able to generate a valid openapi.json for schemas including self-referential models.
@dmontagu I made the change on Pydantic since it looked like the issue (yup the exact issue this one addresses) is happening on Pydantic which in turn affects FastAPI but the solution I wrote seemed pretty hacky by using a ‘magic’ key in the definitions object and just naively removing it prior to the final output
Thanks for the report and debugging @dmontagu, @euri10 , and @wongpat . It's solved in https://github.com/samuelcolvin/pydantic/pull/621. I'll update once we get a new Pydantic release with that fix.
samuelcolvin/pydantic#621 is now released.
I am currently encountering this issue, is there any timetable on when this might be integrated into FastAPI and released?
I am doing a demo next week and I need the Swagger docs to generate, so I might need to revert to a previous version of FastAPI. Just trying to decide whether to bite the bullet now.
I confirm this is happening with 0.31.0 too, but I guess it is expected since pydantic is still 0.29
also with error with this code:
class PnlDetail(BaseModel):
gross_pnl: float
taxes: float = None
comissions: float = None
total_pnl: float
class PnL(PnlDetail):
operation: UUID
dep: PnlDetail = None
ind: PnlDetail = None
waiting for fastapi with pydantic==0.30
Cheers!
Thanks everyone!
The latest version of FastAPI, 0.33.0, includes Pydantic 0.30.0. That should fix these problems.
Could you please update and check it with your specific cases? :heavy_check_mark: :rocket:
pip install --upgrade fastapi
The update worked for me. Thanks @tiangolo ✔️ 🚀
Also for me. Thanks
Great, thanks for reporting!
Let's wait for @dmontagu to give it a try.
Yes it’s fixed for me too, thanks!
Thanks @dmontagu for reporting back and closing the issue! :tada:
Most helpful comment
Thanks everyone!
The latest version of FastAPI,
0.33.0, includes Pydantic0.30.0. That should fix these problems.Could you please update and check it with your specific cases? :heavy_check_mark: :rocket: