This is an automated email from the ASF dual-hosted git repository.
pierrejeambrun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 0e1832cd3cd Fix make_partial_model (#63716)
0e1832cd3cd is described below
commit 0e1832cd3cd586ea4a53cc012550fbc084fb0fc4
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Wed Mar 18 13:05:21 2026 +0100
Fix make_partial_model (#63716)
* Fix make_partial_model
* Revert "Bump pydantic min version to 2.12.3 (#63570)"
This reverts commit 9516a771f402da94ba35f0b53f66258716f04948.
* Fix CI
---
airflow-core/pyproject.toml | 2 +-
.../src/airflow/api_fastapi/core_api/base.py | 4 +--
.../tests/unit/api_fastapi/core_api/test_base.py | 30 ++++++++++++++++++++++
dev/registry/pyproject.toml | 2 +-
providers/edge3/docs/index.rst | 2 +-
providers/edge3/pyproject.toml | 2 +-
shared/secrets_masker/pyproject.toml | 2 +-
task-sdk/pyproject.toml | 2 +-
uv.lock | 10 ++++----
9 files changed, 43 insertions(+), 13 deletions(-)
diff --git a/airflow-core/pyproject.toml b/airflow-core/pyproject.toml
index c853c7de833..eaaf4538058 100644
--- a/airflow-core/pyproject.toml
+++ b/airflow-core/pyproject.toml
@@ -125,7 +125,7 @@ dependencies = [
'pendulum>=3.1.0',
"pluggy>=1.5.0",
"psutil>=5.8.0",
- "pydantic>=2.12.3",
+ "pydantic>=2.11.0",
# Pygments 2.19.0 improperly renders .ini files with dictionaries as values
# See https://github.com/pygments/pygments/issues/2834
"pygments>=2.0.1,!=2.19.0",
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/base.py
b/airflow-core/src/airflow/api_fastapi/core_api/base.py
index 600a6d896e2..b88cb955650 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/base.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/base.py
@@ -17,7 +17,6 @@
from __future__ import annotations
from abc import ABC, abstractmethod
-from copy import deepcopy
from typing import TYPE_CHECKING, Generic, TypeVar, Union, get_args, get_origin
from pydantic import BaseModel as PydanticBaseModel, ConfigDict, create_model
@@ -54,12 +53,13 @@ def make_partial_model(model: type[PydanticBaseModel]) ->
type[PydanticBaseModel
"""Create a version of a Pydantic model where all fields are Optional with
default=None."""
field_overrides: dict = {}
for field_name, field_info in model.model_fields.items():
- new_info = deepcopy(field_info)
ann = field_info.annotation
origin = get_origin(ann)
if not (origin is Union and type(None) in get_args(ann)):
ann = ann | None # type: ignore[operator, assignment]
+ new_info = field_info._copy()
new_info.default = None
+ new_info._attributes_set["default"] = None
field_overrides[field_name] = (ann, new_info)
return create_model(
diff --git a/airflow-core/tests/unit/api_fastapi/core_api/test_base.py
b/airflow-core/tests/unit/api_fastapi/core_api/test_base.py
index 15a47efac11..e806ee1b80e 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/test_base.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/test_base.py
@@ -41,6 +41,16 @@ class SampleModel(StrictBaseModel):
SampleModelPartial = make_partial_model(SampleModel)
+class ModelWithFieldAttributes(StrictBaseModel):
+ """Model with alias, title, and description to test full attribute
preservation."""
+
+ name: str = Field(alias="user_name", title="User Name", description="The
user's full name")
+ age: int = Field(ge=0, le=150, title="Age", description="Age in years")
+
+
+ModelWithFieldAttributesPartial = make_partial_model(ModelWithFieldAttributes)
+
+
class TestMakePartialModel:
def test_all_fields_become_optional(self):
instance = SampleModelPartial()
@@ -76,3 +86,23 @@ class TestMakePartialModel:
def test_partial_model_name(self):
assert SampleModelPartial.__name__ == "SampleModelPartial"
+
+ def test_field_alias_preserved(self):
+ instance = ModelWithFieldAttributesPartial(user_name="Alice")
+ assert instance.name == "Alice"
+
+ def test_field_title_and_description_preserved(self):
+ name_field = ModelWithFieldAttributesPartial.model_fields["name"]
+ assert name_field.alias == "user_name"
+ assert name_field.title == "User Name"
+ assert name_field.description == "The user's full name"
+
+ age_field = ModelWithFieldAttributesPartial.model_fields["age"]
+ assert age_field.title == "Age"
+ assert age_field.description == "Age in years"
+
+ def test_field_ge_le_constraints_preserved(self):
+ with pytest.raises(ValidationError):
+ ModelWithFieldAttributesPartial(age=-1)
+ with pytest.raises(ValidationError):
+ ModelWithFieldAttributesPartial(age=200)
diff --git a/dev/registry/pyproject.toml b/dev/registry/pyproject.toml
index 6adc5ea8d5a..30d70642216 100644
--- a/dev/registry/pyproject.toml
+++ b/dev/registry/pyproject.toml
@@ -33,7 +33,7 @@ version = "0.0.1"
requires-python = ">=3.10"
classifiers = ["Private :: Do Not Upload"]
dependencies = [
- "pydantic>=2.12.3",
+ "pydantic>=2.12.0",
"pyyaml>=6.0.3",
]
diff --git a/providers/edge3/docs/index.rst b/providers/edge3/docs/index.rst
index 6e68e0bd4b9..9e7d9acaf60 100644
--- a/providers/edge3/docs/index.rst
+++ b/providers/edge3/docs/index.rst
@@ -122,7 +122,7 @@ PIP package Version required
========================================== ===================
``apache-airflow`` ``>=3.0.0,!=3.1.0``
``apache-airflow-providers-common-compat`` ``>=1.14.0``
-``pydantic`` ``>=2.12.3``
+``pydantic`` ``>=2.11.0``
``retryhttp`` ``>=1.4.0``
``aiofiles`` ``>=23.2.0``
``aiohttp`` ``>=3.9.2``
diff --git a/providers/edge3/pyproject.toml b/providers/edge3/pyproject.toml
index 2b234b8c244..f1209231b80 100644
--- a/providers/edge3/pyproject.toml
+++ b/providers/edge3/pyproject.toml
@@ -67,7 +67,7 @@ requires-python = ">=3.10"
dependencies = [
"apache-airflow>=3.0.0,!=3.1.0",
"apache-airflow-providers-common-compat>=1.14.0",
- "pydantic>=2.12.3",
+ "pydantic>=2.11.0",
"retryhttp>=1.4.0",
"aiofiles>=23.2.0",
"aiohttp>=3.9.2",
diff --git a/shared/secrets_masker/pyproject.toml
b/shared/secrets_masker/pyproject.toml
index 9826a2e908d..04e1e862f44 100644
--- a/shared/secrets_masker/pyproject.toml
+++ b/shared/secrets_masker/pyproject.toml
@@ -27,7 +27,7 @@ dependencies = [
'pendulum>=3.1.0',
"methodtools>=0.4.7",
"colorlog>=6.8.2",
- "pydantic>2.12.3",
+ "pydantic>2.11.0",
]
[dependency-groups]
diff --git a/task-sdk/pyproject.toml b/task-sdk/pyproject.toml
index bd0382b9a89..1fcd6e8817c 100644
--- a/task-sdk/pyproject.toml
+++ b/task-sdk/pyproject.toml
@@ -68,7 +68,7 @@ dependencies = [
# End of shared timezones dependencies
# Start of shared secrets_masker dependencies
"colorlog>=6.8.2",
- "pydantic>2.12.3",
+ "pydantic>2.11.0",
# End of shared secrets_masker dependencies
# Start of shared logging dependencies
"pygtrie>=2.5.0",
diff --git a/uv.lock b/uv.lock
index 2d162bd3776..f25e27ec5a1 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1759,7 +1759,7 @@ requires-dist = [
{ name = "pendulum", specifier = ">=3.1.0" },
{ name = "pluggy", specifier = ">=1.5.0" },
{ name = "psutil", specifier = ">=5.8.0" },
- { name = "pydantic", specifier = ">=2.12.3" },
+ { name = "pydantic", specifier = ">=2.11.0" },
{ name = "pygments", specifier = ">=2.0.1,!=2.19.0" },
{ name = "pygtrie", specifier = ">=2.5.0" },
{ name = "pyjwt", specifier = ">=2.11.0" },
@@ -4420,7 +4420,7 @@ requires-dist = [
{ name = "aiohttp", specifier = ">=3.9.2" },
{ name = "apache-airflow", editable = "." },
{ name = "apache-airflow-providers-common-compat", editable =
"providers/common/compat" },
- { name = "pydantic", specifier = ">=2.12.3" },
+ { name = "pydantic", specifier = ">=2.11.0" },
{ name = "retryhttp", specifier = ">=1.4.0" },
]
@@ -7516,7 +7516,7 @@ dev = [
[package.metadata]
requires-dist = [
- { name = "pydantic", specifier = ">=2.12.3" },
+ { name = "pydantic", specifier = ">=2.12.0" },
{ name = "pyyaml", specifier = ">=6.0.3" },
]
@@ -7847,7 +7847,7 @@ requires-dist = [
{ name = "colorlog", specifier = ">=6.8.2" },
{ name = "methodtools", specifier = ">=0.4.7" },
{ name = "pendulum", specifier = ">=3.1.0" },
- { name = "pydantic", specifier = ">2.12.3" },
+ { name = "pydantic", specifier = ">2.11.0" },
]
[package.metadata.requires-dev]
@@ -7990,7 +7990,7 @@ requires-dist = [
{ name = "pendulum", specifier = ">=3.1.0" },
{ name = "pluggy", specifier = ">=1.5.0" },
{ name = "psutil", specifier = ">=6.1.0" },
- { name = "pydantic", specifier = ">2.12.3" },
+ { name = "pydantic", specifier = ">2.11.0" },
{ name = "pygtrie", specifier = ">=2.5.0" },
{ name = "python-dateutil", specifier = ">=2.7.0" },
{ name = "sentry-sdk", marker = "extra == 'all'", specifier = ">=2.30.0" },