From a25446da34213383155a4bc75f9cf477a43a863b Mon Sep 17 00:00:00 2001 From: Vibe Nuage Agent Date: Fri, 5 Jun 2026 21:05:38 +0000 Subject: [PATCH] Add host parameter to API client Priority: - Env variable KOYEB_API_HOST - Parameter host - "https://app.koyeb.com" (default) This change allows users to directly specify the Koyeb API host URL when creating sandboxes, instead of relying solely on the KOYEB_API_HOST environment variable. The host parameter is now available in: - Sandbox.create(host=...) - AsyncSandbox.create(host=...) - Sandbox.get_from_id(host=...) - AsyncSandbox.get_from_id(host=...) The host parameter is passed through to get_api_clients() which already supported it. This makes the SDK more flexible for users who need to connect to different Koyeb API endpoints. Co-authored-by: nicoche --- koyeb/sandbox/sandbox.py | 35 ++++++++++++++++++++++++++--------- koyeb/sandbox/utils.py | 4 +++- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/koyeb/sandbox/sandbox.py b/koyeb/sandbox/sandbox.py index ad5ebf51..049516a6 100644 --- a/koyeb/sandbox/sandbox.py +++ b/koyeb/sandbox/sandbox.py @@ -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 @@ -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 @@ -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. @@ -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 @@ -222,6 +226,7 @@ def create( entrypoint=entrypoint, command=command, args=args, + host=host, ) if wait_ready: @@ -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 @@ -335,6 +341,7 @@ def _create_sync( api_token=api_token, sandbox_secret=sandbox_secret, poll_interval=poll_interval, + host=host, ) @classmethod @@ -342,6 +349,7 @@ def get_from_id( cls, id: str, api_token: Optional[str] = None, + host: Optional[str] = None, ) -> "Sandbox": """ Get a sandbox by service ID. @@ -349,6 +357,7 @@ def get_from_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 @@ -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 @@ -412,6 +421,7 @@ def get_from_id( name=sandbox_name, api_token=api_token, sandbox_secret=sandbox_secret, + host=host, ) _DEPLOYMENT_ERROR_STATUSES = { @@ -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) @@ -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]]: @@ -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) @@ -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) @@ -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 @@ -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) @@ -1091,6 +1101,7 @@ 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. @@ -1098,6 +1109,7 @@ async def get_from_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: AsyncSandbox: The AsyncSandbox instance @@ -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 @@ -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 @@ -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. @@ -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 @@ -1234,6 +1249,7 @@ async def create( entrypoint=entrypoint, command=command, args=args, + host=host, ), ) @@ -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 diff --git a/koyeb/sandbox/utils.py b/koyeb/sandbox/utils.py index 4d3ce6fb..6449511f 100644 --- a/koyeb/sandbox/utils.py +++ b/koyeb/sandbox/utils.py @@ -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"