From 590a5e535587cc07041ba12d308c748433ccb168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Feb 2026 08:07:19 -0800 Subject: [PATCH 01/11] =?UTF-8?q?=E2=9C=A8=20Serialize=20JSON=20response?= =?UTF-8?q?=20with=20Pydantic=20(in=20Rust),=20when=20there's=20a=20Pydant?= =?UTF-8?q?ic=20return=20type=20or=20response=20model=20(#14962)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/advanced/custom-response.md | 80 +++++-------------- docs/en/docs/advanced/response-directly.md | 28 +++++-- docs/en/docs/how-to/general.md | 4 + docs/en/docs/tutorial/response-model.md | 1 + docs_src/custom_response/tutorial010_py310.py | 6 +- fastapi/_compat/v2.py | 26 ++++++ fastapi/routing.py | 23 +++++- tests/test_dump_json_fast_path.py | 51 ++++++++++++ .../test_custom_response/test_tutorial001.py | 1 - .../test_custom_response/test_tutorial010.py | 50 ++++++++++++ 10 files changed, 196 insertions(+), 74 deletions(-) create mode 100644 tests/test_dump_json_fast_path.py create mode 100644 tests/test_tutorial/test_custom_response/test_tutorial010.py diff --git a/docs/en/docs/advanced/custom-response.md b/docs/en/docs/advanced/custom-response.md index 8b4b3da339..e88e958657 100644 --- a/docs/en/docs/advanced/custom-response.md +++ b/docs/en/docs/advanced/custom-response.md @@ -1,6 +1,6 @@ # Custom Response - HTML, Stream, File, others { #custom-response-html-stream-file-others } -By default, **FastAPI** will return the responses using `JSONResponse`. +By default, **FastAPI** will return JSON responses. You can override it by returning a `Response` directly as seen in [Return a Response directly](response-directly.md){.internal-link target=_blank}. @@ -10,43 +10,27 @@ But you can also declare the `Response` that you want to be used (e.g. any `Resp The contents that you return from your *path operation function* will be put inside of that `Response`. -And if that `Response` has a JSON media type (`application/json`), like is the case with the `JSONResponse` and `UJSONResponse`, the data you return will be automatically converted (and filtered) with any Pydantic `response_model` that you declared in the *path operation decorator*. - /// note If you use a response class with no media type, FastAPI will expect your response to have no content, so it will not document the response format in its generated OpenAPI docs. /// -## Use `ORJSONResponse` { #use-orjsonresponse } - -For example, if you are squeezing performance, you can install and use `orjson` and set the response to be `ORJSONResponse`. - -Import the `Response` class (sub-class) you want to use and declare it in the *path operation decorator*. +## JSON Responses { #json-responses } -For large responses, returning a `Response` directly is much faster than returning a dictionary. +By default FastAPI returns JSON responses. -This is because by default, FastAPI will inspect every item inside and make sure it is serializable as JSON, using the same [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} explained in the tutorial. This is what allows you to return **arbitrary objects**, for example database models. +If you declare a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} FastAPI will use it to serialize the data to JSON, using Pydantic. -But if you are certain that the content that you are returning is **serializable with JSON**, you can pass it directly to the response class and avoid the extra overhead that FastAPI would have by passing your return content through the `jsonable_encoder` before passing it to the response class. - -{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} - -/// info - -The parameter `response_class` will also be used to define the "media type" of the response. - -In this case, the HTTP header `Content-Type` will be set to `application/json`. - -And it will be documented as such in OpenAPI. +If you don't declare a response model, FastAPI will use the `jsonable_encoder` explained in [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} and put it in a `JSONResponse`. -/// +If you declare a `response_class` with a JSON media type (`application/json`), like is the case with the `JSONResponse`, the data you return will be automatically converted (and filtered) with any Pydantic `response_model` that you declared in the *path operation decorator*. But the data won't be serialized to JSON bytes with Pydantic, instead it will be converted with the `jsonable_encoder` and then passed to the `JSONResponse` class, which will serialize it to bytes using the standard JSON library in Python. -/// tip +### JSON Performance { #json-performance } -The `ORJSONResponse` is only available in FastAPI, not in Starlette. +In short, if you want the maximum performance, use a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} and don't declare a `response_class` in the *path operation decorator*. -/// +{* ../../docs_src/response_model/tutorial001_01_py310.py ln[15:17] hl[16] *} ## HTML Response { #html-response } @@ -154,40 +138,6 @@ Takes some data and returns an `application/json` encoded response. This is the default response used in **FastAPI**, as you read above. -### `ORJSONResponse` { #orjsonresponse } - -A fast alternative JSON response using `orjson`, as you read above. - -/// info - -This requires installing `orjson` for example with `pip install orjson`. - -/// - -### `UJSONResponse` { #ujsonresponse } - -An alternative JSON response using `ujson`. - -/// info - -This requires installing `ujson` for example with `pip install ujson`. - -/// - -/// warning - -`ujson` is less careful than Python's built-in implementation in how it handles some edge-cases. - -/// - -{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *} - -/// tip - -It's possible that `ORJSONResponse` might be a faster alternative. - -/// - ### `RedirectResponse` { #redirectresponse } Returns an HTTP redirect. Uses a 307 status code (Temporary Redirect) by default. @@ -268,7 +218,7 @@ In this case, you can return the file path directly from your *path operation* f You can create your own custom response class, inheriting from `Response` and using it. -For example, let's say that you want to use `orjson`, but with some custom settings not used in the included `ORJSONResponse` class. +For example, let's say that you want to use `orjson` with some settings. Let's say you want it to return indented and formatted JSON, so you want to use the orjson option `orjson.OPT_INDENT_2`. @@ -292,13 +242,21 @@ Now instead of returning: Of course, you will probably find much better ways to take advantage of this than formatting JSON. 😉 +### `orjson` or Response Model { #orjson-or-response-model } + +If what you are looking for is performance, you are probably better off using a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} than an `orjson` response. + +With a response model, FastAPI will use Pydantic to serialize the data to JSON, without using intermediate steps, like converting it with `jsonable_encoder`, which would happen in any other case. + +And under the hood, Pydantic uses the same underlying Rust mechanisms as `orjson` to serialize to JSON, so you will already get the best performance with a response model. + ## Default response class { #default-response-class } When creating a **FastAPI** class instance or an `APIRouter` you can specify which response class to use by default. The parameter that defines this is `default_response_class`. -In the example below, **FastAPI** will use `ORJSONResponse` by default, in all *path operations*, instead of `JSONResponse`. +In the example below, **FastAPI** will use `HTMLResponse` by default, in all *path operations*, instead of JSON. {* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *} diff --git a/docs/en/docs/advanced/response-directly.md b/docs/en/docs/advanced/response-directly.md index 76cc50d03c..9d58490eb1 100644 --- a/docs/en/docs/advanced/response-directly.md +++ b/docs/en/docs/advanced/response-directly.md @@ -2,19 +2,23 @@ When you create a **FastAPI** *path operation* you can normally return any data from it: a `dict`, a `list`, a Pydantic model, a database model, etc. -By default, **FastAPI** would automatically convert that return value to JSON using the `jsonable_encoder` explained in [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}. +If you declare a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} FastAPI will use it to serialize the data to JSON, using Pydantic. -Then, behind the scenes, it would put that JSON-compatible data (e.g. a `dict`) inside of a `JSONResponse` that would be used to send the response to the client. +If you don't declare a response model, FastAPI will use the `jsonable_encoder` explained in [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} and put it in a `JSONResponse`. -But you can return a `JSONResponse` directly from your *path operations*. +You could also create a `JSONResponse` directly and return it. -It might be useful, for example, to return custom headers or cookies. +/// tip + +You will normally have much better performance using a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} than returning a `JSONResponse` directly, as that way it serializes the data using Pydantic, in Rust. + +/// ## Return a `Response` { #return-a-response } -In fact, you can return any `Response` or any sub-class of it. +You can return any `Response` or any sub-class of it. -/// tip +/// info `JSONResponse` itself is a sub-class of `Response`. @@ -56,6 +60,18 @@ You could put your XML content in a string, put that in a `Response`, and return {* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} +## How a Response Model Works { #how-a-response-model-works } + +When you declare a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} in a path operation, **FastAPI** will use it to serialize the data to JSON, using Pydantic. + +{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *} + +As that will happen on the Rust side, the performance will be much better than if it was done with regular Python and the `JSONResponse` class. + +When using a response model FastAPI won't use the `jsonable_encoder` to convert the data (which would be slower) nor the `JSONResponse` class. + +Instead it takes the JSON bytes generated with Pydantic using the response model and returns a `Response` with the right media type for JSON directly (`application/json`). + ## Notes { #notes } When you return a `Response` directly its data is not validated, converted (serialized), or documented automatically. diff --git a/docs/en/docs/how-to/general.md b/docs/en/docs/how-to/general.md index 9347192607..4f611dab05 100644 --- a/docs/en/docs/how-to/general.md +++ b/docs/en/docs/how-to/general.md @@ -6,6 +6,10 @@ Here are several pointers to other places in the docs, for general or frequent q To ensure that you don't return more data than you should, read the docs for [Tutorial - Response Model - Return Type](../tutorial/response-model.md){.internal-link target=_blank}. +## Optimize Response Performance - Response Model - Return Type { #optimize-response-performance-response-model-return-type } + +To optimize performance when returning JSON data, use a return type or response model, that way Pydantic will handle the serialization to JSON on the Rust side, without going through Python. Read more in the docs for [Tutorial - Response Model - Return Type](../tutorial/response-model.md){.internal-link target=_blank}. + ## Documentation Tags - OpenAPI { #documentation-tags-openapi } To add tags to your *path operations*, and group them in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Tags](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}. diff --git a/docs/en/docs/tutorial/response-model.md b/docs/en/docs/tutorial/response-model.md index 51492722ae..c8312d92c6 100644 --- a/docs/en/docs/tutorial/response-model.md +++ b/docs/en/docs/tutorial/response-model.md @@ -13,6 +13,7 @@ FastAPI will use this return type to: * Add a **JSON Schema** for the response, in the OpenAPI *path operation*. * This will be used by the **automatic docs**. * It will also be used by automatic client code generation tools. +* **Serialize** the returned data to JSON using Pydantic, which is written in **Rust**, so it will be **much faster**. But most importantly: diff --git a/docs_src/custom_response/tutorial010_py310.py b/docs_src/custom_response/tutorial010_py310.py index 57cb062604..d5bc783aa0 100644 --- 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 "

