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

shahar1 pushed a commit to branch airflow-ctl/v0-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/airflow-ctl/v0-1-test by this 
push:
     new f58c0dfccee [airflow-ctl/v0-1-test] Make airflowctl command generation 
safe to read more than once (#73097) (#76048)
f58c0dfccee is described below

commit f58c0dfccee94f7bafb3f6a4b0f503f02cad35ef
Author: Shahar Epstein <[email protected]>
AuthorDate: Fri Oct 30 18:07:31 2026 +0200

    [airflow-ctl/v0-1-test] Make airflowctl command generation safe to read 
more than once (#73097) (#76048)
    
    The generated command tree is exposed through a module-level CommandFactory
    singleton that anyone can import, so a second read of the property is a
    reachable state rather than a hypothetical one. Today only the single
    import-time access exists, which is why nothing is visibly broken; the most
    likely way in is a routine test refactor that shares one factory instance
    across cases, which would then fail in a way that points nowhere near the
    cause.
    
    (cherry picked from commit 17d887c87229f3fbae8dcca33b266ae24af514e1)
    
    Co-authored-by: Y-C <[email protected]>
    Co-authored-by: Eason09053360 
<[email protected]>
---
 airflow-ctl/src/airflowctl/ctl/cli_config.py         | 12 +++++++++---
 airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py | 13 +++++++++++++
 2 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/airflow-ctl/src/airflowctl/ctl/cli_config.py 
b/airflow-ctl/src/airflowctl/ctl/cli_config.py
index 389afbc66cb..869d7c96ba9 100755
--- a/airflow-ctl/src/airflowctl/ctl/cli_config.py
+++ b/airflow-ctl/src/airflowctl/ctl/cli_config.py
@@ -30,7 +30,7 @@ import sys
 from argparse import Namespace
 from collections.abc import Callable, Iterable
 from enum import Enum
-from functools import partial
+from functools import cached_property, partial
 from pathlib import Path
 from typing import Any, NamedTuple
 
@@ -923,9 +923,15 @@ class CommandFactory:
                 )
             )
 
-    @property
+    @cached_property
     def group_commands(self) -> list[CLICommand]:
-        """List of GroupCommands generated for airflowctl."""
+        """
+        List of GroupCommands generated for airflowctl.
+
+        Cached because the builders below append to ``self.operations`` /
+        ``self.commands_map`` / ``self.group_commands_list``: recomputing would
+        duplicate every group and subcommand instead of replacing them.
+        """
         self._inspect_operations()
         self._create_args_map_from_operation()
         self._create_func_map_from_operation()
diff --git a/airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py 
b/airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py
index 955c554b58e..6ba7ac3e9f2 100644
--- a/airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py
+++ b/airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py
@@ -359,6 +359,19 @@ class TestCommandFactory:
 
         assert list_type("dag1, dag2") == ["dag1", "dag2"]
 
+    def test_group_commands_is_stable_across_repeated_access(self):
+        """Reading ``group_commands`` twice must not duplicate groups or 
subcommands."""
+        command_factory = CommandFactory()
+
+        # Snapshot the names and sizes rather than the list itself: both 
accesses
+        # hand back the same object, so only values captured before the second
+        # access can witness it mutating them.
+        first = [(group.name, len(group.subcommands)) for group in 
command_factory.group_commands]
+        second = [(group.name, len(group.subcommands)) for group in 
command_factory.group_commands]
+
+        assert second == first
+        assert len(second) == len({name for name, _ in second})
+
     def test_json_dict_type_returns_dict_input_unchanged(self):
         """A dict input is returned as-is without re-parsing."""
         value = {"my-key": "my-value"}

Reply via email to