This is an automated email from the ASF dual-hosted git repository.

eladkal pushed a commit to branch v3-2-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-2-test by this push:
     new f6aca26aaa2 [v3-2-test] Validate operator arg types at decoration time 
in @task decorator (#65041) (#65050)
f6aca26aaa2 is described below

commit f6aca26aaa2b1c35f1a67ed41ce6c307a63fa677
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Apr 13 19:42:58 2026 +0300

    [v3-2-test] Validate operator arg types at decoration time in @task 
decorator (#65041) (#65050)
    
    (cherry picked from commit 7d3364b22f3217a9639e2680c81587981433fcbc)
    
    Co-authored-by: Kaxil Naik <[email protected]>
---
 task-sdk/src/airflow/sdk/bases/decorator.py     |  8 ++++++
 task-sdk/tests/task_sdk/bases/test_decorator.py | 33 +++++++++++++++++++++++++
 2 files changed, 41 insertions(+)

diff --git a/task-sdk/src/airflow/sdk/bases/decorator.py 
b/task-sdk/src/airflow/sdk/bases/decorator.py
index 2e98f826c7e..8634fbe9964 100644
--- a/task-sdk/src/airflow/sdk/bases/decorator.py
+++ b/task-sdk/src/airflow/sdk/bases/decorator.py
@@ -30,6 +30,7 @@ import typing_extensions
 
 from airflow.sdk import TriggerRule, timezone
 from airflow.sdk.bases.operator import (
+    BASEOPERATOR_ARGS_EXPECTED_TYPES,
     BaseOperator,
     coerce_resources,
     coerce_timedelta,
@@ -498,6 +499,13 @@ class _TaskDecorator(ExpandableFactory, Generic[FParams, 
FReturn, OperatorSubcla
         if "self" in self.function_signature.parameters:
             raise TypeError(f"@{self.decorator_name} does not support methods")
         self.kwargs.setdefault("task_id", self.function.__name__)
+        for arg_name, expected_type in 
BASEOPERATOR_ARGS_EXPECTED_TYPES.items():
+            if arg_name in self.kwargs:
+                value = self.kwargs[arg_name]
+                if value is not None and not isinstance(value, expected_type):
+                    raise TypeError(
+                        f"Expected {arg_name!r} to be {expected_type}, got 
{type(value).__name__}: {value!r}"
+                    )
         update_wrapper(self, self.function)
 
     def __call__(self, *args: FParams.args, **kwargs: FParams.kwargs) -> 
XComArg:
diff --git a/task-sdk/tests/task_sdk/bases/test_decorator.py 
b/task-sdk/tests/task_sdk/bases/test_decorator.py
index 6021f6f0aad..860eb6e7b33 100644
--- a/task-sdk/tests/task_sdk/bases/test_decorator.py
+++ b/task-sdk/tests/task_sdk/bases/test_decorator.py
@@ -197,6 +197,39 @@ class TestDefaultFillingLogic:
 
         assert make_op(dummy_task, op_kwargs={"a": 1, "kw_required": "x"}) is 
not None
 
+    @pytest.mark.parametrize(
+        ("kwargs", "match"),
+        [
+            pytest.param(
+                {"task_id": "fetch_{}".format},
+                "Expected 'task_id' to be .*, got builtin_function_or_method",
+                id="task_id_bound_method",
+            ),
+            pytest.param(
+                {"retries": "three"},
+                "Expected 'retries' to be .*, got str",
+                id="retries_string",
+            ),
+            pytest.param(
+                {"queue": 42},
+                "Expected 'queue' to be .*, got int",
+                id="queue_int",
+            ),
+            pytest.param(
+                {"priority_weight": 1.5},
+                "Expected 'priority_weight' to be .*, got float",
+                id="priority_weight_float",
+            ),
+        ],
+    )
+    def test_wrong_arg_type_raises_type_error_at_decoration_time(self, kwargs, 
match):
+        """Non-matching types for operator kwargs raise TypeError at 
decoration time."""
+        with pytest.raises(TypeError, match=match):
+
+            @task(**kwargs)
+            def my_task():
+                return 1
+
 
 def make_op(func, op_args=None, op_kwargs=None, **kwargs):
     return DummyDecoratedOperator(

Reply via email to