Skip to content

feat: themed API docs and versioned route namespace - #18

Merged
sagargg merged 7 commits into
mainfrom
feat/themed-docs-and-versioned-routes
Aug 19, 2026
Merged

feat: themed API docs and versioned route namespace#18
sagargg merged 7 commits into
mainfrom
feat/themed-docs-and-versioned-routes

Conversation

@sagargg

@sagargg sagargg commented Aug 19, 2026

Copy link
Copy Markdown
Member

Restructures the docs surface and the route namespace.

Docs

Swagger UI is now served from vendored assets rather than a CDN, so /datastore/api/v2/docs renders with no outbound network. The theme is ported from ckanext-openapidocs so this service's docs and the CKAN portal's read as one family.

The page is a Jinja template (api/templates/docs.html). Branding comes from DOCS_PRIMARY_COLOR / DOCS_HEADER_COLOR / DOCS_SITE_TITLE / DOCS_LOGO_URL; the colours are validated as CSS colours at startup since they land inside a <style> block.

All docs concerns moved out of main.py into api/docs.py, taking main.py from 327 to 77 lines — it's now just the app factory and lifespan.

Routes

Breaking. Actions move from /api/3/action/ to /datastore/api/v2/:

Path
Actions /datastore/api/v2/datastore_*
Health /datastore/api/health · /datastore/api/ready
Dump /datastore/api/dump/query · /datastore/api/dump/{resource_id}

Probes and downloads sit outside the version deliberately: an orchestrator's probe URL and a download link shouldn't churn when the action contract gets a new version.

info.version now carries the API contract version (v2), not the package build (0.2.0). The two are independent — the app can ship any number of releases without the contract changing.

Response help

The envelope's help field echoed the request URL back — no use to a caller who just typed it. It now deep-links into Swagger at the operation that served the request, so every response, errors included, points at that endpoint's docs:

"help": "https://host/datastore/api/v2/docs#/Datastore/datastore_search"

Operation ids are derived from the handler names, so anchors read as #/Datastore/datastore_search rather than FastAPI's generated datastore_search_datastore_api_v2_datastore_search_get. Generated client SDKs get sane method names as a side effect.

Also

  • Removed the welcome endpoint (GET /) and its now-dead APP_MESSAGE config. The root returns 404.
  • Updated the Dockerfile and docker-compose healthchecks, which pointed at the old /health and would otherwise have restart-looped on deploy.

Notes for reviewers

  • CKAN client paths are unchanged. ckan_client.py and auth/ckan/provider.py still call upstream CKAN's own /api/3/action/ — only this service's routes moved.
  • The vendored bundle is ~1.6MB of JS/CSS. That's the cost of no-CDN; happy to switch to CDN-with-theme if you'd rather keep the repo lean.
  • 441 tests pass, ruff clean, mypy unchanged from baseline. Test env isolation was extended so a developer's .env can't leak into assertions.

Restructure the docs surface and the route namespace.

Docs: serve Swagger UI from vendored assets instead of a CDN, themed to
match ckanext-openapidocs so both sets of docs read as one family. The page
is a Jinja template; branding comes from DOCS_* env vars, validated as CSS
colours since they land in a <style> block. All docs concerns moved out of
main.py into api/docs.py (327 -> 77 lines).

Routes: actions are versioned at /datastore/api/v2/, while health probes and
downloads sit unversioned at /datastore/api/ so probe URLs and download links
survive a contract bump. info.version now carries the API contract version
rather than the package build version -- the two are independent.

Also removes the welcome endpoint and its APP_MESSAGE config.
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e5e03667-8c17-48b6-8b34-0bffe9cf221d


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

sagargg and others added 6 commits August 19, 2026 11:45
`help` echoed the request URL back, which told the caller nothing they
hadn't just typed. It now deep-links into Swagger UI at the operation that
served the request, so every response — errors included — points at the
documentation for the endpoint you called.

