From db660db4632409334e646237c3dd214764729cd4 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 8 Jun 2020 20:32:10 -0700 Subject: [PATCH] Docs + unit tests for Response, closes #821 --- datasette/utils/asgi.py | 9 ++++++ docs/internals.rst | 48 ++++++++++++++++++++++++++++++++ docs/plugins.rst | 2 +- tests/test_internals_response.py | 28 +++++++++++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 tests/test_internals_response.py diff --git a/datasette/utils/asgi.py b/datasette/utils/asgi.py index 349f2a0a..9e6c82dd 100644 --- a/datasette/utils/asgi.py +++ b/datasette/utils/asgi.py @@ -405,6 +405,15 @@ class Response: content_type="text/plain; charset=utf-8", ) + @classmethod + def json(cls, body, status=200, headers=None): + return cls( + json.dumps(body), + status=status, + headers=headers, + content_type="application/json; charset=utf-8", + ) + @classmethod def redirect(cls, path, status=302, headers=None): headers = headers or {} diff --git a/docs/internals.rst b/docs/internals.rst index 83dbd897..b0096cfa 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -80,6 +80,54 @@ Consider the querystring ``?foo=1&foo=2&bar=3`` - with two values for ``foo`` an ``len(request.args)`` - integer Returns the number of keys. +.. _internals_response: + +Response class +~~~~~~~~~~~~~~ + +The ``Response`` class can be returned from view functions that have been registered using the :ref:`plugin_register_routes` hook. + +The ``Response()`` constructor takes the following arguments: + +``body`` - string + The body of the response. + +``status`` - integer (optional) + The HTTP status - defaults to 200. + +``headers`` - dictionary (optional) + A dictionary of extra HTTP headers, e.g. ``{"x-hello": "world"}``. + +``content_type`` - string (optional) + The content-type for the response. Defaults to ``text/plain``. + +For example: + +.. code-block:: python + + from datasette.utils.asgi import Response + + response = Response( + "This is XML", + content_type="application/xml; charset=utf-8" + ) + +The easiest way to create responses is using the ``Response.text(...)``, ``Response.html(...)``, ``Response.json(...)`` or ``Response.redirect(...)`` helper methods: + +.. code-block:: python + + from datasette.utils.asgi import Response + + html_response = Response.html("This is HTML") + json_response = Response.json({"this_is": "json"}) + text_response = Response.text("This will become utf-8 encoded text") + # Redirects are served as 302, unless you pass status=301: + redirect_response = Response.redirect("https://latest.datasette.io/") + +Each of these responses will use the correct corresponding content-type - ``text/html; charset=utf-8``, ``application/json; charset=utf-8`` or ``text/plain; charset=utf-8`` respectively. + +Each of the helper methods take optional ``status=`` and ``headers=`` arguments, documented above. + .. _internals_datasette: Datasette class diff --git a/docs/plugins.rst b/docs/plugins.rst index caca0019..465fcd52 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -882,7 +882,7 @@ The optional view function arguments are as follows: ``receive`` - function The ASGI receive function. -The function can either return a ``Response`` or it can return nothing and instead respond directly to the request using the ASGI ``receive`` function (for advanced uses only). +The function can either return a :ref:`internals_response` or it can return nothing and instead respond directly to the request using the ASGI ``send`` function (for advanced uses only). .. _plugin_register_facet_classes: diff --git a/tests/test_internals_response.py b/tests/test_internals_response.py new file mode 100644 index 00000000..7c11f858 --- /dev/null +++ b/tests/test_internals_response.py @@ -0,0 +1,28 @@ +from datasette.utils.asgi import Response + + +def test_response_html(): + response = Response.html("Hello from HTML") + assert 200 == response.status + assert "Hello from HTML" == response.body + assert "text/html; charset=utf-8" == response.content_type + + +def test_response_text(): + response = Response.text("Hello from text") + assert 200 == response.status + assert "Hello from text" == response.body + assert "text/plain; charset=utf-8" == response.content_type + + +def test_response_json(): + response = Response.json({"this_is": "json"}) + assert 200 == response.status + assert '{"this_is": "json"}' == response.body + assert "application/json; charset=utf-8" == response.content_type + + +def test_response_redirect(): + response = Response.redirect("/foo") + assert 302 == response.status + assert "/foo" == response.headers["Location"]