Items

This is a list of items.

" diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 0535c806f2..79fba93188 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -199,6 +199,32 @@ class ModelField: 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. diff --git a/fastapi/routing.py b/fastapi/routing.py index ea82ab14a3..528c962965 100644 --- 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 @@ def get_request_handler( 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 @@ def get_request_handler( 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) diff --git a/tests/test_dump_json_fast_path.py b/tests/test_dump_json_fast_path.py new file mode 100644 index 0000000000..d41d5aa66f --- /dev/null +++ b/tests/test_dump_json_fast_path.py @@ -0,0 +1,51 @@ +from unittest.mock import patch + +from fastapi import FastAPI +from fastapi.responses import JSONResponse +from fastapi.testclient import TestClient +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + price: float + + +app = FastAPI() + + +@app.get("/default") +def get_default() -> Item: + return Item(name="widget", price=9.99) + + +@app.get("/explicit", response_class=JSONResponse) +def get_explicit() -> Item: + return Item(name="widget", price=9.99) + + +client = TestClient(app) + + +def test_default_response_class_skips_json_dumps(): + """When no response_class is set, the fast path serializes directly to + JSON bytes via Pydantic's dump_json and never calls json.dumps.""" + with patch( + "starlette.responses.json.dumps", wraps=__import__("json").dumps + ) as mock_dumps: + response = client.get("/default") + assert response.status_code == 200 + assert response.json() == {"name": "widget", "price": 9.99} + mock_dumps.assert_not_called() + + +def test_explicit_response_class_uses_json_dumps(): + """When response_class is explicitly set to JSONResponse, the normal path + is used and json.dumps is called via JSONResponse.render().""" + with patch( + "starlette.responses.json.dumps", wraps=__import__("json").dumps + ) as mock_dumps: + response = client.get("/explicit") + assert response.status_code == 200 + assert response.json() == {"name": "widget", "price": 9.99} + mock_dumps.assert_called_once() diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001.py b/tests/test_tutorial/test_custom_response/test_tutorial001.py index a5fe4c8f4c..cec5ebe6cb 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001.py @@ -9,7 +9,6 @@ from inline_snapshot import snapshot name="client", params=[ pytest.param("tutorial001_py310"), - pytest.param("tutorial010_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_custom_response/test_tutorial010.py b/tests/test_tutorial/test_custom_response/test_tutorial010.py new file mode 100644 index 0000000000..ffb005cb67 --- /dev/null +++ b/tests/test_tutorial/test_custom_response/test_tutorial010.py @@ -0,0 +1,50 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial010_py310"), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.custom_response.{request.param}") + client = TestClient(mod.app) + return client + + +def test_get_custom_response(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.text == snapshot("

