From b2815d7feec92148f75849850aeb98c6628af59a Mon Sep 17 00:00:00 2001 From: Gujiassh Date: Fri, 6 Mar 2026 18:15:31 +0900 Subject: [PATCH 1/2] refactor(sdk-python): replace exception name matching with type checks --- .../adapters/converter/exception_converter.py | 34 +++++++++++++------ .../test_converters_and_error_handling.py | 30 ++++++++++++++++ 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/sdks/sandbox/python/src/opensandbox/adapters/converter/exception_converter.py b/sdks/sandbox/python/src/opensandbox/adapters/converter/exception_converter.py index 88e43f7d..3c2f0273 100644 --- a/sdks/sandbox/python/src/opensandbox/adapters/converter/exception_converter.py +++ b/sdks/sandbox/python/src/opensandbox/adapters/converter/exception_converter.py @@ -30,6 +30,18 @@ import logging from typing import Any +from httpx import ( + ConnectError, + HTTPStatusError, + NetworkError, + ReadTimeout, + TimeoutException, + WriteTimeout, +) +from opensandbox.api.execd.errors import UnexpectedStatus as ExecdUnexpectedStatus +from opensandbox.api.lifecycle.errors import ( + UnexpectedStatus as LifecycleUnexpectedStatus, +) from opensandbox.exceptions import ( InvalidArgumentException, SandboxApiException, @@ -40,6 +52,15 @@ logger = logging.getLogger(__name__) +UNEXPECTED_STATUS_TYPES = (LifecycleUnexpectedStatus, ExecdUnexpectedStatus) +HTTPX_NETWORK_ERROR_TYPES = ( + ConnectError, + TimeoutException, + NetworkError, + ReadTimeout, + WriteTimeout, +) + class ExceptionConverter: """ @@ -124,24 +145,17 @@ def to_sandbox_exception(e: Exception) -> SandboxException: def _is_unexpected_status_error(e: Exception) -> bool: """Check if exception is an openapi-python-client UnexpectedStatus error.""" - return type(e).__name__ == "UnexpectedStatus" + return isinstance(e, UNEXPECTED_STATUS_TYPES) def _is_httpx_status_error(e: Exception) -> bool: """Check if exception is an httpx HTTPStatusError.""" - return type(e).__name__ == "HTTPStatusError" + return isinstance(e, HTTPStatusError) def _is_httpx_network_error(e: Exception) -> bool: """Check if exception is an httpx network-related error.""" - error_types = ( - "ConnectError", - "TimeoutException", - "NetworkError", - "ReadTimeout", - "WriteTimeout", - ) - return type(e).__name__ in error_types + return isinstance(e, HTTPX_NETWORK_ERROR_TYPES) def _convert_unexpected_status_to_api_exception(e: Exception) -> SandboxApiException: diff --git a/sdks/sandbox/python/tests/test_converters_and_error_handling.py b/sdks/sandbox/python/tests/test_converters_and_error_handling.py index c4650603..af3a8b7b 100644 --- a/sdks/sandbox/python/tests/test_converters_and_error_handling.py +++ b/sdks/sandbox/python/tests/test_converters_and_error_handling.py @@ -18,7 +18,9 @@ from datetime import datetime, timedelta import pytest +from httpx import HTTPStatusError, Request, Response +from opensandbox.api.lifecycle.errors import UnexpectedStatus from opensandbox.adapters.converter.exception_converter import ( ExceptionConverter, parse_sandbox_error, @@ -96,6 +98,34 @@ def test_exception_converter_maps_common_types() -> None: assert isinstance(se2, SandboxInternalException) +def test_exception_converter_maps_generated_unexpected_status_to_api_exception() -> ( + None +): + err = UnexpectedStatus(400, b'{"code":"X","message":"bad"}') + + converted = ExceptionConverter.to_sandbox_exception(err) + + assert isinstance(converted, SandboxApiException) + assert converted.status_code == 400 + assert converted.error is not None + assert converted.error.code == "X" + + +def test_exception_converter_maps_httpx_status_error_to_api_exception() -> None: + request = Request("GET", "https://example.test") + response = Response( + 502, request=request, content=b'{"code":"UPSTREAM","message":"gateway"}' + ) + err = HTTPStatusError("bad gateway", request=request, response=response) + + converted = ExceptionConverter.to_sandbox_exception(err) + + assert isinstance(converted, SandboxApiException) + assert converted.status_code == 502 + assert converted.error is not None + assert converted.error.code == "UPSTREAM" + + def test_execution_converter_to_api_run_command_request() -> None: from opensandbox.api.execd.types import UNSET From 59d824ef8e0738a15139873706e23ec66a8f1f59 Mon Sep 17 00:00:00 2001 From: Gujiassh Date: Tue, 10 Mar 2026 10:14:32 +0900 Subject: [PATCH 2/2] fix(sdk-python): satisfy ruff after rebasing Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus --- .../src/opensandbox/adapters/converter/exception_converter.py | 1 + sdks/sandbox/python/tests/test_converters_and_error_handling.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sdks/sandbox/python/src/opensandbox/adapters/converter/exception_converter.py b/sdks/sandbox/python/src/opensandbox/adapters/converter/exception_converter.py index 3c2f0273..0a4ac817 100644 --- a/sdks/sandbox/python/src/opensandbox/adapters/converter/exception_converter.py +++ b/sdks/sandbox/python/src/opensandbox/adapters/converter/exception_converter.py @@ -38,6 +38,7 @@ TimeoutException, WriteTimeout, ) + from opensandbox.api.execd.errors import UnexpectedStatus as ExecdUnexpectedStatus from opensandbox.api.lifecycle.errors import ( UnexpectedStatus as LifecycleUnexpectedStatus, diff --git a/sdks/sandbox/python/tests/test_converters_and_error_handling.py b/sdks/sandbox/python/tests/test_converters_and_error_handling.py index af3a8b7b..fd5be5f4 100644 --- a/sdks/sandbox/python/tests/test_converters_and_error_handling.py +++ b/sdks/sandbox/python/tests/test_converters_and_error_handling.py @@ -20,7 +20,6 @@ import pytest from httpx import HTTPStatusError, Request, Response -from opensandbox.api.lifecycle.errors import UnexpectedStatus from opensandbox.adapters.converter.exception_converter import ( ExceptionConverter, parse_sandbox_error, @@ -38,6 +37,7 @@ from opensandbox.adapters.converter.sandbox_model_converter import ( SandboxModelConverter, ) +from opensandbox.api.lifecycle.errors import UnexpectedStatus from opensandbox.exceptions import ( InvalidArgumentException, SandboxApiException,