← oracle_full

fastapi_14962

failed WRONG_FIX UNSUBMITTED wrong_fix_unsubmitted(None) · None tool calls · 0 s · fastapi/fastapi

Task input

✨ Serialize JSON response with Pydantic (in Rust), when there's a Pydantic return type or response model

✨ Serialize JSON response with Pydantic (in Rust), when there's a Pydantic return type or response model

Tool calls (0)

#ToolArgumentsResult
No trace captured.

Patch

--- a/docs_src/custom_response/tutorial010_py310.py
+++ b/docs_src/custom_response/tutorial010_py310.py
@@ -1,9 +1,9 @@
 from fastapi import FastAPI
-from fastapi.responses import ORJSONResponse
+from fastapi.responses import HTMLResponse
 
-app = FastAPI(default_response_class=ORJSONResponse)
+app = FastAPI(default_response_class=HTMLResponse)
 
 
 @app.get("/items/")
 async def read_items():
-    return [{"item_id": "Foo"}]
+    return "<h1>Items</h1><p>This is a list of items.</p>"
--- a/fastapi/_compat/v2.py
+++ b/fastapi/_compat/v2.py
@@ -199,6 +199,32 @@ def serialize(
             exclude_none=exclude_none,
         )
 
+    def serialize_json(
+        self,
+        value: Any,
+        *,
+        include: IncEx | None = None,
+        exclude: IncEx | None = None,
+        by_alias: bool = True,
+        exclude_unset: bool = False,
+        exclude_defaults: bool = False,
+        exclude_none: bool = False,
+    ) -> bytes:
+        # What calls this code passes a value that already called
+        # self._type_adapter.validate_python(value)
+        # This uses Pydantic's dump_json() which serializes directly to JSON
+        # bytes in one pass (via Rust), avoiding the intermediate Python dict
+        # step of dump_python(mode="json") + json.dumps().
+        return self._type_adapter.dump_json(
+            value,
+            include=include,
+            exclude=exclude,
+            by_alias=by_alias,
+            exclude_unset=exclude_unset,
+            exclude_defaults=exclude_defaults,
+            exclude_none=exclude_none,
+        )
+
     def __hash__(self) -> int:
         # Each ModelField is unique for our purposes, to allow making a dict from
         # ModelField to its JSON Schema.
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -271,6 +271,7 @@ async def serialize_response(
     exclude_none: bool = False,
     is_coroutine: bool = True,
     endpoint_ctx: EndpointContext | None = None,
+    dump_json: bool = False,
 ) -> Any:
     if field:
         if is_coroutine:
@@ -286,8 +287,8 @@ async def serialize_response(
                 body=response_content,
                 endpoint_ctx=ctx,
             )
-
-        return field.serialize(
+        serializer = field.serialize_json if dump_json else field.serialize
+        return serializer(
             value,
             include=include,
             exclude=exclude,
@@ -443,6 +444,14 @@ async def app(request: Request) -> Response:
                     response_args["status_code"] = current_status_code
                 if solved_result.response.status_code:
                     response_args["status_code"] = solved_result.response.status_code
+                # Use the fast path (dump_json) when no custom response
+                # class was set and a response field with a TypeAdapter
+                # exists. Serializes directly to JSON bytes via Pydantic's
+                # Rust core, skipping the intermediate Python dict +
+                # json.dumps() step.
+                use_dump_json = response_field is not None and isinstance(
+                    response_class, DefaultPlaceholder
+                )
                 content = await serialize_response(
                     field=response_field,
                     response_content=raw_response,
@@ -454,8 +463,16 @@ async def app(request: Request) -> Response:
                     exclude_none=response_model_exclude_none,
                     is_coroutine=is_coroutine,
                     endpoint_ctx=endpoint_ctx,
+                    dump_json=use_dump_json,
                 )
-                response = actual_response_class(content, **response_args)
+                if use_dump_json:
+                    response = Response(
+                        content=content,
+                        media_type="application/json",
+                        **response_args,
+                    )
+                else:
+                    response = actual_response_class(content, **response_args)
                 if not is_body_allowed_for_status_code(response.status_code):
                     response.body = b""
                 response.headers.raw.extend(solved_result.response.headers.raw)

Test output

show
42: in _send_handling_auth
    response = self._send_handling_redirects(
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/fastapi/lib/python3.13/site-packages/httpx/_client.py:979: in _send_handling_redirects
    response = self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/fastapi/lib/python3.13/site-packages/httpx/_client.py:1014: in _send_single_request
    response = transport.handle_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/testclient.py:348: in handle_request
    raise exc
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/testclient.py:345: in handle_request
    portal.call(self.app, scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/fastapi/lib/python3.13/site-packages/anyio/from_thread.py:340: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/Users/jp/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/concurrent/futures/_base.py:460: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/Users/jp/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/concurrent/futures/_base.py:402: in __get_result
    raise self._exception
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/fastapi/lib/python3.13/site-packages/anyio/from_thread.py:265: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi/applications.py:1134: in __call__
    await super().__call__(scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/applications.py:107: in __call__
    await self.middleware_stack(scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/middleware/errors.py:186: in __call__
    raise exc
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/routing.py:716: in __call__
    await self.middleware_stack(scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/routing.py:736: in app
    await route.handle(scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/routing.py:290: in handle
    await self.app(scope, receive, send)
fastapi/routing.py:119: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
fastapi/routing.py:105: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
fastapi/routing.py:475: in app
    response = actual_response_class(content, **response_args)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/responses.py:189: in __init__
    super().__init__(content, status_code, headers, media_type, background)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/responses.py:46: in __init__
    self.body = self.render(content)
                ^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <fastapi.responses.UJSONResponse object at 0x103b86900>
content = [{'item_id': 'Foo'}]

    def render(self, content: Any) -> bytes:
>       assert ujson is not None, "ujson must be installed to use UJSONResponse"
               ^^^^^^^^^^^^^^^^^
E       AssertionError: ujson must be installed to use UJSONResponse

fastapi/responses.py:32: AssertionError
=============================== warnings summary ===============================
../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/testclient.py:45
  /Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/testclient.py:45: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.
    _PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 2 passed, 1 warning in 0.90s