Skip to content

fix(deps): mark the sqlalchemy floor and complete pydantic's - #186

Merged
lesnik512 merged 1 commit into
mainfrom
fix/sqlalchemy-floor
Sep 20, 2026
Merged

lesnik512 merged 1 commit into
mainfrom
fix/sqlalchemy-floor

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Follow-up to #185. Continues the audit from modern-python/.github#107.

1. sqlalchemy

sqlalchemy[asyncio]>=2.0 resolves to 2.0.0, which predates SQLAlchemy's handling of the __static_attributes__ and __firstlineno__ attributes Python 3.13 added. It installs fine and then fails its own check at import:

AssertionError: Class <class 'sqlalchemy.sql.elements.SQLCoreOperations'> directly inherits
TypingOnly but has additional attributes {'__static_attributes__', '__firstlineno__'}

This only reached the surface once #185 cleared the build failures ahead of it. Bisected on 3.13:

version import
2.0.0, 2.0.25, 2.0.28, 2.0.30 fails
2.0.31 OK

2.0.31 imports cleanly on 3.14 too.

"sqlalchemy[asyncio]>=2.0; python_version < '3.13'",
"sqlalchemy[asyncio]>=2.0.31; python_version >= '3.13'",

2. pydantic, completing what #185 started

#185 declared pydantic>=2.12; python_version >= '3.14' for wheel coverage and stopped there. That left every interpreter below 3.14 with no pydantic floor at all, and fast-depends[pydantic] allows it back to 1.x — so the lowest declared combination resolved pydantic 1.10.26, under which three tests fail:

FAILED tests/test_unit.py::test_encode_payload_serializes_pydantic_model_with_default_serializer
FAILED tests/test_unit.py::test_broker_publish_encodes_pydantic_model
FAILED tests/test_unit.py::test_broker_publish_batch_encodes_pydantic_models
E   AttributeError: '_PydanticBody' object has no attribute 'model_dump'

model_dump is FastStream's serializer, not our code, but the promise is ours: we declared a range that admits a pydantic our publish path cannot use. v2 is a hard requirement, and the two marked floors above it are wheel coverage — pydantic-core ships no cp313 wheel before pydantic 2.8, and no cp314 wheel before 2.12 (2.7.1 and 2.7.4 both fail on 3.13; 2.8.0 is the first that works).

"pydantic>=2; python_version < '3.13'",
"pydantic>=2.8; python_version == '3.13'",
"pydantic>=2.12; python_version >= '3.14'",

Where the lower bound stands now

uv pip install --resolution lowest-direct ".[all]" plus current test tooling:

interpreter install tests/test_unit.py + tests/test_fake.py resolved floors
3.11 OK 385 passed asyncpg 0.29.0, sqlalchemy 2.0.0, pydantic 2.0.2
3.12 OK 385 passed asyncpg 0.29.0, sqlalchemy 2.0.0, pydantic 2.0.2
3.13 OK 385 passed asyncpg 0.30.0, sqlalchemy 2.0.31, pydantic 2.8.0
3.14 OK 385 passed asyncpg 0.31.0, sqlalchemy 2.0.31, pydantic 2.12.0
3.14t OK 385 passed asyncpg 0.31.0, sqlalchemy 2.0.31, pydantic 2.12.0

The full suite is not green at the lower bound yet. Running everything, with Postgres, on 3.11 leaves 12 failures from two floors this PR does not touch:

  • opentelemetry-api>=1.20 / opentelemetry-sdk>=1.20 — 5 failures in tests/test_middleware_opentelemetry.py. Upgrading opentelemetry to current in place fixes all 5, so the floor is the cause; the exact boundary is not bisected.
  • fastapi>=0.95 — 7 failures in tests/test_fastapi.py, still failing at the resolved 0.100.0 and after the opentelemetry upgrade.

Both are left for a follow-up rather than folded in here.

At the normal (highest) resolution nothing changes — uv.lock picks sqlalchemy 2.0.54 and pydantic 2.13.5 — so this is inert for anyone not installing at the lower bound.

No test

Same as #185: the defect is that a declared range does not resolve or does not work, which only a resolver on a given interpreter can show. A unit test reading a floor back out of pyproject.toml would restate the string. The tables above are the verification, and modern-python/.github#107 is the job that would make it standing. Worth noting for that issue: the sqlalchemy and pydantic failures here were invisible to a job that only installs — it has to import and run tests.

Verification

just lint-ci clean (eof-fixer, ruff format, ruff check, ty). Full suite against Postgres 17 at normal resolution: 624 passed, 100% coverage.

sqlalchemy 2.0.0 fails SQLAlchemy's own TypingOnly check at import on 3.13+,
which only reached the surface once the build failures ahead of it were gone.
2.0.31 is the first release that tolerates __static_attributes__ and
__firstlineno__.

pydantic's floor was declared only for 3.14, so everything below it still
admitted pydantic 1.x, under which FastStream's serializer calls model_dump
and publishing a model raises AttributeError. v2 is a hard requirement; the
marked floors above it are wheel coverage.
@github-actions

Copy link
Copy Markdown

Benchmark gate

✅ gate passed

scenario msg/s delete/msg WALrec/msg WALB/msg fpi upd del dead_tup
consumer/w1/b10 1032 1.000 6.79 915 242 5000 5000 10000
consumer/w1/b100 1049 1.000 6.83 920 243 5000 5000 10000
consumer/w2/b10 1159 1.000 6.81 973 242 5000 5000 10000
consumer/w2/b100 1418 1.000 6.72 956 243 5000 5000 10000
consumer/w4/b10 1062 1.000 6.82 995 261 5000 5000 10000
consumer/w4/b100 1548 1.000 6.87 1039 244 5000 5000 10000
consumer/w1/b100/tfbs100 5812 0.010 6.07 1116 243 5000 5000 10000
producer/w1/b100 2381 0.000 3.04 584 0 0 0 0

Gated (fails the build): delete_calls + tuple counters (upd/del/ins) + the producer's insert_calls, exact; select_calls within +2; wal_records within a 10% band. msg/s, WAL bytes and total calls are informational (timing/FPI noise).

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.

1 participant