diff --git a/datasette/app.py b/datasette/app.py index e13f0731..b554f9bc 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -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(\.(?Pjson))?$", ) diff --git a/docs/introspection.rst b/docs/introspection.rst index d0780763..99238204 100644 --- a/docs/introspection.rst +++ b/docs/introspection.rst @@ -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. diff --git a/existing-api.md b/existing-api.md index 0e7ec907..db613d30 100644 --- a/existing-api.md +++ b/existing-api.md @@ -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) diff --git a/tests/test_api.py b/tests/test_api.py index 4635b236..3583503c 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -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) diff --git a/tests/test_config_dir.py b/tests/test_config_dir.py index 0a9b30d8..74407e20 100644 --- a/tests/test_config_dir.py +++ b/tests/test_config_dir.py @@ -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): diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 59b1c0bf..5c4034db 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -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: diff --git a/tests/test_success_envelope.py b/tests/test_success_envelope.py index 48407660..cecc8d70 100644 --- a/tests/test_success_envelope.py +++ b/tests/test_success_envelope.py @@ -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"])