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 f081ffb0f38 CLI: Add --limit flag to airflow dags list-runs (#71729)
f081ffb0f38 is described below

commit f081ffb0f38b01951ad6f561c7404bf0263d97db
Author: Deepak kumar <[email protected]>
AuthorDate: Tue Sep 22 16:34:14 2026 -0700

    CLI: Add --limit flag to airflow dags list-runs (#71729)
    
    airflow dags list-jobs already accepts --limit but the neighbouring
    list-runs command does not, so operators on busy DAGs have to pipe
    through head or use date filters to shorten output. Mirroring the
    existing flag closes that asymmetry and makes the two commands
    predictable together.
---
 airflow-core/newsfragments/71729.improvement.rst   |  1 +
 airflow-core/src/airflow/cli/cli_config.py         |  9 ++++-
 .../src/airflow/cli/commands/dag_command.py        |  4 +++
 .../tests/unit/cli/commands/test_dag_command.py    | 38 ++++++++++++++++++++++
 4 files changed, 51 insertions(+), 1 deletion(-)

diff --git a/airflow-core/newsfragments/71729.improvement.rst 
b/airflow-core/newsfragments/71729.improvement.rst
new file mode 100644
index 00000000000..d85f81ee6fe
--- /dev/null
+++ b/airflow-core/newsfragments/71729.improvement.rst
@@ -0,0 +1 @@
+Add ``--limit`` option to ``airflow dags list-runs`` to cap output to the N 
most recent Dag runs after filters and sorting are applied, mirroring ``airflow 
dags list-jobs --limit``.
diff --git a/airflow-core/src/airflow/cli/cli_config.py 
b/airflow-core/src/airflow/cli/cli_config.py
index e6f1a01975f..0274fc4402e 100644
--- a/airflow-core/src/airflow/cli/cli_config.py
+++ b/airflow-core/src/airflow/cli/cli_config.py
@@ -291,6 +291,11 @@ ARG_DR_STATE = Arg(
     metavar=", ".join(dagrun_states),
     choices=dagrun_states,
 )
+ARG_DR_LIMIT = Arg(
+    ("--limit",),
+    type=positive_int(allow_zero=False),
+    help="Return a limited number of Dag runs, ordered by most recent 
run_after first",
+)
 
 # list_jobs
 ARG_DAG_ID_OPT = Arg(("-d", "--dag-id"), help="The id of the dag")
@@ -1253,13 +1258,15 @@ DAGS_COMMANDS = (
             "dagruns with the given state. If no_backfill option is given, it 
will filter out all "
             "backfill dagruns for given dag id. If start_date is given, it 
will filter out all the "
             "dagruns that were executed before this date. If end_date is 
given, it will filter out "
-            "all the dagruns that were executed after this date. "
+            "all the dagruns that were executed after this date. If limit is 
given, it will return "
+            "only the most recent N runs after filters and sorting are 
applied."
         ),
         
func=lazy_load_command("airflow.cli.commands.dag_command.dag_list_dag_runs"),
         args=(
             ARG_DAG_ID,
             ARG_NO_BACKFILL,
             ARG_DR_STATE,
+            ARG_DR_LIMIT,
             ARG_OUTPUT,
             ARG_VERBOSE,
             ARG_START_DATE,
diff --git a/airflow-core/src/airflow/cli/commands/dag_command.py 
b/airflow-core/src/airflow/cli/commands/dag_command.py
index 9ab41b426c3..bd42dadf925 100644
--- a/airflow-core/src/airflow/cli/commands/dag_command.py
+++ b/airflow-core/src/airflow/cli/commands/dag_command.py
@@ -799,6 +799,10 @@ def dag_list_dag_runs(args, dag: DAG | None = None, *, 
session: Session = NEW_SE
         session=session,
     )
     dag_runs.sort(key=operator.attrgetter("run_after"), reverse=True)
+    # Slice after sorting so `--limit` reliably returns the most recent runs,
+    # independent of insertion order in DagRun.find().
+    if getattr(args, "limit", None):
+        dag_runs = dag_runs[: args.limit]
 
     def _render_dagrun(dr: DagRun) -> dict[str, str]:
         return {
diff --git a/airflow-core/tests/unit/cli/commands/test_dag_command.py 
b/airflow-core/tests/unit/cli/commands/test_dag_command.py
index 2714ed9d387..fcf819910e6 100644
--- a/airflow-core/tests/unit/cli/commands/test_dag_command.py
+++ b/airflow-core/tests/unit/cli/commands/test_dag_command.py
@@ -541,6 +541,44 @@ class TestCliDags:
         )
         dag_command.dag_list_dag_runs(args)
 
+    @pytest.mark.parametrize(
+        ("limit", "expected_count"),
+        [
+            (None, 3),
+            (1, 1),
+            (2, 2),
+            (5, 3),
+        ],
+    )
+    @mock.patch("airflow.cli.commands.dag_command.AirflowConsole")
+    def test_cli_list_dag_runs_limit(self, mock_console, limit, 
expected_count):
+        """`--limit N` caps output to the N most recent runs; without it, all 
runs are returned."""
+        for i in range(3):
+            dag_command.dag_trigger(
+                self.parser.parse_args(
+                    ["dags", "trigger", "example_bash_operator", "--run-id", 
f"cli_limit_test_{i}"]
+                )
+            )
+
+        argv = ["dags", "list-runs", "example_bash_operator"]
+        if limit is not None:
+            argv += ["--limit", str(limit)]
+        args = self.parser.parse_args(argv)
+        dag_command.dag_list_dag_runs(args)
+
+        printed = mock_console.return_value.print_as.call_args.kwargs["data"]
+        assert len(printed) == expected_count
+        # Slicing happens after the run_after DESC sort, so the returned rows 
must
+        # remain monotonically non-increasing on run_after.
+        run_afters = [dr.run_after for dr in printed]
+        assert run_afters == sorted(run_afters, reverse=True)
+
+    @pytest.mark.parametrize("bad_value", ["0", "-1", "abc"])
+    def test_cli_list_dag_runs_limit_rejects_invalid(self, bad_value):
+        """`--limit` must be a positive int; argparse rejects invalid inputs 
before the command runs."""
+        with pytest.raises(SystemExit):
+            self.parser.parse_args(["dags", "list-runs", 
"example_bash_operator", "--limit", bad_value])
+
     def test_cli_list_jobs_with_args(self):
         args = self.parser.parse_args(
             [

Reply via email to