We have a discriminator field type
which we want to hide from the Swagger UI docs:
class Foo(BDCBaseModel):
type: Literal["Foo"] = Field("Foo", exclude=True)
Name: str
class Bar(BDCBaseModel):
type: Literal["Bar"] = Field("Bar", exclude=True)
Name: str
class Demo(BDCBaseModel):
example: Union[Foo, Bar] = Field(discriminator="type")
The following router:
@router.post("/demo")
async def demo(
foo: Foo,
):
demo = Demo(example=foo)
return demo
And this is shown in the Swagger docs:
We don't want the user to see the type field as it is useless for him/her anyways.
We tried making the field private: _type
which hides it from the docs but then it cannot be used as discriminator anymore:
class Demo(BDCBaseModel):
File "pydantic\main.py", line 205, in pydantic.main.ModelMetaclass.__new__
File "pydantic\fields.py", line 491, in pydantic.fields.ModelField.infer
File "pydantic\fields.py", line 421, in pydantic.fields.ModelField.__init__
File "pydantic\fields.py", line 537, in pydantic.fields.ModelField.prepare
File "pydantic\fields.py", line 639, in pydantic.fields.ModelField._type_analysis
File "pydantic\fields.py", line 753, in pydantic.fields.ModelField.prepare_discriminated_union_sub_fields
File "pydantic\utils.py", line 739, in pydantic.utils.get_discriminator_alias_and_values
pydantic.errors.ConfigError: Model 'Foo' needs a discriminator field for key '_type'
This is a very common situation and the solution is farily simple. Factor out that type
field into its own separate model.
The typical way to go about this is to create one FooBase
with all the fields, validators etc. that all child models will share (in this example only name
) and then subclass it as needed. In this example you would create one Foo
subclass with that type
field that you then use for the Demo
annotation, and one FooRequest
class without any additions.
Here is a full working example:
from typing import Literal, Union
from fastapi import FastAPI
from pydantic import BaseModel, Field
class FooBase(BaseModel):
name: str
class FooRequest(FooBase):
pass # possibly configure other request specific things here
class Foo(FooBase):
type: Literal["Foo"] = Field("Foo", exclude=True)
class Config:
orm_mode = True
class Bar(BaseModel):
type: Literal["Bar"] = Field("Bar", exclude=True)
name: str
class Demo(BaseModel):
example: Union[Foo, Bar] = Field(discriminator="type")
api = FastAPI()
@api.post("/demo")
async def demo(foo: FooRequest):
foo = Foo.from_orm(foo)
return Demo(example=foo)
Note that I used the orm_mode = True
setting just to have a very concise way of converting a FooRequest
instance into a Foo
instance inside the route handler function. This is not necessary. You could also just do foo = Foo.parse_obj(foo.dict())
there.
Also, the addition of the FooRequest
model is redundant here of course. You can just as well use the FooBase
as the request model. I wrote it this way just to demonstrate a typical pattern because sometimes the request model has additional things that distinguish it from its siblings. In your example it is overkill.