I read through every open bug and couldn't find something directly comparable (though in general it seems like nested objects in DTOs is a recurring source of issues).
The crux is that I don't want to provide a default for the None
case of a field because I want to explicitly set it, even if it's None
. That works fine if the field is a simple type like str
but when it's a nested type like a Struct
of its own then an error appears stating TypeError: Missing required argument
even when the argument is provided.
No response
from litestar import Litestar, get
from litestar.dto import MsgspecDTO
from msgspec import Struct
class Nested(Struct):
item: str
class IWorkOut(Struct):
foo: Nested | None = None
@get('/works', return_dto=MsgspecDTO[IWorkOut])
async def works() -> IWorkOut:
return IWorkOut(foo=None)
class IFail(Struct):
# the only difference here vs `IWorkOut` above is not including `= None`
# also note that changing `Nested` to a simple type like `str` here makes the error go away
foo: Nested | None
@get('/fails', return_dto=MsgspecDTO[IFail])
async def fails() -> IFail:
return IFail(foo=None)
app = Litestar([works, fails])
Worth noting: I'm reporting this here and not with Msgspec because just running `IFail(foo=None)` by itself works correctly. The issue appears to only occur because of the `return_dto`.
1. Run the app, I use uvicorn: `uvicorn main:app`
2. `GET /works` and it correctly returns a 200 with `{"foo": null}`
3. `GET /fails` and it returns a 500 with `{"status_code":500,"detail":"Internal Server Error"}`
4. See error logging below
No response
INFO: 127.0.0.1:49410 - "GET /fails HTTP/1.1" 500 Internal Server Error
ERROR - 2024-10-12 21:39:00,401 - root - example - Missing required argument 'foo'
Traceback (most recent call last):
File "[...]/litestar/middleware/_internal/exceptions/middleware.py", line 159, in __call__
await self.app(scope, receive, capture_response_started)
File "[...]/litestar/_asgi/asgi_router.py", line 100, in __call__
await asgi_app(scope, receive, send)
File "[...]/litestar/routes/http.py", line 80, in handle
response = await self._get_response_for_request(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "[...]/litestar/routes/http.py", line 132, in _get_response_for_request
return await self._call_handler_function(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "[...]/litestar/routes/http.py", line 156, in _call_handler_function
response: ASGIApp = await route_handler.to_response(app=scope["app"], data=response_data, request=request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "[...]/litestar/handlers/http_handlers/base.py", line 555, in to_response
data = return_dto_type(request).data_to_encodable_type(data)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "[...]/litestar/dto/base_dto.py", line 119, in data_to_encodable_type
return backend.encode_data(data)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "[...]/litestar/dto/_codegen_backend.py", line 161, in encode_data
return cast("LitestarEncodableType", self._encode_data(data))
^^^^^^^^^^^^^^^^^^^^^^^
File "<string>", line 33, in func
TypeError: Missing required argument 'foo'
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