diff --git a/README.md b/README.md index 735b66694..0b4508124 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,6 @@ Starlette does not have any hard dependencies, but the following are optional: * [`itsdangerous`][itsdangerous] - Required for `SessionMiddleware` support. * [`pyyaml`][pyyaml] - Required for `SchemaGenerator` support. * [`graphene`][graphene] - Required for `GraphQLApp` support. -* [`ujson`][ujson] - Required if you want to use `UJSONResponse`. You can install all of these with `pip3 install starlette[full]`. @@ -140,7 +139,6 @@ as [one of the fastest Python frameworks available](https://www.techempower.com/ For high throughput loads you should: -* Make sure to install `ujson` and use `UJSONResponse`. * Run using gunicorn using the `uvicorn` worker class. * Use one or two workers per-CPU core. (You might need to experiment with this.) * Disable access logging. @@ -178,4 +176,3 @@ gunicorn -k uvicorn.workers.UvicornH11Worker ... [itsdangerous]: https://pythonhosted.org/itsdangerous/ [sqlalchemy]: https://www.sqlalchemy.org [pyyaml]: https://pyyaml.org/wiki/PyYAMLDocumentation -[ujson]: https://github.com/esnme/ultrajson diff --git a/docs/index.md b/docs/index.md index 7a04fed15..70ba5ced7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -89,7 +89,6 @@ Starlette does not have any hard dependencies, but the following are optional: * [`itsdangerous`][itsdangerous] - Required for `SessionMiddleware` support. * [`pyyaml`][pyyaml] - Required for `SchemaGenerator` support. * [`graphene`][graphene] - Required for `GraphQLApp` support. -* [`ujson`][ujson] - Required if you want to use `UJSONResponse`. You can install all of these with `pip3 install starlette[full]`. @@ -134,7 +133,6 @@ as [one of the fastest Python frameworks available](https://www.techempower.com/ For high throughput loads you should: -* Make sure to install `ujson` and use `UJSONResponse`. * Run using Gunicorn using the `uvicorn` worker class. * Use one or two workers per-CPU core. (You might need to experiment with this.) * Disable access logging. @@ -172,4 +170,3 @@ gunicorn -k uvicorn.workers.UvicornH11Worker ... [itsdangerous]: https://pythonhosted.org/itsdangerous/ [sqlalchemy]: https://www.sqlalchemy.org [pyyaml]: https://pyyaml.org/wiki/PyYAMLDocumentation -[ujson]: https://github.com/esnme/ultrajson diff --git a/docs/responses.md b/docs/responses.md index 1a4fda44b..2ac7c4e71 100644 --- a/docs/responses.md +++ b/docs/responses.md @@ -92,26 +92,30 @@ async def app(scope, receive, send): await response(scope, receive, send) ``` -### UJSONResponse +#### Custom JSON serialization -A JSON response class that uses the optimised `ujson` library for serialisation. +If you need fine-grained control over JSON serialization, you can subclass +`JSONResponse` and override the `render` method. -Using `ujson` will result in faster JSON serialisation, but is also less careful -than Python's built-in implementation in how it handles some edge-cases. - -In general you *probably* want to stick with `JSONResponse` by default unless -you are micro-optimising a particular endpoint. +For example, if you wanted to use a third-party JSON library such as +[orjson](https://pypi.org/project/orjson/): ```python -from starlette.responses import UJSONResponse +from typing import Any +import orjson +from starlette.responses import JSONResponse -async def app(scope, receive, send): - assert scope['type'] == 'http' - response = UJSONResponse({'hello': 'world'}) - await response(scope, receive, send) + +class OrjsonResponse(JSONResponse): + def render(self, content: Any) -> bytes: + return orjson.dumps(content) ``` +In general you *probably* want to stick with `JSONResponse` by default unless +you are micro-optimising a particular endpoint or need to serialize non-standard +object types. + ### RedirectResponse Returns an HTTP redirect. Uses a 307 status code by default. diff --git a/requirements.txt b/requirements.txt index 4d4e205fa..55fb0768b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,6 @@ jinja2 python-multipart pyyaml requests -ujson # Testing autoflake diff --git a/setup.py b/setup.py index 08989cee8..37a09ea48 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,6 @@ def get_packages(package): "python-multipart", "pyyaml", "requests", - "ujson", ] }, classifiers=[ diff --git a/starlette/responses.py b/starlette/responses.py index d5f273467..ff122fba1 100644 --- a/starlette/responses.py +++ b/starlette/responses.py @@ -24,11 +24,6 @@ aiofiles = None # type: ignore aio_stat = None # type: ignore -try: - import ujson -except ImportError: # pragma: nocover - ujson = None # type: ignore - # Compatibility wrapper for `mimetypes.guess_type` to support `os.PathLike` on bytes: ).encode("utf-8") -class UJSONResponse(JSONResponse): - media_type = "application/json" - - def render(self, content: typing.Any) -> bytes: - assert ujson is not None, "ujson must be installed to use UJSONResponse" - return ujson.dumps(content, ensure_ascii=False).encode("utf-8") - - class RedirectResponse(Response): def __init__( self, diff --git a/tests/test_responses.py b/tests/test_responses.py index d1d26fce2..10fbe673c 100644 --- a/tests/test_responses.py +++ b/tests/test_responses.py @@ -12,7 +12,6 @@ RedirectResponse, Response, StreamingResponse, - UJSONResponse, ) from starlette.testclient import TestClient @@ -37,16 +36,6 @@ async def app(scope, receive, send): assert response.content == b"xxxxx" -def test_ujson_response(): - async def app(scope, receive, send): - response = UJSONResponse({"hello": "world"}) - await response(scope, receive, send) - - client = TestClient(app) - response = client.get("/") - assert response.json() == {"hello": "world"} - - def test_json_none_response(): async def app(scope, receive, send): response = JSONResponse(None)