Operation ids are derived from the handler names, so the anchors read as
`#/Datastore/datastore_search` rather than FastAPI's generated
`datastore_search_datastore_api_v2_datastore_search_get`. That also gives
generated client SDKs sane method names.

Unrouted requests fall back to the request URL, since a 404 has no
operation to link to.
The `ErrorEnvelope` example hardcoded its host. It is now composed from
`API_URL` and `API_PREFIX` in `core/constants.py`, so the example can't
drift out of sync with the paths the service actually serves.

Both live in constants rather than config because `schemas/` builds the
string and may not import config. Runtime `help` values are unaffected —
those come from the incoming request, so they stay correct behind a proxy.
Adds an `API_URL` setting (default https://example.com) so a deployment's
published examples show its own host instead of a placeholder.

`schemas/` declares the example with a relative path, since it may not read
config; `api/docs.py` fills in the host when the schema is built. Runtime
`help` values are untouched — those come from the incoming request, so they
stay correct behind a proxy or on any host.
Resolves tests/auth/test_orchestration.py: `authorize` always returns a
"user" key, so the incoming assertion was the correct one; kept HEAD's
formatting to match the rest of the file.

The merge also had a conflict git could not see. The analytics middleware
matched `/api/3/action/` and `/datastore/dump/`, both of which this branch
had moved, so it silently recorded nothing — analytics would have gone dark
in production, not just in tests. Its prefixes now derive from API_PREFIX /
API_BASE_PREFIX, as do the analytics tests' URLs.

Excluding the docs surface then had to become explicit: docs, redoc,
openapi.json and static now sit *inside* the versioned prefix, so they
matched the action pattern and were recorded as actions named "docs",
"redoc", and so on. Sitting outside `/api/3/action/` used to exclude them
for free.
@sagargg
sagargg merged commit 80ddb23 into main Aug 19, 2026
2 checks passed
sagargg added a commit that referenced this pull request Aug 19, 2026
* feat: themed API docs and versioned route namespace

Restructure the docs surface and the route namespace.

Docs: serve Swagger UI from vendored assets instead of a CDN, themed to
match ckanext-openapidocs so both sets of docs read as one family. The page
is a Jinja template; branding comes from DOCS_* env vars, validated as CSS
colours since they land in a <style> block. All docs concerns moved out of
main.py into api/docs.py (327 -> 77 lines).

Routes: actions are versioned at /datastore/api/v2/, while health probes and
downloads sit unversioned at /datastore/api/ so probe URLs and download links
survive a contract bump. info.version now carries the API contract version
rather than the package build version -- the two are independent.

Also removes the welcome endpoint and its APP_MESSAGE config.

* feat: point envelope `help` at the endpoint's docs

`help` echoed the request URL back, which told the caller nothing they
hadn't just typed. It now deep-links into Swagger UI at the operation that
served the request, so every response — errors included — points at the
documentation for the endpoint you called.

Operation ids are derived from the handler names, so the anchors read as
`#/Datastore/datastore_search` rather than FastAPI's generated
`datastore_search_datastore_api_v2_datastore_search_get`. That also gives
generated client SDKs sane method names.

Unrouted requests fall back to the request URL, since a 404 has no
operation to link to.

* refactor: build the schema example URL from constants

The `ErrorEnvelope` example hardcoded its host. It is now composed from
`API_URL` and `API_PREFIX` in `core/constants.py`, so the example can't
drift out of sync with the paths the service actually serves.

Both live in constants rather than config because `schemas/` builds the
string and may not import config. Runtime `help` values are unaffected —
those come from the incoming request, so they stay correct behind a proxy.

* feat: make the OpenAPI example host configurable via API_URL

Adds an `API_URL` setting (default https://example.com) so a deployment's
published examples show its own host instead of a placeholder.

`schemas/` declares the example with a relative path, since it may not read
config; `api/docs.py` fills in the host when the schema is built. Runtime
`help` values are untouched — those come from the incoming request, so they
stay correct behind a proxy or on any host.

* Fix: re-formatting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant