From 5cb2bc6909be9a9f9cbd23e7f19f142091cbb5af Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Jul 2026 16:45:19 +0000 Subject: [PATCH] Homepage JSON returns databases as a list /.json previously returned databases as an object keyed by database name, unlike /-/databases.json and every other collection in the API. It now returns a list of database objects. The HTML template already consumed the list. This endpoint remains undocumented. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GrHZSypDfMnym1tM5XJAFZ --- datasette/views/index.py | 2 +- existing-api.md | 4 ++-- stable-api-recommendations.md | 6 ++++-- tests/test_api.py | 18 ++++++++++-------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/datasette/views/index.py b/datasette/views/index.py index 86f8ae7d..8c5534cb 100644 --- a/datasette/views/index.py +++ b/datasette/views/index.py @@ -152,7 +152,7 @@ class IndexView(BaseView): json.dumps( { "ok": True, - "databases": {db["name"]: db for db in databases}, + "databases": databases, "metadata": await self.ds.get_instance_metadata(), }, cls=CustomJSONEncoder, diff --git a/existing-api.md b/existing-api.md index af7ca3b9..8d390f76 100644 --- a/existing-api.md +++ b/existing-api.md @@ -201,8 +201,8 @@ Routes: `/(\.(?Pjsono?))?$` and `/-/(\.(?Pjsono?))?$` - **Parameters:** `_sort=relationships` sorts each database's truncated table list by foreign-key relationship count. - **JSON response** (index.py:147-161) — includes `ok: true` plus: - - `databases` — an **object keyed by database name** (not a list). Each - value: `name`, `hash` (or null), `color`, `path`, + - `databases` — a **list** of database objects (undocumented API, subject + to change). Each item: `name`, `hash` (or null), `color`, `path`, `tables_and_views_truncated` (up to 5 items: `name`, `columns`, `primary_keys`, `count` (int or null), `hidden`, `fts_table`, `num_relationships_for_sorting`, `private`; view items are just diff --git a/stable-api-recommendations.md b/stable-api-recommendations.md index 3c216c04..d64b3fb8 100644 --- a/stable-api-recommendations.md +++ b/stable-api-recommendations.md @@ -156,10 +156,12 @@ Endpoints disagree about the success envelope: ### 2a. Collection representations disagree (P2) -- Homepage `/.json` returns `databases` as an **object keyed by name** +- ~~Homepage `/.json` returns `databases` as an **object keyed by name** (index.py:147-161); `/-/databases.json` returns an **array**; the database page returns `tables` as an array. Choose arrays-of-objects everywhere - (objects-keyed-by-name break when names need ordering or pagination). + (objects-keyed-by-name break when names need ordering or pagination).~~ + ✅ **Done** — the homepage returns a list, matching `/-/databases.json`. + The homepage JSON remains deliberately undocumented. - ~~Insert/upsert with `return: true` respond with `rows` (plural, list); row update with `return: true` responds with `row` (singular, object) (views/row.py:837-844). Pick one (`rows` everywhere, even for one row, diff --git a/tests/test_api.py b/tests/test_api.py index 7a575144..17edc0e5 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -26,8 +26,9 @@ async def test_homepage(ds_client): "title", ] databases = data.get("databases") - assert databases.keys() == {"fixtures": 0}.keys() - d = databases["fixtures"] + assert isinstance(databases, list) + assert [d["name"] for d in databases] == ["fixtures"] + d = databases[0] assert d["name"] == "fixtures" assert isinstance(d["tables_count"], int) assert isinstance(len(d["tables_and_views_truncated"]), int) @@ -43,7 +44,7 @@ async def test_homepage_sort_by_relationships(ds_client): assert response.status_code == 200 tables = [ t["name"] - for t in response.json()["databases"]["fixtures"]["tables_and_views_truncated"] + for t in response.json()["databases"][0]["tables_and_views_truncated"] ] assert tables == [ "simple_primary_key", @@ -251,8 +252,8 @@ def test_no_files_uses_memory_database(app_client_no_files): assert response.status == 200 assert { "ok": True, - "databases": { - "_memory": { + "databases": [ + { "name": "_memory", "hash": None, "color": "a6c7b9", @@ -267,7 +268,7 @@ def test_no_files_uses_memory_database(app_client_no_files): "views_count": 0, "private": False, }, - }, + ], "metadata": {}, } == response.json # Try that SQL query @@ -852,8 +853,9 @@ async def test_tilde_encoded_database_names(db_name): ds = Datasette() ds.add_memory_database(db_name) response = await ds.client.get("/.json") - assert db_name in response.json()["databases"].keys() - path = response.json()["databases"][db_name]["path"] + databases_by_name = {d["name"]: d for d in response.json()["databases"]} + assert db_name in databases_by_name + path = databases_by_name[db_name]["path"] # And the JSON for that database response2 = await ds.client.get(path + ".json") assert response2.status_code == 200