Conversation
…ot found Agent-Logs-Url: https://github.com/Serverless-Devs/agentrun-sdk-python/sessions/82dd0492-f264-497a-9397-ffb5e79a1d90 Co-authored-by: OhYee <13498329+OhYee@users.noreply.github.com>
|
@copilot 修复方向正确(解决 issue #83 的 TERMINATED 残留场景),但建议对实现做一个关键调整。 问题:当前实现把 not-found 静默吞为成功if "sandbox not found" in message.lower():
return Sandbox.model_validate(
{"sandboxId": sandbox_id}, by_alias=True
)这有三个隐患:
建议:改抛
|
…ry tests Agent-Logs-Url: https://github.com/Serverless-Devs/agentrun-sdk-python/sessions/5b18eef6-655f-4a7b-be96-f7e39f852e04 Co-authored-by: OhYee <13498329+OhYee@users.noreply.github.com>
Done in eec1443. Changes made:
All 11 |
There was a problem hiding this comment.
Pull request overview
This PR makes SandboxClient.delete_sandbox() / delete_sandbox_async() raise ResourceNotExistError("Sandbox", sandbox_id) when the data plane returns a business-level failure containing “sandbox not found”, aligning it with the existing HTTP 404 behavior so callers can implement idempotent deletion reliably.
Changes:
- Detect data-plane “sandbox not found” failures (case-insensitive) and raise
ResourceNotExistErrorinstead of a genericClientError(sync + async). - Update method docstrings to clarify
ResourceNotExistErrorcovers both HTTP 404 and data-plane not-found. - Extend unit tests to cover not-found message variants and ensure unrelated failures still raise
ClientError.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
agentrun/sandbox/client.py |
Adds message-based not-found detection in delete APIs and updates docstrings/error handling. |
tests/unittests/sandbox/test_client.py |
Adds/updates unit tests for data-plane not-found vs other failure messages (sync + async). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if result.get("code") != "SUCCESS": | ||
| message = result.get("message", "") | ||
| # 数据面报告 sandbox 不存在时,与 HTTP 404 路径保持一致, | ||
| # 统一抛出 ResourceNotExistError,方便调用方幂等处理。 | ||
| # When the data plane reports sandbox not found, raise | ||
| # ResourceNotExistError for consistency with the HTTP 404 path. | ||
| # Callers can catch ResourceNotExistError to implement idempotent | ||
| # deletion (e.g. when TERMINATED instances still appear in list | ||
| # results but have already been removed from the data plane). | ||
| # Note: long-term the server should return a stable error_code | ||
| # (e.g. SandboxNotFound) so the SDK can match on that instead | ||
| # of a message string. | ||
| if "sandbox not found" in message.lower(): |
| if result.get("code") != "SUCCESS": | ||
| message = result.get("message", "") | ||
| # 数据面报告 sandbox 不存在时,与 HTTP 404 路径保持一致, | ||
| # 统一抛出 ResourceNotExistError,方便调用方幂等处理。 | ||
| # When the data plane reports sandbox not found, raise | ||
| # ResourceNotExistError for consistency with the HTTP 404 path. | ||
| # Callers can catch ResourceNotExistError to implement idempotent | ||
| # deletion (e.g. when TERMINATED instances still appear in list | ||
| # results but have already been removed from the data plane). | ||
| # Note: long-term the server should return a stable error_code | ||
| # (e.g. SandboxNotFound) so the SDK can match on that instead | ||
| # of a message string. | ||
| if "sandbox not found" in message.lower(): | ||
| raise ResourceNotExistError("Sandbox", sandbox_id) |
| if result.get("code") != "SUCCESS": | ||
| message = result.get("message", "") | ||
| # 数据面报告 sandbox 不存在时,与 HTTP 404 路径保持一致, | ||
| # 统一抛出 ResourceNotExistError,方便调用方幂等处理。 | ||
| # When the data plane reports sandbox not found, raise | ||
| # ResourceNotExistError for consistency with the HTTP 404 path. | ||
| # Callers can catch ResourceNotExistError to implement idempotent | ||
| # deletion (e.g. when TERMINATED instances still appear in list | ||
| # results but have already been removed from the data plane). | ||
| # Note: long-term the server should return a stable error_code | ||
| # (e.g. SandboxNotFound) so the SDK can match on that instead | ||
| # of a message string. | ||
| if "sandbox not found" in message.lower(): | ||
| raise ResourceNotExistError("Sandbox", sandbox_id) |
| raise ClientError( | ||
| status_code=0, | ||
| message=( | ||
| "Failed to stop sandbox:" |
| Returns: | ||
| Sandbox: 停止后的 Sandbox 对象 | ||
|
|
||
| Raises: | ||
| ResourceNotExistError: Sandbox 不存在 | ||
| ResourceNotExistError: Sandbox 不存在(包括 HTTP 404 与数据面业务层 | ||
| not-found 两种情形)。调用方可 catch 此异常实现幂等删除。 | ||
| Sandbox does not exist (covers both HTTP 404 and data-plane | ||
| business-level not-found). Callers can catch this exception | ||
| for idempotent delete logic. |
| def test_delete_sandbox_empty_message_raises_client_error( | ||
| self, mock_data_api_class, mock_control_api_class | ||
| ): | ||
| """message 为空时不应误触 not-found 逻辑,应抛 ClientError。""" | ||
| mock_data_api = MagicMock() | ||
| mock_data_api.delete_sandbox.return_value = { | ||
| "code": "FAILED", | ||
| "message": "", | ||
| } | ||
| mock_data_api_class.return_value = mock_data_api | ||
|
|
||
| client = SandboxClient() | ||
| with pytest.raises(ClientError, match="Failed to stop sandbox"): | ||
| client.delete_sandbox("sandbox-123") |
|
|
||
| Returns: | ||
| Sandbox: 停止后的 Sandbox 对象 | ||
|
|
||
| Raises: | ||
| ResourceNotExistError: Sandbox 不存在 | ||
| ResourceNotExistError: Sandbox 不存在(包括 HTTP 404 与数据面业务层 | ||
| not-found 两种情形)。调用方可 catch 此异常实现幂等删除。 | ||
| Sandbox does not exist (covers both HTTP 404 and data-plane | ||
| business-level not-found). Callers can catch this exception | ||
| for idempotent delete logic. |
| raise ClientError( | ||
| status_code=0, | ||
| message=( | ||
| "Failed to stop sandbox:" |
Control plane (
list_sandboxes) and data plane (delete_sandbox) can diverge: a TERMINATED sandbox remains visible in list results, but the data plane has already removed it, causingdelete_sandboxto raiseClientError('Failed to stop sandbox: sandbox not found')instead of a recognisable "not found" signal.Changes
agentrun/sandbox/client.py— In bothdelete_sandboxanddelete_sandbox_async, when the data plane returns a non-SUCCESS response with a message containing"sandbox not found", raiseResourceNotExistError("Sandbox", sandbox_id)instead ofClientError. This is consistent with the existing HTTP 404 path and avoids returning an incompleteSandboxobject. Updated docstrings document thatResourceNotExistErrorcovers both HTTP 404 and data-plane business-level not-found. A code comment tracks the long-term goal of switching to a stable server-sideerror_code(e.g.SandboxNotFound).tests/unittests/sandbox/test_client.py— Updated tests to assertResourceNotExistErroris raised for the data-plane not-found case; added boundary and negative tests: case-insensitive message variants, unrelated failure messages still raiseClientError, and empty message raisesClientError.Before / After