Repository: encode/starlette

Issue:
Consider removing UJSONResponse from core
Sorry if this isn’t the place to discuss this. I created this issue for visibility of a [comment](https://github.com/encode/starlette/pull/870#issuecomment-614258113) I made in a pull request to add a `ORJSONResponse` class (similar to the `UJSONResponse` class). I'm gonna quote it here:

> [...] I don't think flavour of the month JSON libraries belong in Starlette core, this applies to `UJSONResponse` as well. Next up someone wants a `SIMDJSONResponse` class[1]. Where should the line be drawn?
> 
> Another aspect to consider is the maintenance burden of implementing custom response classes like this in core, because that means a contract has been signed. So if orjson becomes unmaintained tomorrow, Starlette would have to go through a deprecation cycle to remove it. Or keep it forever... Bugs in the renderer(s) would likely end up as a Starlette issue as well, instead of in the upstream JSON library.
> 
> But enough complaining and on to more constructive feedback. It's evident by this pull request that it's pretty trivial to implement your own custom `JSONResponse` class, so maybe just a section in the documentation describing how to do it is enough? Then someone can (and probably will) publish a package to PyPI that Starlette later can link to, e.g. starlette-orjson. Maybe even a starlette-contrib repo under the encode org? 🤷‍♂️ 
> 
> Another option is to make the default `JSONResponse` more pluggable/configurable, similar to how you can configure renderers in Django REST Framework[2]. Personally, I like this approach.
>
> [...]
> 
> [1] https://github.com/TkTech/pysimdjson
> [2] https://www.django-rest-framework.org/api-guide/settings/#default_parser_classes


Discussion:
It seems like this has already been hinted at by @tomchristie in a another issue as well, see https://github.com/encode/starlette/issues/304#issuecomment-451397552.
Yup, indeed - I'm in favour of removing it from core.

Resolving patch:
diff --git a/setup.py b/setup.py
--- 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
--- 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 <py3.8
 def guess_type(
@@ -172,14 +167,6 @@ def render(self, content: typing.Any) -> 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,


Test patch:
diff --git a/tests/test_responses.py b/tests/test_responses.py
--- 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)