-
-
Notifications
You must be signed in to change notification settings - Fork 501
Description
Description
PydanticDTO fails to validate request payloads containing list[NestedModel] | None fields, while native Pydantic handles them correctly.
Python version: 3.12
MCVE reproduces the issue.
Expected Behaviour of the MCVE:
Both endpoints should return 201 Created since the payload is valid.
Actual Behaviour of the MCVE:
The /dto endpoint returns 400 Bad Request while the native pydantic(/native) endpoint returns 201 as expected:
{ "status_code": 400, "detail": "Bad Request", "extra": [ { "type": "model_type", "loc": ["tool_calls", 0], "msg": "Input should be a valid dictionary or instance of ToolCall", "input": {"id": "call_1", "type": "function", "function": {"name": "fn", "arguments": "{}"}}, "ctx": {"class_name": "ToolCall"} } ] }
Additional Notes:
- Simple optional nested models (UsageMetadata | None) work correctly with PydanticDTO
- The issue is specific to list[NestedModel] | None fields
URL to code causing the issue
No response
MCVE
from litestar import Litestar, post
from litestar.dto import DTOConfig
from litestar.plugins.pydantic import PydanticDTO
from litestar.testing import TestClient
from pydantic import BaseModel
class ToolCallFunction(BaseModel):
name: str
arguments: str
class ToolCall(BaseModel):
id: str
type: str = "function"
function: ToolCallFunction
class CreateMessageRequest(BaseModel):
session_id: str
content: str
tool_calls: list[ToolCall] | None = None
# Without DTO - works
@post("/native")
async def native_handler(data: CreateMessageRequest) -> dict:
return {"tool_count": len(data.tool_calls) if data.tool_calls else 0}
# With DTO - fails
class CreateMessageDTO(PydanticDTO[CreateMessageRequest]):
config = DTOConfig(max_nested_depth=10)
@post("/dto", dto=CreateMessageDTO)
async def dto_handler(data: CreateMessageRequest) -> dict:
return {"tool_count": len(data.tool_calls) if data.tool_calls else 0}
app = Litestar([native_handler, dto_handler])
payload = {
"session_id": "s1",
"content": "test",
"tool_calls": [
{"id": "call_1", "type": "function", "function": {"name": "fn", "arguments": "{}"}}
],
}
with TestClient(app) as client:
r1 = client.post("/native", json=payload)
print(f"Native Pydantic: {r1.status_code}") # 201
r2 = client.post("/dto", json=payload)
print(f"With DTO: {r2.status_code}") # 400
print(r2.json())Steps to reproduce
Run MCVE
Screenshots
No response
Logs
Litestar Version
2.19
Platform
- Linux
- Mac
- Windows
- Other (Please specify in the description above)