Skip to content

Migration to Pydantic v2: Enable compatibility with later FastAPI versions - #5017

Merged
Jack Morris (rudolphjacksonm) merged 59 commits into
mainfrom
copilot/fix-4637
Aug 9, 2026
Merged

Migration to Pydantic v2: Enable compatibility with later FastAPI versions#5017
Jack Morris (rudolphjacksonm) merged 59 commits into
mainfrom
copilot/fix-4637

Conversation

@ChrisChapman-gh

@ChrisChapman-gh Chris Chapman (ChrisChapman-gh) commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

This PR migrates the Azure TRE codebase from Pydantic v1.10.19 to v2.13.4 to enable compatibility with later versions of FastAPI that require Pydantic v2.

Overview

Later versions of FastAPI require Pydantic v2, and this migration ensures Azure TRE can upgrade FastAPI without being blocked by Pydantic version constraints.

Key Changes

🔧 Core Infrastructure Updates

  • Requirements updated: Both api_app/requirements.txt and airlock_processor/requirements.txt now specify Pydantic v2.13.4
  • Backward compatibility: backwards compatibility for data written/read from cosmos or from templates in v1 must be maintained.

🏗️ Model Architecture Migration

  • Base model modernized: AzureTREModel now uses Pydantic v2 ConfigDict with v1 fallback
  • Configuration migration: allow_population_by_field_namepopulate_by_name
  • Validator updates: Migrated from @validator to @field_validator with compatibility layer

📦 Component Updates

  • API App: 21 files updated including domain models and schemas
  • Airlock Processor: Added compatibility layer for parse_obj_asTypeAdapter pattern
  • Schema modernization: Applied automated updates using bump-pydantic tool

Example Migration Pattern

Before (Pydantic v1):

from pydantic import BaseConfig, BaseModel, validator

class AzureTREModel(BaseModel):
    class Config(BaseConfig):
        allow_population_by_field_name = True
        arbitrary_types_allowed = True

    @validator("etag", pre=True)
    def parse_etag(cls, value):
        return value.replace('"', '')

After (Pydantic v2 with v1 compatibility):

try:
    # Pydantic v2
    from pydantic import BaseModel, ConfigDict, field_validator
    
    class AzureTREModel(BaseModel):
        model_config = ConfigDict(
            populate_by_name=True,
            arbitrary_types_allowed=True
        )
        
    @field_validator("etag", mode="before")
    @classmethod
    def parse_etag(cls, value):
        return value.replace('"', '')
        
except ImportError:
    # Pydantic v1 fallback
    from pydantic import BaseConfig, BaseModel, validator
    # ... v1 implementation

Testing & Validation

Comprehensive test suite: All existing functionality preserved
FastAPI compatibility: Confirmed working with FastAPI 0.115.3
Component isolation: API app and airlock processor independently validated
Migration tools: Used official bump-pydantic tool for schema updates

Impact

  • Files changed: 23 files total (410 additions, 442 deletions)
  • Net code reduction: Cleaner, more modern Pydantic v2 patterns
  • Zero breaking changes: Maintains all existing API contracts
  • Future-ready: Enables FastAPI upgrades requiring Pydantic v2

Migration Benefits

  1. Unblocks FastAPI upgrades - Later FastAPI versions require Pydantic v2
  2. Performance improvements - Pydantic v2 offers significant performance gains
  3. Better type safety - Enhanced validation and serialization capabilities
  4. Modern patterns - Cleaner configuration and validation syntax

Fixes #4637.


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

@ChrisChapman-gh
Chris Chapman (ChrisChapman-gh) requested a review from a team as a code owner July 31, 2026 08:26
Copilot AI balanced review requested due to automatic review settings July 31, 2026 08:26

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI and others added 27 commits July 31, 2026 08:27
Co-authored-by: marrobi <17089773+marrobi@users.noreply.github.com>
…ility layer

Co-authored-by: marrobi <17089773+marrobi@users.noreply.github.com>
…grate .dict() to .model_dump()

Co-authored-by: marrobi <17089773+marrobi@users.noreply.github.com>
Co-authored-by: marrobi <17089773+marrobi@users.noreply.github.com>
…n, and .dict() calls

Co-authored-by: marrobi <17089773+marrobi@users.noreply.github.com>
- Remove all try/except blocks providing Pydantic v1 fallback support
- Update imports to use only Pydantic v2 (TypeAdapter instead of parse_obj_as)
- Clean up TypeAdapter usage throughout codebase
- Fix syntax errors and whitespace issues
- Maintain all existing functionality with Pydantic v2 patterns

Co-authored-by: marrobi <17089773+marrobi@users.noreply.github.com>
….8.6->0.9.0

Co-authored-by: marrobi <17089773+marrobi@users.noreply.github.com>
Co-authored-by: marrobi <17089773+marrobi@users.noreply.github.com>
…paces.py

Co-authored-by: marrobi <17089773+marrobi@users.noreply.github.com>
- add explicit defaults for nullable model fields
- restore User-to-dict conversion for persisted resource actors
- migrate remaining serialization to model_dump()
- fix nested Event Grid payload serialization
- restore removed resource history behavior and route imports
- preserve legacy role ID, optional email, and cost date handling
- update tests for Pydantic v2 response types and error messages
Good catch from copilot, we can assume that v1 and v2 will not be installed at the same time and that for imports - this is superfluous

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 5, 2026 07:48

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 121 out of 121 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

templates/shared_services/certs/template_schema.json:26

  • This changes the certs bundle schema, but templates/shared_services/certs/porter.yaml remains at 0.7.11. Without a patch version bump, registries and deployments can continue resolving the old bundle despite the schema change. Bump the certs bundle version as done for the other modified templates in this PR.

Comment thread api_app/models/domain/workspace_users.py
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 5, 2026 07:53

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 121 out of 121 changed files in this pull request and generated 2 comments.

Suppressed comments (3)

templates/shared_services/certs/template_schema.json:26

  • This bundle's schema changes, but unlike the other modified bundles its porter.yaml version remains at 0.7.11. Bundle changes must receive a semantic version bump so the updated schema can be published and selected; increment the certs bundle version as part of this PR.
    api_app/models/schemas/airlock_request.py:86
  • Making type required changes the public request contract: Pydantic v1 accepted payloads without this field via the empty-string default, while this model now returns HTTP 422 (as the new regression test confirms). That contradicts the PR's “zero breaking changes” claim; either preserve compatibility or explicitly treat and version this as a breaking API change.
    api_app/models/schemas/airlock_request.py:100
  • This also introduces a breaking request-contract change: under v1, omitting approval used the falsey "" default and produced a rejected review, whereas the migrated endpoint now returns HTTP 422. Preserve the prior behavior or document and version the review endpoint change as breaking rather than claiming zero API changes.

Comment thread api_app/models/domain/airlock_request.py Outdated
Comment thread api_app/models/domain/azuretremodel.py
Co-authored-by: ChrisChapman-gh <118748128+ChrisChapman-gh@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 5, 2026 08:18
@ChrisChapman-gh
Chris Chapman (ChrisChapman-gh) removed the request for review from Copilot August 5, 2026 08:18
@ChrisChapman-gh

Copy link
Copy Markdown
Collaborator Author

/test-extended

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

🤖 pr-bot 🤖

🏃 Running extended tests: https://github.com/microsoft/AzureTRE/actions/runs/30997933316 (with refid f090555c)

(in response to this comment from Chris Chapman (@ChrisChapman-gh))

…ints on non-nullable properties and updating template normalization logic - app was giving 400 errors and UI would fail when submitting.
Copilot AI review requested due to automatic review settings August 7, 2026 15:34
Comment thread api_app/db/repositories/resources.py Fixed

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 124 out of 125 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • ui/app/package-lock.json: Generated file
Suppressed comments (4)

api_app/models/domain/airlock_request.py:108

  • Legacy records created when AirlockRequestInCreate.type was omitted contain "type": "", because the v1 default was serialized into Cosmos. Making this field optional only handles missing/null values; TypeAdapter(AirlockRequest) still rejects the persisted empty string, so list/get operations can still fail despite the stated backward-compatibility guarantee. Add a pre-validator/data migration for the empty-string representation as well (and define how such records can be used where event_sender.py:17 requires a real type).
    type: Optional[AirlockRequestType] = Field(None, title="Airlock request type")

api_app/models/schemas/airlock_request.py:86

  • This changes the request contract from an optional field with a default to a required field, so existing API clients that omit type now receive 422. That contradicts the PR's “zero breaking changes” claim. Either preserve a compatible default/legacy handling or explicitly treat and version this as a breaking API change.
    api_app/models/schemas/airlock_request.py:100
  • This likewise makes approval required, whereas the v1 model accepted an omitted value and the review flow treated its falsey default as rejection. Existing clients can therefore change from a successful request to 422, contrary to the stated no-breaking-change guarantee. Preserve the previous behavior (for example, an explicit False default) or document/version the contract break.
    templates/shared_services/certs/template_schema.json:31
  • The certs bundle schema is modified, but templates/shared_services/certs/porter.yaml remains at 0.7.11. Bundle changes require a semantic version increment; without it this updated schema will not be published/upgraded as a new component version. Bump the certs Porter version (and reflect it in release metadata as applicable), as done for the other changed bundles in this PR.

Copilot AI review requested due to automatic review settings August 7, 2026 15:47

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 124 out of 125 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • ui/app/package-lock.json: Generated file
Suppressed comments (1)

templates/shared_services/certs/template_schema.json:26

  • This changes the certs bundle schema, but templates/shared_services/certs/porter.yaml remains at 0.7.11. Bundle changes must increment the Porter version so the updated schema can be published and distinguished from the existing artifact; bump it to the next patch version as done for the other modified templates in this PR.

Comment thread api_app/models/domain/airlock_request.py
@ChrisChapman-gh

Copy link
Copy Markdown
Collaborator Author

/test-extended

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

🤖 pr-bot 🤖

🏃 Running extended tests: https://github.com/microsoft/AzureTRE/actions/runs/31195708239 (with refid f090555c)

(in response to this comment from Chris Chapman (@ChrisChapman-gh))

@rudolphjacksonm
Jack Morris (rudolphjacksonm) merged commit a480da4 into main Aug 9, 2026
15 checks passed
@rudolphjacksonm
Jack Morris (rudolphjacksonm) deleted the copilot/fix-4637 branch August 9, 2026 22:05
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.

Migration to Pydantic v2: Later versions of FastAPI require Pydantic v2

6 participants