mirror of
https://github.com/simonw/datasette.git
synced 2026-07-08 08:34:42 +02:00
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GrHZSypDfMnym1tM5XJAFZ
This commit is contained in:
parent
19e54b10d4
commit
23ccdaeffc
7 changed files with 56 additions and 18 deletions
|
|
@ -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",
|
||||
),
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
{% include "_permissions_debug_tabs.html" %}
|
||||
|
||||
<p style="margin-bottom: 2em;">
|
||||
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.
|
||||
</p>
|
||||
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for action in data %}
|
||||
{% for action in data.actions %}
|
||||
<tr>
|
||||
<td><strong>{{ action.name }}</strong></td>
|
||||
<td>{% if action.abbr %}<code>{{ action.abbr }}</code>{% endif %}</td>
|
||||
|
|
|
|||
|
|
@ -155,6 +155,30 @@ Shows currently attached databases. `Databases example <https://latest.datasette
|
|||
]
|
||||
}
|
||||
|
||||
.. _JsonDataView_actions:
|
||||
|
||||
/-/actions
|
||||
----------
|
||||
|
||||
Shows all actions registered with the permission system, including those added by plugins. Requires the ``permissions-debug`` permission.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"ok": true,
|
||||
"actions": [
|
||||
{
|
||||
"name": "view-instance",
|
||||
"abbr": "vi",
|
||||
"description": "View Datasette instance",
|
||||
"takes_parent": false,
|
||||
"takes_child": false,
|
||||
"resource_class": null,
|
||||
"also_requires": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. _JumpView:
|
||||
|
||||
/-/jump
|
||||
|
|
|
|||
|
|
@ -48,9 +48,9 @@ directory: every claim below is based on the route table in `datasette/app.py`
|
|||
Every JSON endpoint that returns an object includes `"ok": true` on
|
||||
success. `JsonDataView` injects it automatically for dict responses
|
||||
(views/special.py); the homepage, jump, schema, permission-debug and
|
||||
autocomplete views add it explicitly. The remaining top-level-array
|
||||
endpoints (`/-/plugins`, `/-/databases`, `/-/actions`) are being converted
|
||||
to objects.
|
||||
autocomplete views add it explicitly. The former top-level-array endpoints
|
||||
(`/-/plugins`, `/-/databases`, `/-/actions`) now return objects wrapping
|
||||
their arrays (`{"ok": true, "plugins": [...]}` etc.).
|
||||
|
||||
### Error shape (canonical)
|
||||
|
||||
|
|
@ -276,9 +276,9 @@ Response: `{"ok": true, "actor": {...}}` or `{"ok": true, "actor": null}` (app.p
|
|||
|
||||
app.py:2580-2589. Permission **`permissions-debug`**. No parameters.
|
||||
|
||||
Response: a JSON array, sorted by name, of `{"name", "abbr", "description",
|
||||
"takes_parent", "takes_child", "resource_class", "also_requires"}`
|
||||
(app.py:2290-2304).
|
||||
Response: `{"ok": true, "actions": [...]}` — each action is
|
||||
`{"name", "abbr", "description", "takes_parent", "takes_child",
|
||||
"resource_class", "also_requires"}`, sorted by name (app.py:2290-2304).
|
||||
|
||||
### GET /-/auth-token
|
||||
|
||||
|
|
|
|||
|
|
@ -102,15 +102,18 @@ key — see §2.)
|
|||
|
||||
---
|
||||
|
||||
## 2. Success envelope: `ok` is not universal, arrays are not extensible (P1/P2) — ✅ PARTIALLY IMPLEMENTED
|
||||
## 2. Success envelope: `ok` is not universal, arrays are not extensible (P1/P2) — ✅ IMPLEMENTED (§2a-2c open)
|
||||
|
||||
> **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).
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"]}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue