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(