Skip to content
Merged
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
35 changes: 26 additions & 9 deletions koyeb/sandbox/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def __init__(
api_token: Optional[str] = None,
sandbox_secret: Optional[str] = None,
poll_interval: float = DEFAULT_POLL_INTERVAL,
host: Optional[str] = None,
):
self.sandbox_id = sandbox_id
self.app_id = app_id
Expand All @@ -95,6 +96,7 @@ def __init__(
self.api_token = api_token
self.sandbox_secret = sandbox_secret
self.poll_interval = poll_interval
self.host = host
self._created_at = time.time()
self._sandbox_url: Optional[Tuple[str, Optional[str]]] = None
self._domain: Optional[str] = None
Expand Down Expand Up @@ -133,6 +135,7 @@ def create(
entrypoint: Optional[List[str]] = None,
command: Optional[str] = None,
args: Optional[List[str]] = None,
host: Optional[str] = None,
) -> Sandbox:
"""
Create a new sandbox instance.
Expand Down Expand Up @@ -173,6 +176,7 @@ def create(
poll_interval: Time between health checks in seconds when wait_ready is True (default: 0.5)
entrypoint: Override the default entrypoint of the Docker image (e.g., ["/bin/sh", "-c"])
command: Override the default command of the Docker image (e.g., "python app.py")
host: Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var (defaults to https://app.koyeb.com)

Returns:
Sandbox: A new Sandbox instance
Expand Down Expand Up @@ -222,6 +226,7 @@ def create(
entrypoint=entrypoint,
command=command,
args=args,
host=host,
)

if wait_ready:
Expand Down Expand Up @@ -261,13 +266,14 @@ def _create_sync(
entrypoint: Optional[List[str]] = None,
command: Optional[str] = None,
args: Optional[List[str]] = None,
host: Optional[str] = None,
) -> Sandbox:
"""
Synchronous creation method that returns creation parameters.
Subclasses can override to return their own type.
"""

clients = get_api_clients(api_token)
clients = get_api_clients(api_token, host)
apps_api = clients.apps
services_api = clients.services

Expand Down Expand Up @@ -335,20 +341,23 @@ def _create_sync(
api_token=api_token,
sandbox_secret=sandbox_secret,
poll_interval=poll_interval,
host=host,
)

@classmethod
def get_from_id(
cls,
id: str,
api_token: Optional[str] = None,
host: Optional[str] = None,
) -> "Sandbox":
"""
Get a sandbox by service ID.

Args:
id: Service ID of the sandbox
api_token: Koyeb API token (if None, will try to get from KOYEB_API_TOKEN env var)
host: Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var (defaults to https://app.koyeb.com)

Returns:
Sandbox: The Sandbox instance
Expand All @@ -367,7 +376,7 @@ def get_from_id(
if not id:
raise ValueError("id is required")

clients = get_api_clients(api_token)
clients = get_api_clients(api_token, host)
services_api = clients.services
deployments_api = clients.deployments

Expand Down Expand Up @@ -412,6 +421,7 @@ def get_from_id(
name=sandbox_name,
api_token=api_token,
sandbox_secret=sandbox_secret,
host=host,
)

_DEPLOYMENT_ERROR_STATUSES = {
Expand All @@ -430,7 +440,7 @@ def _is_deployment_healthy(self) -> bool:
SandboxDeploymentError: If the deployment has reached a terminal error state
"""
try:
clients = get_api_clients(self.api_token)
clients = get_api_clients(self.api_token, self.host)
services_api = clients.services
deployments_api = clients.deployments
service_response = services_api.get_service(self.service_id)
Expand Down Expand Up @@ -532,7 +542,7 @@ def wait_tcp_proxy_ready(

def delete(self) -> None:
"""Delete the sandbox instance."""
clients = get_api_clients(self.api_token)
clients = get_api_clients(self.api_token, self.host)
clients.apps.delete_app(self.app_id)

def _get_url_and_header_from_metadata(self) -> Optional[Tuple[str, str]]:
Expand All @@ -544,7 +554,7 @@ def _get_url_and_header_from_metadata(self) -> Optional[Tuple[str, str]]:

from .utils import get_api_clients

clients = get_api_clients(self.api_token)
clients = get_api_clients(self.api_token, self.host)
services_api = clients.services
deployments_api = clients.deployments
service_response = services_api.get_service(self.service_id)
Expand Down Expand Up @@ -572,7 +582,7 @@ def _get_domain(self) -> Optional[str]:

from .utils import get_api_clients

clients = get_api_clients(self.api_token)
clients = get_api_clients(self.api_token, self.host)
apps_api = clients.apps
services_api = clients.services
service_response = services_api.get_service(self.service_id)
Expand Down Expand Up @@ -643,7 +653,7 @@ def get_tcp_proxy_info(self) -> Optional[tuple[str, int]]:

from .utils import get_api_clients

clients = get_api_clients(self.api_token)
clients = get_api_clients(self.api_token, self.host)
services_api = clients.services
service_response = services_api.get_service(self.service_id)
service = service_response.service
Expand Down Expand Up @@ -1017,7 +1027,7 @@ def update_lifecycle(
>>> sandbox.update_life_cycle(delete_after_delay=600, delete_after_inactivity=300)
"""
try:
clients = get_api_clients(self.api_token)
clients = get_api_clients(self.api_token, self.host)
services_api = clients.services
deployments_api = clients.deployments
service_response = services_api.get_service(self.service_id)
Expand Down Expand Up @@ -1091,13 +1101,15 @@ async def get_from_id(
cls,
id: str,
api_token: Optional[str] = None,
host: Optional[str] = None,
) -> "AsyncSandbox":
"""
Get a sandbox by service ID asynchronously.

Args:
id: Service ID of the sandbox
api_token: Koyeb API token (if None, will try to get from KOYEB_API_TOKEN env var)
host: Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var (defaults to https://app.koyeb.com)

Returns:
AsyncSandbox: The AsyncSandbox instance
Expand All @@ -1107,7 +1119,7 @@ async def get_from_id(
SandboxError: If sandbox is not found or retrieval fails
"""
sync_sandbox = await run_sync_in_executor(
Sandbox.get_from_id, id=id, api_token=api_token
Sandbox.get_from_id, id=id, api_token=api_token, host=host
)

# Convert Sandbox instance to AsyncSandbox instance
Expand All @@ -1118,6 +1130,7 @@ async def get_from_id(
name=sync_sandbox.name,
api_token=sync_sandbox.api_token,
sandbox_secret=sync_sandbox.sandbox_secret,
host=sync_sandbox.host,
)
async_sandbox._created_at = sync_sandbox._created_at

Expand Down Expand Up @@ -1150,6 +1163,7 @@ async def create(
entrypoint: Optional[List[str]] = None,
command: Optional[str] = None,
args: Optional[List[str]] = None,
host: Optional[str] = None,
) -> AsyncSandbox:
"""
Create a new sandbox instance with async support.
Expand Down Expand Up @@ -1192,6 +1206,7 @@ async def create(
poll_interval: Time between health checks in seconds when wait_ready is True (default: 0.5)
entrypoint: Override the default entrypoint of the Docker image (e.g., ["/bin/sh", "-c"])
command: Override the default command of the Docker image (e.g., "python app.py")
host: Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var (defaults to https://app.koyeb.com)

Returns:
AsyncSandbox: A new AsyncSandbox instance
Expand Down Expand Up @@ -1234,6 +1249,7 @@ async def create(
entrypoint=entrypoint,
command=command,
args=args,
host=host,
),
)

Expand All @@ -1246,6 +1262,7 @@ async def create(
api_token=sync_result.api_token,
sandbox_secret=sync_result.sandbox_secret,
poll_interval=poll_interval,
host=sync_result.host,
)
sandbox._created_at = sync_result._created_at

Expand Down
4 changes: 3 additions & 1 deletion koyeb/sandbox/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ def get_api_clients(
"API token is required. Set KOYEB_API_TOKEN environment variable or pass api_token parameter"
)

api_host = host or os.getenv("KOYEB_API_HOST", "https://app.koyeb.com")
api_host = os.getenv("KOYEB_API_HOST", host)
if not api_host:
api_host = "https://app.koyeb.com"
configuration = Configuration(host=api_host)
configuration.api_key["Bearer"] = token
configuration.api_key_prefix["Bearer"] = "Bearer"
Expand Down
Loading