Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion airflow-core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions airflow-core/src/airflow/api_fastapi/core_api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
30 changes: 30 additions & 0 deletions airflow-core/tests/unit/api_fastapi/core_api/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ def name_must_not_be_empty(cls, v: str) -> str:
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()
Expand Down Expand Up @@ -76,3 +86,23 @@ def test_already_optional_fields_stay_optional(self):

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)
2 changes: 1 addition & 1 deletion dev/registry/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]

Expand Down
2 changes: 1 addition & 1 deletion providers/edge3/docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,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``
Expand Down
2 changes: 1 addition & 1 deletion providers/edge3/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion shared/secrets_masker/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion task-sdk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 5 additions & 5 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading