From 23ccdaeffc1d403563933af5fcfa90be6947f7f4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Jul 2026 13:46:26 +0000 Subject: [PATCH] Convert /-/actions.json from top-level array to object /-/actions.json now returns {"ok": true, "actions": [...]} instead of a bare JSON array, so the response can grow additional keys without a breaking change. The debug_actions.html template reads data.actions, and the endpoint is now documented in docs/introspection.rst. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GrHZSypDfMnym1tM5XJAFZ --- datasette/app.py | 2 +- datasette/templates/debug_actions.html | 4 ++-- docs/introspection.rst | 24 ++++++++++++++++++++++++ existing-api.md | 12 ++++++------ stable-api-recommendations.md | 19 +++++++++++-------- tests/test_api.py | 2 +- tests/test_success_envelope.py | 11 +++++++++++ 7 files changed, 56 insertions(+), 18 deletions(-) diff --git a/datasette/app.py b/datasette/app.py index bc35669f..5afca2a2 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -2588,7 +2588,7 @@ class Datasette: JsonDataView.as_view( self, "actions.json", - self._actions, + lambda: {"actions": self._actions()}, template="debug_actions.html", permission="permissions-debug", ), diff --git a/datasette/templates/debug_actions.html b/datasette/templates/debug_actions.html index 0ef7b329..c9dccaaa 100644 --- a/datasette/templates/debug_actions.html +++ b/datasette/templates/debug_actions.html @@ -9,7 +9,7 @@ {% include "_permissions_debug_tabs.html" %}

- This Datasette instance has registered {{ data|length }} action{{ data|length != 1 and "s" or "" }}. + This Datasette instance has registered {{ data.actions|length }} action{{ data.actions|length != 1 and "s" or "" }}. Actions are used by the permission system to control access to different features.

@@ -26,7 +26,7 @@ - {% for action in data %} + {% for action in data.actions %} {{ action.name }} {% if action.abbr %}{{ action.abbr }}{% endif %} diff --git a/docs/introspection.rst b/docs/introspection.rst index ab47c0a5..0010e8b7 100644 --- a/docs/introspection.rst +++ b/docs/introspection.rst @@ -155,6 +155,30 @@ Shows currently attached databases. `Databases example **Status:** recommendation 2 and 3 are implemented — every JSON-object +> **Status:** recommendations 1-3 are implemented. Every JSON-object > success response now includes `"ok": true` (`JsonDataView` injects it for > dict responses; homepage, jump, schema, permission-debug and autocomplete -> views set it explicitly; covered by `tests/test_success_envelope.py`). -> Recommendation 1 (wrapping the `/-/plugins`, `/-/databases`, `/-/actions` -> top-level arrays in objects) is being landed as separate per-endpoint -> commits. §2a/2b/2c remain open. +> views set it explicitly), and the three top-level-array endpoints now +> return objects: `/-/plugins` → `{"ok": true, "plugins": [...]}`, +> `/-/databases` → `{"ok": true, "databases": [...]}`, `/-/actions` → +> `{"ok": true, "actions": [...]}`. Covered by +> `tests/test_success_envelope.py`. The sub-findings §2a (collection +> representations), §2b (`_extra`/`_shape` coverage) and §2c (count +> truncation) remain open. Endpoints disagree about the success envelope: @@ -372,8 +375,8 @@ Two details make tiering urgent rather than optional: 2. `Forbidden` → JSON 403 for JSON requests (§1a). 3. No `ok: false` with HTTP 200 (§1b: `_shape=object`, write canned-query SQL errors). -4. Wrap `/-/plugins`, `/-/databases`, `/-/actions` top-level arrays in - objects (§2). +4. ~~Wrap `/-/plugins`, `/-/databases`, `/-/actions` top-level arrays in + objects (§2).~~ ✅ Done. 5. Filter `/-/databases.json` by `view-database` or gate it behind `permissions-debug` (§6). 6. 401 (not silent-anonymous) for invalid/expired bearer tokens (§1c). diff --git a/tests/test_api.py b/tests/test_api.py index 3263a88c..b035edb9 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -579,7 +579,7 @@ async def test_actions_json(ds_client): try: ds_client.ds.root_enabled = True response = await ds_client.get("/-/actions.json", actor={"id": "root"}) - data = response.json() + data = response.json()["actions"] finally: ds_client.ds.root_enabled = original_root_enabled assert isinstance(data, list) diff --git a/tests/test_success_envelope.py b/tests/test_success_envelope.py index 290a51e0..22ce6580 100644 --- a/tests/test_success_envelope.py +++ b/tests/test_success_envelope.py @@ -101,3 +101,14 @@ async def test_databases_json_is_object(ds_client): assert data["ok"] is True assert isinstance(data["databases"], list) assert "fixtures" in {db["name"] for db in data["databases"]} + + +@pytest.mark.asyncio +async def test_actions_json_is_object(ds_envelope): + response = await ds_envelope.client.get("/-/actions.json", actor={"id": "root"}) + assert response.status_code == 200 + data = response.json() + assert set(data.keys()) == {"ok", "actions"} + assert data["ok"] is True + assert isinstance(data["actions"], list) + assert "view-instance" in {action["name"] for action in data["actions"]}