Convert /-/plugins.json from top-level array to object

/-/plugins.json now returns {"ok": true, "plugins": [...]} instead of a
bare JSON array, so the response can grow additional keys without a
breaking change. The `datasette plugins` CLI command still outputs a
plain array.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GrHZSypDfMnym1tM5XJAFZ
This commit is contained in:
Claude 2026-07-04 13:42:24 +00:00
commit b74a8e5b12
No known key found for this signature in database
7 changed files with 39 additions and 18 deletions

View file

@ -2551,7 +2551,10 @@ class Datasette:
)
add_route(
JsonDataView.as_view(
self, "plugins.json", self._plugins, needs_request=True
self,
"plugins.json",
lambda request: {"plugins": self._plugins(request)},
needs_request=True,
),
r"/-/plugins(\.(?P<format>json))?$",
)

View file

@ -78,15 +78,18 @@ Shows a list of currently installed plugins and their versions. `Plugins example
.. code-block:: json
[
{
"name": "datasette_cluster_map",
"static": true,
"templates": false,
"version": "0.10",
"hooks": ["extra_css_urls", "extra_js_urls", "extra_body_script"]
}
]
{
"ok": true,
"plugins": [
{
"name": "datasette_cluster_map",
"static": true,
"templates": false,
"version": "0.10",
"hooks": ["extra_css_urls", "extra_js_urls", "extra_body_script"]
}
]
}
Add ``?all=1`` to include details of the default plugins baked into Datasette.

View file

@ -228,8 +228,8 @@ app.py:2552-2557, `Datasette._plugins` (app.py:2247-2266). Permission
- **Parameters:** `?all=1` — include Datasette's built-in default plugins
(filtered out by default).
- **Response:** a JSON **array**, sorted by name, of
`{"name", "static", "templates", "version", "hooks"}`.
- **Response:** `{"ok": true, "plugins": [...]}` — each plugin is
`{"name", "static", "templates", "version", "hooks"}`, sorted by name.
### GET /-/settings(.json)

View file

@ -541,13 +541,13 @@ async def test_plugins_json(ds_client):
response = await ds_client.get("/-/plugins.json")
# Filter out TrackEventPlugin
actual_plugins = sorted(
[p for p in response.json() if p["name"] != "TrackEventPlugin"],
[p for p in response.json()["plugins"] if p["name"] != "TrackEventPlugin"],
key=lambda p: p["name"],
)
assert EXPECTED_PLUGINS == actual_plugins
# Try with ?all=1
response = await ds_client.get("/-/plugins.json?all=1")
names = {p["name"] for p in response.json()}
names = {p["name"] for p in response.json()["plugins"]}
assert names.issuperset(p["name"] for p in EXPECTED_PLUGINS)
assert names.issuperset(DEFAULT_PLUGINS)

View file

@ -109,9 +109,10 @@ def test_settings(config_dir_client):
def test_plugins(config_dir_client):
response = config_dir_client.get("/-/plugins.json")
assert 200 == response.status
assert "hooray.py" in {p["name"] for p in response.json}
assert "non_py_file.txt" not in {p["name"] for p in response.json}
assert "mypy_cache" not in {p["name"] for p in response.json}
plugins = response.json["plugins"]
assert "hooray.py" in {p["name"] for p in plugins}
assert "non_py_file.txt" not in {p["name"] for p in plugins}
assert "mypy_cache" not in {p["name"] for p in plugins}
def test_templates_and_plugin(config_dir_client):

View file

@ -1482,7 +1482,7 @@ async def test_plugin_is_installed():
datasette.pm.register(DummyPlugin(), name="DummyPlugin")
response = await datasette.client.get("/-/plugins.json")
assert response.status_code == 200
installed_plugins = {p["name"] for p in response.json()}
installed_plugins = {p["name"] for p in response.json()["plugins"]}
assert "DummyPlugin" in installed_plugins
finally:

View file

@ -76,3 +76,17 @@ async def test_permissions_post_success_has_ok_true(ds_envelope):
)
assert response.status_code == 200
assert response.json()["ok"] is True
@pytest.mark.asyncio
async def test_plugins_json_is_object(ds_client):
response = await ds_client.get("/-/plugins.json")
assert response.status_code == 200
data = response.json()
assert set(data.keys()) == {"ok", "plugins"}
assert data["ok"] is True
assert isinstance(data["plugins"], list)
# ?all=1 should include Datasette's default plugins in the same shape
response_all = await ds_client.get("/-/plugins.json?all=1")
all_plugins = response_all.json()["plugins"]
assert len(all_plugins) > len(data["plugins"])