From 126352573622ece470d8c07a5272a131cb8b3b6c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 16:58:11 +0000 Subject: [PATCH] Show POST on the CATMAID docs page, not just GET The interactive docs page and /docs.json build every endpoint card from a shared renderer that hard-coded a single "GET" badge, because until now every endpoint in this API was GET-only. The new /catmaid POST route (previous commit) went live without the docs page ever mentioning it: the generic /catmaid/{instance}/{command} card and all ~40 per-command runnable cards still only claimed GET. Add a `methods` field to the endpoint spec (defaulting to ["GET"] where absent, so nothing else changes) and render one badge per method instead of a fixed string. Both the generic CATMAID command entry and the auto-generated per-command cards now say GET and POST, and the description spells out the body format and gives a curl example, since the page's own "Run" button only ever issues a GET. --- src/test/test_catmaid_passthrough.py | 18 ++++++++++++++++++ src/vfbquery/api_docs.py | 26 ++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/test/test_catmaid_passthrough.py b/src/test/test_catmaid_passthrough.py index f379450..2e2702d 100644 --- a/src/test/test_catmaid_passthrough.py +++ b/src/test/test_catmaid_passthrough.py @@ -681,3 +681,21 @@ def test_live_swc_alignments_on_fafb(): assert any(r["template"] == "VFB_00101567" and r["swc_available"] for r in vfb_rows) # JRC2018U copy exists assert envelope["result"]["skid"] == "13146" + + +# --------------------------------------------------------------------------- +# Docs page: the generic /catmaid/{instance}/{command} card must say POST +# is supported too, not just GET — this is what /docs.json and the +# interactive docs page at "/" are built from. +# --------------------------------------------------------------------------- + +def test_docs_spec_advertises_post_on_the_generic_catmaid_command(): + import vfbquery.api_docs as api_docs + + spec = api_docs.build_docs_spec("0.0.0-test") + group = next(g for g in spec["groups"] + if g["group"] == "CATMAID pass-through") + entry = next(e for e in group["endpoints"] + if e["path"] == "/catmaid/{instance}/{command}") + assert entry.get("methods") == ["GET", "POST"] + assert "POST" in entry["description"] diff --git a/src/vfbquery/api_docs.py b/src/vfbquery/api_docs.py index e1d962c..584fb12 100644 --- a/src/vfbquery/api_docs.py +++ b/src/vfbquery/api_docs.py @@ -285,6 +285,7 @@ }, { "path": "/catmaid/{instance}/{command}", + "methods": ["GET", "POST"], "summary": "Run a read-only CATMAID command", "description": ( "The curated CATMAID query surface. Commands taking " @@ -297,7 +298,17 @@ "aligned= names a template space for VFB's registered " "copy, and the swc_alignments command lists the spaces " "available. Every command also has its own runnable " - "card in the expanded section below."), + "card in the expanded section below. Also accepts POST: " + "put the same parameters in a JSON object body (or an " + "application/x-www-form-urlencoded/multipart form) " + "instead of the query string — the only way to send an " + "id list too long for a URL. project and raw stay on " + "the query string either way; a GET and the equivalent " + "POST share one cache entry. The Run button below only " + "exercises GET — for example, " + "curl -X POST '/catmaid/fafb/annotations_for_skeletons' " + "-H 'Content-Type: application/json' " + "-d '{\"ids\": [1, 2, 3]}'."), "path_params": [ {"name": "instance", "required": True, "doc": "Instance id (see /catmaid)", @@ -589,12 +600,18 @@ def build_docs_spec(version, query_types=None, catmaid_commands=None): } const meta = el("span", {class: "meta"}); const runBtn = el("button", {class: "run", text: "Run"}); + const methods = endpoint.methods || ["GET"]; + if (methods.length > 1) body.append(el("p", {class: "desc"}, + "The Run button below sends a ", el("code", {text: "GET"}), + " — the other method", + methods.length > 2 ? "s take" : " takes", " the same parameters, " + + "either as a JSON body or a form body; see the description above.")); body.append(el("div", {class: "runrow"}, runBtn, meta), el("div", {class: "url"}), el("pre", {class: "result", hidden: "hidden"})); const details = el("details", {class: "ep", id: slug(endpoint.path)}, el("summary", null, - el("span", {class: "method", text: "GET"}), + ...methods.map((m) => el("span", {class: "method", text: m})), el("span", {class: "path", text: endpoint.path}), el("span", {class: "summ", text: endpoint.summary || ""})), body); @@ -642,6 +659,10 @@ def build_docs_spec(version, query_types=None, catmaid_commands=None): doc: "true returns the untouched CATMAID response"}); return { path: "/catmaid/{instance}/" + name, + // Client-facing transport: every command runs through the generic + // GET-or-POST route, regardless of which method it uses against + // CATMAID itself (that's info.method, shown in upstream/summary below). + methods: ["GET", "POST"], summary: info.local ? "answered by VFBquery" : info.method + " " + info.path, upstream: info.local ? "(served by VFBquery, not CATMAID)" : "CATMAID: " + info.method + " " + info.path, @@ -668,6 +689,7 @@ def build_docs_spec(version, query_types=None, catmaid_commands=None): const wrap = el("details", {class: "ep", id: "ep-catmaid-commands"}, el("summary", null, el("span", {class: "method", text: "GET"}), + el("span", {class: "method", text: "POST"}), el("span", {class: "path", text: "/catmaid/{instance}/…"}), el("span", {class: "summ", text: "All " + names.length + " commands, expanded"})),