Skip to content

refactor(api): assemble api_router in the v1 package __init__ - #585

Merged
abdulrafey1 merged 7 commits into
mainfrom
refey/refactor/lang-06-api-init
Aug 12, 2026
Merged

refactor(api): assemble api_router in the v1 package __init__#585
abdulrafey1 merged 7 commits into
mainfrom
refey/refactor/lang-06-api-init

Conversation

@abdulrafey1

@abdulrafey1 abdulrafey1 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What

Moves the api_router assembly from sparkth/api/v1/api.py into the package's own sparkth/api/v1/__init__.py and deletes the module, so importing the package yields the router directly.

Changes

  • refactor(api): assemble api_router in sparkth/api/v1/__init__.py and delete sparkth/api/v1/api.py
  • refactor(api): import it in sparkth/main.py as from sparkth.api.v1 import api_router
  • docs(api): point the endpoint-module convention section at the new home

How to Test

  1. uv run pytest -q — 1726 passed, 3 skipped.
  2. make test.frontend.api — no drift, so the OpenAPI document is byte-identical.
  3. uv run python -c "from sparkth.api.v1 import api_router; print(len(api_router.routes))" prints 9.
  4. uv run python -c "import sparkth.api.v1.user.schemas" — importing a submodule directly still works, confirming the package __init__ importing every endpoint module introduces no cycle.
  5. make backend.up.dev and hit any endpoint, e.g. curl http://localhost:7727/api/v1/languages.

Notes

No migration, no env var, no dependency change. Prefixes and OpenAPI tags are untouched, so no URL moves and no client regeneration is neededgenerated.ts is unchanged and the drift check proves it.

api.py sat inside the api/v1 package repeating its parent's name while the package __init__ was empty. Every endpoint module in the package already exposes a router; this puts the thing that aggregates them where callers naturally look for it.

#582 and #583 merged after this branch was cut, so main was merged in. They landed as squash commits against this branch's copies of the same work, which git surfaced as six conflicts; each file this branch does not own is resolved to main's version, including the review fixes those PRs picked up. api.py's deletion is kept — its nine include_router calls were verified identical to the ones in __init__.py — and the convention section in architectural_patterns.md is a union of main's amended shared-models rule and this branch's rename.

This description was written with the assistance of an LLM (Claude).

@abdulrafey1
abdulrafey1 force-pushed the refey/refactor/lang-06-api-init branch from 2f62930 to 70033d6 Compare August 11, 2026 23:23
@abdulrafey1
abdulrafey1 force-pushed the refey/refactor/lang-06-api-init branch from 70033d6 to 120aa80 Compare August 11, 2026 23:31
abdulrafey1 and others added 6 commits August 12, 2026 11:36
The frontend picker and the static-UI translation layer both need this list;
serving it keeps the allowlist defined once instead of duplicated client-side.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PATCH /user/me validates the tag against the supported-language allowlist in the
request model, so an unsupported value is a 422 before any domain logic runs. GET
returns the raw column, null included, so the frontend can tell a user who never
chose from one who chose English.

PATCH loads the row it is about to write with session.get rather than mutating the
injected principal. get_current_user resolves the same request-scoped session, so in
production that is an identity-map hit; the guard exists because a principal that is
not attached to the request session would otherwise make commit() a silent no-op
while the endpoint still answered 200. Such a principal is now a 404. updated_at
carries a default_factory but no onupdate, so the write bumps it explicitly.

The test double for get_current_user takes the same session dependency the real one
does, so a value one request writes is visible to a later request on the same client.
That rewrote _override_current_user — the shared helper the three pre-existing
is_admin tests also call — from a detached make_transient snapshot to a live
select(User) on the request session; those three tests are themselves left unmodified
and still pass.

Also covers the PATCH auth gate (401 unauthenticated), that a PATCH only ever writes
to the caller's own row, that a principal with no row is refused rather than handed a
200 for a write that never happened, and that a successful PATCH advances updated_at.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
openapi.json is a gitignored build artifact and is not committed; only the
generated TypeScript client changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Records when an api/v1 module is a single file and when it is a package with
routes.py and schemas.py, now that user/ and language/ join permissions/ and
whitelist/ in the second shape while five modules remain single files.

Covers what belongs in each file, why __init__.py re-exporting router is a
deliberate exception to the avoid-re-exports rule, that domain exception -> HTTP
mappings register there, and that route paths come from the prefix in api.py so
converting a file to a package never moves a URL.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Covers the supported list, how to read and set a user's choice, what null means,
and what happens to a stored tag once its language leaves the allowlist. The guide
is explicit that generated content does not consume the preference yet, so nothing
here promises behaviour the platform does not have. The DEFAULT_LANGUAGE reference
entry ships with the setting itself, so this change is guide-only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Moves the router assembly out of sparkth/api/v1/api.py into the package's own
__init__.py and deletes the module, so importing the package yields the router
rather than requiring a nested api submodule whose name repeated its parent's.

sparkth/main.py imports it as `from sparkth.api.v1 import api_router`. Prefixes
and tags are unchanged, so the OpenAPI document and the generated frontend client
are byte-identical and no URL moves. The endpoint-module convention doc is updated
to point at the new home.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@abdulrafey1
abdulrafey1 force-pushed the refey/refactor/lang-06-api-init branch from 120aa80 to cb884d1 Compare August 12, 2026 06:37
Base automatically changed from refey/docs/lang-05-guide to main August 12, 2026 08:33
#582 and #583 landed on main as squash commits while this branch carried their original
commits, so git saw the same files added on both sides and flagged six conflicts where
there was one real question: which copy is current.

main's is. Its copies include the review fixes made after this branch was cut — the
languages endpoint dropped its auth gate, and the User schema moved back to the root
sparkth/schemas.py — so every file this branch does not own is resolved to main's version,
and the branch keeps only what it exists to change: the api_router assembly moving from
sparkth/api/v1/api.py into the package __init__.

Two of those needed more than a side chosen:

- api.py was modified in main (#582 registered the language router) and deleted here.
  Kept deleted; the nine include_router calls in __init__.py were verified identical to
  main's, prefixes and tags included, so the deletion drops no route.
- architectural_patterns.md is a union: main's amended shared-models rule, which now names
  User, plus this branch's rename of api.py to the package __init__. The re-export bullet
  is reworded because the rename leaves two different __init__.py roles in one section.

auth.py auto-merged without a conflict and silently kept this branch's pre-fix import,
dropping main's. Restored from main. Verified by diffing the whole tree against main: no
file outside the four this branch owns differs from it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@abdulrafey1
abdulrafey1 merged commit 99e3ce9 into main Aug 12, 2026
6 checks passed
@abdulrafey1
abdulrafey1 deleted the refey/refactor/lang-06-api-init branch August 12, 2026 09:59
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.

2 participants