From ea193e595a60b050fb25cf854187a7276823b511 Mon Sep 17 00:00:00 2001 From: Peter Schutt Date: Tue, 7 May 2024 11:21:55 +1000 Subject: [PATCH] fix(pydantic v1): field not optional if default value Fix issue where a pydantic v1 field annotation is wrapped with `Optional` if it is marked not required, but has a default value. Closes #3471 --- .../pydantic/pydantic_schema_plugin.py | 3 +- tests/e2e/test_pydantic.py | 80 ++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/litestar/contrib/pydantic/pydantic_schema_plugin.py b/litestar/contrib/pydantic/pydantic_schema_plugin.py index 5d7045e4f7..2eda65f2fc 100644 --- a/litestar/contrib/pydantic/pydantic_schema_plugin.py +++ b/litestar/contrib/pydantic/pydantic_schema_plugin.py @@ -286,7 +286,8 @@ def for_pydantic_model(cls, field_definition: FieldDefinition, schema_creator: S else: # pydantic v1 requires some workarounds here model_annotations = { - k: f.outer_type_ if f.required else Optional[f.outer_type_] for k, f in model.__fields__.items() + k: f.outer_type_ if f.required or f.default else Optional[f.outer_type_] + for k, f in model.__fields__.items() } if is_generic_model: diff --git a/tests/e2e/test_pydantic.py b/tests/e2e/test_pydantic.py index ad95441ae6..ba9598b891 100644 --- a/tests/e2e/test_pydantic.py +++ b/tests/e2e/test_pydantic.py @@ -1,6 +1,7 @@ import pydantic +from pydantic import v1 as pydantic_v1 -from litestar import get +from litestar import get, post from litestar.testing import create_test_client @@ -22,3 +23,80 @@ def handler_v2() -> ModelV2: with create_test_client([handler_v1, handler_v2]) as client: assert client.get("/v1").json() == {"foo": "bar"} assert client.get("/v2").json() == {"foo": "bar"} + + +def test_pydantic_v1_model_with_field_default() -> None: + # https://github.com/litestar-org/litestar/issues/3471 + + class TestDto(pydantic_v1.BaseModel): + test_str: str = pydantic_v1.Field(default="some_default", max_length=100) + + @post(path="/test") + async def test(data: TestDto) -> str: + return "success" + + with create_test_client(route_handlers=[test]) as client: + response = client.get("/schema/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "components": { + "schemas": { + "test_pydantic_v1_model_with_field_default.TestDto": { + "properties": {"test_str": {"default": "some_default", "maxLength": 100, "type": "string"}}, + "required": [], + "title": "TestDto", + "type": "object", + } + } + }, + "info": {"title": "Litestar API", "version": "1.0.0"}, + "openapi": "3.1.0", + "paths": { + "/test": { + "post": { + "deprecated": False, + "operationId": "TestTest", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/test_pydantic_v1_model_with_field_default.TestDto" + } + } + }, + "required": True, + }, + "responses": { + "201": { + "content": {"text/plain": {"schema": {"type": "string"}}}, + "description": "Document " "created, " "URL " "follows", + "headers": {}, + }, + "400": { + "content": { + "application/json": { + "schema": { + "description": "Validation " "Exception", + "examples": [{"detail": "Bad " "Request", "extra": {}, "status_code": 400}], + "properties": { + "detail": {"type": "string"}, + "extra": { + "additionalProperties": {}, + "type": ["null", "object", "array"], + }, + "status_code": {"type": "integer"}, + }, + "required": ["detail", "status_code"], + "type": "object", + } + } + }, + "description": "Bad " "request " "syntax or " "unsupported " "method", + }, + }, + "summary": "Test", + } + } + }, + "servers": [{"url": "/"}], + }