287 lines
8.5 KiB
Python
287 lines
8.5 KiB
Python
"""Custom exceptions for the sandbox client.
|
|
|
|
All sandbox exceptions extend LangSmithError for unified error handling.
|
|
The exceptions are organized by error type rather than resource type,
|
|
with a resource_type attribute for specific handling when needed.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
|
|
from langsmith.utils import LangSmithError
|
|
|
|
|
|
class SandboxClientError(LangSmithError):
|
|
"""Base exception for sandbox client errors."""
|
|
|
|
pass
|
|
|
|
|
|
# =============================================================================
|
|
# Connection and Authentication Errors
|
|
# =============================================================================
|
|
|
|
|
|
class SandboxAPIError(SandboxClientError):
|
|
"""Raised when the API endpoint returns an unexpected error.
|
|
|
|
For example, this is raised for wrong URL or path.
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
class SandboxAuthenticationError(SandboxClientError):
|
|
"""Raised when authentication fails (invalid or missing API key)."""
|
|
|
|
pass
|
|
|
|
|
|
class SandboxConnectionError(SandboxClientError):
|
|
"""Raised when connection to the sandbox server fails."""
|
|
|
|
pass
|
|
|
|
|
|
class SandboxServerReloadError(SandboxConnectionError):
|
|
"""Raised when the server sends a 1001 Going Away close frame.
|
|
|
|
This indicates a server hot-reload, not a true connection failure.
|
|
The command is still running on the server.
|
|
|
|
This is a subclass of SandboxConnectionError, so the auto-reconnect
|
|
logic in CommandHandle catches it along with all other
|
|
connection errors. The distinction matters for retry strategy:
|
|
SandboxServerReloadError triggers immediate reconnect (no backoff),
|
|
while other SandboxConnectionError triggers exponential backoff.
|
|
|
|
Users typically never see this exception — it's handled internally.
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
# =============================================================================
|
|
# Resource Errors (type-based, with resource_type attribute)
|
|
# =============================================================================
|
|
|
|
|
|
class ResourceNotFoundError(SandboxClientError):
|
|
"""Raised when a resource is not found.
|
|
|
|
Attributes:
|
|
resource_type: Type of resource (sandbox, template, volume, pool, file).
|
|
"""
|
|
|
|
def __init__(self, message: str, resource_type: Optional[str] = None):
|
|
"""Initialize the error."""
|
|
super().__init__(message)
|
|
self.resource_type = resource_type
|
|
|
|
|
|
class ResourceTimeoutError(SandboxClientError):
|
|
"""Raised when an operation times out.
|
|
|
|
Attributes:
|
|
resource_type: Type of resource (sandbox, volume, pool).
|
|
last_status: The last known status before timeout (for sandboxes).
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
resource_type: Optional[str] = None,
|
|
last_status: Optional[str] = None,
|
|
):
|
|
"""Initialize the error."""
|
|
super().__init__(message)
|
|
self.resource_type = resource_type
|
|
self.last_status = last_status
|
|
|
|
def __str__(self) -> str:
|
|
"""Return string representation."""
|
|
base = super().__str__()
|
|
if self.last_status:
|
|
return f"{base} (last_status: {self.last_status})"
|
|
return base
|
|
|
|
|
|
class ResourceInUseError(SandboxClientError):
|
|
"""Raised when deleting a resource that is still in use.
|
|
|
|
Attributes:
|
|
resource_type: Type of resource (template, volume).
|
|
"""
|
|
|
|
def __init__(self, message: str, resource_type: Optional[str] = None):
|
|
"""Initialize the error."""
|
|
super().__init__(message)
|
|
self.resource_type = resource_type
|
|
|
|
|
|
class ResourceAlreadyExistsError(SandboxClientError):
|
|
"""Raised when creating a resource that already exists.
|
|
|
|
Attributes:
|
|
resource_type: Type of resource (e.g., pool).
|
|
"""
|
|
|
|
def __init__(self, message: str, resource_type: Optional[str] = None):
|
|
"""Initialize the error."""
|
|
super().__init__(message)
|
|
self.resource_type = resource_type
|
|
|
|
|
|
class ResourceNameConflictError(SandboxClientError):
|
|
"""Raised when updating a resource name to one that already exists.
|
|
|
|
Attributes:
|
|
resource_type: Type of resource (volume, template, pool, sandbox).
|
|
"""
|
|
|
|
def __init__(self, message: str, resource_type: Optional[str] = None):
|
|
"""Initialize the error."""
|
|
super().__init__(message)
|
|
self.resource_type = resource_type
|
|
|
|
|
|
# =============================================================================
|
|
# Validation and Quota Errors
|
|
# =============================================================================
|
|
|
|
|
|
class ValidationError(SandboxClientError):
|
|
"""Raised when request validation fails.
|
|
|
|
This includes:
|
|
- Resource values exceeding server-defined limits (CPU, memory, storage)
|
|
- Invalid resource units
|
|
- Invalid name formats
|
|
- Pool validation failures (e.g., template has volumes)
|
|
|
|
Attributes:
|
|
field: The field that failed validation (e.g., "cpu", "memory").
|
|
details: List of validation error details from the API.
|
|
error_type: Machine-readable error type from the API.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
field: Optional[str] = None,
|
|
details: Optional[list[dict]] = None,
|
|
error_type: Optional[str] = None,
|
|
):
|
|
"""Initialize the error."""
|
|
super().__init__(message)
|
|
self.field = field
|
|
self.details = details or []
|
|
self.error_type = error_type
|
|
|
|
|
|
class QuotaExceededError(SandboxClientError):
|
|
"""Raised when organization quota limits are exceeded.
|
|
|
|
Users should contact support@langchain.dev to increase quotas.
|
|
|
|
Attributes:
|
|
quota_type: Type of quota exceeded (e.g., "sandbox_count", "cpu").
|
|
"""
|
|
|
|
def __init__(self, message: str, quota_type: Optional[str] = None):
|
|
"""Initialize the error."""
|
|
super().__init__(message)
|
|
self.quota_type = quota_type
|
|
|
|
|
|
# =============================================================================
|
|
# Resource Creation Errors
|
|
# =============================================================================
|
|
|
|
|
|
class ResourceCreationError(SandboxClientError):
|
|
"""Raised when resource provisioning fails.
|
|
|
|
Attributes:
|
|
resource_type: Type of resource (sandbox, volume, pool).
|
|
error_type: Machine-readable error type (ImagePull, CrashLoop,
|
|
SandboxConfig, Unschedulable, VolumeProvisioning).
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
resource_type: Optional[str] = None,
|
|
error_type: Optional[str] = None,
|
|
):
|
|
"""Initialize the error."""
|
|
super().__init__(message)
|
|
self.resource_type = resource_type
|
|
self.error_type = error_type
|
|
|
|
def __str__(self) -> str:
|
|
"""Return string representation."""
|
|
if self.error_type:
|
|
return f"{super().__str__()} [{self.error_type}]"
|
|
return super().__str__()
|
|
|
|
|
|
# =============================================================================
|
|
# Sandbox Operation Errors (runtime errors during sandbox interaction)
|
|
# =============================================================================
|
|
|
|
|
|
class DataplaneNotConfiguredError(SandboxClientError):
|
|
"""Raised when dataplane_url is not available for the sandbox.
|
|
|
|
This occurs when the sandbox-router URL is not configured for the cluster.
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
class SandboxNotReadyError(SandboxClientError):
|
|
"""Raised when attempting to interact with a sandbox that is not ready."""
|
|
|
|
pass
|
|
|
|
|
|
class SandboxOperationError(SandboxClientError):
|
|
"""Raised when a sandbox operation fails (run, read, write).
|
|
|
|
Attributes:
|
|
operation: The operation that failed (command, read, write).
|
|
error_type: Machine-readable error type from the API.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
operation: Optional[str] = None,
|
|
error_type: Optional[str] = None,
|
|
):
|
|
"""Initialize the error."""
|
|
super().__init__(message)
|
|
self.operation = operation
|
|
self.error_type = error_type
|
|
|
|
def __str__(self) -> str:
|
|
"""Return string representation."""
|
|
if self.error_type:
|
|
return f"{super().__str__()} [{self.error_type}]"
|
|
return super().__str__()
|
|
|
|
|
|
class CommandTimeoutError(SandboxOperationError):
|
|
"""Raised when a command exceeds its timeout.
|
|
|
|
Attributes:
|
|
timeout: The timeout value in seconds that was exceeded.
|
|
"""
|
|
|
|
def __init__(self, message: str, timeout: Optional[int] = None):
|
|
"""Initialize the error."""
|
|
super().__init__(message, operation="command", error_type="CommandTimeout")
|
|
self.timeout = timeout
|