Fastapi: [BUG] OpenAPI schema generation sometimes fails

Created on 25 Jun 2019  ·  16Comments  ·  Source: tiangolo/fastapi

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:

  • OS: Ubuntu
  • FastAPI version: 0.30.0
  • Pydantic version: 0.28
  • Python version: 3.7.3
bug

Most helpful comment

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

All 16 comments

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 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
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:

Was this page helpful?
0 / 5 - 0 ratings