🎨 [pre-commit.ci] Auto format from pre-commit.com hooks

This commit is contained in:
pre-commit-ci[bot] 2025-04-15 02:47:42 +00:00
parent f63e983b60
commit cad08bbc4d
8 changed files with 56 additions and 34 deletions

View File

@ -97,7 +97,7 @@ and the JSON response is also correct:
``` ```
When the checkbox is *unchecked*, though, something strange happens. 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`! 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, 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. 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, 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 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!). (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, in the `before` mode - before the defaults from the model are applied,
to differentiate between an explicit `False` value and an unset value. 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`` - We also don't want to just treat any time the value is unset as ``False`` -
that would defeat the purpose of the default! 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.* 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 So we can additionally use the `'fastapi_field'` passed to the

View File

@ -1,12 +1,14 @@
from pydantic import BaseModel
from fastapi import FastAPI, Form, Request from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from jinja2 import DictLoader, Environment from jinja2 import DictLoader, Environment
from pydantic import BaseModel
class MyModel(BaseModel): class MyModel(BaseModel):
checkbox: bool = True checkbox: bool = True
form_template = """ form_template = """
<form action="/form" method="POST"> <form action="/form" method="POST">
{% for field_name, field in model.model_fields.items() %} {% for field_name, field in model.model_fields.items() %}
@ -31,12 +33,14 @@ templates = Jinja2Templates(env=Environment(loader=loader))
app = FastAPI() app = FastAPI()
@app.get("/form", response_class=HTMLResponse) @app.get("/form", response_class=HTMLResponse)
async def show_form(request: Request): async def show_form(request: Request):
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="form.html", context={"model": MyModel} request=request, name="form.html", context={"model": MyModel}
) )
@app.post('/form')
@app.post("/form")
async def submit_form(data: MyModel = Form()) -> MyModel: async def submit_form(data: MyModel = Form()) -> MyModel:
return data return data

View File

@ -1,14 +1,16 @@
from typing import Annotated from typing import Annotated
from pydantic import BaseModel
from fastapi import FastAPI, Form, Request from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from jinja2 import DictLoader, Environment from jinja2 import DictLoader, Environment
from pydantic import BaseModel
class MyModel(BaseModel): class MyModel(BaseModel):
checkbox: bool = True checkbox: bool = True
form_template = """ form_template = """
<form action="/form" method="POST"> <form action="/form" method="POST">
{% for field_name, field in model.model_fields.items() %} {% for field_name, field in model.model_fields.items() %}
@ -16,7 +18,7 @@ form_template = """
<label for="{{ field_name }}">{{ field_name }}</label> <label for="{{ field_name }}">{{ field_name }}</label>
{% if field.annotation.__name__ == "bool" %} {% if field.annotation.__name__ == "bool" %}
<input type="checkbox" name="{{field_name}}" <input type="checkbox" name="{{field_name}}"
{% if field.default %} {% if field.default %}
checked="checked" checked="checked"
{% endif %} {% endif %}
> >
@ -33,12 +35,14 @@ templates = Jinja2Templates(env=Environment(loader=loader))
app = FastAPI() app = FastAPI()
@app.get("/form", response_class=HTMLResponse) @app.get("/form", response_class=HTMLResponse)
async def show_form(request: Request): async def show_form(request: Request):
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="form.html", context={"model": MyModel} request=request, name="form.html", context={"model": MyModel}
) )
@app.post('/form')
@app.post("/form")
async def submit_form(data: Annotated[MyModel, Form()]) -> MyModel: async def submit_form(data: Annotated[MyModel, Form()]) -> MyModel:
return data return data

View File

@ -1,8 +1,9 @@
from pydantic import BaseModel, ValidationInfo, model_validator
from fastapi import FastAPI, Form, Request from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from jinja2 import DictLoader, Environment from jinja2 import DictLoader, Environment
from pydantic import BaseModel, ValidationInfo, model_validator
class MyModel(BaseModel): class MyModel(BaseModel):
checkbox: bool = True checkbox: bool = True
@ -10,15 +11,15 @@ class MyModel(BaseModel):
@model_validator(mode="before") @model_validator(mode="before")
def handle_defaults(cls, value: dict, info: ValidationInfo) -> dict: def handle_defaults(cls, value: dict, info: ValidationInfo) -> dict:
# if this model is being used outside of fastapi, return normally # 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 return value
# check if we are being validated from form input, # check if we are being validated from form input,
# and if so, treat the unset checkbox as False # 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" is_form = type(field_info).__name__ == "Form"
if is_form and 'checkbox' not in value: if is_form and "checkbox" not in value:
value['checkbox'] = False value["checkbox"] = False
return value return value
@ -29,7 +30,7 @@ form_template = """
<label for="{{ field_name }}">{{ field_name }}</label> <label for="{{ field_name }}">{{ field_name }}</label>
{% if field.annotation.__name__ == "bool" %} {% if field.annotation.__name__ == "bool" %}
<input type="checkbox" name="{{field_name}}" <input type="checkbox" name="{{field_name}}"
{% if field.default %} {% if field.default %}
checked="checked" checked="checked"
{% endif %} {% endif %}
> >
@ -46,12 +47,14 @@ templates = Jinja2Templates(env=Environment(loader=loader))
app = FastAPI() app = FastAPI()
@app.get("/form", response_class=HTMLResponse) @app.get("/form", response_class=HTMLResponse)
async def show_form(request: Request): async def show_form(request: Request):
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="form.html", context={"model": MyModel} request=request, name="form.html", context={"model": MyModel}
) )
@app.post('/form')
@app.post("/form")
async def submit_form(data: MyModel = Form()) -> MyModel: async def submit_form(data: MyModel = Form()) -> MyModel:
return data return data

View File

@ -1,10 +1,11 @@
from typing import Annotated from typing import Annotated
from pydantic import BaseModel, ValidationInfo, model_validator
from fastapi import FastAPI, Form, Request from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from jinja2 import DictLoader, Environment from jinja2 import DictLoader, Environment
from pydantic import BaseModel, ValidationInfo, model_validator
class MyModel(BaseModel): class MyModel(BaseModel):
checkbox: bool = True checkbox: bool = True
@ -12,15 +13,15 @@ class MyModel(BaseModel):
@model_validator(mode="before") @model_validator(mode="before")
def handle_defaults(cls, value: dict, info: ValidationInfo) -> dict: def handle_defaults(cls, value: dict, info: ValidationInfo) -> dict:
# if this model is being used outside of fastapi, return normally # 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 return value
# check if we are being validated from form input, # check if we are being validated from form input,
# and if so, treat the unset checkbox as False # 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" is_form = type(field_info).__name__ == "Form"
if is_form and 'checkbox' not in value: if is_form and "checkbox" not in value:
value['checkbox'] = False value["checkbox"] = False
return value return value
@ -31,7 +32,7 @@ form_template = """
<label for="{{ field_name }}">{{ field_name }}</label> <label for="{{ field_name }}">{{ field_name }}</label>
{% if field.annotation.__name__ == "bool" %} {% if field.annotation.__name__ == "bool" %}
<input type="checkbox" name="{{field_name}}" <input type="checkbox" name="{{field_name}}"
{% if field.default %} {% if field.default %}
checked="checked" checked="checked"
{% endif %} {% endif %}
> >
@ -48,12 +49,14 @@ templates = Jinja2Templates(env=Environment(loader=loader))
app = FastAPI() app = FastAPI()
@app.get("/form", response_class=HTMLResponse) @app.get("/form", response_class=HTMLResponse)
async def show_form(request: Request): async def show_form(request: Request):
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="form.html", context={"model": MyModel} request=request, name="form.html", context={"model": MyModel}
) )
@app.post('/form')
@app.post("/form")
async def submit_form(data: Annotated[MyModel, Form()]) -> MyModel: async def submit_form(data: Annotated[MyModel, Form()]) -> MyModel:
return data return data

View File

@ -1,8 +1,9 @@
from pydantic import BaseModel, model_validator
from fastapi import FastAPI, Form, Request from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from jinja2 import DictLoader, Environment from jinja2 import DictLoader, Environment
from pydantic import BaseModel, model_validator
class MyModel(BaseModel): class MyModel(BaseModel):
checkbox: bool = True checkbox: bool = True
@ -11,8 +12,8 @@ class MyModel(BaseModel):
def handle_defaults(cls, value: dict) -> dict: def handle_defaults(cls, value: dict) -> dict:
# We can't tell if we're being validated by fastAPI, # We can't tell if we're being validated by fastAPI,
# so we have to just YOLO this. # so we have to just YOLO this.
if 'checkbox' not in value: if "checkbox" not in value:
value['checkbox'] = False value["checkbox"] = False
return value return value
@ -23,7 +24,7 @@ form_template = """
<label for="{{ field_name }}">{{ field_name }}</label> <label for="{{ field_name }}">{{ field_name }}</label>
{% if field.annotation.__name__ == "bool" %} {% if field.annotation.__name__ == "bool" %}
<input type="checkbox" name="{{field_name}}" <input type="checkbox" name="{{field_name}}"
{% if field.default %} {% if field.default %}
checked="checked" checked="checked"
{% endif %} {% endif %}
> >
@ -40,12 +41,14 @@ templates = Jinja2Templates(env=Environment(loader=loader))
app = FastAPI() app = FastAPI()
@app.get("/form", response_class=HTMLResponse) @app.get("/form", response_class=HTMLResponse)
async def show_form(request: Request): async def show_form(request: Request):
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="form.html", context={"model": MyModel} request=request, name="form.html", context={"model": MyModel}
) )
@app.post('/form')
@app.post("/form")
async def submit_form(data: MyModel = Form()) -> MyModel: async def submit_form(data: MyModel = Form()) -> MyModel:
return data return data

View File

@ -1,10 +1,11 @@
from typing import Annotated from typing import Annotated
from pydantic import BaseModel, model_validator
from fastapi import FastAPI, Form, Request from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from jinja2 import DictLoader, Environment from jinja2 import DictLoader, Environment
from pydantic import BaseModel, model_validator
class MyModel(BaseModel): class MyModel(BaseModel):
checkbox: bool = True checkbox: bool = True
@ -13,8 +14,8 @@ class MyModel(BaseModel):
def handle_defaults(cls, value: dict) -> dict: def handle_defaults(cls, value: dict) -> dict:
# We can't tell if we're being validated by fastAPI, # We can't tell if we're being validated by fastAPI,
# so we have to just YOLO this. # so we have to just YOLO this.
if 'checkbox' not in value: if "checkbox" not in value:
value['checkbox'] = False value["checkbox"] = False
return value return value
@ -25,7 +26,7 @@ form_template = """
<label for="{{ field_name }}">{{ field_name }}</label> <label for="{{ field_name }}">{{ field_name }}</label>
{% if field.annotation.__name__ == "bool" %} {% if field.annotation.__name__ == "bool" %}
<input type="checkbox" name="{{field_name}}" <input type="checkbox" name="{{field_name}}"
{% if field.default %} {% if field.default %}
checked="checked" checked="checked"
{% endif %} {% endif %}
> >
@ -42,12 +43,14 @@ templates = Jinja2Templates(env=Environment(loader=loader))
app = FastAPI() app = FastAPI()
@app.get("/form", response_class=HTMLResponse) @app.get("/form", response_class=HTMLResponse)
async def show_form(request: Request): async def show_form(request: Request):
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="form.html", context={"model": MyModel} request=request, name="form.html", context={"model": MyModel}
) )
@app.post('/form')
@app.post("/form")
async def submit_form(data: Annotated[MyModel, Form()]) -> MyModel: async def submit_form(data: Annotated[MyModel, Form()]) -> MyModel:
return data return data

View File

@ -126,7 +126,9 @@ if PYDANTIC_V2:
) -> Tuple[Any, Union[List[Dict[str, Any]], None]]: ) -> Tuple[Any, Union[List[Dict[str, Any]], None]]:
try: try:
return ( 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, None,
) )
except ValidationError as exc: except ValidationError as exc: