Skip to content

LITE-33583: Bumb to pydantic v2 and newer fastapi#127

Merged
pcaro merged 3 commits into
cr/LITE-33583-support-python-3.13-3.14from
cr/LITE-33583-migrate-pydantic-v2
Jul 6, 2026
Merged

LITE-33583: Bumb to pydantic v2 and newer fastapi#127
pcaro merged 3 commits into
cr/LITE-33583-support-python-3.13-3.14from
cr/LITE-33583-migrate-pydantic-v2

Conversation

@pcaro

@pcaro pcaro commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Migrates connect-eaas-core from 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)

  • Every Optional field gets an explicit = None default — in v2 Optional no longer implies a default, so these would otherwise become required and break deserialization.
  • Message.data (discriminated union incl. None) now defaults to None, so omitting it keeps working (e.g. shutdown messages).
  • BaseModel.__repr_args__ drops the removed pydantic.utils.DUNDER_ATTRIBUTES import and reads field metadata via type(self).model_fields. Sensitive-field obfuscation is unchanged.
  • self.dict()self.model_dump() (serialize); context.dict()context.model_dump().

Tests

  • v2 tightened BaseModel.__eq__ (a model no longer equals a plain dict), so schedulable/transformation assertions compare model_dump() output.
  • .dict().model_dump().

fastapi range: ^0.115.0>=0.115,<0.137

fastapi began requiring pydantic v2 at 0.128 (its pydantic floor jumped from >=1.7.4,<3 to >=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_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.

Declare typing-inspect

fastapi-utils 0.8.0's cbv module imports typing_inspect in its pydantic-2 code path but only declares it 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). Declared directly (matching fastapi-utils' own >=0.9,<0.10 range) rather than via fastapi-utils[all], which would also drag in pydantic-settings and sqlalchemy.

Verification

  • 368 passed, flake8 clean, on pydantic 2.13.4.
  • Validated at both ends of the fastapi range (0.115.x and 0.136.0).
  • Reproduced the typing_inspect crash from declared deps only, then confirmed the added dependency resolves it.
  • No pydantic deprecation warnings remain.

LITE-33583

pcaro added 3 commits July 2, 2026 18:03
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.
@pcaro pcaro changed the title LITE-33583: Migrate to pydantic v2 LITE-33583: Bumb to pydantic v2 and newer fastapi Jul 2, 2026
Comment thread pyproject.toml
connect-openapi-client = ">=25.20"
pydantic = "^1.10"
fastapi = "^0.115.0"
pydantic = "^2.9"

@qarlosh qarlosh Jul 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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).

@pcaro
pcaro merged commit d9d74b0 into cr/LITE-33583-support-python-3.13-3.14 Jul 6, 2026
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.

3 participants