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