Skip to content

Commit 74ff53c

Browse files
authored
Add option for proxying SDK requests to API and sandbox (#695)
PR that allows you to pass `proxy` (of the type ProxyTypes) when calling methods in the SDK that send requests. Also, if you pass `proxy` when creating a sandbox, all the sandbox requests (called on the Sandbox/AsyncSandbox instance) will automatically use the same proxy. The PR also contains a small bugfix—adding missing passing of limits and passing these directly instead of through transport to be able to pass the proxy parameter properly. This change should also be compatible with the code interpreter—it uses the client transport, which uses the passed proxy if there is any.
1 parent 4f6563c commit 74ff53c

8 files changed

Lines changed: 112 additions & 34 deletions

File tree

.changeset/cuddly-buckets-tie.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@e2b/python-sdk': patch
3+
---
4+
5+
Add support for passing proxy

packages/python-sdk/e2b/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
)
3232
from .connection_config import (
3333
ConnectionConfig,
34+
ProxyTypes,
3435
)
3536
from .exceptions import (
3637
SandboxException,

packages/python-sdk/e2b/api/__init__.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import json
22
import logging
3+
from typing import Optional
4+
from httpx import Limits
35
from dataclasses import dataclass
46

5-
from typing import Optional, Union
6-
from httpx import HTTPTransport, AsyncHTTPTransport
77

88
from e2b.api.client.client import AuthenticatedClient
99
from e2b.connection_config import ConnectionConfig
@@ -50,7 +50,7 @@ def __init__(
5050
config: ConnectionConfig,
5151
require_api_key: bool = True,
5252
require_access_token: bool = False,
53-
transport: Optional[Union[HTTPTransport, AsyncHTTPTransport]] = None,
53+
limits: Optional[Limits] = None,
5454
*args,
5555
**kwargs,
5656
):
@@ -97,7 +97,8 @@ def __init__(
9797
"request": [self._log_request],
9898
"response": [self._log_response],
9999
},
100-
"transport": transport,
100+
"proxy": config.proxy,
101+
"limits": limits,
101102
},
102103
headers=headers,
103104
token=token,

packages/python-sdk/e2b/connection_config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22

33
from typing import Literal, Optional, Dict
4+
from httpx._types import ProxyTypes
45

56
REQUEST_TIMEOUT: float = 30.0 # 30 seconds
67

@@ -37,12 +38,14 @@ def __init__(
3738
access_token: Optional[str] = None,
3839
request_timeout: Optional[float] = None,
3940
headers: Optional[Dict[str, str]] = None,
41+
proxy: Optional[ProxyTypes] = None,
4042
):
4143
self.domain = domain or ConnectionConfig._domain()
4244
self.debug = debug or ConnectionConfig._debug()
4345
self.api_key = api_key or ConnectionConfig._api_key()
4446
self.access_token = access_token or ConnectionConfig._access_token()
4547
self.headers = headers
48+
self.proxy = proxy
4649

