From c7adb75b8ea9ebb0f1718a76870dfb5ba8977bcb Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Tue, 14 Apr 2026 18:19:12 -0700 Subject: [PATCH] drop pydantic v1/v2 switching in forms defaults tests --- tests/test_forms_defaults.py | 171 ++++++++++++++--------------------- 1 file changed, 66 insertions(+), 105 deletions(-) diff --git a/tests/test_forms_defaults.py b/tests/test_forms_defaults.py index 16a45a170c..a12a329b17 100644 --- a/tests/test_forms_defaults.py +++ b/tests/test_forms_defaults.py @@ -2,17 +2,9 @@ from typing import Annotated import pytest from fastapi import FastAPI, Form -from fastapi._compat import PYDANTIC_V2 -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, model_validator from starlette.testclient import TestClient -from .utils import needs_pydanticv2 - -if PYDANTIC_V2: - from pydantic import model_validator -else: - from pydantic import root_validator - def _validate_input(value: dict) -> dict: """ @@ -33,16 +25,9 @@ class Parent(BaseModel): init_input: dict # importantly, no default here - if PYDANTIC_V2: - - @model_validator(mode="before") - def validate_inputs(cls, value: dict) -> dict: - return _validate_input(value) - else: - - @root_validator(pre=True) - def validate_inputs(cls, value: dict) -> dict: - return _validate_input(value) + @model_validator(mode="before") + def validate_inputs(cls, value: dict) -> dict: + return _validate_input(value) class StandardModel(Parent): @@ -63,29 +48,29 @@ class FieldModel(Parent): true_if_unset: bool | None = Field(default=None) -if PYDANTIC_V2: +class AnnotatedFieldModel(Parent): + default_true: Annotated[bool, Field(default=True)] + default_false: Annotated[bool, Field(default=False)] + default_none: Annotated[bool | None, Field(default=None)] + default_zero: Annotated[int, Field(default=0)] + default_str: Annotated[str, Field(default="foo")] + true_if_unset: Annotated[bool | None, Field(default=None)] - class AnnotatedFieldModel(Parent): - default_true: Annotated[bool, Field(default=True)] - default_false: Annotated[bool, Field(default=False)] - default_none: Annotated[bool | None, Field(default=None)] - default_zero: Annotated[int, Field(default=0)] - default_str: Annotated[str, Field(default="foo")] - true_if_unset: Annotated[bool | None, Field(default=None)] - class AnnotatedFormModel(Parent): - default_true: Annotated[bool, Form(default=True)] - default_false: Annotated[bool, Form(default=False)] - default_none: Annotated[bool | None, Form(default=None)] - default_zero: Annotated[int, Form(default=0)] - default_str: Annotated[str, Form(default="foo")] - true_if_unset: Annotated[bool | None, Form(default=None)] +class AnnotatedFormModel(Parent): + default_true: Annotated[bool, Form(default=True)] + default_false: Annotated[bool, Form(default=False)] + default_none: Annotated[bool | None, Form(default=None)] + default_zero: Annotated[int, Form(default=0)] + default_str: Annotated[str, Form(default="foo")] + true_if_unset: Annotated[bool | None, Form(default=None)] - class SimpleForm(BaseModel): - """https://github.com/fastapi/fastapi/pull/13464#issuecomment-2708378172""" - foo: Annotated[str, Form(default="bar")] - alias_with: Annotated[str, Form(alias="with", default="nothing")] +class SimpleForm(BaseModel): + """https://github.com/fastapi/fastapi/pull/13464#issuecomment-2708378172""" + + foo: Annotated[str, Form(default="bar")] + alias_with: Annotated[str, Form(alias="with", default="nothing")] class ResponseModel(BaseModel): @@ -98,26 +83,16 @@ class ResponseModel(BaseModel): @classmethod def from_value(cls, value: Parent) -> "ResponseModel": - if PYDANTIC_V2: - return ResponseModel( - init_input=value.init_input, - fields_set=list(value.model_fields_set), - dumped_fields_no_exclude=value.model_dump(), - dumped_fields_exclude_default=value.model_dump(exclude_defaults=True), - dumped_fields_exclude_unset=value.model_dump(exclude_unset=True), - dumped_fields_no_meta=value.model_dump( - exclude={"init_input", "fields_set"} - ), - ) - else: - return ResponseModel( - init_input=value.init_input, - fields_set=list(value.__fields_set__), - dumped_fields_no_exclude=value.dict(), - dumped_fields_exclude_default=value.dict(exclude_defaults=True), - dumped_fields_exclude_unset=value.dict(exclude_unset=True), - dumped_fields_no_meta=value.dict(exclude={"init_input", "fields_set"}), - ) + return ResponseModel( + init_input=value.init_input, + fields_set=list(value.model_fields_set), + dumped_fields_no_exclude=value.model_dump(), + dumped_fields_exclude_default=value.model_dump(exclude_defaults=True), + dumped_fields_exclude_unset=value.model_dump(exclude_unset=True), + dumped_fields_no_meta=value.model_dump( + exclude={"init_input", "fields_set"} + ), + ) app = FastAPI() @@ -133,19 +108,18 @@ async def form_field(value: Annotated[FieldModel, Form()]) -> ResponseModel: return ResponseModel.from_value(value) -if PYDANTIC_V2: +@app.post("/form/annotated-field") +async def form_annotated_field( + value: Annotated[AnnotatedFieldModel, Form()], +) -> ResponseModel: + return ResponseModel.from_value(value) - @app.post("/form/annotated-field") - async def form_annotated_field( - value: Annotated[AnnotatedFieldModel, Form()], - ) -> ResponseModel: - return ResponseModel.from_value(value) - @app.post("/form/annotated-form") - async def form_annotated_form( - value: Annotated[AnnotatedFormModel, Form()], - ) -> ResponseModel: - return ResponseModel.from_value(value) +@app.post("/form/annotated-form") +async def form_annotated_form( + value: Annotated[AnnotatedFormModel, Form()], +) -> ResponseModel: + return ResponseModel.from_value(value) @app.post("/form/inlined") @@ -188,34 +162,28 @@ async def json_field(value: FieldModel) -> ResponseModel: return ResponseModel.from_value(value) -if PYDANTIC_V2: +@app.post("/json/annotated-field") +async def json_annotated_field(value: AnnotatedFieldModel) -> ResponseModel: + return ResponseModel.from_value(value) - @app.post("/json/annotated-field") - async def json_annotated_field(value: AnnotatedFieldModel) -> ResponseModel: - return ResponseModel.from_value(value) - @app.post("/json/annotated-form") - async def json_annotated_form(value: AnnotatedFormModel) -> ResponseModel: - return ResponseModel.from_value(value) +@app.post("/json/annotated-form") +async def json_annotated_form(value: AnnotatedFormModel) -> ResponseModel: + return ResponseModel.from_value(value) - @app.post("/simple-form") - def form_endpoint(model: Annotated[SimpleForm, Form()]) -> dict: - """https://github.com/fastapi/fastapi/pull/13464#issuecomment-2708378172""" - return model.model_dump() +@app.post("/simple-form") +def form_endpoint(model: Annotated[SimpleForm, Form()]) -> dict: + """https://github.com/fastapi/fastapi/pull/13464#issuecomment-2708378172""" + return model.model_dump() -if PYDANTIC_V2: - MODEL_TYPES = { - "standard": StandardModel, - "field": FieldModel, - "annotated-field": AnnotatedFieldModel, - "annotated-form": AnnotatedFormModel, - } -else: - MODEL_TYPES = { - "standard": StandardModel, - "field": FieldModel, - } + +MODEL_TYPES = { + "standard": StandardModel, + "field": FieldModel, + "annotated-field": AnnotatedFieldModel, + "annotated-form": AnnotatedFormModel, +} ENCODINGS = ("form", "json") @@ -260,18 +228,12 @@ def test_no_prefill_defaults_partially_set(encoding, model_type, client): data = {"true_if_unset": False, "default_false": True, "default_zero": 0} res = client.post(endpoint, json=data) - if PYDANTIC_V2: - dumped_exclude_unset = MODEL_TYPES[model_type](**data).model_dump( - exclude_unset=True - ) - dumped_exclude_default = MODEL_TYPES[model_type](**data).model_dump( - exclude_defaults=True - ) - else: - dumped_exclude_unset = MODEL_TYPES[model_type](**data).dict(exclude_unset=True) - dumped_exclude_default = MODEL_TYPES[model_type](**data).dict( - exclude_defaults=True - ) + dumped_exclude_unset = MODEL_TYPES[model_type](**data).model_dump( + exclude_unset=True + ) + dumped_exclude_default = MODEL_TYPES[model_type](**data).model_dump( + exclude_defaults=True + ) assert res.status_code == 200 response_model = ResponseModel(**res.json()) @@ -283,7 +245,6 @@ def test_no_prefill_defaults_partially_set(encoding, model_type, client): assert "default_zero" not in response_model.dumped_fields_exclude_default -@needs_pydanticv2 def test_casted_empty_defaults(client: TestClient): """https://github.com/fastapi/fastapi/pull/13464#issuecomment-2708378172""" form_content = {"foo": "", "with": ""}