From 5d3c654eb3de7fc6f12ccdcee52bdad61b2fc418 Mon Sep 17 00:00:00 2001 From: Deepak Jain Date: Mon, 29 Jun 2026 21:37:45 -0700 Subject: [PATCH 1/4] Clarify task decorator argument errors (#49875) --- airflow-core/newsfragments/49875.bugfix.rst | 1 + task-sdk/src/airflow/sdk/bases/decorator.py | 23 +++++++++++++++---- .../tests/task_sdk/bases/test_decorator.py | 19 +++++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 airflow-core/newsfragments/49875.bugfix.rst diff --git a/airflow-core/newsfragments/49875.bugfix.rst b/airflow-core/newsfragments/49875.bugfix.rst new file mode 100644 index 0000000000000..281c5effafe34 --- /dev/null +++ b/airflow-core/newsfragments/49875.bugfix.rst @@ -0,0 +1 @@ +Clarify ``@task`` decorated callable errors when extra positional arguments are passed. diff --git a/task-sdk/src/airflow/sdk/bases/decorator.py b/task-sdk/src/airflow/sdk/bases/decorator.py index e45778725cc16..9b9ea3455d95b 100644 --- a/task-sdk/src/airflow/sdk/bases/decorator.py +++ b/task-sdk/src/airflow/sdk/bases/decorator.py @@ -253,6 +253,16 @@ def determine_kwargs( return KeywordParameters.determine(func, args, kwargs).unpacking() +_TASK_DECORATOR_CALL_HINT = ( + "This can happen when a @task-decorated function shadows another callable and the decorated task " + "object is called like a regular function. Rename the task function or call the original callable instead." +) + + +def _should_add_task_decorator_call_hint(err: TypeError, op_args: Collection[Any]) -> bool: + return bool(op_args) and "too many positional arguments" in str(err) + + class DecoratedOperator(BaseOperator): """ Wraps a Python callable and captures args/kwargs when called for execution. @@ -365,10 +375,15 @@ def __init__( # check all the arguments we know are valid. Whether these are enough # can only be known at execution time, when unmapping happens, and this # is called without the _airflow_mapped_validation_only flag. - if kwargs.get("_airflow_mapped_validation_only"): - signature.bind_partial(*op_args, **op_kwargs) - else: - signature.bind(*op_args, **op_kwargs) + try: + if kwargs.get("_airflow_mapped_validation_only"): + signature.bind_partial(*op_args, **op_kwargs) + else: + signature.bind(*op_args, **op_kwargs) + except TypeError as err: + if _should_add_task_decorator_call_hint(err, op_args): + raise TypeError(f"{err}. {_TASK_DECORATOR_CALL_HINT}") from err + raise # Params in injected_for_ordering are semantically required even though they received a # None default to satisfy Python's ordering constraint. Verify they are actually provided. diff --git a/task-sdk/tests/task_sdk/bases/test_decorator.py b/task-sdk/tests/task_sdk/bases/test_decorator.py index 860eb6e7b3312..00a6bf8087f3a 100644 --- a/task-sdk/tests/task_sdk/bases/test_decorator.py +++ b/task-sdk/tests/task_sdk/bases/test_decorator.py @@ -189,6 +189,25 @@ def dummy_task(required_arg): with pytest.raises(TypeError): make_op(dummy_task) + def test_bind_validation_hints_for_accidental_task_decorator_call(self): + def sleep(): + return None + + with pytest.raises( + TypeError, + match="too many positional arguments.*@task-decorated function shadows another callable", + ): + make_op(sleep, op_args=[3600]) + + def test_bind_validation_missing_required_args_has_no_accidental_call_hint(self): + def dummy_task(required_arg): + return required_arg + + with pytest.raises(TypeError) as ctx: + make_op(dummy_task) + + assert "@task-decorated function shadows another callable" not in str(ctx.value) + def test_variadic_and_keyword_only_params_are_not_assigned_defaults(self): """Construction succeeds when variadic and keyword-only params are present.""" From 9f72c97afaf806795a898bbe55bda5f7c16f351e Mon Sep 17 00:00:00 2001 From: Deepak Jain Date: Mon, 29 Jun 2026 21:57:55 -0700 Subject: [PATCH 2/4] Rename task decorator newsfragment for PR check (#49875) --- airflow-core/newsfragments/{49875.bugfix.rst => 69157.bugfix.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename airflow-core/newsfragments/{49875.bugfix.rst => 69157.bugfix.rst} (100%) diff --git a/airflow-core/newsfragments/49875.bugfix.rst b/airflow-core/newsfragments/69157.bugfix.rst similarity index 100% rename from airflow-core/newsfragments/49875.bugfix.rst rename to airflow-core/newsfragments/69157.bugfix.rst From 4517891d01b5c86a2af2f5f09866764c71eacdb9 Mon Sep 17 00:00:00 2001 From: Deepak Jain Date: Mon, 10 Aug 2026 10:41:47 +0530 Subject: [PATCH 3/4] Narrow task decorator error hint detection (#49875) --- task-sdk/src/airflow/sdk/bases/decorator.py | 28 +++++++++++++++++-- .../tests/task_sdk/bases/test_decorator.py | 15 ++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/task-sdk/src/airflow/sdk/bases/decorator.py b/task-sdk/src/airflow/sdk/bases/decorator.py index 9b9ea3455d95b..2e64bc8beb357 100644 --- a/task-sdk/src/airflow/sdk/bases/decorator.py +++ b/task-sdk/src/airflow/sdk/bases/decorator.py @@ -259,8 +259,30 @@ def determine_kwargs( ) -def _should_add_task_decorator_call_hint(err: TypeError, op_args: Collection[Any]) -> bool: - return bool(op_args) and "too many positional arguments" in str(err) +def _is_python_callable_already_executing(python_callable: Callable) -> bool: + target_code = getattr(python_callable, "__code__", None) + if target_code is None: + return False + + frame = inspect.currentframe() + try: + while frame is not None: + if frame.f_code is target_code: + return True + frame = frame.f_back + return False + finally: + del frame + + +def _should_add_task_decorator_call_hint( + err: TypeError, python_callable: Callable, op_args: Collection[Any] +) -> bool: + return ( + bool(op_args) + and "too many positional arguments" in str(err) + and _is_python_callable_already_executing(python_callable) + ) class DecoratedOperator(BaseOperator): @@ -381,7 +403,7 @@ def __init__( else: signature.bind(*op_args, **op_kwargs) except TypeError as err: - if _should_add_task_decorator_call_hint(err, op_args): + if _should_add_task_decorator_call_hint(err, python_callable, op_args): raise TypeError(f"{err}. {_TASK_DECORATOR_CALL_HINT}") from err raise diff --git a/task-sdk/tests/task_sdk/bases/test_decorator.py b/task-sdk/tests/task_sdk/bases/test_decorator.py index 00a6bf8087f3a..b3dfd1630c5d4 100644 --- a/task-sdk/tests/task_sdk/bases/test_decorator.py +++ b/task-sdk/tests/task_sdk/bases/test_decorator.py @@ -190,14 +190,25 @@ def dummy_task(required_arg): make_op(dummy_task) def test_bind_validation_hints_for_accidental_task_decorator_call(self): + @task def sleep(): - return None + sleep(3600) with pytest.raises( TypeError, match="too many positional arguments.*@task-decorated function shadows another callable", ): - make_op(sleep, op_args=[3600]) + sleep.function() + + def test_bind_validation_plain_arity_error_has_no_accidental_call_hint(self): + @task + def dummy_task(required_arg): + return required_arg + + with pytest.raises(TypeError) as ctx: + dummy_task(1, 2) + + assert "@task-decorated function shadows another callable" not in str(ctx.value) def test_bind_validation_missing_required_args_has_no_accidental_call_hint(self): def dummy_task(required_arg): From 4ca4c64c27ee439310f2cc25c3f37ec0ba80b5fb Mon Sep 17 00:00:00 2001 From: Deepak Jain Date: Tue, 11 Aug 2026 10:17:13 +0530 Subject: [PATCH 4/4] Remove unnecessary task decorator newsfragment (#49875) --- airflow-core/newsfragments/69157.bugfix.rst | 1 - 1 file changed, 1 deletion(-) delete mode 100644 airflow-core/newsfragments/69157.bugfix.rst diff --git a/airflow-core/newsfragments/69157.bugfix.rst b/airflow-core/newsfragments/69157.bugfix.rst deleted file mode 100644 index 281c5effafe34..0000000000000 --- a/airflow-core/newsfragments/69157.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Clarify ``@task`` decorated callable errors when extra positional arguments are passed.