Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Migration Guide

## Control-Plane API Error Handling (Breaking Change)

### What changed

Control-plane API functions (under `blaxel.core.client.api`) now **raise exceptions** on error responses (4xx/5xx) instead of returning `Union[Model, Error]`. This aligns the control-plane client with the Go SDK and the existing Python domain wrappers (`SandboxAPIError`, `DriveAPIError`, `VolumeAPIError`).

### New exception hierarchy

```
BlaxelAPIError # base class (blaxel.core.errors)
├── ControlPlaneError # control-plane 4xx/5xx (blaxel.core.client.errors)
├── SandboxAPIError # sandbox domain errors
├── DriveAPIError # drive domain errors
└── VolumeAPIError # volume domain errors
```

All domain errors now inherit from `BlaxelAPIError`, so you can catch every API error with a single `except BlaxelAPIError`.

### Before (old pattern)

```python
from blaxel.core.client.api.agents.get_agent import sync as get_agent
from blaxel.core.client.models.error import Error

result = get_agent(agent_name="my-agent", client=client)
if isinstance(result, Error):
print(f"Error {result.code}: {result.message}")
else:
print(result.metadata.name)
```

### After (new pattern)

```python
from blaxel.core.client.api.agents.get_agent import sync as get_agent
from blaxel.core import ControlPlaneError

try:
result = get_agent(agent_name="my-agent", client=client)
print(result.metadata.name)
except ControlPlaneError as e:
print(f"Error {e.status_code}: {e}")
# e.error_model contains the original Error data model if needed
```

### Opting out

If you prefer the old union-return behaviour, set `raise_on_error=False` on the client:

```python
from blaxel.core.client.client import Client

client = Client(base_url="...", raise_on_error=False)
```

With this flag, the functions continue to return `Union[Model, Error]` as before.

### Catching all Blaxel errors

```python
from blaxel.core import BlaxelAPIError

try:
# any SDK operation
...
except BlaxelAPIError as e:
print(f"Blaxel API error ({e.status_code}): {e}")
```
4 changes: 4 additions & 0 deletions src/blaxel/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .authentication import BlaxelAuth, auth, get_credentials
from .cache import find_from_cache
from .client.client import client
from .client.errors import ControlPlaneError
from .common import (
autoload,
env,
Expand All @@ -12,6 +13,7 @@
verify_webhook_signature,
)
from .drive import DriveAPIError, DriveCreateConfiguration, DriveInstance, SyncDriveInstance
from .errors import BlaxelAPIError
from .image import ImageBuildContext, ImageInstance, LocalFile
from .jobs import BlJobWrapper
from .mcp import BlaxelMcpServerTransport, websocket_client
Expand All @@ -35,6 +37,8 @@
from .volume import SyncVolumeInstance, VolumeCreateConfiguration, VolumeInstance

__all__ = [
"BlaxelAPIError",
"ControlPlaneError",
"BlAgent",
"bl_agent",
"BlaxelAuth",
Expand Down
10 changes: 10 additions & 0 deletions src/blaxel/core/client/api/agents/create_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,32 @@ def _parse_response(*, client: Client, response: httpx.Response) -> Union[Agent,
if response.status_code == 400:
response_400 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_400, response)
return response_400
if response.status_code == 401:
response_401 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_401, response)
return response_401
if response.status_code == 403:
response_403 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_403, response)
return response_403
if response.status_code == 409:
response_409 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_409, response)
return response_409
if response.status_code == 500:
response_500 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_500, response)
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
Expand Down
8 changes: 8 additions & 0 deletions src/blaxel/core/client/api/agents/delete_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,26 @@ def _parse_response(*, client: Client, response: httpx.Response) -> Union[Agent,
if response.status_code == 401:
response_401 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_401, response)
return response_401
if response.status_code == 403:
response_403 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_403, response)
return response_403
if response.status_code == 404:
response_404 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_404, response)
return response_404
if response.status_code == 500:
response_500 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_500, response)
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
Expand Down
8 changes: 8 additions & 0 deletions src/blaxel/core/client/api/agents/get_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,26 @@ def _parse_response(*, client: Client, response: httpx.Response) -> Union[Agent,
if response.status_code == 401:
response_401 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_401, response)
return response_401
if response.status_code == 403:
response_403 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_403, response)
return response_403
if response.status_code == 404:
response_404 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_404, response)
return response_404
if response.status_code == 500:
response_500 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_500, response)
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
Expand Down
6 changes: 6 additions & 0 deletions src/blaxel/core/client/api/agents/list_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,20 @@ def _parse_response(
if response.status_code == 401:
response_401 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_401, response)
return response_401
if response.status_code == 403:
response_403 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_403, response)
return response_403
if response.status_code == 500:
response_500 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_500, response)
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
Expand Down
10 changes: 10 additions & 0 deletions src/blaxel/core/client/api/agents/update_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,32 @@ def _parse_response(*, client: Client, response: httpx.Response) -> Union[Agent,
if response.status_code == 400:
response_400 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_400, response)
return response_400
if response.status_code == 401:
response_401 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_401, response)
return response_401
if response.status_code == 403:
response_403 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_403, response)
return response_403
if response.status_code == 404:
response_404 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_404, response)
return response_404
if response.status_code == 500:
response_500 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_500, response)
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
Expand Down
8 changes: 8 additions & 0 deletions src/blaxel/core/client/api/compute/delete_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,26 @@ def _parse_response(*, client: Client, response: httpx.Response) -> Union[Error,
if response.status_code == 401:
response_401 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_401, response)
return response_401
if response.status_code == 403:
response_403 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_403, response)
return response_403
if response.status_code == 404:
response_404 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_404, response)
return response_404
if response.status_code == 500:
response_500 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_500, response)
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
Expand Down
8 changes: 8 additions & 0 deletions src/blaxel/core/client/api/compute/get_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,26 @@ def _parse_response(*, client: Client, response: httpx.Response) -> Union[Error,
if response.status_code == 401:
response_401 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_401, response)
return response_401
if response.status_code == 403:
response_403 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_403, response)
return response_403
if response.status_code == 404:
response_404 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_404, response)
return response_404
if response.status_code == 500:
response_500 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_500, response)
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
Expand Down
6 changes: 6 additions & 0 deletions src/blaxel/core/client/api/compute/list_sandboxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,20 @@ def _parse_response(
if response.status_code == 401:
response_401 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_401, response)
return response_401
if response.status_code == 403:
response_403 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_403, response)
return response_403
if response.status_code == 500:
response_500 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_500, response)
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
Expand Down
10 changes: 10 additions & 0 deletions src/blaxel/core/client/api/compute/update_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,32 @@ def _parse_response(*, client: Client, response: httpx.Response) -> Union[Error,
if response.status_code == 400:
response_400 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_400, response)
return response_400
if response.status_code == 401:
response_401 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_401, response)
return response_401
if response.status_code == 403:
response_403 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_403, response)
return response_403
if response.status_code == 404:
response_404 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_404, response)
return response_404
if response.status_code == 500:
response_500 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_500, response)
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
Expand Down
6 changes: 6 additions & 0 deletions src/blaxel/core/client/api/feature_flags/test_feature_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,20 @@ def _parse_response(
if response.status_code == 401:
response_401 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_401, response)
return response_401
if response.status_code == 403:
response_403 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_403, response)
return response_403
if response.status_code == 404:
response_404 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_404, response)
return response_404
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
Expand Down
10 changes: 10 additions & 0 deletions src/blaxel/core/client/api/functions/create_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,32 @@ def _parse_response(*, client: Client, response: httpx.Response) -> Union[Error,
if response.status_code == 400:
response_400 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_400, response)
return response_400
if response.status_code == 401:
response_401 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_401, response)
return response_401
if response.status_code == 403:
response_403 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_403, response)
return response_403
if response.status_code == 409:
response_409 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_409, response)
return response_409
if response.status_code == 500:
response_500 = Error.from_dict(response.json())

if client.raise_on_error:
raise errors.ControlPlaneError(response_500, response)
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
Expand Down
Loading
Loading