4750
self.request_timeout = ConnectionConfig._get_request_timeout(
4851
REQUEST_TIMEOUT,

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

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import Dict, Optional, TypedDict, overload
55
from typing_extensions import Unpack
66

7-
from e2b.connection_config import ConnectionConfig
7+
from e2b.connection_config import ConnectionConfig, ProxyTypes
88
from e2b.envd.api import ENVD_API_HEALTH_ROUTE, ahandle_envd_api_exception
99
from e2b.exceptions import format_request_timeout_error
1010
from e2b.sandbox.main import SandboxSetup
@@ -106,7 +106,9 @@ def __init__(self, **opts: Unpack[AsyncSandboxOpts]):
106106
self._envd_api_url = f"{'http' if self.connection_config.debug else 'https'}://{self.get_host(self.envd_port)}"
107107
self._envd_version = opts["envd_version"]
108108

109-
self._transport = AsyncTransportWithLogger(limits=self._limits)
109+
self._transport = AsyncTransportWithLogger(
110+
limits=self._limits, proxy=self._connection_config.proxy
111+
)
110112
self._envd_api = httpx.AsyncClient(
111113
base_url=self.envd_api_url,
112114
transport=self._transport,
@@ -177,6 +179,7 @@ async def create(
177179
domain: Optional[str] = None,
178180
debug: Optional[bool] = None,
179181
request_timeout: Optional[float] = None,
182+
proxy: Optional[ProxyTypes] = None,
180183
):
181184
"""
182185
Create a new sandbox.
@@ -189,6 +192,7 @@ async def create(
189192
:param envs: Custom environment variables for the sandbox
190193
:param api_key: E2B API Key to use for authentication, defaults to `E2B_API_KEY` environment variable
191194
:param request_timeout: Timeout for the request in **seconds**
195+
:param proxy: Proxy to use for the request and for the **requests made to the returned sandbox**
192196
193197
:return: sandbox instance for the new sandbox
194198
@@ -199,6 +203,7 @@ async def create(
199203
domain=domain,
200204
debug=debug,
201205
request_timeout=request_timeout,
206+
proxy=proxy,
202207
)
203208

204209
if connection_config.debug:
@@ -214,6 +219,7 @@ async def create(
214219
debug=debug,
215220
request_timeout=request_timeout,
216221
env_vars=envs,
222+
proxy=proxy,
217223
)
218224
sandbox_id = response.sandbox_id
219225
envd_version = response.envd_version
@@ -231,13 +237,15 @@ async def connect(
231237
api_key: Optional[str] = None,
232238
domain: Optional[str] = None,
233239
debug: Optional[bool] = None,
240+
proxy: Optional[ProxyTypes] = None,
234241
):
235242
"""
236243
Connect to an existing sandbox.
237244
With sandbox ID you can connect to the same sandbox from different places or environments (serverless functions, etc).
238245
239246
:param sandbox_id: Sandbox ID
240247
:param api_key: E2B API Key to use for authentication, defaults to `E2B_API_KEY` environment variable
248+
:param proxy: Proxy to use for the request and for the **requests made to the returned sandbox**
241249
242250
:return: sandbox instance for the existing sandbox
243251
@@ -253,6 +261,7 @@ async def connect(
253261
api_key=api_key,
254262
domain=domain,
255263
debug=debug,
264+
proxy=proxy,
256265
)
257266

258267
return cls(
@@ -286,20 +295,25 @@ async def kill(
286295
domain: Optional[str] = None,
287296
debug: Optional[bool] = None,
288297
request_timeout: Optional[float] = None,
298+
proxy: Optional[ProxyTypes] = None,
289299
) -> bool:
290300
"""
291301
Kill the sandbox specified by sandbox ID.
292302
293303
:param sandbox_id: Sandbox ID
294304
:param api_key: E2B API Key to use for authentication, defaults to `E2B_API_KEY` environment variable
295305
:param request_timeout: Timeout for the request in **seconds**
306+
:param proxy: Proxy to use for the request
296307
297308
:return: `True` if the sandbox was killed, `False` if the sandbox was not found
298309
"""
299310
...
300311

301312
@class_method_variant("_cls_kill")
302-
async def kill(self, request_timeout: Optional[float] = None) -> bool: # type: ignore
313+
async def kill(
314+
self,
315+
request_timeout: Optional[float] = None,
316+
) -> bool: # type: ignore
303317
config_dict = self.connection_config.__dict__
304318
config_dict.pop("access_token", None)
305319
config_dict.pop("api_url", None)
@@ -309,7 +323,7 @@ async def kill(self, request_timeout: Optional[float] = None) -> bool: # type:
309323

310324
await SandboxApi._cls_kill(
311325
sandbox_id=self.sandbox_id,
312-
**self.connection_config.__dict__,
326+
**config_dict,
313327
)
314328

315329
@overload
@@ -339,6 +353,7 @@ async def set_timeout(
339353
domain: Optional[str] = None,
340354
debug: Optional[bool] = None,
341355
request_timeout: Optional[float] = None,
356+
proxy: Optional[ProxyTypes] = None,
342357
) -> None:
343358
"""
344359
Set the timeout of the specified sandbox.
@@ -350,6 +365,7 @@ async def set_timeout(
350365
:param sandbox_id: Sandbox ID
351366
:param timeout: Timeout for the sandbox in **seconds**
352367
:param request_timeout: Timeout for the request in **seconds**
368+
:param proxy: Proxy to use for the request
353369
"""
354370
...
355371

@@ -369,7 +385,7 @@ async def set_timeout( # type: ignore
369385
await SandboxApi._cls_set_timeout(
370386
sandbox_id=self.sandbox_id,
371387
timeout=timeout,
372-
**self.connection_config.__dict__,
388+
**config_dict,
373389
)
374390

375391
async def get_info( # type: ignore
@@ -391,5 +407,5 @@ async def get_info( # type: ignore
391407

392408
return await SandboxApi.get_info(
393409
sandbox_id=self.sandbox_id,
394-
**self.connection_config.__dict__,
410+
**config_dict,
395411
)

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

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import urllib.parse
2+
23
from typing import Optional, Dict, List
34
from packaging.version import Version
45

6+
57
from e2b.sandbox.sandbox_api import SandboxInfo, SandboxApiBase, SandboxQuery
68
from e2b.exceptions import TemplateException
79
from e2b.api import AsyncApiClient, SandboxCreateResponse
@@ -13,7 +15,7 @@
1315
delete_sandboxes_sandbox_id,
1416
post_sandboxes,
1517
)
16-
from e2b.connection_config import ConnectionConfig
18+
from e2b.connection_config import ConnectionConfig, ProxyTypes
1719
from e2b.api import handle_api_exception
1820

1921

@@ -27,6 +29,7 @@ async def list(
2729
debug: Optional[bool] = None,
2830
request_timeout: Optional[float] = None,
2931
headers: Optional[Dict[str, str]] = None,
32+
proxy: Optional[ProxyTypes] = None,
3033
) -> List[SandboxInfo]:
3134
"""
3235
List all running sandboxes.
@@ -37,6 +40,7 @@ async def list(
3740
:param debug: Enable debug mode, all requested are then sent to localhost
3841
:param request_timeout: Timeout for the request in **seconds**
3942
:param headers: Additional headers to send with the request
43+
:param proxy: Proxy to use for the request
4044
4145
:return: List of running sandboxes
4246
"""
@@ -46,6 +50,7 @@ async def list(
4650
debug=debug,
4751
request_timeout=request_timeout,
4852
headers=headers,
53+
proxy=proxy,
4954
)
5055

5156
# Convert filters to the format expected by the API
@@ -58,7 +63,10 @@ async def list(
5863
}
5964
metadata = urllib.parse.urlencode(quoted_metadata)
6065

61-
async with AsyncApiClient(config) as api_client:
66+
async with AsyncApiClient(
67+
config,
68+
limits=SandboxApiBase._limits,
69+
) as api_client:
6270
res = await get_sandboxes.asyncio_detailed(
6371
client=api_client,
6472
metadata=metadata,
@@ -96,6 +104,7 @@ async def get_info(
96104
debug: Optional[bool] = None,
97105
request_timeout: Optional[float] = None,
98106
headers: Optional[Dict[str, str]] = None,
107+
proxy: Optional[ProxyTypes] = None,
99108
) -> SandboxInfo:
100109
"""
101110
Get the sandbox info.
@@ -105,6 +114,7 @@ async def get_info(
105114
:param debug: Debug mode, defaults to `E2B_DEBUG` environment variable
106115
:param request_timeout: Timeout for the request in **seconds**
107116
:param headers: Additional headers to send with the request
117+
:param proxy: Proxy to use for the request
108118
109119
:return: Sandbox info
110120
"""
@@ -114,9 +124,13 @@ async def get_info(
114124
debug=debug,
115125
request_timeout=request_timeout,
116126
headers=headers,
127+
proxy=proxy,
117128
)
118129

119-
async with AsyncApiClient(config) as api_client:
130+
async with AsyncApiClient(
131+
config,
132+
limits=SandboxApiBase._limits,
133+
) as api_client:
120134
res = await get_sandboxes_sandbox_id.asyncio_detailed(
121135
sandbox_id,
122136
client=api_client,
@@ -151,20 +165,25 @@ async def _cls_kill(
151165
debug: Optional[bool] = None,
152166
request_timeout: Optional[float] = None,
153167
headers: Optional[Dict[str, str]] = None,
168+
proxy: Optional[ProxyTypes] = None,
154169
) -> bool:
155170
config = ConnectionConfig(
156171
api_key=api_key,
157172
domain=domain,
158173
debug=debug,
159174
request_timeout=request_timeout,
160175
headers=headers,
176+
proxy=proxy,
161177
)
162178

163179
if config.debug:
164180
# Skip killing the sandbox in debug mode
165181
return True
166182

167-
async with AsyncApiClient(config) as api_client:
183+
async with AsyncApiClient(
184+
config,
185+
limits=SandboxApiBase._limits,
186+
) as api_client:
168187
res = await delete_sandboxes_sandbox_id.asyncio_detailed(
169188
sandbox_id,
170189
client=api_client,
@@ -188,20 +207,25 @@ async def _cls_set_timeout(
188207
debug: Optional[bool] = None,
189208
request_timeout: Optional[float] = None,
190209
headers: Optional[Dict[str, str]] = None,
210+
proxy: Optional[ProxyTypes] = None,
191211
) -> None:
192212
config = ConnectionConfig(
193213
api_key=api_key,
194214
domain=domain,
195215
debug=debug,
196216
request_timeout=request_timeout,
197217
headers=headers,
218+
proxy=proxy,
198219
)
199220

200221
if config.debug:
201222
# Skip setting the timeout in debug mode
202223
return
203224

204-
async with AsyncApiClient(config) as api_client:
225+
async with AsyncApiClient(
226+
config,
227+
limits=SandboxApiBase._limits,
228+
) as api_client:
205229
res = await post_sandboxes_sandbox_id_timeout.asyncio_detailed(
206230
sandbox_id,
207231
client=api_client,
@@ -223,16 +247,21 @@ async def _create_sandbox(
223247
debug: Optional[bool] = None,
224248
request_timeout: Optional[float] = None,
225249
headers: Optional[Dict[str, str]] = None,
250+
proxy: Optional[ProxyTypes] = None,
226251
) -> SandboxCreateResponse:
227252
config = ConnectionConfig(
228253
api_key=api_key,
229254
domain=domain,
230255
debug=debug,
231256
request_timeout=request_timeout,
232257
headers=headers,
258+
proxy=proxy,
233259
)
234260

235-
async with AsyncApiClient(config) as api_client:
261+
async with AsyncApiClient(
262+
config,
263+
limits=SandboxApiBase._limits,
264+
) as api_client:
236265
res = await post_sandboxes.asyncio_detailed(
237266
body=NewSandbox(
238267
template_id=template,
@@ -268,7 +297,3 @@ async def _create_sandbox(
268297
),
269298
envd_version=res.parsed.envd_version,
270299
)
271-
272-
@staticmethod
273-
def _get_sandbox_id(sandbox_id: str, client_id: str) -> str:
274-
return f"{sandbox_id}-{client_id}"

0 commit comments

Comments
 (0)