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"

Reply via email to