|
2 | 2 | import logging |
3 | 3 | import httpx |
4 | 4 |
|
5 | | -from typing import Dict, Optional, TypedDict, overload, List |
| 5 | +from typing import ( |
| 6 | + Dict, |
| 7 | + Optional, |
| 8 | + TypedDict, |
| 9 | + overload, |
| 10 | + List, |
| 11 | + Type, |
| 12 | +) |
6 | 13 |
|
7 | 14 | from packaging.version import Version |
8 | 15 | from typing_extensions import Unpack |
9 | 16 |
|
| 17 | +from e2b.api import AsyncApiClient, handle_api_exception |
| 18 | +from e2b.api.client.api.sandboxes import ( |
| 19 | + post_sandboxes_sandbox_id_pause, |
| 20 | + post_sandboxes_sandbox_id_resume, |
| 21 | +) |
| 22 | +from e2b.api.client.models import ResumedSandbox |
10 | 23 | from e2b.api.client.types import Unset |
11 | 24 | from e2b.connection_config import ConnectionConfig, ApiParams |
12 | 25 | from e2b.envd.api import ENVD_API_HEALTH_ROUTE, ahandle_envd_api_exception |
13 | | -from e2b.exceptions import format_request_timeout_error, SandboxException |
14 | | -from e2b.sandbox.sandbox_api import SandboxMetrics |
| 26 | +from e2b.exceptions import ( |
| 27 | + format_request_timeout_error, |
| 28 | + SandboxException, |
| 29 | + NotFoundException, |
| 30 | +) |
| 31 | +from e2b.sandbox.main import SandboxBase |
| 32 | +from e2b.sandbox.sandbox_api import SandboxMetrics, SandboxQueryBeta |
15 | 33 | from e2b.sandbox.utils import class_method_variant |
16 | 34 | from e2b.sandbox_async.filesystem.filesystem import Filesystem |
17 | 35 | from e2b.sandbox_async.commands.command import Commands |
18 | 36 | from e2b.sandbox_async.commands.pty import Pty |
19 | 37 | from e2b.sandbox_async.sandbox_api import SandboxApi, SandboxInfo |
| 38 | +from e2b.sandbox_async.sandbox_beta import AsyncSandboxPaginator |
20 | 39 |
|
21 | 40 | logger = logging.getLogger(__name__) |
22 | 41 |
|
@@ -45,7 +64,210 @@ class AsyncSandboxOpts(TypedDict): |
45 | 64 | connection_config: ConnectionConfig |
46 | 65 |
|
47 | 66 |
|
48 | | -class AsyncSandbox(SandboxApi): |
| 67 | +class AsyncBeta: |
| 68 | + def __init__(self, sandbox: "AsyncSandbox"): |
| 69 | + self._sandbox = sandbox |
| 70 | + |
| 71 | + @staticmethod |
| 72 | + def list( |
| 73 | + query: Optional[SandboxQueryBeta] = None, |
| 74 | + limit: Optional[int] = None, |
| 75 | + next_token: Optional[str] = None, |
| 76 | + **opts: Unpack[ApiParams], |
| 77 | + ) -> AsyncSandboxPaginator: |
| 78 | + """ |
| 79 | + List all running sandboxes. |
| 80 | +
|
| 81 | + :param query: Filter the list of sandboxes by metadata or state, e.g. `SandboxListQuery(metadata={"key": "value"})` or `SandboxListQuery(state=[SandboxState.RUNNING])` |
| 82 | + :param limit: Maximum number of sandboxes to return |
| 83 | + :param next_token: Token for pagination |
| 84 | +
|
| 85 | + :return: List of running sandboxes |
| 86 | + """ |
| 87 | + return AsyncSandboxPaginator( |
| 88 | + query=query, |
| 89 | + limit=limit, |
| 90 | + next_token=next_token, |
| 91 | + **opts, |
| 92 | + ) |
| 93 | + |
| 94 | + @overload |
| 95 | + async def pause( |
| 96 | + self, |
| 97 | + **opts: Unpack[ApiParams], |
| 98 | + ) -> str: |
| 99 | + """ |
| 100 | + Pause the sandbox. |
| 101 | +
|
| 102 | + :return: Sandbox ID that can be used to resume the sandbox |
| 103 | + """ |
| 104 | + ... |
| 105 | + |
| 106 | + @overload |
| 107 | + @staticmethod |
| 108 | + async def pause( |
| 109 | + sandbox_id: str, |
| 110 | + **opts: Unpack[ApiParams], |
| 111 | + ) -> str: |
| 112 | + """ |
| 113 | + Pause the sandbox specified by sandbox ID. |
| 114 | +
|
| 115 | + :param sandbox_id: Sandbox ID |
| 116 | +
|
| 117 | + :return: Sandbox ID that can be used to resume the sandbox |
| 118 | + """ |
| 119 | + ... |
| 120 | + |
| 121 | + @class_method_variant("_cls_pause") |
| 122 | + async def pause( |
| 123 | + self, |
| 124 | + **opts: Unpack[ApiParams], |
| 125 | + ) -> str: |
| 126 | + """ |
| 127 | + Pause the sandbox. |
| 128 | +
|
| 129 | + :param request_timeout: Timeout for the request in **seconds** |
| 130 | +
|
| 131 | + :return: Sandbox ID that can be used to resume the sandbox |
| 132 | + """ |
| 133 | + |
| 134 | + await self._cls_pause( |
| 135 | + sandbox_id=self._sandbox.sandbox_id, |
| 136 | + **opts, |
| 137 | + ) |
| 138 | + |
| 139 | + return self._sandbox.sandbox_id |
| 140 | + |
| 141 | + @classmethod |
| 142 | + async def _cls_pause( |
| 143 | + cls, |
| 144 | + sandbox_id: str, |
| 145 | + **opts: Unpack[ApiParams], |
| 146 | + ) -> bool: |
| 147 | + config = ConnectionConfig(**opts) |
| 148 | + |
| 149 | + async with AsyncApiClient( |
| 150 | + config, |
| 151 | + limits=SandboxBase._limits, |
| 152 | + ) as api_client: |
| 153 | + res = await post_sandboxes_sandbox_id_pause.asyncio_detailed( |
| 154 | + sandbox_id, |
| 155 | + client=api_client, |
| 156 | + ) |
| 157 | + |
| 158 | + if res.status_code == 404: |
| 159 | + raise NotFoundException(f"Sandbox {sandbox_id} not found") |
| 160 | + |
| 161 | + if res.status_code == 409: |
| 162 | + return False |
| 163 | + |
| 164 | + if res.status_code >= 300: |
| 165 | + raise handle_api_exception(res) |
| 166 | + |
| 167 | + return True |
| 168 | + |
| 169 | + @overload |
| 170 | + async def resume( |
| 171 | + self, |
| 172 | + timeout: Optional[int] = None, |
| 173 | + **opts: Unpack[ApiParams], |
| 174 | + ) -> "AsyncSandbox": |
| 175 | + """ |
| 176 | + Resume the sandbox. |
| 177 | +
|
| 178 | + :return: A running sandbox instance |
| 179 | + """ |
| 180 | + ... |
| 181 | + |
| 182 | + @overload |
| 183 | + @staticmethod |
| 184 | + async def resume( |
| 185 | + sandbox_id: str, |
| 186 | + timeout: Optional[int] = None, |
| 187 | + **opts: Unpack[ApiParams], |
| 188 | + ) -> "AsyncSandbox": |
| 189 | + """ |
| 190 | + Resume the sandbox. |
| 191 | +
|
| 192 | + :param sandbox_id: Sandbox ID |
| 193 | + :param timeout: Timeout for the sandbox in **seconds** |
| 194 | +
|
| 195 | + :return: A running sandbox instance |
| 196 | + """ |
| 197 | + ... |
| 198 | + |
| 199 | + @class_method_variant("_cls_resume") |
| 200 | + async def resume( |
| 201 | + self, |
| 202 | + timeout: Optional[int] = None, |
| 203 | + **opts: Unpack[ApiParams], |
| 204 | + ) -> "AsyncSandbox": |
| 205 | + """ |
| 206 | + Resume the sandbox. |
| 207 | +
|
| 208 | + The **default sandbox timeout of 300 seconds** will be used for the resumed sandbox. |
| 209 | + If you pass a custom timeout via the `timeout` parameter, it will be used instead. |
| 210 | +
|
| 211 | + :param sandbox_id: Sandbox ID |
| 212 | + :param timeout: Timeout for the sandbox in **seconds** |
| 213 | +
|
| 214 | + :return: A running sandbox instance |
| 215 | + """ |
| 216 | + |
| 217 | + await self._cls_resume( |
| 218 | + sandbox_id=self._sandbox.sandbox_id, |
| 219 | + timeout=timeout, |
| 220 | + **opts, |
| 221 | + ) |
| 222 | + |
| 223 | + return await self._sandbox.connect( |
| 224 | + sandbox_id=self._sandbox.sandbox_id, |
| 225 | + **opts, |
| 226 | + ) |
| 227 | + |
| 228 | + @classmethod |
| 229 | + async def _cls_resume( |
| 230 | + cls, |
| 231 | + sandbox_id: str, |
| 232 | + timeout: Optional[int] = None, |
| 233 | + **opts: Unpack[ApiParams], |
| 234 | + ) -> bool: |
| 235 | + timeout = timeout or SandboxBase.default_sandbox_timeout |
| 236 | + |
| 237 | + config = ConnectionConfig(**opts) |
| 238 | + |
| 239 | + async with AsyncApiClient( |
| 240 | + config, |
| 241 | + limits=SandboxBase._limits, |
| 242 | + ) as api_client: |
| 243 | + res = await post_sandboxes_sandbox_id_resume.asyncio_detailed( |
| 244 | + sandbox_id, |
| 245 | + client=api_client, |
| 246 | + body=ResumedSandbox(timeout=timeout), |
| 247 | + ) |
| 248 | + |
| 249 | + if res.status_code == 404: |
| 250 | + raise NotFoundException(f"Paused sandbox {sandbox_id} not found") |
| 251 | + |
| 252 | + if res.status_code == 409: |
| 253 | + return False |
| 254 | + |
| 255 | + if res.status_code >= 300: |
| 256 | + raise handle_api_exception(res) |
| 257 | + |
| 258 | + return True |
| 259 | + |
| 260 | + |
| 261 | +class _AsyncSandboxMeta(type): |
| 262 | + """Metaclass for AsyncSandbox to provide class-level beta access.""" |
| 263 | + |
| 264 | + @property |
| 265 | + def beta(cls) -> Type[AsyncBeta]: |
| 266 | + """Access to beta features at class level.""" |
| 267 | + return AsyncBeta |
| 268 | + |
| 269 | + |
| 270 | +class AsyncSandbox(SandboxApi, metaclass=_AsyncSandboxMeta): |
49 | 271 | """ |
50 | 272 | E2B cloud sandbox is a secure and isolated cloud environment. |
51 | 273 |
|
@@ -89,6 +311,13 @@ def pty(self) -> Pty: |
89 | 311 | """ |
90 | 312 | return self._pty |
91 | 313 |
|
| 314 | + @property |
| 315 | + def beta(self) -> AsyncBeta: |
| 316 | + """ |
| 317 | + Module for beta features. |
| 318 | + """ |
| 319 | + return AsyncBeta(self) |
| 320 | + |
92 | 321 | def __init__(self, **opts: Unpack[AsyncSandboxOpts]): |
93 | 322 | """ |
94 | 323 | Use `AsyncSandbox.create()` to create a new sandbox instead. |
|
0 commit comments