Skip to content

Commit da7c60d

Browse files
authored
Simplify connection config handling in Python SDK (#849)
Several improvements and refactors to the Python SDK's sandbox connection and configuration logic. The main focus is on making header management more flexible, improving API parameter handling, and simplifying class structures for better maintainability and usability. Also unifies doc strings
1 parent 22df213 commit da7c60d

15 files changed

Lines changed: 378 additions & 653 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@e2b/python-sdk': minor
3+
---
4+
5+
Refactor connection config and parameters for all calls to API

packages/python-sdk/e2b/connection_config.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import os
22

3-
from typing import Literal, Optional, Dict
3+
from typing import Literal, Optional, Dict, TypedDict
44
from httpx._types import ProxyTypes
5+
from typing_extensions import Unpack
56

67
from e2b.api.metadata import package_version
78

@@ -11,6 +12,32 @@
1112
KEEPALIVE_PING_HEADER = "Keepalive-Ping-Interval"
1213

1314

15+
class ApiParams(TypedDict, total=False):
16+
"""
17+
Parameters for a request.
18+
19+
In the case of a sandbox, it applies to all **requests made to the returned sandbox**.
20+
"""
21+
22+
request_timeout: Optional[float]
23+
"""Timeout for the request in **seconds**, defaults to 60 seconds."""
24+
25+
headers: Optional[Dict[str, str]]
26+
"""Additional headers to send with the request."""
27+
28+
api_key: Optional[str]
29+
"""E2B API Key to use for authentication, defaults to `E2B_API_KEY` environment variable."""
30+
31+
domain: Optional[str]
32+
"""E2B domain to use for authentication, defaults to `E2B_DOMAIN` environment variable."""
33+
34+
debug: Optional[bool]
35+
"""Whether to use debug mode, defaults to `E2B_DEBUG` environment variable."""
36+
37+
proxy: Optional[ProxyTypes]
38+
"""Proxy to use for the request. In case of a sandbox it applies to all **requests made to the returned sandbox**."""
39+
40+
1441
class ConnectionConfig:
1542
"""
1643
Configuration for the connection to the API.
@@ -40,6 +67,7 @@ def __init__(
4067
access_token: Optional[str] = None,
4168
request_timeout: Optional[float] = None,
4269
headers: Optional[Dict[str, str]] = None,
70+
extra_sandbox_headers: Optional[Dict[str, str]] = None,
4371
proxy: Optional[ProxyTypes] = None,
4472
):
4573
self.domain = domain or ConnectionConfig._domain()
@@ -48,6 +76,7 @@ def __init__(
4876
self.access_token = access_token or ConnectionConfig._access_token()
4977
self.headers = headers or {}
5078
self.headers["User-Agent"] = f"e2b-python-sdk/{package_version}"
79+
self.__extra_sandbox_headers = extra_sandbox_headers or {}
5180

5281
self.proxy = proxy
5382

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

114+
def get_api_params(
115+
self,
116+
**opts: Unpack[ApiParams],
117+
) -> dict:
118+
"""
119+
Get the parameters for the API call.
120+
121+
This is used to avoid passing the following attributes to the API call:
122+
- access_token
123+
- api_url
124+
125+
It also returns a copy, so the original object is not modified.
126+
127+
:return: Dictionary of parameters for the API call
128+
"""
129+
headers = opts.get("headers")
130+
request_timeout = opts.get("request_timeout")
131+
api_key = opts.get("api_key")
132+
domain = opts.get("domain")
133+
debug = opts.get("debug")
134+
proxy = opts.get("proxy")
135+
136+
req_headers = self.headers.copy()
137+
if headers is not None:
138+
req_headers.update(headers)
139+
140+
return dict(
141+
ApiParams(
142+
api_key=api_key if api_key is not None else self.api_key,
143+
domain=domain if domain is not None else self.domain,
144+
debug=debug if debug is not None else self.debug,
145+
request_timeout=self.get_request_timeout(request_timeout),
146+
headers=req_headers,
147+
proxy=proxy if proxy is not None else self.proxy,
148+
)
149+
)
150+
151+
@property
152+
def sandbox_headers(self):
153+
"""
154+
# We need this separate as we use the same header for E2B access token to API and envd access token to sandbox.
155+
"""
156+
return {
157+
**self.headers,
158+
**self.__extra_sandbox_headers,
159+
}
160+
85161

86162
Username = Literal["root", "user"]
87163
"""

packages/python-sdk/e2b/sandbox/main.py

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import urllib.parse
22

3-
from abc import ABC, abstractmethod
43
from typing import Optional
54

65
from e2b.sandbox.signature import get_signature
@@ -9,7 +8,7 @@
98
from httpx import Limits
109

1110

12-
class SandboxSetup(ABC):
11+
class SandboxBase:
1312
_limits = Limits(
1413
max_keepalive_connections=40,
1514
max_connections=40,
@@ -21,30 +20,48 @@ class SandboxSetup(ABC):
2120
default_sandbox_timeout = 300
2221
default_template = "base"
2322

23+
def __init__(
24+
self,
25+
sandbox_id: str,
26+
envd_version: Optional[str],
27+
envd_access_token: Optional[str],
28+
sandbox_domain: Optional[str],
29+
connection_config: ConnectionConfig,
30+
):
31+
self.__connection_config = connection_config
32+
self.__sandbox_id = sandbox_id
33+
self.__sandbox_domain = sandbox_domain or self.connection_config.domain
34+
self.__envd_version = envd_version
35+
self.__envd_access_token = envd_access_token
36+
self.__envd_api_url = f"{'http' if self.connection_config.debug else 'https'}://{self.get_host(self.envd_port)}"
37+
38+
@property
39+
def _envd_access_token(self) -> Optional[str]:
40+
"""Private property to access the envd token"""
41+
return self.__envd_access_token
42+
2443
@property
25-
@abstractmethod
2644
def connection_config(self) -> ConnectionConfig:
27-
...
45+
return self.__connection_config
2846

2947
@property
30-
@abstractmethod
31-
def _envd_access_token(self) -> Optional[str]:
32-
...
48+
def _envd_version(self) -> Optional[str]:
49+
return self.__envd_version
3350

3451
@property
35-
@abstractmethod
36-
def envd_api_url(self) -> str:
37-
...
52+
def sandbox_domain(self) -> Optional[str]:
53+
return self.__sandbox_domain
3854

3955
@property
40-
@abstractmethod
41-
def sandbox_id(self) -> str:
42-
...
56+
def envd_api_url(self) -> str:
57+
return self.__envd_api_url
4358

4459
@property
45-
@abstractmethod
46-
def sandbox_domain(self) -> str:
47-
...
60+
def sandbox_id(self) -> str:
61+
"""
62+
Unique identifier of the sandbox.
63+
"""
64+
return self.__sandbox_id
4865

4966
def _file_url(
5067
self,

packages/python-sdk/e2b/sandbox/sandbox_api.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
from abc import ABC
21
from dataclasses import dataclass
32
from typing import Optional, Dict
43
from datetime import datetime
54

6-
from httpx import Limits
7-
85
from e2b.api.client.models import SandboxState
96

107

@@ -81,11 +78,3 @@ class SandboxMetrics:
8178
"""Memory used in bytes."""
8279
timestamp: datetime
8380
"""Timestamp of the metric entry."""
84-
85-
86-
class SandboxApiBase(ABC):
87-
_limits = Limits(
88-
max_keepalive_connections=10,
89-
max_connections=20,
90-
keepalive_expiry=20,
91-
)

packages/python-sdk/e2b/sandbox_async/commands/command.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def __init__(
3535
# compressor=e2b_connect.GzipCompressor,
3636
async_pool=pool,
3737
json=True,
38-
headers=connection_config.headers,
38+
headers=connection_config.sandbox_headers,
3939
)
4040

4141
async def list(

packages/python-sdk/e2b/sandbox_async/commands/pty.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def __init__(
3939
# compressor=e2b_connect.GzipCompressor,
4040
async_pool=pool,
4141
json=True,
42+
headers=connection_config.sandbox_headers,
4243
)
4344

4445
async def kill(

packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def __init__(
5151
# compressor=e2b_connect.GzipCompressor,
5252
async_pool=pool,
5353
json=True,
54-
headers=connection_config.headers,
54+
headers=connection_config.sandbox_headers,
5555
)
5656

5757
@overload

0 commit comments

Comments
 (0)