diff --git a/docs/en/docs/tutorial/request-form-models.md b/docs/en/docs/tutorial/request-form-models.md index 00ab1c341..7f1ddd57c 100644 --- a/docs/en/docs/tutorial/request-form-models.md +++ b/docs/en/docs/tutorial/request-form-models.md @@ -81,7 +81,7 @@ Say, for example, you were generating an HTML form from a model, and that model had a boolean field in it that you wanted to display as a checkbox with a default `True` value: -{* ../../docs_src/request_form_models/tutorial003_an_py39.py hl[10,18:22] *} +{* ../../docs_src/request_form_models/tutorial003_an_py39.py hl[11,10:23] *} This works as expected when the checkbox remains checked, the form encoded data in the request looks like this: @@ -130,7 +130,7 @@ Validation context is a pydantic v2 only feature! /// -{* ../../docs_src/request_form_models/tutorial004_an_py39.py hl[3,12:24] *} +{* ../../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! 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..0340d7d7d 100644 --- a/docs_src/request_form_models/tutorial004_pv1.py +++ b/docs_src/request_form_models/tutorial004_pv1.py @@ -1,18 +1,19 @@ -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, root_validator + class MyModel(BaseModel): checkbox: bool = True - @model_validator(mode="before") + @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 + 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..d0b45003f 100644 --- a/docs_src/request_form_models/tutorial004_pv1_an_py39.py +++ b/docs_src/request_form_models/tutorial004_pv1_an_py39.py @@ -1,20 +1,21 @@ 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, root_validator + class MyModel(BaseModel): checkbox: bool = True - @model_validator(mode="before") + @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 + 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: