LITE-33583: Bumb to pydantic v2 and newer fastapi#127
Conversation
Move the codebase off pydantic v1 to v2 (^2.9), resolving the v1 deprecation warnings emitted on Python 3.13/3.14 that were noted as follow-up work in the 3.13/3.14 support change. Model changes (proto.py, inject/models.py, validation/models.py): - Give every Optional field an explicit '= None' default. In v2 an Optional annotation no longer implies a default, so these fields would otherwise become required and break deserialization. - Message.data (a discriminated union including None) now defaults to None so omitting it keeps working (e.g. shutdown messages). - BaseModel.__repr_args__ no longer imports the removed pydantic.utils.DUNDER_ATTRIBUTES and reads field metadata via type(self).model_fields; sensitive-field obfuscation is unchanged. - self.dict() -> self.model_dump() in serialize(); context.dict() -> context.model_dump() in inject/common.py. Tests: v2 tightened BaseModel.__eq__ (a model no longer compares equal to a plain dict), so schedulable/transformation assertions now compare model_dump() output; .dict() calls switched to .model_dump().
Allow fastapi 0.116-0.136 alongside the pydantic v2 bump. fastapi began requiring pydantic v2 at 0.128 (its pydantic floor jumped from >=1.7.4,<3 to >=2.7), so this range is only reachable now that we run on pydantic v2. Capped below 0.137: that release replaced eager route flattening in include_router with a lazy _IncludedRouter placeholder in router.routes, which breaks WebApplicationBase.get_routers() (it assumes every entry is an APIRoute with .endpoint). Lifting the cap requires adapting that code to the new routing model and is tracked separately. Verified: 368 tests pass and flake8 is clean on both ends of the range (0.115.x and 0.136.0) with pydantic 2.13.4.
fastapi-utils 0.8.0's cbv module imports typing_inspect in its pydantic 2 code path (`if PYDANTIC_VERSION[0] == "2": from typing_inspect import is_classvar`) but only declares typing-inspect under its `all` extra. Migrating to pydantic 2 therefore makes importing connect-eaas-core crash with ModuleNotFoundError: typing_inspect for any consumer that doesn't already pull it in (e.g. connect-cli). Declare typing-inspect directly (matching fastapi-utils' own >=0.9,<0.10 range) rather than depending on fastapi-utils[all], which would also drag in pydantic-settings and sqlalchemy.
| connect-openapi-client = ">=25.20" | ||
| pydantic = "^1.10" | ||
| fastapi = "^0.115.0" | ||
| pydantic = "^2.9" |
There was a problem hiding this comment.
[Info] This pydantic v1→v2 migration is a breaking change for downstream consumers, so it should ship as a major version bump (38.0), not a 37.x minor.
connect-extension-runner pins connect-eaas-core = ">=37.4,<38" (and the other internal consumers similarly cap it to the 37.x line); that cap is the firewall that keeps pydantic 1 in the extension runtime. A 37.x release would be pulled into the extension runtime base image on its next rebuild (it resolves via the pyproject constraint, not the lock), forcing pydantic 2 on every extension. And since the extension image build installs with pip install --no-deps and strips the extension's own connect-eaas-core pin, an extension's pydantic pin won't shield it — extensions using pydantic v1 APIs would break at runtime.
Releasing as 38.0 lets consumers that want v2 (e.g. connect-cli, which pins connect-eaas-core >=37.0 uncapped) opt in, while connect-extension-runner (<38) and the 37.x-pinned consumers stay on 37.x until they adopt v2 deliberately. Adopting v2 in the runner should then be a coordinated bump (runner major + extension-developer migration to pydantic 2 / pydantic.v1).
Summary
Migrates
connect-eaas-corefrom pydantic v1 to v2 (^2.9), resolving the v1 deprecation warnings that Python 3.13/3.14 emit — the follow-up flagged in #125. Stacked on top of #125 (targets its branch).Along the way it also widens the fastapi range and fixes a packaging gap that only surfaces once we run on pydantic v2.
Changes
pydantic v1 → v2
Models (
proto.py,inject/models.py,validation/models.py)Optionalfield gets an explicit= Nonedefault — in v2Optionalno longer implies a default, so these would otherwise become required and break deserialization.Message.data(discriminated union incl.None) now defaults toNone, so omitting it keeps working (e.g. shutdown messages).BaseModel.__repr_args__drops the removedpydantic.utils.DUNDER_ATTRIBUTESimport and reads field metadata viatype(self).model_fields. Sensitive-field obfuscation is unchanged.self.dict()→self.model_dump()(serialize);context.dict()→context.model_dump().Tests
BaseModel.__eq__(a model no longer equals a plain dict), so schedulable/transformation assertions comparemodel_dump()output..dict()→.model_dump().fastapi range:
^0.115.0→>=0.115,<0.137fastapi began requiring pydantic v2 at 0.128 (its pydantic floor jumped from
>=1.7.4,<3to>=2.7), so this wider range is only reachable now that we run on pydantic v2.Capped below 0.137: that release replaced eager route flattening in
include_routerwith a lazy_IncludedRouterplaceholder inrouter.routes, which breaksWebApplicationBase.get_routers()(it assumes every entry is anAPIRoutewith.endpoint). Lifting the cap requires adapting that code to the new routing model and is tracked separately.Declare
typing-inspectfastapi-utils0.8.0'scbvmodule importstyping_inspectin its pydantic-2 code path but only declares it under itsallextra. Migrating to pydantic 2 therefore makes importingconnect-eaas-corecrash withModuleNotFoundError: typing_inspectfor any consumer that doesn't already pull it in (e.g. connect-cli). Declared directly (matching fastapi-utils' own>=0.9,<0.10range) rather than viafastapi-utils[all], which would also drag inpydantic-settingsandsqlalchemy.Verification
pydantic 2.13.4.typing_inspectcrash from declared deps only, then confirmed the added dependency resolves it.LITE-33583