From cad08bbc4d82450fd7730fe4252512c621457065 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 02:47:42 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20format?= =?UTF-8?q?=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/tutorial/request-form-models.md | 8 ++++---- docs_src/request_form_models/tutorial003.py | 8 ++++++-- .../request_form_models/tutorial003_an_py39.py | 10 +++++++--- docs_src/request_form_models/tutorial004.py | 17 ++++++++++------- .../request_form_models/tutorial004_an_py39.py | 17 ++++++++++------- docs_src/request_form_models/tutorial004_pv1.py | 13 ++++++++----- .../tutorial004_pv1_an_py39.py | 13 ++++++++----- fastapi/_compat.py | 4 +++- 8 files changed, 56 insertions(+), 34 deletions(-) diff --git a/docs/en/docs/tutorial/request-form-models.md b/docs/en/docs/tutorial/request-form-models.md index 00ab1c341..885dfc96e 100644 --- a/docs/en/docs/tutorial/request-form-models.md +++ b/docs/en/docs/tutorial/request-form-models.md @@ -97,7 +97,7 @@ and the JSON response is also correct: ``` When the checkbox is *unchecked*, though, something strange happens. -The submitted form data is *empty*, +The submitted form data is *empty*, and the returned JSON data still shows `checkbox` still being `true`! This is because checkboxes in HTML forms don't work exactly like the boolean inputs we expect, @@ -108,7 +108,7 @@ 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. In some cases, we can resolve the problem by changing or removing the default, -but we don't always have that option - +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!). @@ -116,8 +116,8 @@ To do this, you can use a [`model_validator`](https://docs.pydantic.dev/latest/c 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 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 diff --git a/docs_src/request_form_models/tutorial003.py b/docs_src/request_form_models/tutorial003.py index 5c2b3dd31..b0cbd6ff3 100644 --- a/docs_src/request_form_models/tutorial003.py +++ b/docs_src/request_form_models/tutorial003.py @@ -1,12 +1,14 @@ -from pydantic import BaseModel 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 + class MyModel(BaseModel): checkbox: bool = True + form_template = """
{% for field_name, field in model.model_fields.items() %} @@ -31,12 +33,14 @@ templates = Jinja2Templates(env=Environment(loader=loader)) app = FastAPI() + @app.get("/form", response_class=HTMLResponse) async def show_form(request: Request): return templates.TemplateResponse( request=request, name="form.html", context={"model": MyModel} ) -@app.post('/form') + +@app.post("/form") async def submit_form(data: MyModel = Form()) -> MyModel: return data diff --git a/docs_src/request_form_models/tutorial003_an_py39.py b/docs_src/request_form_models/tutorial003_an_py39.py index f8e9668b4..2c7f6a4b7 100644 --- a/docs_src/request_form_models/tutorial003_an_py39.py +++ b/docs_src/request_form_models/tutorial003_an_py39.py @@ -1,14 +1,16 @@ from typing import Annotated -from pydantic import BaseModel 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 + class MyModel(BaseModel): checkbox: bool = True + form_template = """ {% for field_name, field in model.model_fields.items() %} @@ -16,7 +18,7 @@ form_template = """ {% if field.annotation.__name__ == "bool" %} @@ -33,12 +35,14 @@ templates = Jinja2Templates(env=Environment(loader=loader)) app = FastAPI() + @app.get("/form", response_class=HTMLResponse) async def show_form(request: Request): return templates.TemplateResponse( request=request, name="form.html", context={"model": MyModel} ) -@app.post('/form') + +@app.post("/form") async def submit_form(data: Annotated[MyModel, Form()]) -> MyModel: return data diff --git a/docs_src/request_form_models/tutorial004.py b/docs_src/request_form_models/tutorial004.py index 26582986b..1dfc6f80d 100644 --- a/docs_src/request_form_models/tutorial004.py +++ b/docs_src/request_form_models/tutorial004.py @@ -1,8 +1,9 @@ -from pydantic import BaseModel, ValidationInfo, model_validator 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 + class MyModel(BaseModel): checkbox: bool = True @@ -10,15 +11,15 @@ class MyModel(BaseModel): @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: + 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 + field_info = info.context["fastapi_field"].field_info is_form = type(field_info).__name__ == "Form" - if is_form and 'checkbox' not in value: - value['checkbox'] = False + if is_form and "checkbox" not in value: + value["checkbox"] = False return value @@ -29,7 +30,7 @@ form_template = """ {% if field.annotation.__name__ == "bool" %} @@ -46,12 +47,14 @@ templates = Jinja2Templates(env=Environment(loader=loader)) app = FastAPI() + @app.get("/form", response_class=HTMLResponse) async def show_form(request: Request): return templates.TemplateResponse( request=request, name="form.html", context={"model": MyModel} ) -@app.post('/form') + +@app.post("/form") async def submit_form(data: MyModel = Form()) -> MyModel: return data diff --git a/docs_src/request_form_models/tutorial004_an_py39.py b/docs_src/request_form_models/tutorial004_an_py39.py index 2529576b8..d1d1fb64f 100644 --- a/docs_src/request_form_models/tutorial004_an_py39.py +++ b/docs_src/request_form_models/tutorial004_an_py39.py @@ -1,10 +1,11 @@ from typing import Annotated -from pydantic import BaseModel, ValidationInfo, model_validator 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 + class MyModel(BaseModel): checkbox: bool = True @@ -12,15 +13,15 @@ class MyModel(BaseModel): @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: + 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 + field_info = info.context["fastapi_field"].field_info is_form = type(field_info).__name__ == "Form" - if is_form and 'checkbox' not in value: - value['checkbox'] = False + if is_form and "checkbox" not in value: + value["checkbox"] = False return value @@ -31,7 +32,7 @@ form_template = """ {% if field.annotation.__name__ == "bool" %} @@ -48,12 +49,14 @@ templates = Jinja2Templates(env=Environment(loader=loader)) app = FastAPI() + @app.get("/form", response_class=HTMLResponse) async def show_form(request: Request): return templates.TemplateResponse( request=request, name="form.html", context={"model": MyModel} ) -@app.post('/form') + +@app.post("/form") async def submit_form(data: Annotated[MyModel, Form()]) -> MyModel: return data diff --git a/docs_src/request_form_models/tutorial004_pv1.py b/docs_src/request_form_models/tutorial004_pv1.py index b0b9de62e..8c7f35670 100644 --- a/docs_src/request_form_models/tutorial004_pv1.py +++ b/docs_src/request_form_models/tutorial004_pv1.py @@ -1,8 +1,9 @@ -from pydantic import BaseModel, model_validator 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, model_validator + class MyModel(BaseModel): checkbox: bool = True @@ -11,8 +12,8 @@ class MyModel(BaseModel): 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 + if "checkbox" not in value: + value["checkbox"] = False return value @@ -23,7 +24,7 @@ form_template = """ {% if field.annotation.__name__ == "bool" %} @@ -40,12 +41,14 @@ templates = Jinja2Templates(env=Environment(loader=loader)) app = FastAPI() + @app.get("/form", response_class=HTMLResponse) async def show_form(request: Request): return templates.TemplateResponse( request=request, name="form.html", context={"model": MyModel} ) -@app.post('/form') + +@app.post("/form") async def submit_form(data: MyModel = Form()) -> MyModel: return data diff --git a/docs_src/request_form_models/tutorial004_pv1_an_py39.py b/docs_src/request_form_models/tutorial004_pv1_an_py39.py index ac80f53dd..8c51553cb 100644 --- a/docs_src/request_form_models/tutorial004_pv1_an_py39.py +++ b/docs_src/request_form_models/tutorial004_pv1_an_py39.py @@ -1,10 +1,11 @@ from typing import Annotated -from pydantic import BaseModel, model_validator 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, model_validator + class MyModel(BaseModel): checkbox: bool = True @@ -13,8 +14,8 @@ class MyModel(BaseModel): 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 + if "checkbox" not in value: + value["checkbox"] = False return value @@ -25,7 +26,7 @@ form_template = """ {% if field.annotation.__name__ == "bool" %} @@ -42,12 +43,14 @@ templates = Jinja2Templates(env=Environment(loader=loader)) app = FastAPI() + @app.get("/form", response_class=HTMLResponse) async def show_form(request: Request): return templates.TemplateResponse( request=request, name="form.html", context={"model": MyModel} ) -@app.post('/form') + +@app.post("/form") async def submit_form(data: Annotated[MyModel, Form()]) -> MyModel: return data diff --git a/fastapi/_compat.py b/fastapi/_compat.py index a6ec1764d..072aa5658 100644 --- a/fastapi/_compat.py +++ b/fastapi/_compat.py @@ -126,7 +126,9 @@ 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, context={"fastapi_field": self} + ), None, ) except ValidationError as exc: