From 4a4922cbfe0b66d6702ab0892e12d67717760a72 Mon Sep 17 00:00:00 2001 From: Sangun-Lee-6 Date: Tue, 5 May 2026 18:47:02 +0900 Subject: [PATCH] Fix airflowctl backfill management API methods --- airflow-ctl/RELEASE_NOTES.rst | 1 + airflow-ctl/src/airflowctl/api/operations.py | 6 +++--- airflow-ctl/tests/airflow_ctl/api/test_operations.py | 7 +++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/airflow-ctl/RELEASE_NOTES.rst b/airflow-ctl/RELEASE_NOTES.rst index dc250d0ec0466..27f78a497f2eb 100644 --- a/airflow-ctl/RELEASE_NOTES.rst +++ b/airflow-ctl/RELEASE_NOTES.rst @@ -29,6 +29,7 @@ Significant Changes Bug Fixes ^^^^^^^^^ +- Fix ``airflowctl backfill pause``, ``unpause``, and ``cancel`` API methods - Declare ``pyyaml`` as a runtime dependency so ``airflowctl`` starts without crashing on ``ModuleNotFoundError`` - Prevent path traversal via AIRFLOW_CLI_ENVIRONMENT in airflowctl (#64618) - Fix ``is_alive`` default in ``airflowctl jobs list`` to show all jobs (#65065) diff --git a/airflow-ctl/src/airflowctl/api/operations.py b/airflow-ctl/src/airflowctl/api/operations.py index f52ba055c1c72..2ed6933c573de 100644 --- a/airflow-ctl/src/airflowctl/api/operations.py +++ b/airflow-ctl/src/airflowctl/api/operations.py @@ -386,7 +386,7 @@ def list(self, dag_id: str) -> BackfillCollectionResponse | ServerResponseError: def pause(self, backfill_id: str) -> BackfillResponse | ServerResponseError: """Pause a backfill.""" try: - self.response = self.client.post(f"backfills/{backfill_id}/pause") + self.response = self.client.put(f"backfills/{backfill_id}/pause") return BackfillResponse.model_validate_json(self.response.content) except ServerResponseError as e: raise e @@ -394,7 +394,7 @@ def pause(self, backfill_id: str) -> BackfillResponse | ServerResponseError: def unpause(self, backfill_id: str) -> BackfillResponse | ServerResponseError: """Unpause a backfill.""" try: - self.response = self.client.post(f"backfills/{backfill_id}/unpause") + self.response = self.client.put(f"backfills/{backfill_id}/unpause") return BackfillResponse.model_validate_json(self.response.content) except ServerResponseError as e: raise e @@ -402,7 +402,7 @@ def unpause(self, backfill_id: str) -> BackfillResponse | ServerResponseError: def cancel(self, backfill_id: str) -> BackfillResponse | ServerResponseError: """Cancel a backfill.""" try: - self.response = self.client.post(f"backfills/{backfill_id}/cancel") + self.response = self.client.put(f"backfills/{backfill_id}/cancel") return BackfillResponse.model_validate_json(self.response.content) except ServerResponseError as e: raise e diff --git a/airflow-ctl/tests/airflow_ctl/api/test_operations.py b/airflow-ctl/tests/airflow_ctl/api/test_operations.py index c6be744845a0b..6811af8fbf7e6 100644 --- a/airflow-ctl/tests/airflow_ctl/api/test_operations.py +++ b/airflow-ctl/tests/airflow_ctl/api/test_operations.py @@ -466,6 +466,7 @@ def test_create(self): expected_body = self.backfill_body.model_dump(mode="json", exclude_none=True) def handle_request(request: httpx.Request) -> httpx.Response: + assert request.method == "POST" assert request.url.path == "/api/v2/backfills" assert request.headers.get("content-type", "").startswith("application/json") assert json.loads(request.content.decode()) == expected_body @@ -479,6 +480,7 @@ def test_create_dry_run(self): expected_body = self.backfill_body.model_dump(mode="json", exclude_none=True) def handle_request(request: httpx.Request) -> httpx.Response: + assert request.method == "POST" assert request.url.path == "/api/v2/backfills/dry_run" assert request.headers.get("content-type", "").startswith("application/json") assert json.loads(request.content.decode()) == expected_body @@ -490,6 +492,7 @@ def handle_request(request: httpx.Request) -> httpx.Response: def test_get(self): def handle_request(request: httpx.Request) -> httpx.Response: + assert request.method == "GET" assert request.url.path == f"/api/v2/backfills/{self.backfill_id}" return httpx.Response(200, json=json.loads(self.backfill_response.model_dump_json())) @@ -499,6 +502,7 @@ def handle_request(request: httpx.Request) -> httpx.Response: def test_list(self): def handle_request(request: httpx.Request) -> httpx.Response: + assert request.method == "GET" assert request.url.path == "/api/v2/backfills" return httpx.Response(200, json=json.loads(self.backfills_collection_response.model_dump_json())) @@ -508,6 +512,7 @@ def handle_request(request: httpx.Request) -> httpx.Response: def test_pause(self): def handle_request(request: httpx.Request) -> httpx.Response: + assert request.method == "PUT" assert request.url.path == f"/api/v2/backfills/{self.backfill_id}/pause" return httpx.Response(200, json=json.loads(self.backfill_response.model_dump_json())) @@ -517,6 +522,7 @@ def handle_request(request: httpx.Request) -> httpx.Response: def test_unpause(self): def handle_request(request: httpx.Request) -> httpx.Response: + assert request.method == "PUT" assert request.url.path == f"/api/v2/backfills/{self.backfill_id}/unpause" return httpx.Response(200, json=json.loads(self.backfill_response.model_dump_json())) @@ -526,6 +532,7 @@ def handle_request(request: httpx.Request) -> httpx.Response: def test_cancel(self): def handle_request(request: httpx.Request) -> httpx.Response: + assert request.method == "PUT" assert request.url.path == f"/api/v2/backfills/{self.backfill_id}/cancel" return httpx.Response(200, json=json.loads(self.backfill_response.model_dump_json()))