Items

This is a list of items.

") + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/html": {"schema": {"type": "string"}} + }, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } + } + }, + } + ) From bc06e4296d588f39f38c7ac2fc96d3913c565962 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Feb 2026 16:07:56 +0000 Subject: [PATCH 02/11] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index edcb3db8d6..ba5ba420bd 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Features + +* ✨ Serialize JSON response with Pydantic (in Rust), when there's a Pydantic return type or response model. PR [#14962](https://github.com/fastapi/fastapi/pull/14962) by [@tiangolo](https://github.com/tiangolo). + ## 0.129.2 ### Internal From eb544e704c02fd9bad34d23127cbb45255c00706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Feb 2026 17:14:53 +0100 Subject: [PATCH 03/11] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.130.?= =?UTF-8?q?0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ba5ba420bd..a5c5241e1b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.130.0 + ### Features * ✨ Serialize JSON response with Pydantic (in Rust), when there's a Pydantic return type or response model. PR [#14962](https://github.com/fastapi/fastapi/pull/14962) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index e6798ddc6d..ffa56faaf1 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.129.2" +__version__ = "0.130.0" from starlette import status as status From 2e62fb151338c39c1f122d13fe6ad255b8ce1ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Feb 2026 17:18:26 +0100 Subject: [PATCH 04/11] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index a5c5241e1b..4b7fba3368 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -12,6 +12,8 @@ hide: ### Features * ✨ Serialize JSON response with Pydantic (in Rust), when there's a Pydantic return type or response model. PR [#14962](https://github.com/fastapi/fastapi/pull/14962) by [@tiangolo](https://github.com/tiangolo). + * This results in 2x (or more) performance increase for JSON responses. + * New docs: [Custom Response - JSON Performance](https://fastapi.tiangolo.com/advanced/custom-response/#json-performance). ## 0.129.2 From 48e983573232eea970fb4e0261818d4ab9a481b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Feb 2026 08:34:59 -0800 Subject: [PATCH 05/11] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20Deprecate=20`ORJS?= =?UTF-8?q?ONResponse`=20and=20`UJSONResponse`=20(#14964)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/reference/responses.md | 8 +- fastapi/responses.py | 52 +++++++++++-- pyproject.toml | 8 +- tests/test_deprecated_responses.py | 73 +++++++++++++++++++ tests/test_orjson_response_class.py | 13 +++- .../test_custom_response/test_tutorial001.py | 2 + .../test_custom_response/test_tutorial001b.py | 10 ++- uv.lock | 20 ++++- 8 files changed, 165 insertions(+), 21 deletions(-) create mode 100644 tests/test_deprecated_responses.py diff --git a/docs/en/docs/reference/responses.md b/docs/en/docs/reference/responses.md index bd57861294..2df53e9701 100644 --- a/docs/en/docs/reference/responses.md +++ b/docs/en/docs/reference/responses.md @@ -22,7 +22,13 @@ from fastapi.responses import ( ## FastAPI Responses -There are a couple of custom FastAPI response classes, you can use them to optimize JSON performance. +There were a couple of custom FastAPI response classes that were intended to optimize JSON performance. + +However, they are now deprecated as you will now get better performance by using a [Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/). + +That way, Pydantic will serialize the data into JSON bytes on the Rust side, which will achieve better performance than these custom JSON responses. + +Read more about it in [Custom Response - HTML, Stream, File, others - `orjson` or Response Model](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model). ::: fastapi.responses.UJSONResponse options: diff --git a/fastapi/responses.py b/fastapi/responses.py index 6c8db6f335..5b1154c046 100644 --- a/fastapi/responses.py +++ b/fastapi/responses.py @@ -1,5 +1,6 @@ from typing import Any +from fastapi.exceptions import FastAPIDeprecationWarning from starlette.responses import FileResponse as FileResponse # noqa from starlette.responses import HTMLResponse as HTMLResponse # noqa from starlette.responses import JSONResponse as JSONResponse # noqa @@ -7,6 +8,7 @@ from starlette.responses import PlainTextResponse as PlainTextResponse # noqa from starlette.responses import RedirectResponse as RedirectResponse # noqa from starlette.responses import Response as Response # noqa from starlette.responses import StreamingResponse as StreamingResponse # noqa +from typing_extensions import deprecated try: import ujson @@ -20,12 +22,29 @@ except ImportError: # pragma: nocover orjson = None # type: ignore +@deprecated( + "UJSONResponse is deprecated, FastAPI now serializes data directly to JSON " + "bytes via Pydantic when a return type or response model is set, which is " + "faster and doesn't need a custom response class. Read more in the FastAPI " + "docs: https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model " + "and https://fastapi.tiangolo.com/tutorial/response-model/", + category=FastAPIDeprecationWarning, + stacklevel=2, +) class UJSONResponse(JSONResponse): - """ - JSON response using the high-performance ujson library to serialize data to JSON. + """JSON response using the ujson library to serialize data to JSON. + + **Deprecated**: `UJSONResponse` is deprecated. FastAPI now serializes data + directly to JSON bytes via Pydantic when a return type or response model is + set, which is faster and doesn't need a custom response class. + + Read more in the + [FastAPI docs for Custom Response](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model) + and the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/). + **Note**: `ujson` is not included with FastAPI and must be installed + separately, e.g. `pip install ujson`. """ def render(self, content: Any) -> bytes: @@ -33,12 +52,29 @@ class UJSONResponse(JSONResponse): return ujson.dumps(content, ensure_ascii=False).encode("utf-8") +@deprecated( + "ORJSONResponse is deprecated, FastAPI now serializes data directly to JSON " + "bytes via Pydantic when a return type or response model is set, which is " + "faster and doesn't need a custom response class. Read more in the FastAPI " + "docs: https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model " + "and https://fastapi.tiangolo.com/tutorial/response-model/", + category=FastAPIDeprecationWarning, + stacklevel=2, +) class ORJSONResponse(JSONResponse): - """ - JSON response using the high-performance orjson library to serialize data to JSON. + """JSON response using the orjson library to serialize data to JSON. + + **Deprecated**: `ORJSONResponse` is deprecated. FastAPI now serializes data + directly to JSON bytes via Pydantic when a return type or response model is + set, which is faster and doesn't need a custom response class. + + Read more in the + [FastAPI docs for Custom Response](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model) + and the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/). + **Note**: `orjson` is not included with FastAPI and must be installed + separately, e.g. `pip install orjson`. """ def render(self, content: Any) -> bytes: diff --git a/pyproject.toml b/pyproject.toml index c51eb8ce9b..79dfc1fd35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,10 +105,6 @@ all = [ "itsdangerous >=1.1.0", # For Starlette's schema generation, would not be used with FastAPI "pyyaml >=5.3.1", - # For UJSONResponse - "ujson >=5.8.0", - # For ORJSONResponse - "orjson >=3.9.3", # To validate email fields "email-validator >=2.0.0", # Uvicorn with uvloop @@ -151,6 +147,10 @@ docs = [ docs-tests = [ "httpx >=0.23.0,<1.0.0", "ruff >=0.14.14", + # For UJSONResponse + "ujson >=5.8.0", + # For ORJSONResponse + "orjson >=3.9.3", ] github-actions = [ "httpx >=0.27.0,<1.0.0", diff --git a/tests/test_deprecated_responses.py b/tests/test_deprecated_responses.py new file mode 100644 index 0000000000..eff5792717 --- /dev/null +++ b/tests/test_deprecated_responses.py @@ -0,0 +1,73 @@ +import warnings + +import pytest +from fastapi import FastAPI +from fastapi.exceptions import FastAPIDeprecationWarning +from fastapi.responses import ORJSONResponse, UJSONResponse +from fastapi.testclient import TestClient +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + price: float + + +# ORJSON + + +def _make_orjson_app() -> FastAPI: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FastAPIDeprecationWarning) + app = FastAPI(default_response_class=ORJSONResponse) + + @app.get("/items") + def get_items() -> Item: + return Item(name="widget", price=9.99) + + return app + + +def test_orjson_response_returns_correct_data(): + app = _make_orjson_app() + client = TestClient(app) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FastAPIDeprecationWarning) + response = client.get("/items") + assert response.status_code == 200 + assert response.json() == {"name": "widget", "price": 9.99} + + +def test_orjson_response_emits_deprecation_warning(): + with pytest.warns(FastAPIDeprecationWarning, match="ORJSONResponse is deprecated"): + ORJSONResponse(content={"hello": "world"}) + + +# UJSON + + +def _make_ujson_app() -> FastAPI: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FastAPIDeprecationWarning) + app = FastAPI(default_response_class=UJSONResponse) + + @app.get("/items") + def get_items() -> Item: + return Item(name="widget", price=9.99) + + return app + + +def test_ujson_response_returns_correct_data(): + app = _make_ujson_app() + client = TestClient(app) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FastAPIDeprecationWarning) + response = client.get("/items") + assert response.status_code == 200 + assert response.json() == {"name": "widget", "price": 9.99} + + +def test_ujson_response_emits_deprecation_warning(): + with pytest.warns(FastAPIDeprecationWarning, match="UJSONResponse is deprecated"): + UJSONResponse(content={"hello": "world"}) diff --git a/tests/test_orjson_response_class.py b/tests/test_orjson_response_class.py index 6fe62daf97..63ea054d1f 100644 --- a/tests/test_orjson_response_class.py +++ b/tests/test_orjson_response_class.py @@ -1,9 +1,14 @@ +import warnings + from fastapi import FastAPI +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.responses import ORJSONResponse from fastapi.testclient import TestClient from sqlalchemy.sql.elements import quoted_name -app = FastAPI(default_response_class=ORJSONResponse) +with warnings.catch_warnings(): + warnings.simplefilter("ignore", FastAPIDeprecationWarning) + app = FastAPI(default_response_class=ORJSONResponse) @app.get("/orjson_non_str_keys") @@ -16,6 +21,8 @@ client = TestClient(app) def test_orjson_non_str_keys(): - with client: - response = client.get("/orjson_non_str_keys") + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FastAPIDeprecationWarning) + with client: + response = client.get("/orjson_non_str_keys") assert response.json() == {"msg": "Hello World", "1": 1} diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001.py b/tests/test_tutorial/test_custom_response/test_tutorial001.py index cec5ebe6cb..a691dd3a84 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001.py @@ -17,12 +17,14 @@ def get_client(request: pytest.FixtureRequest): return client +@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning") def test_get_custom_response(client: TestClient): response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == [{"item_id": "Foo"}] +@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning") def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001b.py b/tests/test_tutorial/test_custom_response/test_tutorial001b.py index 32437db86b..11ce813b76 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001b.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001b.py @@ -1,17 +1,25 @@ +import warnings + +import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.custom_response.tutorial001b_py310 import app +with warnings.catch_warnings(): + warnings.simplefilter("ignore", FastAPIDeprecationWarning) + from docs_src.custom_response.tutorial001b_py310 import app client = TestClient(app) +@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning") def test_get_custom_response(): response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == [{"item_id": "Foo"}] +@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning") def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text diff --git a/uv.lock b/uv.lock index 15ca8714f6..0d16c930b1 100644 --- a/uv.lock +++ b/uv.lock @@ -1083,12 +1083,10 @@ all = [ { name = "httpx" }, { name = "itsdangerous" }, { name = "jinja2" }, - { name = "orjson" }, { name = "pydantic-extra-types" }, { name = "pydantic-settings" }, { name = "python-multipart" }, { name = "pyyaml" }, - { name = "ujson" }, { name = "uvicorn", extra = ["standard"] }, ] standard = [ @@ -1134,6 +1132,7 @@ dev = [ { name = "mkdocs-redirects" }, { name = "mkdocstrings", extra = ["python"] }, { name = "mypy" }, + { name = "orjson" }, { name = "pillow" }, { name = "playwright" }, { name = "prek" }, @@ -1151,6 +1150,7 @@ dev = [ { name = "typer" }, { name = "types-orjson" }, { name = "types-ujson" }, + { name = "ujson" }, ] docs = [ { name = "black" }, @@ -1165,15 +1165,19 @@ docs = [ { name = "mkdocs-material" }, { name = "mkdocs-redirects" }, { name = "mkdocstrings", extra = ["python"] }, + { name = "orjson" }, { name = "pillow" }, { name = "python-slugify" }, { name = "pyyaml" }, { name = "ruff" }, { name = "typer" }, + { name = "ujson" }, ] docs-tests = [ { name = "httpx" }, + { name = "orjson" }, { name = "ruff" }, + { name = "ujson" }, ] github-actions = [ { name = "httpx" }, @@ -1192,6 +1196,7 @@ tests = [ { name = "httpx" }, { name = "inline-snapshot" }, { name = "mypy" }, + { name = "orjson" }, { name = "pwdlib", extra = ["argon2"] }, { name = "pyjwt" }, { name = "pytest" }, @@ -1202,6 +1207,7 @@ tests = [ { name = "strawberry-graphql" }, { name = "types-orjson" }, { name = "types-ujson" }, + { name = "ujson" }, ] translations = [ { name = "gitpython" }, @@ -1225,7 +1231,6 @@ requires-dist = [ { name = "jinja2", marker = "extra == 'all'", specifier = ">=3.1.5" }, { name = "jinja2", marker = "extra == 'standard'", specifier = ">=3.1.5" }, { name = "jinja2", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=3.1.5" }, - { name = "orjson", marker = "extra == 'all'", specifier = ">=3.9.3" }, { name = "pydantic", specifier = ">=2.7.0" }, { name = "pydantic-extra-types", marker = "extra == 'all'", specifier = ">=2.0.0" }, { name = "pydantic-extra-types", marker = "extra == 'standard'", specifier = ">=2.0.0" }, @@ -1240,7 +1245,6 @@ requires-dist = [ { name = "starlette", specifier = ">=0.40.0,<1.0.0" }, { name = "typing-extensions", specifier = ">=4.8.0" }, { name = "typing-inspection", specifier = ">=0.4.2" }, - { name = "ujson", marker = "extra == 'all'", specifier = ">=5.8.0" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'all'", specifier = ">=0.12.0" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'standard'", specifier = ">=0.12.0" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=0.12.0" }, @@ -1269,6 +1273,7 @@ dev = [ { name = "mkdocs-redirects", specifier = ">=1.2.1,<1.3.0" }, { name = "mkdocstrings", extras = ["python"], specifier = ">=0.30.1" }, { name = "mypy", specifier = ">=1.14.1" }, + { name = "orjson", specifier = ">=3.9.3" }, { name = "pillow", specifier = ">=11.3.0" }, { name = "playwright", specifier = ">=1.57.0" }, { name = "prek", specifier = ">=0.2.22" }, @@ -1286,6 +1291,7 @@ dev = [ { name = "typer", specifier = ">=0.21.1" }, { name = "types-orjson", specifier = ">=3.6.2" }, { name = "types-ujson", specifier = ">=5.10.0.20240515" }, + { name = "ujson", specifier = ">=5.8.0" }, ] docs = [ { name = "black", specifier = ">=25.1.0" }, @@ -1300,15 +1306,19 @@ docs = [ { name = "mkdocs-material", specifier = ">=9.7.0" }, { name = "mkdocs-redirects", specifier = ">=1.2.1,<1.3.0" }, { name = "mkdocstrings", extras = ["python"], specifier = ">=0.30.1" }, + { name = "orjson", specifier = ">=3.9.3" }, { name = "pillow", specifier = ">=11.3.0" }, { name = "python-slugify", specifier = ">=8.0.4" }, { name = "pyyaml", specifier = ">=5.3.1,<7.0.0" }, { name = "ruff", specifier = ">=0.14.14" }, { name = "typer", specifier = ">=0.21.1" }, + { name = "ujson", specifier = ">=5.8.0" }, ] docs-tests = [ { name = "httpx", specifier = ">=0.23.0,<1.0.0" }, + { name = "orjson", specifier = ">=3.9.3" }, { name = "ruff", specifier = ">=0.14.14" }, + { name = "ujson", specifier = ">=5.8.0" }, ] github-actions = [ { name = "httpx", specifier = ">=0.27.0,<1.0.0" }, @@ -1327,6 +1337,7 @@ tests = [ { name = "httpx", specifier = ">=0.23.0,<1.0.0" }, { name = "inline-snapshot", specifier = ">=0.21.1" }, { name = "mypy", specifier = ">=1.14.1" }, + { name = "orjson", specifier = ">=3.9.3" }, { name = "pwdlib", extras = ["argon2"], specifier = ">=0.2.1" }, { name = "pyjwt", specifier = ">=2.9.0" }, { name = "pytest", specifier = ">=9.0.0" }, @@ -1337,6 +1348,7 @@ tests = [ { name = "strawberry-graphql", specifier = ">=0.200.0,<1.0.0" }, { name = "types-orjson", specifier = ">=3.6.2" }, { name = "types-ujson", specifier = ">=5.10.0.20240515" }, + { name = "ujson", specifier = ">=5.8.0" }, ] translations = [ { name = "gitpython", specifier = ">=3.1.46" }, From 70e8558352ef5f60dbd95c95725a427e06e86a24 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Feb 2026 16:35:25 +0000 Subject: [PATCH 06/11] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 4b7fba3368..b9848e4607 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Breaking Changes + +* 🗑️ Deprecate `ORJSONResponse` and `UJSONResponse`. PR [#14964](https://github.com/fastapi/fastapi/pull/14964) by [@tiangolo](https://github.com/tiangolo). + ## 0.130.0 ### Features From b423b73c355bc401cb49f43cb5ca14515ac088cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Feb 2026 17:36:21 +0100 Subject: [PATCH 07/11] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.131.?= =?UTF-8?q?0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index b9848e4607..c42133a201 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.131.0 + ### Breaking Changes * 🗑️ Deprecate `ORJSONResponse` and `UJSONResponse`. PR [#14964](https://github.com/fastapi/fastapi/pull/14964) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index ffa56faaf1..944fdd58f4 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.130.0" +__version__ = "0.131.0" from starlette import status as status From 1cea8f659c5ca6ffef1a62b3ac78b9d7c63c3c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Feb 2026 10:13:49 -0800 Subject: [PATCH 08/11] =?UTF-8?q?=F0=9F=91=B7=20Do=20not=20include=20bench?= =?UTF-8?q?mark=20tests=20in=20coverage=20to=20speed=20up=20coverage=20pro?= =?UTF-8?q?cessing=20(#14965)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 49 ++++++++++++++++++++++++++++---------- pyproject.toml | 1 + 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d61b4add94..0d3515efb7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -68,10 +68,8 @@ jobs: python-version: "3.13" coverage: coverage uv-resolution: highest - # Ubuntu with 3.13 needs coverage for CodSpeed benchmarks - os: ubuntu-latest python-version: "3.13" - coverage: coverage uv-resolution: highest codspeed: codspeed - os: ubuntu-latest @@ -109,20 +107,10 @@ jobs: run: uv pip install "git+https://github.com/Kludex/starlette@main" - run: mkdir coverage - name: Test - if: matrix.codspeed != 'codspeed' run: uv run --no-sync bash scripts/test.sh env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} - - name: CodSpeed benchmarks - if: matrix.codspeed == 'codspeed' - uses: CodSpeedHQ/action@v4 - env: - COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} - CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} - with: - mode: simulation - run: uv run --no-sync coverage run -m pytest tests/ --codspeed # Do not store coverage for all possible combinations to avoid file size max errors in Smokeshow - name: Store coverage files if: matrix.coverage == 'coverage' @@ -132,6 +120,42 @@ jobs: path: coverage include-hidden-files: true + benchmark: + needs: + - changes + if: needs.changes.outputs.src == 'true' || github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + env: + UV_PYTHON: "3.13" + UV_RESOLUTION: highest + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + - name: Setup uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + cache-dependency-glob: | + pyproject.toml + uv.lock + - name: Install Dependencies + run: uv sync --no-dev --group tests --extra all + - name: CodSpeed benchmarks + uses: CodSpeedHQ/action@v4 + env: + COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py3.13 + CONTEXT: ${{ runner.os }}-py3.13 + with: + mode: simulation + run: uv run --no-sync coverage run -m pytest tests/ --codspeed + coverage-combine: needs: - test @@ -176,6 +200,7 @@ jobs: if: always() needs: - coverage-combine + - benchmark runs-on: ubuntu-latest steps: - name: Dump GitHub context diff --git a/pyproject.toml b/pyproject.toml index 79dfc1fd35..76b53726ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -242,6 +242,7 @@ relative_files = true context = '${CONTEXT}' dynamic_context = "test_function" omit = [ + "tests/benchmarks/*", "docs_src/response_model/tutorial003_04_py39.py", "docs_src/response_model/tutorial003_04_py310.py", "docs_src/dependencies/tutorial013_an_py310.py", # temporary code example? From c5559a66dd76c229997c673dd48bbf4f46a2cf68 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Feb 2026 18:14:11 +0000 Subject: [PATCH 09/11] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index c42133a201..7ac314280e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Internal + +* 👷 Do not include benchmark tests in coverage to speed up coverage processing. PR [#14965](https://github.com/fastapi/fastapi/pull/14965) by [@tiangolo](https://github.com/tiangolo). + ## 0.131.0 ### Breaking Changes From 4da264f0f32a1b0eb1c260997d0ab3453f4fdb98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Feb 2026 10:21:38 -0800 Subject: [PATCH 10/11] =?UTF-8?q?=F0=9F=91=B7=20Do=20not=20run=20codspeed?= =?UTF-8?q?=20with=20coverage=20as=20it's=20not=20tracked=20(#14966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0d3515efb7..86e6bfc148 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -149,12 +149,9 @@ jobs: run: uv sync --no-dev --group tests --extra all - name: CodSpeed benchmarks uses: CodSpeedHQ/action@v4 - env: - COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py3.13 - CONTEXT: ${{ runner.os }}-py3.13 with: mode: simulation - run: uv run --no-sync coverage run -m pytest tests/ --codspeed + run: uv run --no-sync pytest tests/benchmarks --codspeed coverage-combine: needs: From 282612437835fd1abbc37cf77c6f4e3bc76de557 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Feb 2026 18:22:03 +0000 Subject: [PATCH 11/11] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 7ac314280e..f0fb43062b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Internal +* 👷 Do not run codspeed with coverage as it's not tracked. PR [#14966](https://github.com/fastapi/fastapi/pull/14966) by [@tiangolo](https://github.com/tiangolo). * 👷 Do not include benchmark tests in coverage to speed up coverage processing. PR [#14965](https://github.com/fastapi/fastapi/pull/14965) by [@tiangolo](https://github.com/tiangolo). ## 0.131.0