Skip to content

fix(api): auto-correct DATABASE_URL driver to psycopg v3 when legacy/unbound - #71

Merged
Roddygithub merged 7 commits into
mainfrom
fix/normalise-database-url-psycopg-driver
Jul 24, 2026
Merged

Roddygithub merged 7 commits into
mainfrom
fix/normalise-database-url-psycopg-driver

Conversation

@Roddygithub

Copy link
Copy Markdown
Owner

Problem

Every uvicorn restart crashes the lifespan with:

ModuleNotFoundError: No module named `psycopg2`

because the default .env / .env.example use DATABASE_URL=postgresql://... (bare, no driver hint), and SQLAlchemy defaults to the legacy psycopg2 driver — but the workspace only ships psycopg[binary]>=3.3.4 (v3).

The same crash fires from uv run alembic upgrade head because alembic/env.py read get_settings().database_url directly without the driver-rewrite helper.

Fix

New _normalise_database_url() helper in database.py (idempotent, private):

  • postgresql://... (no hint) → rewrite to postgresql+psycopg://
  • postgres://... (shorthand, Heroku etc.) → rewrite
  • postgresql+psycopg2://... (explicit legacy) → rewrite
  • Other dialects (, asyncpg, pg8000) left untouched

Threaded through:

  1. get_engine() — in-process fix
  2. alembic/env.py — CLI fix so migrations resolve correctly

Example files synced to the explicit +psycopg form. New regression test (test_database.py) locks the 6 URL shapes.

Validation

  • ✅ ruff check + ruff format: clean
  • ✅ mypy: 0 issues on all touched files
  • ✅ vitest: 385/385 passed
  • ✅ pytest test_database.py: 8/8 passed
  • ✅ alembic upgrade head: clean (schema drift cleared)
  • ✅ Fresh uvicorn start: no psycopg2 errors
  • ✅ All 6 API routes return 200
  • ✅ 9 Playwright screenshots: all have real content (10-95 KB)

roddy added 7 commits July 24, 2026 20:40
…unbound

SQLAlchemy lazily imports the dialect module at engine-creation time. A bare
`postgresql://...` URL (no +<driver> hint) makes it default to the legacy
`psycopg2` driver, but the workspace only ships `psycopg[binary]>=3.3.4`
(v3). This crashes the lifespan schema-drift guard (and every subsequent
`get_engine()` call) with `ModuleNotFoundError: No module named psycopg2`.

Changes:
- database.py: new `_normalise_database_url()` helper that rewrites
  `postgresql://`, `postgres://` and `postgresql+psycopg2://` to
  `postgresql+psycopg://`. Other dialects (asyncpg, pg8000, etc.) are
  left untouched. Idempotent on already-correct URLs.
- get_engine() now passes URL through the helper before create_engine().
- alembic/env.py: threads the same helper so `uv run alembic upgrade head`
  also resolves to the right driver.
- .env.example, apps/api/.env.example: sync example URL to the explicit
  `+psycopg` form so future contributors don hit the same hole.
- apps/api/tests/conftest.py: test fixture URL synced.
- apps/api/tests/test_database.py: new regression test (7 parametrized
  cases) locking the helper contract.

Signed-off-by: RoddyGitHub <roddy@users.noreply.github.com>
…ault -> .python-version

Signed-off-by: RoddyGitHub <roddy@users.noreply.github.com>
…est.yml

Signed-off-by: RoddyGitHub <roddy@users.noreply.github.com>
Signed-off-by: RoddyGitHub <roddy@users.noreply.github.com>
Signed-off-by: RoddyGitHub <roddy@users.noreply.github.com>
…ion-test

Signed-off-by: RoddyGitHub <roddy@users.noreply.github.com>
…igration-test

Signed-off-by: RoddyGitHub <roddy@users.noreply.github.com>
@Roddygithub
Roddygithub merged commit 891a750 into main Jul 24, 2026
11 of 13 checks passed
@Roddygithub
Roddygithub deleted the fix/normalise-database-url-psycopg-driver branch July 24, 2026 19:40
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