This is an automated email from the ASF dual-hosted git repository.
bugraoz93 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 5a36ec1e72e Convert CLI Deprecations to maintainer idetifiable only
(#68726)
5a36ec1e72e is described below
commit 5a36ec1e72ec010c2248e83281b7d64749a88168
Author: Bugra Ozturk <[email protected]>
AuthorDate: Tue Jun 23 22:49:50 2026 +0200
Convert CLI Deprecations to maintainer idetifiable only (#68726)
* Do not warn the user about CLI Deprecations
* Update significant to reflect direction
* Rename significant to reflect warning changes
* Remove not used method docs and remove significant file
* Update documentation to explain CLI CTL relation better
---
airflow-core/newsfragments/68175.significant.rst | 24 --------
airflow-core/src/airflow/cli/utils.py | 29 ++++------
.../unit/cli/commands/test_command_deprecations.py | 64 +++++++++-------------
airflow-core/tests/unit/cli/test_utils.py | 20 ++++---
4 files changed, 49 insertions(+), 88 deletions(-)
diff --git a/airflow-core/newsfragments/68175.significant.rst
b/airflow-core/newsfragments/68175.significant.rst
deleted file mode 100644
index 4c9f7b4dffc..00000000000
--- a/airflow-core/newsfragments/68175.significant.rst
+++ /dev/null
@@ -1,24 +0,0 @@
-Airflow CLI commands are moving to talk to the API server
-
-The CLI is being migrated to reach Airflow through the API server (via the
``airflowctl``
-client) instead of the metadata database directly. Migrated so far: ``dags
trigger``,
-``dags delete``, ``pools`` (list/get/set/delete/import/export), and ``assets
materialize``;
-this fragment is updated as more commands migrate rather than adding new ones.
-
-These commands now require a reachable API server and mint a short-lived token
in memory
-(set ``AIRFLOW_CLI_TOKEN`` for auth managers that cannot mint locally, or for
remote servers).
-``airflow.api.client`` is removed — use
``airflow.cli.api_client.get_cli_api_client``.
-
-Each migrated command emits a ``RemovedInAirflow4Warning`` and will be removed
in a future
-Airflow release; use the equivalent ``airflowctl`` command instead.
-
-* Types of change
-
- * [ ] Dag changes
- * [ ] Config changes
- * [ ] API changes
- * [ ] CLI changes
- * [x] Behaviour changes
- * [ ] Plugin changes
- * [ ] Dependency changes
- * [x] Code interface changes
diff --git a/airflow-core/src/airflow/cli/utils.py
b/airflow-core/src/airflow/cli/utils.py
index a19d761c304..35a62c9df91 100644
--- a/airflow-core/src/airflow/cli/utils.py
+++ b/airflow-core/src/airflow/cli/utils.py
@@ -17,14 +17,10 @@
from __future__ import annotations
-import functools
import sys
-import warnings
from collections.abc import Callable
from typing import TYPE_CHECKING, TypeVar
-from airflow.exceptions import RemovedInAirflow4Warning
-
# Placeholder for masking sensitive values in CLI output
SENSITIVE_PLACEHOLDER = "***"
@@ -44,25 +40,22 @@ def deprecated_for_airflowctl(replacement: str) ->
Callable[[F], F]:
"""
Mark an ``airflow`` CLI command as deprecated in favour of an
``airflowctl`` equivalent.
- These commands now reach Airflow through the API server via the
``airflowctl`` client. They
- are kept for backwards compatibility but will be removed in a future
Airflow release; users
- should switch to ``airflowctl`` directly.
+ The command keeps its existing implementation and stays in the ``airflow``
CLI as a supported
+ entry point, so it emits **no user-facing deprecation warning** at
runtime. The intent is to
+ point future development at ``airflowctl``: the equivalent ``airflowctl``
command is recorded
+ for maintainers only, on the ``_migrated_to_airflowctl`` attribute (the
migration registry test
+ in ``test_command_deprecations.py`` reads it). The decorator at the
command's definition site is
+ the developer-facing trace -- it is source-only and never rendered to
users.
+
+ See ``contributing-docs/27_cli_implementation_guide.rst`` for the CLI /
``airflowctl``
+ development guidance.
:param replacement: The equivalent ``airflowctl`` command, e.g.
``airflowctl dags trigger``.
"""
def decorator(func: F) -> F:
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- warnings.warn(
- f"This `airflow` CLI command is deprecated and will be removed
in a future "
- f"Airflow release. Use `{replacement}` instead.",
- RemovedInAirflow4Warning,
- stacklevel=2,
- )
- return func(*args, **kwargs)
-
- return wrapper # type: ignore[return-value]
+ func._migrated_to_airflowctl = replacement # type:
ignore[attr-defined]
+ return func
return decorator
diff --git a/airflow-core/tests/unit/cli/commands/test_command_deprecations.py
b/airflow-core/tests/unit/cli/commands/test_command_deprecations.py
index b4eb6840c90..9300219fe5a 100644
--- a/airflow-core/tests/unit/cli/commands/test_command_deprecations.py
+++ b/airflow-core/tests/unit/cli/commands/test_command_deprecations.py
@@ -18,55 +18,45 @@
"""
Single source of truth for the ``airflow`` CLI commands deprecated in favour
of ``airflowctl``.
-Every command decorated with ``deprecated_for_airflowctl`` must have one entry
below. When a new
-command is migrated and deprecated, add a row to ``DEPRECATED_CLI_COMMANDS``
-- the test then
-verifies it emits ``RemovedInAirflow4Warning`` pointing at the right
``airflowctl`` command.
+Every command decorated with ``deprecated_for_airflowctl`` must have one entry
below. When a
+command is deprecated, add a row to ``MIGRATED_CLI_COMMANDS`` -- the test then
verifies the decorator
+recorded the right ``airflowctl`` replacement for maintainers. The commands
stay in the ``airflow``
+CLI as supported entry points, so they emit no user-facing deprecation
warning; they are simply no
+longer developed here -- new work belongs in ``airflowctl``. See
+``contributing-docs/27_cli_implementation_guide.rst`` for the CLI /
``airflowctl`` direction.
"""
from __future__ import annotations
-import contextlib
-import re
-
import pytest
from airflow.cli.commands import asset_command, dag_command, pool_command
-from airflow.exceptions import RemovedInAirflow4Warning
-# (command callable, argv to parse, expected airflowctl replacement named in
the warning)
-DEPRECATED_CLI_COMMANDS = [
- (dag_command.dag_trigger, ["dags", "trigger", "example_dag",
"--run-id=x"], "airflowctl dags trigger"),
- (dag_command.dag_delete, ["dags", "delete", "example_dag", "--yes"],
"airflowctl dags delete"),
- (pool_command.pool_list, ["pools", "list"], "airflowctl pools list"),
- (pool_command.pool_get, ["pools", "get", "foo"], "airflowctl pools get"),
- (pool_command.pool_set, ["pools", "set", "foo", "1", "desc"], "airflowctl
pools create"),
- (pool_command.pool_delete, ["pools", "delete", "foo"], "airflowctl pools
delete"),
- (pool_command.pool_import, ["pools", "import", "/nonexistent.json"],
"airflowctl pools import"),
- (
- pool_command.pool_export,
- ["pools", "export", "/tmp/airflow_pools_export.json"],
- "airflowctl pools export",
- ),
- (
- asset_command.asset_materialize,
- ["assets", "materialize", "--name=foo"],
- "airflowctl assets materialize",
- ),
+# (command callable, expected airflowctl replacement recorded by the decorator)
+MIGRATED_CLI_COMMANDS = [
+ (dag_command.dag_trigger, "airflowctl dags trigger"),
+ (dag_command.dag_delete, "airflowctl dags delete"),
+ (pool_command.pool_list, "airflowctl pools list"),
+ (pool_command.pool_get, "airflowctl pools get"),
+ (pool_command.pool_set, "airflowctl pools create"),
+ (pool_command.pool_delete, "airflowctl pools delete"),
+ (pool_command.pool_import, "airflowctl pools import"),
+ (pool_command.pool_export, "airflowctl pools export"),
+ (asset_command.asset_materialize, "airflowctl assets materialize"),
]
@pytest.mark.parametrize(
- ("command", "argv", "replacement"),
- DEPRECATED_CLI_COMMANDS,
- ids=[argv[0] + "-" + argv[1] for _, argv, _ in DEPRECATED_CLI_COMMANDS],
+ ("command", "replacement"),
+ MIGRATED_CLI_COMMANDS,
+ ids=[replacement for _, replacement in MIGRATED_CLI_COMMANDS],
)
-def test_deprecated_cli_command_points_to_airflowctl(command, argv,
replacement, parser, mock_cli_api_client):
- """Each migrated command warns it will become an alias for its
``airflowctl`` counterpart.
+def test_migrated_cli_command_records_airflowctl_replacement(command,
replacement):
+ """Each migrated command records its ``airflowctl`` counterpart for
maintainers.
- We only assert the deprecation warning fires (and names the right
replacement); the command
- body itself is exercised by the per-command test modules, so any error it
raises against the
- bare mocked client is irrelevant here and suppressed.
+ The marker is the maintainer-facing trace of the migration; users see no
runtime deprecation
+ warning. The command body itself is exercised by the per-command test
modules.
+ ``functools.wraps`` on the outer ``action_cli`` decorator propagates the
attribute up to the
+ command object imported here.
"""
- with pytest.warns(RemovedInAirflow4Warning, match=re.escape(replacement)):
- with contextlib.suppress(Exception, SystemExit):
- command(parser.parse_args(argv))
+ assert getattr(command, "_migrated_to_airflowctl", None) == replacement
diff --git a/airflow-core/tests/unit/cli/test_utils.py
b/airflow-core/tests/unit/cli/test_utils.py
index 4fb137ad482..f98a9ef6b19 100644
--- a/airflow-core/tests/unit/cli/test_utils.py
+++ b/airflow-core/tests/unit/cli/test_utils.py
@@ -17,32 +17,34 @@
# under the License.
from __future__ import annotations
-import pytest
+import warnings
from airflow.cli.utils import deprecated_for_airflowctl
-from airflow.exceptions import RemovedInAirflow4Warning
class TestDeprecatedForAirflowctl:
- def test_emits_warning_naming_replacement(self):
+ def test_records_replacement_without_emitting_a_user_warning(self):
@deprecated_for_airflowctl("airflowctl dags trigger")
def command(args):
return "result"
- with pytest.warns(RemovedInAirflow4Warning, match="airflowctl dags
trigger"):
+ # Calling the command emits nothing to users (any warning would become
an error here).
+ with warnings.catch_warnings():
+ warnings.simplefilter("error")
result = command(args=None)
- # The wrapped command still runs and returns its value.
assert result == "result"
+ # The replacement is recorded for maintainers, not shown to users.
+ assert command._migrated_to_airflowctl == "airflowctl dags trigger"
- def test_passes_through_args_and_preserves_metadata(self):
+ def test_passes_through_args_and_leaves_function_untouched(self):
@deprecated_for_airflowctl("airflowctl pools create")
def command(a, b, *, c):
"""Original docstring."""
return (a, b, c)
- with pytest.warns(RemovedInAirflow4Warning):
- assert command(1, 2, c=3) == (1, 2, 3)
-
+ assert command(1, 2, c=3) == (1, 2, 3)
+ # The decorator returns the original function untouched apart from the
metadata it records.
assert command.__name__ == "command"
assert command.__doc__ == "Original docstring."
+ assert command._migrated_to_airflowctl == "airflowctl pools create"