thermograph/paths.py
Emi Griffith d17ac794fd Split the backend into domain packages (#217)
* Centralize filesystem paths in a single module

Add paths.py, which resolves the repo root once and derives the cache,
accounts DB, logs, templates, frontend and bundled-city-data locations
from it. Replace the 13 per-module `dirname(__file__)/..` anchors with
references to it, so a module's location no longer determines where the
app reads its data. Env overrides (accounts DB, VAPID, IndexNow) are
unchanged; every resolved path is byte-identical to before.

Groundwork for moving modules into packages without re-pointing paths.

Claude-Session: https://claude.ai/code/session_01XXxmNFy9cZ6Gh8Y9thZn62

* Split the backend into domain packages

Group the flat backend modules into packages that mirror their concerns:
  data/          climate, grading, scoring, grid, places, cities,
                 city_events, store
  web/           app, views, homepage, content, schemas
  notifications/ notify, digest, push, mailer, discord,
                 discord_interactions, discord_link
  accounts/      models, users, api_accounts, db
  core/          metrics, singleton, audit

Intra-project imports are rewritten to the package-qualified form. The
entry scripts (indexnow, warm_cities, migrate, gen_cities, gen_flavor)
and paths.py stay at the backend/ root, and backend/app.py becomes a
shim re-exporting web.app:app so the launch target stays `app:app` —
run.sh, the systemd units, and CI need no change.

Verified: full suite (318) passes, `uvicorn app:app` boots and serves
the home/SEO/static/API surfaces, and every root script imports clean.

Claude-Session: https://claude.ai/code/session_01XXxmNFy9cZ6Gh8Y9thZn62
2026-07-20 05:31:03 +00:00

42 lines
1.7 KiB
Python

"""Canonical filesystem locations, resolved once from the repo root.
Every path the backend needs (the climate cache, the accounts DB, logs, the
Jinja templates, the frontend static dir, the bundled city data) is derived
here from the repository root rather than from each module's own ``__file__``.
That keeps the locations stable no matter how deep in the package tree a module
lives, so moving a module never silently re-points the cache or the accounts DB.
Callers that support an environment override (e.g. ``THERMOGRAPH_ACCOUNTS_DB``)
keep doing so; these are only the defaults.
"""
import os
_HERE = os.path.dirname(os.path.abspath(__file__))
def _repo_root() -> str:
"""Walk up from this file until a directory holds both ``backend/`` and
``frontend/`` — the repository root, in every checkout (dev, prod, worktree)."""
d = _HERE
while True:
if os.path.isdir(os.path.join(d, "backend")) and os.path.isdir(os.path.join(d, "frontend")):
return d
parent = os.path.dirname(d)
if parent == d:
raise RuntimeError(
"cannot locate the repo root (no ancestor has sibling backend/ and frontend/)"
)
d = parent
REPO_DIR = _repo_root()
BACKEND_DIR = os.path.join(REPO_DIR, "backend")
DATA_DIR = os.path.join(REPO_DIR, "data")
LOGS_DIR = os.path.join(REPO_DIR, "logs")
FRONTEND_DIR = os.path.join(REPO_DIR, "frontend")
TEMPLATES_DIR = os.path.join(BACKEND_DIR, "templates")
# Bundled reference data shipped alongside the code (generated by gen_cities /
# gen_flavor), not runtime state — hence under backend/, not data/.
CITIES_JSON = os.path.join(BACKEND_DIR, "cities.json")
CITIES_FLAVOR_JSON = os.path.join(BACKEND_DIR, "cities_flavor.json")