From f329f8973cd91c2fde7a7d182cb0d1c10d9a6f1d Mon Sep 17 00:00:00 2001 From: Ryan Min Date: Mon, 26 Oct 2026 21:47:29 +0900 Subject: [PATCH 1/2] fix: annotate scalar_one results for SQLAlchemy 2.1 typing SQLAlchemy 2.1.0 (2026-09-24) is within the declared sqlalchemy>=2.0,<3.0 range and types Result with PEP 646 variadic generics. Under the pinned mypy 2.3.0 that makes three bare `.scalar_one()` assignments fail gate2 with [var-annotated]: - src/backtest_engine/backtest_request_intake.py: latest - tests/persistence/test_money_precision.py: stored_text - tests/persistence/test_runtime_no_ddl.py: exists Declare the SQL result types explicitly (bigint COALESCE -> int, numeric::text -> str, IS NOT NULL -> bool). mypy does not verify these against the query; they were checked against the schema by hand. Verified on CPython 3.12.13: mypy is clean on SQLAlchemy 2.1.6 and 2.0.55, ruff check/format pass, and the Docker suite passes on 2.1.6 (241 passed, 0 skipped). Co-Authored-By: Claude Opus 5.5 --- src/backtest_engine/backtest_request_intake.py | 2 +- tests/persistence/test_money_precision.py | 2 +- tests/persistence/test_runtime_no_ddl.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backtest_engine/backtest_request_intake.py b/src/backtest_engine/backtest_request_intake.py index 5cc9fa5..fc436df 100644 --- a/src/backtest_engine/backtest_request_intake.py +++ b/src/backtest_engine/backtest_request_intake.py @@ -408,7 +408,7 @@ def claim( ) return RequestClaim(RequestClaimDisposition.CLAIMED) - latest = connection.execute( + latest: int = connection.execute( text( """ SELECT COALESCE(MAX(o.aggregate_sequence), 0) diff --git a/tests/persistence/test_money_precision.py b/tests/persistence/test_money_precision.py index b869888..fce4eba 100644 --- a/tests/persistence/test_money_precision.py +++ b/tests/persistence/test_money_precision.py @@ -75,7 +75,7 @@ def test_numeric_24_8_round_trips_without_precision_loss(persistence: BacktestPe with persistence.unit_of_work() as uow: stored = uow.runs.get(row.id).initial_cash_amount # `==` on Decimal ignores trailing zeros, so read the stored digits as text too. - stored_text = uow.connection.execute( + stored_text: str = uow.connection.execute( text("SELECT initial_cash_amount::text FROM backtest.runs WHERE id = :id"), {"id": str(row.id)}, ).scalar_one() diff --git a/tests/persistence/test_runtime_no_ddl.py b/tests/persistence/test_runtime_no_ddl.py index c642e72..dd97895 100644 --- a/tests/persistence/test_runtime_no_ddl.py +++ b/tests/persistence/test_runtime_no_ddl.py @@ -167,6 +167,6 @@ def test_ddl_really_did_not_happen(persistence: BacktestPersistence, admin_engin uow.connection.execute(text("CREATE TABLE backtest.sneaky (id uuid PRIMARY KEY)")) with admin_engine.connect() as connection: - exists = connection.execute(text("SELECT to_regclass('backtest.sneaky') IS NOT NULL")).scalar_one() + exists: bool = connection.execute(text("SELECT to_regclass('backtest.sneaky') IS NOT NULL")).scalar_one() assert exists is False From d6e0b0b593f1e0814797b5e4eb7fceee4e9e8364 Mon Sep 17 00:00:00 2001 From: Ryan Min Date: Mon, 26 Oct 2026 21:55:43 +0900 Subject: [PATCH 2/2] chore: cap SQLAlchemy below 2.2 and record the shared mypy pin SQLAlchemy 2.1 retyped Result, and mypy 2.3.0 then reported var-annotated errors here (fixed by annotating the scalar_one() sites). A <3.0 bound let that minor arrive on a fresh install with no change in this repository. Narrow it to <2.2 so the next minor is a deliberate bump; the suites pass on 2.1.6, so 2.1 stays allowed. mypy==2.3.0 is unchanged. data-pipeline now pins the same release, and the comment says the two move together. Co-Authored-By: Claude Opus 5.5 --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 84e8f09..5e632d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,9 @@ dependencies = [ "psutil==7.2.2", "psycopg[binary]>=3.2,<4.0", "pyarrow==25.0.0", - "sqlalchemy>=2.0,<3.0", + # Capped below the next minor, not the next major: SQLAlchemy 2.1 retyped `Result`, + # which failed the type gate, so moving past 2.1 is a deliberate bump. + "sqlalchemy>=2.0,<2.2", "tzdata==2026.3", "uvicorn==0.52.1", ] @@ -38,6 +40,7 @@ test = [ ] dev = [ "idea2strategy-backtest-engine[test]", + # The same release data-pipeline pins, so the two type gates agree. Bump both together. "mypy==2.3.0", "mutmut==3.3.1", "ruff==0.16.1",