"""Pure database-URL resolution logic for Alembic migrations. Kept separate from `env.py` so it is importable and unit-testable on its own: `env.py` has import-time side effects tied to an active Alembic `MigrationContext` (it calls `alembic.context.config...` at module scope) and cannot be imported outside of an actual `alembic` invocation. This module has no such dependency — it only takes plain values in and returns plain values out. Resolution order implemented by `resolve_database_url` (first match wins): 1. An explicit `-x sqlalchemy.url=...` argument — a documented way to point a one-off Alembic run at a scratch database without touching the environment. 2. `APP_SQLITE_PATH` read directly from `os.environ` — but only when it is genuinely present and non-empty. This deliberately does NOT go through `app.config.Settings`: `Settings.sqlite_path` has a default (`"./foraldraledig.db"`), so a `Settings()` instance can never tell "the env var was set to the default's value" apart from "the env var was never set at all" — the default always wins, which made step 3 below dead code and meant a `sqlalchemy.url` set programmatically on the Alembic `Config` (e.g. `cfg.set_main_option(...)` in a test) was silently ignored in favour of whatever `./foraldraledig.db` resolved to, including the developer's real local database. Reading `os.environ` directly is the only way to distinguish "explicitly set" from "defaulted". Only the real `APP_SQLITE_PATH` environment variable counts here — a value from an `.env` file is deliberately NOT treated as "explicitly set" (this resolver never loads `.env`). `.env` is a developer convenience with quietly-applied defaults, exactly the kind of implicit source that caused this bug; requiring a real env var keeps "what database does this migration run touch" answerable by looking at the process environment alone, and it means an `.env` file can never silently retarget migrations for anyone running Alembic (locally or in CI) who does not also have that exact environment variable exported. 3. The `sqlalchemy.url` value from `alembic.ini` — or from `Config.set_main_option("sqlalchemy.url", ...)`, which is indistinguishable from `alembic.ini` by the time it reaches `context.config.get_main_option` — as the fallback when neither of the above applies. This is the single place that decides which database Alembic touches — never read `alembic.ini`'s `sqlalchemy.url` directly anywhere else, so migrations can never silently target a different database than the one the running app actually uses. """ from __future__ import annotations import os def resolve_database_url(x_args: dict[str, str], ini_url: str | None) -> str | None: """Resolve the database URL from -x args, a real APP_SQLITE_PATH env var, then the ini fallback.""" explicit_url = x_args.get("sqlalchemy.url") if explicit_url: return explicit_url sqlite_path = os.environ.get("APP_SQLITE_PATH") if sqlite_path: return f"sqlite:///{sqlite_path}" return ini_url def coerce_sqlite_async(url: str) -> str: """sqlite:/// → sqlite+aiosqlite:/// so the async engine can use it.""" if url.startswith("sqlite:///"): return url.replace("sqlite:///", "sqlite+aiosqlite:///", 1) return url