diff --git a/datasette/app.py b/datasette/app.py index 860f456322..8db650e975 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -44,6 +44,7 @@ from .database import Database, QueryInterrupted from .utils import ( + PrefixedUrlString, async_call_with_supported_arguments, await_me_maybe, call_with_supported_arguments, @@ -1242,9 +1243,12 @@ class NotFoundExplicit(NotFound): class DatasetteClient: def __init__(self, ds): + self.ds = ds self.app = ds.app() def _fix(self, path): + if not isinstance(path, PrefixedUrlString): + path = self.ds.urls.path(path) if path.startswith("/"): path = "http://localhost{}".format(path) return path diff --git a/datasette/utils/asgi.py b/datasette/utils/asgi.py index f438f8292d..e4c8ce5ca2 100644 --- a/datasette/utils/asgi.py +++ b/datasette/utils/asgi.py @@ -387,9 +387,9 @@ def text(cls, body, status=200, headers=None): ) @classmethod - def json(cls, body, status=200, headers=None): + def json(cls, body, status=200, headers=None, default=None): return cls( - json.dumps(body), + json.dumps(body, default=default), status=status, headers=headers, content_type="application/json; charset=utf-8", diff --git a/docs/internals.rst b/docs/internals.rst index 8594e36a0c..d3d0be8e6e 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -387,6 +387,18 @@ It offers the following methods: ``await datasette.client.request(method, path, **kwargs)`` - returns HTTPX Response Execute an internal request with the given HTTP method against that path. +These methods can be used with :ref:`internals_datasette_urls` - for example: + +.. code-block:: python + + table_json = ( + await datasette.client.get( + datasette.urls.table("fixtures", "facetable", format="json") + ) + ).json() + +``datasette.client`` methods automatically take the current :ref:`config_base_url` setting into account, whether or not you use the ``datasette.urls`` family of methods to construct the path. + For documentation on available ``**kwargs`` options and the shape of the HTTPX Response object refer to the `HTTPX Async documentation `__. .. _internals_datasette_urls: diff --git a/tests/plugins/my_plugin.py b/tests/plugins/my_plugin.py index b487cdf0fb..767c363d63 100644 --- a/tests/plugins/my_plugin.py +++ b/tests/plugins/my_plugin.py @@ -257,6 +257,9 @@ def login_as_root(datasette, request): ) ) + def asgi_scope(scope): + return Response.json(scope, default=repr) + return [ (r"/one/$", one), (r"/two/(?P.*)$", two), @@ -267,6 +270,7 @@ def login_as_root(datasette, request): (r"/not-async/$", not_async), (r"/add-message/$", add_message), (r"/render-message/$", render_message), + (r"/asgi-scope$", asgi_scope), ] diff --git a/tests/test_internals_datasette_client.py b/tests/test_internals_datasette_client.py index d73fbb06cc..0b1c5f0e61 100644 --- a/tests/test_internals_datasette_client.py +++ b/tests/test_internals_datasette_client.py @@ -31,14 +31,37 @@ async def test_client_methods(datasette, method, path, expected_status): @pytest.mark.asyncio -async def test_client_post(datasette): - response = await datasette.client.post( - "/-/messages", - data={ - "message": "A message", - }, - allow_redirects=False, - ) - assert isinstance(response, httpx.Response) - assert response.status_code == 302 - assert "ds_messages" in response.cookies +@pytest.mark.parametrize("prefix", [None, "/prefix/"]) +async def test_client_post(datasette, prefix): + original_base_url = datasette._config["base_url"] + try: + if prefix is not None: + datasette._config["base_url"] = prefix + response = await datasette.client.post( + "/-/messages", + data={ + "message": "A message", + }, + allow_redirects=False, + ) + assert isinstance(response, httpx.Response) + assert response.status_code == 302 + assert "ds_messages" in response.cookies + finally: + datasette._config["base_url"] = original_base_url + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "prefix,expected_path", [(None, "/asgi-scope"), ("/prefix/", "/prefix/asgi-scope")] +) +async def test_client_path(datasette, prefix, expected_path): + original_base_url = datasette._config["base_url"] + try: + if prefix is not None: + datasette._config["base_url"] = prefix + response = await datasette.client.get("/asgi-scope") + path = response.json()["path"] + assert path == expected_path + finally: + datasette._config["base_url"] = original_base_url