From 0f088acee301624e60bd00cc870cab90c2e58ed9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 21:08:10 +0000 Subject: [PATCH] fix: Send the action attempt poll id as a query Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Y1RzepycXEYA3LStfjt8cY --- seam/modules/action_attempts.py | 8 ++-- test/wait_for_action_attempt_test.py | 61 ++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/seam/modules/action_attempts.py b/seam/modules/action_attempts.py index 03ee846f..2c503e08 100644 --- a/seam/modules/action_attempts.py +++ b/seam/modules/action_attempts.py @@ -25,8 +25,8 @@ def validate_poll_options(timeout: float, polling_interval: float) -> None: def get_action_attempt(client: SeamHttpClient, action_attempt_id: str) -> ActionAttempt: - res = client.post( - "/action_attempts/get", json={"action_attempt_id": action_attempt_id} + res = client.get( + "/action_attempts/get", params={"action_attempt_id": action_attempt_id} ) return action_attempt_from_dict(res["action_attempt"]) @@ -93,8 +93,8 @@ def resolve_action_attempt( async def get_action_attempt_async( client: AsyncSeamHttpClient, action_attempt_id: str ) -> ActionAttempt: - res = await client.post( - "/action_attempts/get", json={"action_attempt_id": action_attempt_id} + res = await client.get( + "/action_attempts/get", params={"action_attempt_id": action_attempt_id} ) return action_attempt_from_dict(res["action_attempt"]) diff --git a/test/wait_for_action_attempt_test.py b/test/wait_for_action_attempt_test.py index 4888943a..ec2f55c9 100644 --- a/test/wait_for_action_attempt_test.py +++ b/test/wait_for_action_attempt_test.py @@ -296,6 +296,67 @@ def test_wait_for_action_attempt_polls_at_least_once_before_timing_out( assert time.monotonic() - start < 5 +def test_wait_for_action_attempt_polls_with_the_generated_route_shape( + recording_server, +): + success_response = { + "action_attempt": { + **PENDING_ACTION_ATTEMPT_RESPONSE["action_attempt"], + "status": "success", + "result": {}, + } + } + + with recording_server( + [(200, PENDING_ACTION_ATTEMPT_RESPONSE), (200, success_response)] + ) as (endpoint, requests): + seam = Seam.from_api_key("seam_apikey_token", endpoint=endpoint) + + action_attempt = seam.action_attempts.get( + action_attempt_id=PENDING_ACTION_ATTEMPT_ID, + wait_for_action_attempt={"timeout": 5, "polling_interval": 0.05}, + ) + + assert action_attempt.status == "success" + + # The poll goes through the same wire shape as the generated route: + # a GET with the id and _strict in the query, and no request body. + poll_request = requests[1] + assert poll_request["method"] == "GET" + assert poll_request["path"] == "/action_attempts/get" + assert f"action_attempt_id={PENDING_ACTION_ATTEMPT_ID}" in poll_request["query"] + assert "_strict=true" in poll_request["query"] + assert poll_request["body"] is None + + +def test_wait_for_action_attempt_retries_a_failed_poll(recording_server): + success_response = { + "action_attempt": { + **PENDING_ACTION_ATTEMPT_RESPONSE["action_attempt"], + "status": "success", + "result": {}, + } + } + + with recording_server( + [ + (200, PENDING_ACTION_ATTEMPT_RESPONSE), + (503, {"error": {"type": "service_unavailable", "message": "Down"}}), + (200, success_response), + ] + ) as (endpoint, requests): + seam = Seam.from_api_key("seam_apikey_token", endpoint=endpoint) + + action_attempt = seam.action_attempts.get( + action_attempt_id=PENDING_ACTION_ATTEMPT_ID, + wait_for_action_attempt={"timeout": 5, "polling_interval": 0.05}, + ) + + # A transient 503 mid-wait is retried instead of aborting the wait. + assert action_attempt.status == "success" + assert len(requests) == 3 + + async def test_wait_for_action_attempt_rejects_a_zero_polling_interval_async( recording_server, ):