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
5 changes: 5 additions & 0 deletions .changeset/strange-hornets-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@e2b/python-sdk': minor
---

Refactor connection config and parameters for all calls to API
78 changes: 77 additions & 1 deletion packages/python-sdk/e2b/connection_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import os

from typing import Literal, Optional, Dict
from typing import Literal, Optional, Dict, TypedDict
from httpx._types import ProxyTypes
from typing_extensions import Unpack

from e2b.api.metadata import package_version

Expand All @@ -11,6 +12,32 @@
KEEPALIVE_PING_HEADER = "Keepalive-Ping-Interval"


class ApiParams(TypedDict, total=False):
"""
Parameters for a request.

In the case of a sandbox, it applies to all **requests made to the returned sandbox**.
"""

request_timeout: Optional[float]
"""Timeout for the request in **seconds**, defaults to 60 seconds."""

headers: Optional[Dict[str, str]]
"""Additional headers to send with the request."""

api_key: Optional[str]
"""E2B API Key to use for authentication, defaults to `E2B_API_KEY` environment variable."""

domain: Optional[str]
"""E2B domain to use for authentication, defaults to `E2B_DOMAIN` environment variable."""

debug: Optional[bool]
"""Whether to use debug mode, defaults to `E2B_DEBUG` environment variable."""

proxy: Optional[ProxyTypes]
"""Proxy to use for the request. In case of a sandbox it applies to all **requests made to the returned sandbox**."""


class ConnectionConfig:
"""
Configuration for the connection to the API.
Expand Down Expand Up @@ -40,6 +67,7 @@ def __init__(
access_token: Optional[str] = None,
request_timeout: Optional[float] = None,
headers: Optional[Dict[str, str]] = None,
extra_sandbox_headers: Optional[Dict[str, str]] = None,
Copy link
Copy Markdown
Member

@mishushakov mishushakov Aug 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it looks weird that you have both headers and extra_sandbox_headers
what about?

default_headers = {'User-Agent': 'xxx'}

def __init__(
    headers: Dict[str, str] = {}
):
    self.headers = {**default_headers, **headers}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mishushakov Are you asking why we need two sets of headers?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

@ValentaTomas ValentaTomas Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to be sending the "api" headers (like the supabase token) to the sandbox endpoints and we also don't want to send the "sandbox" headers (like the envd access token) to the API, so to prevent mixing them on the requests made from the methods on the Sandbox instance we need two separate sets.

It is possible that it can be handled more elegantly internally, but we are mostly making sure it behaves sanely to the outside now.

proxy: Optional[ProxyTypes] = None,
):
self.domain = domain or ConnectionConfig._domain()
Expand All @@ -48,6 +76,7 @@ def __init__(
self.access_token = access_token or ConnectionConfig._access_token()
self.headers = headers or {}
self.headers["User-Agent"] = f"e2b-python-sdk/{package_version}"
self.__extra_sandbox_headers = extra_sandbox_headers or {}

self.proxy = proxy

Expand Down Expand Up @@ -82,6 +111,53 @@ def _get_request_timeout(
def get_request_timeout(self, request_timeout: Optional[float] = None):
return self._get_request_timeout(self.request_timeout, request_timeout)

def get_api_params(
self,
**opts: Unpack[ApiParams],
) -> dict:
"""
Get the parameters for the API call.

This is used to avoid passing the following attributes to the API call:
- access_token
- api_url

It also returns a copy, so the original object is not modified.

:return: Dictionary of parameters for the API call
"""
headers = opts.get("headers")
request_timeout = opts.get("request_timeout")
api_key = opts.get("api_key")
domain = opts.get("domain")
debug = opts.get("debug")
proxy = opts.get("proxy")

req_headers = self.headers.copy()
if headers is not None:
req_headers.update(headers)

return dict(
ApiParams(
api_key=api_key if api_key is not None else self.api_key,
domain=domain if domain is not None else self.domain,
debug=debug if debug is not None else self.debug,
request_timeout=self.get_request_timeout(request_timeout),
headers=req_headers,
proxy=proxy if proxy is not None else self.proxy,
)
)

@property
def sandbox_headers(self):
"""
# We need this separate as we use the same header for E2B access token to API and envd access token to sandbox.
"""
return {
**self.headers,
**self.__extra_sandbox_headers,
}


Username = Literal["root", "user"]
"""
Expand Down
49 changes: 33 additions & 16 deletions packages/python-sdk/e2b/sandbox/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import urllib.parse

from abc import ABC, abstractmethod
from typing import Optional

from e2b.sandbox.signature import get_signature
Expand All @@ -9,7 +8,7 @@
from httpx import Limits


class SandboxSetup(ABC):
class SandboxBase:
_limits = Limits(
max_keepalive_connections=40,
max_connections=40,
Expand All @@ -21,30 +20,48 @@ class SandboxSetup(ABC):
default_sandbox_timeout = 300
default_template = "base"

def __init__(
self,
sandbox_id: str,
envd_version: Optional[str],
envd_access_token: Optional[str],
sandbox_domain: Optional[str],
connection_config: ConnectionConfig,
):
self.__connection_config = connection_config
self.__sandbox_id = sandbox_id
self.__sandbox_domain = sandbox_domain or self.connection_config.domain
self.__envd_version = envd_version
self.__envd_access_token = envd_access_token
self.__envd_api_url = f"{'http' if self.connection_config.debug else 'https'}://{self.get_host(self.envd_port)}"

@property
def _envd_access_token(self) -> Optional[str]:
"""Private property to access the envd token"""
return self.__envd_access_token

@property
@abstractmethod
def connection_config(self) -> ConnectionConfig:
...
return self.__connection_config

@property
@abstractmethod
def _envd_access_token(self) -> Optional[str]:
...
def _envd_version(self) -> Optional[str]:
return self.__envd_version

@property
@abstractmethod
def envd_api_url(self) -> str:
...
def sandbox_domain(self) -> Optional[str]:
return self.__sandbox_domain

@property
@abstractmethod
def sandbox_id(self) -> str:
...
def envd_api_url(self) -> str:
return self.__envd_api_url

@property
@abstractmethod
def sandbox_domain(self) -> str:
...
def sandbox_id(self) -> str:
"""
Unique identifier of the sandbox.
"""
return self.__sandbox_id

def _file_url(
self,
Expand Down
11 changes: 0 additions & 11 deletions packages/python-sdk/e2b/sandbox/sandbox_api.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
from abc import ABC
from dataclasses import dataclass
from typing import Optional, Dict
from datetime import datetime

from httpx import Limits

from e2b.api.client.models import SandboxState


Expand Down Expand Up @@ -81,11 +78,3 @@ class SandboxMetrics:
"""Memory used in bytes."""
timestamp: datetime
"""Timestamp of the metric entry."""


class SandboxApiBase(ABC):
_limits = Limits(
max_keepalive_connections=10,
max_connections=20,
keepalive_expiry=20,
)
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def __init__(
# compressor=e2b_connect.GzipCompressor,
async_pool=pool,
json=True,
headers=connection_config.headers,
headers=connection_config.sandbox_headers,
)

async def list(
Expand Down
1 change: 1 addition & 0 deletions packages/python-sdk/e2b/sandbox_async/commands/pty.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def __init__(
# compressor=e2b_connect.GzipCompressor,
async_pool=pool,
json=True,
headers=connection_config.sandbox_headers,
)

async def kill(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def __init__(
# compressor=e2b_connect.GzipCompressor,
async_pool=pool,
json=True,
headers=connection_config.headers,
headers=connection_config.sandbox_headers,
)

@overload
Expand Down
Loading