In the below example that uses the litestar DTO system with msgspec and pydantic v1, the API validator fails with the following error: litestar.exceptions.http_exceptions.ClientException: 400: Unsupported type: <class 'str'> - at $.display_name
.
This is a potential regression after fixing this: #3710 as this case works fine in Litestar 2.11.
Python version: 3.11.9
Pydantic version: 1.10.18
No response
app.py
import time
from litestar import Litestar, Router
from controllers.graph_crud_v1_controller import GraphCrudController
def create_app() -> Litestar:
# Routes Declarations
base_v1_router = Router(
path="/api/v1/accounts/{account_id:str}",
route_handlers=[
GraphCrudController,
],
)
return Litestar(
route_handlers=[base_v1_router],
debug=True,
)
app = create_app()
graph_curd_v1_controller.py
__all__ = ["GraphCrudController"]
import logging
import shortuuid
from litestar import Controller, post
from litestar.dto import DTOData
from dto.test_dtos import GraphReadDto, GraphWriteDto, GraphDto
log = logging.getLogger(__name__)
class GraphCrudController(Controller):
path = "/graphs"
dto = GraphWriteDto
return_dto = GraphReadDto
temp_data: GraphDto | None = None
@post(
path="/create",
summary="Create Graph",
)
async def create_graph(self, account_id: str, data: DTOData[GraphDto]) -> GraphDto:
log.info(
"Got a request to create a new graph object in account: %s", account_id
)
current_ts = time.time()
self.temp_data = data.create_instance(
id=shortuuid.uuid(),
account_id=account_id,
created_at=current_ts,
created_by="mock_user",
updated_at=current_ts,
updated_by="mock_user",
)
return self.temp_data
test_dtos.py
from typing import Annotated, Any
import shortuuid
from litestar.contrib.pydantic import PydanticDTO
from litestar.dto import DTOConfig
from pydantic import BaseModel
from pydantic.class_validators import validator
from pydantic.config import Extra
from pydantic.fields import Field
import time
class GeneralIdentifiers(BaseModel):
id: str = Field(default_factory=lambda: shortuuid.uuid())
created_at: int = Field(default_factory=lambda: time.time())
created_by: str
updated_at: int = Field(default_factory=lambda: time.time())
updated_by: str
class GraphBaseMeta(BaseModel):
display_name: str = Field(default_factory=lambda: shortuuid.uuid(), max_length=64)
version: float = Field(default=1.0)
account_id: str | None = Field(default=None)
description: str | None = Field(default=None, max_length=600)
class NodePosition(BaseModel):
x: float
y: float
class NodeParamData(BaseModel):
value: Any
show: bool = True
class Config:
extra = Extra.forbid
class Node(BaseModel):
id: str = Field(default_factory=lambda: shortuuid.uuid())
type: str
data: dict[str, NodeParamData]
position: NodePosition
class Edge(BaseModel):
id: str = Field(default_factory=lambda: shortuuid.uuid())
source: str
target: str
class GraphNodesEdges(BaseModel):
nodes: list[Node]
edges: list[Edge]
class GraphBase(GraphBaseMeta, GraphNodesEdges):
pass
class Graph(GraphBase, GeneralIdentifiers):
pass
class GraphDto(Graph):
@validator("nodes")
def validate_nodes(cls, value: list[Node]) -> list[Node]:
node_ids: set[str] = set()
for node in value:
if node.id:
if node.id in node_ids:
raise ValueError("Duplicate node ids are not allowed")
node_ids.add(node.id)
return value
write_config = DTOConfig(
exclude={
"id",
"account_id",
"created_at",
"created_by",
"updated_at",
"updated_by",
},
max_nested_depth=3,
)
read_config = DTOConfig(max_nested_depth=3)
GraphWriteDto = PydanticDTO[Annotated[GraphDto, write_config]]
GraphReadDto = PydanticDTO[Annotated[GraphDto, read_config]]
### Steps to reproduce
```bash
1. Run the application with the above MCVE. The dto class is under the Python package "dto", and the controller class is under the Python package "controllers". app.py located is in the root level of the project.
2. Run the following cURL(change the host/port if yours are configured differently):
curl --location 'localhost:8080/api/v1/accounts/123/graphs/create' \
--header 'Content-Type: application/json' \
--data '{
"display_name": "Test Graph",
"edges": [
{
"source": "source_test_id",
"sourceHandle": "handle_test",
"target": "target_test_id",
"targetHandle": "handle_test"
}
],
"public": true,
"nodes": [
{
"id": "source_test_id",
"base_type": "test",
"type": "test",
"position": {
"x": 10.5,
"y": 18.31231231
},
"data": {
"name": {
"show": true,
"value": "test"
}
}
},
{
"id": "target_test_id",
"base_type": "test",
"type": "test",
"position": {
"x": 15.5,
"y": 32.31231231
},
"data": {
"name": {
"show": true,
"value": "test"
}
}
}
]
}'
### Screenshots
```bash
"![SCREENSHOT_DESCRIPTION](SCREENSHOT_LINK.png)"
import sys; print('Python %s on %s' % (sys.version, sys.platform))
/Users/sergeyk/PycharmProjects/litestar-playground/.venv/bin/python -X pycache_prefix=/Users/sergeyk/Library/Caches/JetBrains/PyCharm2024.2/cpython-cache /Applications/PyCharm.app/Contents/plugins/python-ce/helpers/pydev/pydevd.py --module --multiprocess --qt-support=auto --client 127.0.0.1 --port 57713 --file litestar run --host 0.0.0.0 --port 8080
Connected to pydev debugger (build 242.23339.19)
INFO: Started server process [99855]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)
INFO: 127.0.0.1:57717 - "POST /api/v1/accounts/123/graphs/create HTTP/1.1" 400 Bad Request
ERROR - 2024-10-01 19:48:33,156 - litestar - config - Uncaught exception (connection_type=http, path=/api/v1/accounts/123/graphs/create):
Traceback (most recent call last):
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/serialization/msgspec_hooks.py", line 139, in default_deserializer
raise TypeError(f"Unsupported type: {type(value)!r}")
TypeError: Unsupported type: <class 'str'>
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/serialization/msgspec_hooks.py", line 209, in decode_json
return msgspec.json.decode(
^^^^^^^^^^^^^^^^^^^^
msgspec.ValidationError: Unsupported type: <class 'str'> - at `$.display_name`
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/routes/http.py", line 173, in _get_response_data
kwargs = await parameter_model.to_kwargs(connection=request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/_kwargs/kwargs_model.py", line 380, in to_kwargs
await extractor(output, connection)
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/_kwargs/extractors.py", line 484, in extractor
values["data"] = await data_extractor(connection)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/_kwargs/extractors.py", line 502, in dto_extractor
return data_dto(connection).decode_bytes(body)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/contrib/pydantic/pydantic_dto_factory.py", line 104, in decode_bytes
return super().decode_bytes(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/dto/base_dto.py", line 115, in decode_bytes
return backend.populate_data_from_raw(value, self.asgi_connection)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/dto/_codegen_backend.py", line 143, in populate_data_from_raw
data_as_builtins=self._transfer_to_dict(self.parse_raw(raw, asgi_connection)),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/dto/_backend.py", line 241, in parse_raw
result = decode_json(value=raw, target_type=self.annotation, type_decoders=type_decoders, strict=False)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/serialization/msgspec_hooks.py", line 219, in decode_json
raise SerializationException(str(msgspec_error)) from msgspec_error
litestar.exceptions.base_exceptions.SerializationException: Unsupported type: <class 'str'> - at `$.display_name`
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/middleware/_internal/exceptions/middleware.py", line 159, in __call__
await self.app(scope, receive, capture_response_started)
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/_asgi/asgi_router.py", line 100, in __call__
await asgi_app(scope, receive, send)
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/routes/http.py", line 80, in handle
response = await self._get_response_for_request(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/routes/http.py", line 132, in _get_response_for_request
return await self._call_handler_function(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/routes/http.py", line 152, in _call_handler_function
response_data, cleanup_group = await self._get_response_data(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/sergeyk/PycharmProjects/litestar-playground/.venv/lib/python3.11/site-packages/litestar/routes/http.py", line 175, in _get_response_data
raise ClientException(str(e)) from e
litestar.exceptions.http_exceptions.ClientException: 400: Unsupported type: <class 'str'> - at `$.display_name`
2.12.1
Pay now to fund the work behind this issue.
Get updates on progress being made.
Maintainer is rewarded once the issue is completed.
You're funding impactful open source efforts
You want to contribute to this effort
You want to get funding like this too