From c7f5421c7933a4737e8e8bb369d949f28e68ce41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 01:37:31 +0000 Subject: [PATCH 1/2] chore(deps): Bump the python-minor-and-patch group with 3 updates Bumps the python-minor-and-patch group with 3 updates: [numpy](https://github.com/numpy/numpy), [claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-python) and [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy). Updates `numpy` from 2.4.6 to 2.5.3 - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v2.4.6...v2.5.3) Updates `claude-agent-sdk` from 0.2.157 to 0.2.159 - [Release notes](https://github.com/anthropics/claude-agent-sdk-python/releases) - [Changelog](https://github.com/anthropics/claude-agent-sdk-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/anthropics/claude-agent-sdk-python/commits/v0.2.159) Updates `sqlalchemy` from 2.0.54 to 2.1.0 - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: numpy dependency-version: 2.5.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-and-patch - dependency-name: claude-agent-sdk dependency-version: 0.2.159 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: python-minor-and-patch - dependency-name: sqlalchemy dependency-version: 2.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-and-patch ... Signed-off-by: dependabot[bot] --- constraints.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/constraints.txt b/constraints.txt index b18a7f0..84beef6 100644 --- a/constraints.txt +++ b/constraints.txt @@ -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 @@ -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 From a61d3c9a78d3a8f6615e512a2e33242d05ff5ce9 Mon Sep 17 00:00:00 2001 From: DoRmAmMu1997 Date: Mon, 28 Sep 2026 15:07:45 +0530 Subject: [PATCH 2/2] fix(types): adopt SQLAlchemy 2.1's PEP 646 row typing SQLAlchemy 2.1 types Row/Result with TypeVarTuple, so a raw text() query's scalar_one() no longer has an inferable type and mypy failed with "Need type annotation" in test_scan_storage_migrations.py. Annotate the one affected local. 2.1 also makes psycopg 3 the default driver for a bare postgresql:// URL. _normalize_database_url still rewrites bare URLs to postgresql+psycopg:// (the short postgres:// scheme is never accepted, and the explicit driver no longer depends on SQLAlchemy's default), so only its docstring and the deployment-runtime LLD row that described the old psycopg2 default change. Checked against the 2.1 migration notes: app code has no filter_by() calls and no Session.execute(text(...)) (the new unconditional autoflush), and Alembic receives the raw URL string, so the URL-escaping change does not alter the SQLite path. Co-Authored-By: Claude Opus 5.5 --- backend/config/settings.py | 10 ++++++---- docs/architecture/components/deployment-runtime.md | 2 +- tests/test_scan_storage_migrations.py | 4 +++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/backend/config/settings.py b/backend/config/settings.py index 55260ef..d2e1ce5 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -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. """ diff --git a/docs/architecture/components/deployment-runtime.md b/docs/architecture/components/deployment-runtime.md index 3321150..ea82b0f 100644 --- a/docs/architecture/components/deployment-runtime.md +++ b/docs/architecture/components/deployment-runtime.md @@ -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 diff --git a/tests/test_scan_storage_migrations.py b/tests/test_scan_storage_migrations.py index da0d00e..92290c6 100644 --- a/tests/test_scan_storage_migrations.py +++ b/tests/test_scan_storage_migrations.py @@ -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"