This is an automated email from the ASF dual-hosted git repository.
potiuk 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 17d887c8722 Make airflowctl command generation safe to read more than
once (#73097)
17d887c8722 is described below
commit 17d887c87229f3fbae8dcca33b266ae24af514e1
Author: Y-C <[email protected]>
AuthorDate: Mon Sep 14 22:04:52 2026 +0800
Make airflowctl command generation safe to read more than once (#73097)
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.
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 4a385537eb1..3603e0cc71d 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
@@ -959,9 +959,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 8eed0b8108f..6cb2fb19192 100644
--- a/airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py
+++ b/airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py
@@ -341,6 +341,19 @@ class TestCommandFactory:
assert parsed_conf == {"my-key": "my-value"}
+ 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_command_factory_parses_comma_separated_list_fields(self):
"""List fields should parse comma-separated CLI values as whole
items."""
command_factory = CommandFactory()