Request body validation errors are very hard to follow when using Literal polymorphism #9039
-
Describe the bugWhen using See below for a concrete example. (In a way this is not really a bug report, but I wouldn't call it a feature request either...) My guess is that this could be resolved if FastAPI were to support a future DiscriminatedUnion from pydantic. To ReproduceSorry for the 100 lines + example, but I think it makes the point more clear. NOTE: The error in the submitted payload is that I passed in an invalid
import uuid
from enum import Enum
from typing import Dict, List, Union
try:
from typing import Literal
except ImportError:
from typing_extensions import Literal
from fastapi import FastAPI
from pydantic import BaseModel, validator
class UrlStatus(str, Enum):
published = "published"
archived = "archived"
class PageStatus(str, Enum):
draft = "draft"
published = "published"
archived = "archived"
class UrlRoute(BaseModel):
path: str
locale: str
class Url(BaseModel):
id: uuid.UUID = None
previous_id: uuid.UUID = None
status: UrlStatus = UrlStatus.published
routes: List[UrlRoute]
name: str = ""
@validator("id", always=True, pre=True)
def ensure_id(cls, v):
return v or uuid.uuid4()
class UrlReference(BaseModel):
type: Literal["url"] = "url"
url: Url
params: Dict[str, str] = {}
query: Dict[str, str] = {}
class Revision(BaseModel):
id: uuid.UUID = None
previous_id: uuid.UUID = None
name: str = ""
@validator("id", always=True, pre=True)
def ensure_id(cls, v):
return v or uuid.uuid4()
class _Layer(BaseModel):
id: uuid.UUID = None
previous_id: uuid.UUID = None
type: str
subtype: str = ""
name: str = ""
references: Dict[str, UrlReference] = {}
@validator("id", always=True, pre=True)
def ensure_id(cls, v):
return v or uuid.uuid4()
class Conditional(_Layer):
type: Literal["conditional"] = "conditional"
children: List[Union["SectionGroup", "Section", "Content"]] = []
class Content(_Layer):
type: Literal["content"] = "content"
class ContentGroup(_Layer):
type: Literal["contentgroup"] = "contentgroup"
children: List[Content] = []
class Cell(_Layer):
type: Literal["cell"] = "cell"
children: List[Union[Conditional, Content, ContentGroup]] = []
class Section(_Layer):
type: Literal["section"] = "section"
children: List[Cell] = []
class SectionGroup(_Layer):
type: Literal["sectiongroup"] = "sectiongroup"
children: List[Union[Section, Conditional]] = []
Conditional.update_forward_refs()
class PageArea(_Layer):
type: Literal["pagearea"] = "pagearea"
children: List[Union[SectionGroup, Section, Conditional]] = []
class Page(_Layer):
type: Literal["page"] = "page"
status: PageStatus = PageStatus.draft
revision: Revision
url: Url
children: List[PageArea] = []
app = FastAPI()
@app.post("/pages")
def create_page(page: Page):
return page
{
"type": "page",
"revision": {},
"url": {
"status": "published",
"routes": [
{
"path": "/",
"locale": "en"
}
],
"name": "Home"
},
"children": [
{
"type": "pagearea",
"children": [
{
"type": "section",
"children": [
{
"type": "cell",
"references": [{"url": {}}]
}
]
}
]
}
]
}
The following validation error response is returned: {
"detail": [
{
"loc": [
"body",
"page",
"children",
0,
"children",
0,
"type"
],
"msg": "unexpected value; permitted: 'sectiongroup'",
"type": "value_error.const",
"ctx": {
"given": "section",
"permitted": [
"sectiongroup"
]
}
},
{
"loc": [
"body",
"page",
"children",
0,
"children",
0,
"children",
0,
"type"
],
"msg": "unexpected value; permitted: 'section'",
"type": "value_error.const",
"ctx": {
"given": "cell",
"permitted": [
"section"
]
}
},
{
"loc": [
"body",
"page",
"children",
0,
"children",
0,
"children",
0,
"references"
],
"msg": "value is not a valid dict",
"type": "type_error.dict"
},
{
"loc": [
"body",
"page",
"children",
0,
"children",
0,
"children",
0,
"type"
],
"msg": "unexpected value; permitted: 'conditional'",
"type": "value_error.const",
"ctx": {
"given": "cell",
"permitted": [
"conditional"
]
}
},
{
"loc": [
"body",
"page",
"children",
0,
"children",
0,
"children",
0,
"references"
],
"msg": "value is not a valid dict",
"type": "type_error.dict"
},
{
"loc": [
"body",
"page",
"children",
0,
"children",
0,
"children",
0,
"references"
],
"msg": "value is not a valid dict",
"type": "type_error.dict"
},
{
"loc": [
"body",
"page",
"children",
0,
"children",
0,
"type"
],
"msg": "unexpected value; permitted: 'conditional'",
"type": "value_error.const",
"ctx": {
"given": "section",
"permitted": [
"conditional"
]
}
},
{
"loc": [
"body",
"page",
"children",
0,
"children",
0,
"children",
0,
"type"
],
"msg": "unexpected value; permitted: 'sectiongroup'",
"type": "value_error.const",
"ctx": {
"given": "cell",
"permitted": [
"sectiongroup"
]
}
},
{
"loc": [
"body",
"page",
"children",
0,
"children",
0,
"children",
0,
"references"
],
"msg": "value is not a valid dict",
"type": "type_error.dict"
},
{
"loc": [
"body",
"page",
"children",
0,
"children",
0,
"children",
0,
"type"
],
"msg": "unexpected value; permitted: 'section'",
"type": "value_error.const",
"ctx": {
"given": "cell",
"permitted": [
"section"
]
}
},
{
"loc": [
"body",
"page",
"children",
0,
"children",
0,
"children",
0,
"references"
],
"msg": "value is not a valid dict",
"type": "type_error.dict"
},
{
"loc": [
"body",
"page",
"children",
0,
"children",
0,
"children",
0,
"type"
],
"msg": "unexpected value; permitted: 'content'",
"type": "value_error.const",
"ctx": {
"given": "cell",
"permitted": [
"content"
]
}
},
{
"loc": [
"body",
"page",
"children",
0,
"children",
0,
"children",
0,
"references"
],
"msg": "value is not a valid dict",
"type": "type_error.dict"
}
]
}EnvironmentTested with fastapi 0.52.0. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
|
Sharing a workaround we found over here for this functionality -- Then we can just refer to the types like so: |
Beta Was this translation helpful? Give feedback.
-
|
Discriminated unions are now supported! |
Beta Was this translation helpful? Give feedback.
Discriminated unions are now supported!
And it makes error responses much clearer