mirror of https://github.com/tiangolo/fastapi.git
rm being able to determine the input format of a model
This commit is contained in:
parent
fec0a068ed
commit
d3ccab4948
|
|
@ -107,33 +107,34 @@ and [the field will be omitted altogether if unchecked](https://developer.mozill
|
|||
When dealing with form models with defaults,
|
||||
we need to take special care to handle cases where the field being *unset* has a specific meaning.
|
||||
|
||||
We also don't want to just treat any time the value is unset as ``False`` -
|
||||
that would defeat the purpose of the default!
|
||||
We want to specifically correct the behavior when it is used in the context of a *form.*
|
||||
|
||||
In some cases, we can resolve the problem by changing or removing the default,
|
||||
but we don't always have that option -
|
||||
particularly when the model is used in other places than the form
|
||||
(model reuse is one of the benefits of building FastAPI on top of pydantic, after all!).
|
||||
|
||||
To do this, you can use a [`model_validator`](https://docs.pydantic.dev/latest/concepts/validators/#model-validators)
|
||||
in the `before` mode - before the defaults from the model are applied,
|
||||
to differentiate between an explicit `False` value and an unset value.
|
||||
|
||||
We also don't want to just treat any time the value is unset as ``False`` -
|
||||
that would defeat the purpose of the default!
|
||||
We want to specifically correct the behavior when it is used in the context of a *form.*
|
||||
|
||||
So we can additionally use the `'fastapi_field'` passed to the
|
||||
[validation context](https://docs.pydantic.dev/latest/concepts/validators/#validation-context)
|
||||
to determine whether our model is being validated from form input.
|
||||
The recommended approach, however, is to duplicate your model:
|
||||
|
||||
/// note
|
||||
|
||||
Validation context is a pydantic v2 only feature!
|
||||
Take care to ensure that your duplicate models don't diverge,
|
||||
particularly if you are using sqlmodel,
|
||||
where you will end up with `MyModel`, `MyModelCreate`, and `MyModelCreateForm`.
|
||||
|
||||
Also remember to make sure that anywhere else you use this model adds the appropriate
|
||||
switching logic and static type annotations to accommodate receiving both the original class
|
||||
and the model we make as a workaround for form handling.
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/request_form_models/tutorial004_an_py39.py hl[7,13:25] *}
|
||||
|
||||
And with that, our form model should behave as expected when it is used with a form,
|
||||
JSON input, or elsewhere in the program!
|
||||
And with that, one of our models should behave as expected when used with a form,
|
||||
and you can switch between it and other permutations of the model for JSON input
|
||||
or any other format you may need to handle!
|
||||
|
||||
## Summary
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,20 @@
|
|||
from typing import cast
|
||||
|
||||
from fastapi import FastAPI, Form, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from jinja2 import DictLoader, Environment
|
||||
from pydantic import BaseModel, ValidationInfo, model_validator
|
||||
from pydantic import BaseModel, model_validator
|
||||
|
||||
|
||||
class MyModel(BaseModel):
|
||||
checkbox: bool = True
|
||||
|
||||
@model_validator(mode="before")
|
||||
def handle_defaults(cls, value: dict, info: ValidationInfo) -> dict:
|
||||
# if this model is being used outside of fastapi, return normally
|
||||
if info.context is None or "fastapi_field" not in info.context:
|
||||
return value
|
||||
|
||||
# check if we are being validated from form input,
|
||||
# and if so, treat the unset checkbox as False
|
||||
field_info = info.context["fastapi_field"].field_info
|
||||
is_form = type(field_info).__name__ == "Form"
|
||||
if is_form and "checkbox" not in value:
|
||||
class MyModelForm(MyModel):
|
||||
@model_validator(mode="before")
|
||||
def handle_defaults(cls, value: dict) -> dict:
|
||||
if "checkbox" not in value:
|
||||
value["checkbox"] = False
|
||||
return value
|
||||
|
||||
|
|
@ -56,5 +52,6 @@ async def show_form(request: Request):
|
|||
|
||||
|
||||
@app.post("/form")
|
||||
async def submit_form(data: MyModel = Form()) -> MyModel:
|
||||
async def submit_form(data: MyModelForm = Form()) -> MyModel:
|
||||
data = cast(MyModel, data)
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated
|
||||
from typing import Annotated, cast
|
||||
|
||||
from fastapi import FastAPI, Form, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
|
@ -10,17 +10,11 @@ from pydantic import BaseModel, ValidationInfo, model_validator
|
|||
class MyModel(BaseModel):
|
||||
checkbox: bool = True
|
||||
|
||||
|
||||
class MyModelForm(MyModel):
|
||||
@model_validator(mode="before")
|
||||
def handle_defaults(cls, value: dict, info: ValidationInfo) -> dict:
|
||||
# if this model is being used outside of fastapi, return normally
|
||||
if info.context is None or "fastapi_field" not in info.context:
|
||||
return value
|
||||
|
||||
# check if we are being validated from form input,
|
||||
# and if so, treat the unset checkbox as False
|
||||
field_info = info.context["fastapi_field"].field_info
|
||||
is_form = type(field_info).__name__ == "Form"
|
||||
if is_form and "checkbox" not in value:
|
||||
if "checkbox" not in value:
|
||||
value["checkbox"] = False
|
||||
return value
|
||||
|
||||
|
|
@ -58,5 +52,6 @@ async def show_form(request: Request):
|
|||
|
||||
|
||||
@app.post("/form")
|
||||
async def submit_form(data: Annotated[MyModel, Form()]) -> MyModel:
|
||||
async def submit_form(data: Annotated[MyModelForm, Form()]) -> MyModel:
|
||||
data = cast(MyModel, data)
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from typing import cast
|
||||
|
||||
from fastapi import FastAPI, Form, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
|
@ -8,10 +10,10 @@ from pydantic import BaseModel, root_validator
|
|||
class MyModel(BaseModel):
|
||||
checkbox: bool = True
|
||||
|
||||
|
||||
class MyModelForm(BaseModel):
|
||||
@root_validator(pre=True)
|
||||
def handle_defaults(cls, value: dict) -> dict:
|
||||
# We can't tell if we're being validated by fastAPI,
|
||||
# so we have to just YOLO this.
|
||||
if "checkbox" not in value:
|
||||
value["checkbox"] = False
|
||||
return value
|
||||
|
|
@ -50,5 +52,6 @@ async def show_form(request: Request):
|
|||
|
||||
|
||||
@app.post("/form")
|
||||
async def submit_form(data: MyModel = Form()) -> MyModel:
|
||||
async def submit_form(data: MyModelForm = Form()) -> MyModel:
|
||||
data = cast(MyModel, data)
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated
|
||||
from typing import Annotated, cast
|
||||
|
||||
from fastapi import FastAPI, Form, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
|
@ -10,10 +10,10 @@ from pydantic import BaseModel, root_validator
|
|||
class MyModel(BaseModel):
|
||||
checkbox: bool = True
|
||||
|
||||
|
||||
class MyModelForm(MyModel):
|
||||
@root_validator(pre=True)
|
||||
def handle_defaults(cls, value: dict) -> dict:
|
||||
# We can't tell if we're being validated by fastAPI,
|
||||
# so we have to just YOLO this.
|
||||
if "checkbox" not in value:
|
||||
value["checkbox"] = False
|
||||
return value
|
||||
|
|
@ -52,5 +52,6 @@ async def show_form(request: Request):
|
|||
|
||||
|
||||
@app.post("/form")
|
||||
async def submit_form(data: Annotated[MyModel, Form()]) -> MyModel:
|
||||
async def submit_form(data: Annotated[MyModelForm, Form()]) -> MyModel:
|
||||
data = cast(MyModel, data)
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -126,9 +126,7 @@ if PYDANTIC_V2:
|
|||
) -> Tuple[Any, Union[List[Dict[str, Any]], None]]:
|
||||
try:
|
||||
return (
|
||||
self._type_adapter.validate_python(
|
||||
value, from_attributes=True, context={"fastapi_field": self}
|
||||
),
|
||||
self._type_adapter.validate_python(value, from_attributes=True),
|
||||
None,
|
||||
)
|
||||
except ValidationError as exc:
|
||||
|
|
|
|||
Loading…
Reference in New Issue