Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions backend/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,10 +251,12 @@ def _normalize_database_url(url: str) -> str:
"""Name the installed psycopg v3 driver in bare Postgres URLs.

Managed-Postgres providers (Render, Heroku, ...) auto-wire connection strings
as ``postgres://`` or ``postgresql://``. SQLAlchemy maps both bare schemes to
the psycopg2 driver, which this project does not install (only psycopg v3 is
pinned). Rewriting the scheme to ``postgresql+psycopg://`` lets a
provider-injected ``DATABASE_URL`` work unedited. SQLite URLs and URLs that
as ``postgres://`` or ``postgresql://``. SQLAlchemy does not recognise the
short ``postgres://`` scheme at all, and before 2.1 it mapped a bare
``postgresql://`` to psycopg2, which this project does not install (only
psycopg v3 is pinned). Rewriting both to ``postgresql+psycopg://`` lets a
provider-injected ``DATABASE_URL`` work unedited and keeps the driver choice
explicit instead of relying on SQLAlchemy's default. SQLite URLs and URLs that
already name a driver (``postgresql+psycopg://``, ``postgresql+psycopg2://``)
are returned unchanged.
"""
Expand Down
6 changes: 3 additions & 3 deletions constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
streamlit==1.64.0
authlib==1.8.0
pandas==3.0.6
numpy==2.4.6
numpy==2.5.3
pyarrow==25.0.1
requests==2.34.2
# pdf_transport.py uses urllib3's inspected 2.7.0 pool API directly; this
Expand All @@ -25,8 +25,8 @@ PyYAML==6.0.3
beautifulsoup4==4.15.0
lxml==6.1.3
pdfplumber==0.11.10
claude-agent-sdk==0.2.157
SQLAlchemy==2.0.54
claude-agent-sdk==0.2.159
SQLAlchemy==2.1.0
# Keep Alembic pinned with SQLAlchemy so local migrations and CI use the same
# migration behavior every time.
alembic==1.20.0
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/components/deployment-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ port on the developer machine.
| **Secrets mounted, not baked** | `.streamlit/secrets.toml` contains Google OIDC credentials and belongs outside Docker layers and build context. | `COPY` secrets into the image — leaks through image history and registries. |
| **CI image + Compose smoke** | Local machines may lack Docker; CI proves both the image and the Compose stack start on every PR. | Trust docs/tests only — broken Compose could ship unnoticed. |
| **Render Blueprint reuses the image; disk on web only, cron ephemeral (DEPLOY-003 / DEPLOY-003B)** | A Render persistent disk is **single-attach**, and the only state both processes must share is scan history — which already lives in the managed Postgres. So the disk (candle cache) attaches to the web service, and the cron runs ephemerally, re-fetching candles and writing results to the shared database. DEPLOY-003B keeps the cron deployable by committing `config/daily_scans.yaml`, the deterministic default schedule with AI-heavy jobs disabled. | Give the cron its own disk (a second copy of the cache, still cold daily) / put the cache in object storage (more infra for a first deploy). |
| **Normalize the auto-wired DATABASE_URL (DEPLOY-003)** | Render's `fromDatabase` emits a bare `postgresql://` URL, which SQLAlchemy maps to the absent psycopg2 driver. `settings._normalize_database_url` rewrites it to the pinned `postgresql+psycopg://` so the Blueprint self-wires and survives DB password rotation. | Hand-paste `postgresql+psycopg://…` in the dashboard — fragile, breaks on rotation. |
| **Normalize the auto-wired DATABASE_URL (DEPLOY-003)** | Render's `fromDatabase` emits a bare `postgresql://` URL, which SQLAlchemy before 2.1 mapped to the absent psycopg2 driver (and the short `postgres://` form is never accepted). `settings._normalize_database_url` rewrites both to the pinned `postgresql+psycopg://`, so the Blueprint self-wires, survives DB password rotation, and does not depend on SQLAlchemy's default driver. | Hand-paste `postgresql+psycopg://…` in the dashboard — fragile, breaks on rotation. |

## 5. Failure modes / degradation

Expand Down
4 changes: 3 additions & 1 deletion tests/test_scan_storage_migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ def test_obs004a_backfills_legacy_rows_and_restores_original_shape(

command.upgrade(config, "head")
with engine.connect() as connection:
status = connection.execute(
# SQLAlchemy 2.1 types a raw ``text()`` result as an open-ended row
# (PEP 646), so mypy cannot infer the scalar's type on its own.
status: str = connection.execute(
text("SELECT observation_status FROM universe_health_snapshots")
).scalar_one()
assert status == "legacy_unknown"
Expand Down
Loading