mirror of
https://github.com/fastapi/fastapi.git
synced 2026-02-23 10:25:02 -05:00
🗑️ Deprecate ORJSONResponse and UJSONResponse (#14964)
This commit is contained in:
committed by
GitHub
parent
2e62fb1513
commit
48e9835732
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/).
|
||||
**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/).
|
||||
|
||||
**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.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/).
|
||||
**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/).
|
||||
|
||||
**Note**: `orjson` is not included with FastAPI and must be installed
|
||||
separately, e.g. `pip install orjson`.
|
||||
"""
|
||||
|
||||
def render(self, content: Any) -> bytes:
|
||||
|
||||
@@ -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",
|
||||
|
||||
73
tests/test_deprecated_responses.py
Normal file
73
tests/test_deprecated_responses.py
Normal file
@@ -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"})
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
20
uv.lock
generated
20
uv.lock
generated
@@ -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" },
|
||||
|
||||
Reference in New Issue
Block a user