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

bugraoz 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 42cb911345d fix(airflowctl): allow listing dag runs without specifying 
dag_id (#61525)
42cb911345d is described below

commit 42cb911345d157dac27aa89d3524a696ece3b69b
Author: Henry Chen <[email protected]>
AuthorDate: Sat Feb 21 04:45:21 2026 +0800

    fix(airflowctl): allow listing dag runs without specifying dag_id (#61525)
    
    Make dag_id parameter optional in DagRunOperations.list() and default to ~
    for querying all DAGs. Also make other filter parameters (state, limit,
    start_date, end_date) optional for better CLI flexibility.
---
 airflow-ctl/src/airflowctl/api/operations.py       | 32 +++++++++++++-----
 airflow-ctl/src/airflowctl/ctl/cli_config.py       |  5 ++-
 .../tests/airflow_ctl/api/test_operations.py       | 38 ++++++++++++++++++++++
 3 files changed, 66 insertions(+), 9 deletions(-)

diff --git a/airflow-ctl/src/airflowctl/api/operations.py 
b/airflow-ctl/src/airflowctl/api/operations.py
index 588d85569e3..e4a046ed774 100644
--- a/airflow-ctl/src/airflowctl/api/operations.py
+++ b/airflow-ctl/src/airflowctl/api/operations.py
@@ -554,20 +554,36 @@ class DagRunOperations(BaseOperations):
 
     def list(
         self,
-        dag_id: str,
-        start_date: datetime.datetime,
-        end_date: datetime.datetime,
         state: str,
         limit: int,
+        start_date: datetime.datetime | None = None,
+        end_date: datetime.datetime | None = None,
+        dag_id: str | None = None,
     ) -> DAGRunCollectionResponse | ServerResponseError:
-        """List all dag runs."""
-        params = {
-            "start_date": start_date,
-            "end_date": end_date,
+        """
+        List all dag runs.
+
+        Args:
+            state: Filter dag runs by state
+            start_date: Filter dag runs by start date (optional)
+            end_date: Filter dag runs by end date (optional)
+            state: Filter dag runs by state
+            limit: Limit the number of results
+            dag_id: The DAG ID to filter by. If None, retrieves dag runs for 
all DAGs (using "~").
+        """
+        # Use "~" for all DAGs if dag_id is not specified
+        if not dag_id:
+            dag_id = "~"
+
+        params: dict[str, object] = {
             "state": state,
             "limit": limit,
-            "dag_id": dag_id,
         }
+        if start_date is not None:
+            params["start_date"] = start_date
+        if end_date is not None:
+            params["end_date"] = end_date
+
         return super().execute_list(
             path=f"/dags/{dag_id}/dagRuns", 
data_model=DAGRunCollectionResponse, params=params
         )
diff --git a/airflow-ctl/src/airflowctl/ctl/cli_config.py 
b/airflow-ctl/src/airflowctl/ctl/cli_config.py
index 2b8e6d8bfd7..5f7114480c2 100644
--- a/airflow-ctl/src/airflowctl/ctl/cli_config.py
+++ b/airflow-ctl/src/airflowctl/ctl/cli_config.py
@@ -464,7 +464,10 @@ class CommandFactory:
             "set",
             "datetime.datetime",
         }
-        return type_name in primitive_types
+        # Handle Optional types (e.g., "datetime.datetime | None", "str | 
None")
+        # Strip " | None" suffix to check the base type
+        base_type = type_name.replace(" | None", "").strip()
+        return base_type in primitive_types
 
     @staticmethod
     def _python_type_from_string(type_name: str | type) -> type | Callable:
diff --git a/airflow-ctl/tests/airflow_ctl/api/test_operations.py 
b/airflow-ctl/tests/airflow_ctl/api/test_operations.py
index ecfa758df32..701440eeb5a 100644
--- a/airflow-ctl/tests/airflow_ctl/api/test_operations.py
+++ b/airflow-ctl/tests/airflow_ctl/api/test_operations.py
@@ -1030,6 +1030,44 @@ class TestDagRunOperations:
         )
         assert response == self.dag_run_collection_response
 
+    def test_list_all_dags(self):
+        """Test listing dag runs for all DAGs using default dag_id='~'."""
+
+        def handle_request(request: httpx.Request) -> httpx.Response:
+            # When dag_id is "~", it should query all DAGs
+            assert request.url.path == "/api/v2/dags/~/dagRuns"
+            return httpx.Response(200, 
json=json.loads(self.dag_run_collection_response.model_dump_json()))
+
+        client = make_api_client(transport=httpx.MockTransport(handle_request))
+        # Call without specifying dag_id - should use default "~"
+        response = client.dag_runs.list(
+            start_date=datetime.datetime(2025, 1, 1, 0, 0, 0),
+            end_date=datetime.datetime(2025, 1, 1, 0, 0, 0),
+            state="running",
+            limit=1,
+        )
+        assert response == self.dag_run_collection_response
+
+    def test_list_with_optional_parameters(self):
+        """Test listing dag runs with only some optional parameters."""
+
+        def handle_request(request: httpx.Request) -> httpx.Response:
+            assert request.url.path == "/api/v2/dags/dag1/dagRuns"
+            # Verify that only state and limit are in query params
+            params = dict(request.url.params)
+            assert "state" in params
+            assert params["state"] == "queued"
+            assert "limit" in params
+            assert params["limit"] == "5"
+            # start_date and end_date should not be present
+            assert "start_date" not in params
+            assert "end_date" not in params
+            return httpx.Response(200, 
json=json.loads(self.dag_run_collection_response.model_dump_json()))
+
+        client = make_api_client(transport=httpx.MockTransport(handle_request))
+        response = client.dag_runs.list(state="queued", limit=5, dag_id="dag1")
+        assert response == self.dag_run_collection_response
+
 
 class TestJobsOperations:
     job_response = JobResponse(

Reply via email to