moru 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. moru/__init__.py +174 -0
  2. moru/api/__init__.py +164 -0
  3. moru/api/client/__init__.py +8 -0
  4. moru/api/client/api/__init__.py +1 -0
  5. moru/api/client/api/sandboxes/__init__.py +1 -0
  6. moru/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +161 -0
  7. moru/api/client/api/sandboxes/get_sandboxes.py +176 -0
  8. moru/api/client/api/sandboxes/get_sandboxes_metrics.py +173 -0
  9. moru/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +163 -0
  10. moru/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +199 -0
  11. moru/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +212 -0
  12. moru/api/client/api/sandboxes/get_v2_sandboxes.py +230 -0
  13. moru/api/client/api/sandboxes/post_sandboxes.py +172 -0
  14. moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
  15. moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +165 -0
  16. moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +181 -0
  17. moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +189 -0
  18. moru/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +193 -0
  19. moru/api/client/api/templates/__init__.py +1 -0
  20. moru/api/client/api/templates/delete_templates_template_id.py +157 -0
  21. moru/api/client/api/templates/get_templates.py +172 -0
  22. moru/api/client/api/templates/get_templates_template_id.py +195 -0
  23. moru/api/client/api/templates/get_templates_template_id_builds_build_id_status.py +217 -0
  24. moru/api/client/api/templates/get_templates_template_id_files_hash.py +180 -0
  25. moru/api/client/api/templates/patch_templates_template_id.py +183 -0
  26. moru/api/client/api/templates/post_templates.py +172 -0
  27. moru/api/client/api/templates/post_templates_template_id.py +181 -0
  28. moru/api/client/api/templates/post_templates_template_id_builds_build_id.py +170 -0
  29. moru/api/client/api/templates/post_v2_templates.py +172 -0
  30. moru/api/client/api/templates/post_v3_templates.py +172 -0
  31. moru/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py +192 -0
  32. moru/api/client/client.py +286 -0
  33. moru/api/client/errors.py +16 -0
  34. moru/api/client/models/__init__.py +123 -0
  35. moru/api/client/models/aws_registry.py +85 -0
  36. moru/api/client/models/aws_registry_type.py +8 -0
  37. moru/api/client/models/build_log_entry.py +89 -0
  38. moru/api/client/models/build_status_reason.py +95 -0
  39. moru/api/client/models/connect_sandbox.py +59 -0
  40. moru/api/client/models/created_access_token.py +100 -0
  41. moru/api/client/models/created_team_api_key.py +166 -0
  42. moru/api/client/models/disk_metrics.py +91 -0
  43. moru/api/client/models/error.py +67 -0
  44. moru/api/client/models/gcp_registry.py +69 -0
  45. moru/api/client/models/gcp_registry_type.py +8 -0
  46. moru/api/client/models/general_registry.py +77 -0
  47. moru/api/client/models/general_registry_type.py +8 -0
  48. moru/api/client/models/identifier_masking_details.py +83 -0
  49. moru/api/client/models/listed_sandbox.py +154 -0
  50. moru/api/client/models/log_level.py +11 -0
  51. moru/api/client/models/max_team_metric.py +78 -0
  52. moru/api/client/models/mcp_type_0.py +44 -0
  53. moru/api/client/models/new_access_token.py +59 -0
  54. moru/api/client/models/new_sandbox.py +172 -0
  55. moru/api/client/models/new_team_api_key.py +59 -0
  56. moru/api/client/models/node.py +155 -0
  57. moru/api/client/models/node_detail.py +165 -0
  58. moru/api/client/models/node_metrics.py +122 -0
  59. moru/api/client/models/node_status.py +11 -0
  60. moru/api/client/models/node_status_change.py +79 -0
  61. moru/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py +59 -0
  62. moru/api/client/models/post_sandboxes_sandbox_id_timeout_body.py +59 -0
  63. moru/api/client/models/resumed_sandbox.py +68 -0
  64. moru/api/client/models/sandbox.py +145 -0
  65. moru/api/client/models/sandbox_detail.py +183 -0
  66. moru/api/client/models/sandbox_log.py +70 -0
  67. moru/api/client/models/sandbox_log_entry.py +93 -0
  68. moru/api/client/models/sandbox_log_entry_fields.py +44 -0
  69. moru/api/client/models/sandbox_logs.py +91 -0
  70. moru/api/client/models/sandbox_metric.py +118 -0
  71. moru/api/client/models/sandbox_network_config.py +92 -0
  72. moru/api/client/models/sandbox_state.py +9 -0
  73. moru/api/client/models/sandboxes_with_metrics.py +59 -0
  74. moru/api/client/models/team.py +83 -0
  75. moru/api/client/models/team_api_key.py +158 -0
  76. moru/api/client/models/team_metric.py +86 -0
  77. moru/api/client/models/team_user.py +68 -0
  78. moru/api/client/models/template.py +217 -0
  79. moru/api/client/models/template_build.py +139 -0
  80. moru/api/client/models/template_build_file_upload.py +70 -0
  81. moru/api/client/models/template_build_info.py +126 -0
  82. moru/api/client/models/template_build_request.py +115 -0
  83. moru/api/client/models/template_build_request_v2.py +88 -0
  84. moru/api/client/models/template_build_request_v3.py +88 -0
  85. moru/api/client/models/template_build_start_v2.py +184 -0
  86. moru/api/client/models/template_build_status.py +11 -0
  87. moru/api/client/models/template_legacy.py +207 -0
  88. moru/api/client/models/template_request_response_v3.py +83 -0
  89. moru/api/client/models/template_step.py +91 -0
  90. moru/api/client/models/template_update_request.py +59 -0
  91. moru/api/client/models/template_with_builds.py +148 -0
  92. moru/api/client/models/update_team_api_key.py +59 -0
  93. moru/api/client/py.typed +1 -0
  94. moru/api/client/types.py +54 -0
  95. moru/api/client_async/__init__.py +50 -0
  96. moru/api/client_sync/__init__.py +52 -0
  97. moru/api/metadata.py +14 -0
  98. moru/connection_config.py +217 -0
  99. moru/envd/api.py +59 -0
  100. moru/envd/filesystem/filesystem_connect.py +193 -0
  101. moru/envd/filesystem/filesystem_pb2.py +76 -0
  102. moru/envd/filesystem/filesystem_pb2.pyi +233 -0
  103. moru/envd/process/process_connect.py +155 -0
  104. moru/envd/process/process_pb2.py +92 -0
  105. moru/envd/process/process_pb2.pyi +304 -0
  106. moru/envd/rpc.py +61 -0
  107. moru/envd/versions.py +6 -0
  108. moru/exceptions.py +95 -0
  109. moru/sandbox/commands/command_handle.py +69 -0
  110. moru/sandbox/commands/main.py +39 -0
  111. moru/sandbox/filesystem/filesystem.py +94 -0
  112. moru/sandbox/filesystem/watch_handle.py +60 -0
  113. moru/sandbox/main.py +210 -0
  114. moru/sandbox/mcp.py +1120 -0
  115. moru/sandbox/network.py +8 -0
  116. moru/sandbox/sandbox_api.py +210 -0
  117. moru/sandbox/signature.py +45 -0
  118. moru/sandbox/utils.py +34 -0
  119. moru/sandbox_async/commands/command.py +336 -0
  120. moru/sandbox_async/commands/command_handle.py +196 -0
  121. moru/sandbox_async/commands/pty.py +240 -0
  122. moru/sandbox_async/filesystem/filesystem.py +531 -0
  123. moru/sandbox_async/filesystem/watch_handle.py +62 -0
  124. moru/sandbox_async/main.py +734 -0
  125. moru/sandbox_async/paginator.py +69 -0
  126. moru/sandbox_async/sandbox_api.py +325 -0
  127. moru/sandbox_async/utils.py +7 -0
  128. moru/sandbox_sync/commands/command.py +328 -0
  129. moru/sandbox_sync/commands/command_handle.py +150 -0
  130. moru/sandbox_sync/commands/pty.py +230 -0
  131. moru/sandbox_sync/filesystem/filesystem.py +518 -0
  132. moru/sandbox_sync/filesystem/watch_handle.py +69 -0
  133. moru/sandbox_sync/main.py +726 -0
  134. moru/sandbox_sync/paginator.py +69 -0
  135. moru/sandbox_sync/sandbox_api.py +308 -0
  136. moru/template/consts.py +30 -0
  137. moru/template/dockerfile_parser.py +275 -0
  138. moru/template/logger.py +232 -0
  139. moru/template/main.py +1360 -0
  140. moru/template/readycmd.py +138 -0
  141. moru/template/types.py +105 -0
  142. moru/template/utils.py +320 -0
  143. moru/template_async/build_api.py +202 -0
  144. moru/template_async/main.py +366 -0
  145. moru/template_sync/build_api.py +199 -0
  146. moru/template_sync/main.py +371 -0
  147. moru-0.1.0.dist-info/METADATA +63 -0
  148. moru-0.1.0.dist-info/RECORD +152 -0
  149. moru-0.1.0.dist-info/WHEEL +4 -0
  150. moru-0.1.0.dist-info/licenses/LICENSE +9 -0
  151. moru_connect/__init__.py +1 -0
  152. moru_connect/client.py +493 -0
@@ -0,0 +1,734 @@
1
+ import datetime
2
+ import json
3
+ import logging
4
+ import uuid
5
+ from typing import Dict, List, Optional, overload
6
+
7
+ import httpx
8
+ from packaging.version import Version
9
+ from typing_extensions import Self, Unpack
10
+
11
+ from moru.api.client.types import Unset
12
+ from moru.connection_config import ApiParams, ConnectionConfig
13
+ from moru.envd.api import ENVD_API_HEALTH_ROUTE, ahandle_envd_api_exception
14
+ from moru.envd.versions import ENVD_DEBUG_FALLBACK
15
+ from moru.exceptions import SandboxException, format_request_timeout_error
16
+ from moru.sandbox.main import SandboxOpts
17
+ from moru.sandbox.sandbox_api import McpServer, SandboxMetrics, SandboxNetworkOpts
18
+ from moru.sandbox.utils import class_method_variant
19
+ from moru.sandbox_async.commands.command import Commands
20
+ from moru.sandbox_async.commands.pty import Pty
21
+ from moru.sandbox_async.filesystem.filesystem import Filesystem
22
+ from moru.sandbox_async.sandbox_api import SandboxApi, SandboxInfo
23
+ from moru.api.client_async import get_transport
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class AsyncSandbox(SandboxApi):
29
+ """
30
+ Moru cloud sandbox is a secure and isolated cloud environment.
31
+
32
+ The sandbox allows you to:
33
+ - Access Linux OS
34
+ - Create, list, and delete files and directories
35
+ - Run commands
36
+ - Run isolated code
37
+ - Access the internet
38
+
39
+ Check docs [here](https://moru.io/docs).
40
+
41
+ Use the `AsyncSandbox.create()` to create a new sandbox.
42
+
43
+ Example:
44
+ ```python
45
+ from moru import AsyncSandbox
46
+
47
+ sandbox = await AsyncSandbox.create()
48
+ ```
49
+ """
50
+
51
+ @property
52
+ def files(self) -> Filesystem:
53
+ """
54
+ Module for interacting with the sandbox filesystem.
55
+ """
56
+ return self._filesystem
57
+
58
+ @property
59
+ def commands(self) -> Commands:
60
+ """
61
+ Module for running commands in the sandbox.
62
+ """
63
+ return self._commands
64
+
65
+ @property
66
+ def pty(self) -> Pty:
67
+ """
68
+ Module for interacting with the sandbox pseudo-terminal.
69
+ """
70
+ return self._pty
71
+
72
+ def __init__(
73
+ self,
74
+ **opts: Unpack[SandboxOpts],
75
+ ):
76
+ """
77
+ Use `AsyncSandbox.create()` to create a new sandbox instead.
78
+ """
79
+ super().__init__(**opts)
80
+
81
+ self._transport = get_transport(self.connection_config)
82
+ self._envd_api = httpx.AsyncClient(
83
+ base_url=self.connection_config.get_sandbox_url(
84
+ self.sandbox_id, self.sandbox_domain
85
+ ),
86
+ transport=self._transport,
87
+ headers=self.connection_config.sandbox_headers,
88
+ )
89
+ self._filesystem = Filesystem(
90
+ self.envd_api_url,
91
+ self._envd_version,
92
+ self.connection_config,
93
+ self._transport.pool,
94
+ self._envd_api,
95
+ )
96
+ self._commands = Commands(
97
+ self.envd_api_url,
98
+ self.connection_config,
99
+ self._transport.pool,
100
+ self._envd_version,
101
+ )
102
+ self._pty = Pty(
103
+ self.envd_api_url,
104
+ self.connection_config,
105
+ self._transport.pool,
106
+ self._envd_version,
107
+ )
108
+
109
+ async def is_running(self, request_timeout: Optional[float] = None) -> bool:
110
+ """
111
+ Check if the sandbox is running.
112
+
113
+ :param request_timeout: Timeout for the request in **seconds**
114
+
115
+ :return: `True` if the sandbox is running, `False` otherwise
116
+
117
+ Example
118
+ ```python
119
+ sandbox = await AsyncSandbox.create()
120
+ await sandbox.is_running() # Returns True
121
+
122
+ await sandbox.kill()
123
+ await sandbox.is_running() # Returns False
124
+ ```
125
+ """
126
+ try:
127
+ r = await self._envd_api.get(
128
+ ENVD_API_HEALTH_ROUTE,
129
+ timeout=self.connection_config.get_request_timeout(request_timeout),
130
+ )
131
+
132
+ if r.status_code == 502:
133
+ return False
134
+
135
+ err = await ahandle_envd_api_exception(r)
136
+
137
+ if err:
138
+ raise err
139
+
140
+ except httpx.TimeoutException:
141
+ raise format_request_timeout_error()
142
+
143
+ return True
144
+
145
+ @classmethod
146
+ async def create(
147
+ cls,
148
+ template: Optional[str] = None,
149
+ timeout: Optional[int] = None,
150
+ metadata: Optional[Dict[str, str]] = None,
151
+ envs: Optional[Dict[str, str]] = None,
152
+ secure: bool = True,
153
+ allow_internet_access: bool = True,
154
+ mcp: Optional[McpServer] = None,
155
+ network: Optional[SandboxNetworkOpts] = None,
156
+ **opts: Unpack[ApiParams],
157
+ ) -> Self:
158
+ """
159
+ Create a new sandbox.
160
+
161
+ By default, the sandbox is created from the default `base` sandbox template.
162
+
163
+ :param template: Sandbox template name or ID
164
+ :param timeout: Timeout for the sandbox in **seconds**, default to 300 seconds. The maximum time a sandbox can be kept alive is 24 hours (86_400 seconds) for Pro users and 1 hour (3_600 seconds) for Hobby users.
165
+ :param metadata: Custom metadata for the sandbox
166
+ :param envs: Custom environment variables for the sandbox
167
+ :param secure: Envd is secured with access token and cannot be used without it, defaults to `True`.
168
+ :param allow_internet_access: Allow sandbox to access the internet, defaults to `True`. If set to `False`, it works the same as setting network `deny_out` to `[0.0.0.0/0]`.
169
+ :param mcp: MCP server to enable in the sandbox
170
+ :param network: Sandbox network configuration
171
+
172
+ :return: A Sandbox instance for the new sandbox
173
+
174
+ Use this method instead of using the constructor to create a new sandbox.
175
+ """
176
+ if not template and mcp is not None:
177
+ template = cls.default_mcp_template
178
+ elif not template:
179
+ template = cls.default_template
180
+
181
+ sandbox = await cls._create(
182
+ template=template,
183
+ timeout=timeout,
184
+ auto_pause=False,
185
+ metadata=metadata,
186
+ envs=envs,
187
+ secure=secure,
188
+ allow_internet_access=allow_internet_access,
189
+ mcp=mcp,
190
+ network=network,
191
+ **opts,
192
+ )
193
+
194
+ if mcp is not None:
195
+ token = str(uuid.uuid4())
196
+ sandbox._mcp_token = token
197
+
198
+ res = await sandbox.commands.run(
199
+ f"mcp-gateway --config '{json.dumps(mcp)}'",
200
+ user="root",
201
+ envs={"GATEWAY_ACCESS_TOKEN": token},
202
+ )
203
+ if res.exit_code != 0:
204
+ raise Exception(f"Failed to start MCP gateway: {res.stderr}")
205
+
206
+ return sandbox
207
+
208
+ @overload
209
+ async def connect(
210
+ self,
211
+ timeout: Optional[int] = None,
212
+ **opts: Unpack[ApiParams],
213
+ ) -> Self:
214
+ """
215
+ Connect to a sandbox. If the sandbox is paused, it will be automatically resumed.
216
+ Sandbox must be either running or be paused.
217
+
218
+ With sandbox ID you can connect to the same sandbox from different places or environments (serverless functions, etc).
219
+
220
+ :param timeout: Timeout for the sandbox in **seconds**
221
+ For running sandboxes, the timeout will update only if the new timeout is longer than the existing one.
222
+ :return: A running sandbox instance
223
+
224
+ @example
225
+ ```python
226
+ sandbox = await AsyncSandbox.create()
227
+ await sandbox.beta_pause()
228
+
229
+ # Another code block
230
+ same_sandbox = await sandbox.connect()
231
+ ```
232
+ """
233
+ ...
234
+
235
+ @overload
236
+ @classmethod
237
+ async def connect(
238
+ cls,
239
+ sandbox_id: str,
240
+ timeout: Optional[int] = None,
241
+ **opts: Unpack[ApiParams],
242
+ ) -> Self:
243
+ """
244
+ Connect to a sandbox. If the sandbox is paused, it will be automatically resumed.
245
+ Sandbox must be either running or be paused.
246
+
247
+ With sandbox ID you can connect to the same sandbox from different places or environments (serverless functions, etc).
248
+
249
+ :param sandbox_id: Sandbox ID
250
+ :param timeout: Timeout for the sandbox in **seconds**
251
+ For running sandboxes, the timeout will update only if the new timeout is longer than the existing one.
252
+ :return: A running sandbox instance
253
+
254
+ @example
255
+ ```python
256
+ sandbox = await AsyncSandbox.create()
257
+ await AsyncSandbox.beta_pause(sandbox.sandbox_id)
258
+
259
+ # Another code block
260
+ same_sandbox = await AsyncSandbox.connect(sandbox.sandbox_id))
261
+ ```
262
+ """
263
+ ...
264
+
265
+ @class_method_variant("_cls_connect")
266
+ async def connect(
267
+ self,
268
+ timeout: Optional[int] = None,
269
+ **opts: Unpack[ApiParams],
270
+ ) -> Self:
271
+ """
272
+ Connect to a sandbox. If the sandbox is paused, it will be automatically resumed.
273
+ Sandbox must be either running or be paused.
274
+
275
+ With sandbox ID you can connect to the same sandbox from different places or environments (serverless functions, etc).
276
+
277
+ :param timeout: Timeout for the sandbox in **seconds**
278
+ For running sandboxes, the timeout will update only if the new timeout is longer than the existing one.
279
+ :return: A running sandbox instance
280
+
281
+ @example
282
+ ```python
283
+ sandbox = await AsyncSandbox.create()
284
+ await sandbox.beta_pause()
285
+
286
+ # Another code block
287
+ same_sandbox = await sandbox.connect()
288
+ ```
289
+ """
290
+ await SandboxApi._cls_connect(
291
+ sandbox_id=self.sandbox_id,
292
+ timeout=timeout,
293
+ **opts,
294
+ )
295
+
296
+ return self
297
+
298
+ async def __aenter__(self):
299
+ return self
300
+
301
+ async def __aexit__(self, exc_type, exc_value, traceback):
302
+ await self.kill()
303
+
304
+ @overload
305
+ async def kill(
306
+ self,
307
+ **opts: Unpack[ApiParams],
308
+ ) -> bool:
309
+ """
310
+ Kill the sandbox.
311
+
312
+ :return: `True` if the sandbox was killed, `False` if the sandbox was not found
313
+ """
314
+ ...
315
+
316
+ @overload
317
+ @staticmethod
318
+ async def kill(
319
+ sandbox_id: str,
320
+ **opts: Unpack[ApiParams],
321
+ ) -> bool:
322
+ """
323
+ Kill the sandbox specified by sandbox ID.
324
+
325
+ :param sandbox_id: Sandbox ID
326
+
327
+ :return: `True` if the sandbox was killed, `False` if the sandbox was not found
328
+ """
329
+ ...
330
+
331
+ @class_method_variant("_cls_kill")
332
+ async def kill(
333
+ self,
334
+ **opts: Unpack[ApiParams],
335
+ ) -> bool:
336
+ """
337
+ Kill the sandbox specified by sandbox ID.
338
+
339
+ :return: `True` if the sandbox was killed, `False` if the sandbox was not found
340
+ """
341
+ return await SandboxApi._cls_kill(
342
+ sandbox_id=self.sandbox_id,
343
+ **self.connection_config.get_api_params(**opts),
344
+ )
345
+
346
+ @overload
347
+ async def set_timeout(
348
+ self,
349
+ timeout: int,
350
+ **opts: Unpack[ApiParams],
351
+ ) -> None:
352
+ """
353
+ Set the timeout of the sandbox.
354
+ After the timeout expires, the sandbox will be automatically killed.
355
+ This method can extend or reduce the sandbox timeout set when creating the sandbox or from the last call to `.set_timeout`.
356
+
357
+ The maximum time a sandbox can be kept alive is 24 hours (86_400 seconds) for Pro users and 1 hour (3_600 seconds) for Hobby users.
358
+
359
+ :param timeout: Timeout for the sandbox in **seconds**
360
+ """
361
+ ...
362
+
363
+ @overload
364
+ @staticmethod
365
+ async def set_timeout(
366
+ sandbox_id: str,
367
+ timeout: int,
368
+ **opts: Unpack[ApiParams],
369
+ ) -> None:
370
+ """
371
+ Set the timeout of the specified sandbox.
372
+ After the timeout expires, the sandbox will be automatically killed.
373
+ This method can extend or reduce the sandbox timeout set when creating the sandbox or from the last call to `.set_timeout`.
374
+
375
+ The maximum time a sandbox can be kept alive is 24 hours (86_400 seconds) for Pro users and 1 hour (3_600 seconds) for Hobby users.
376
+
377
+ :param sandbox_id: Sandbox ID
378
+ :param timeout: Timeout for the sandbox in **seconds**
379
+ """
380
+ ...
381
+
382
+ @class_method_variant("_cls_set_timeout")
383
+ async def set_timeout(
384
+ self,
385
+ timeout: int,
386
+ **opts: Unpack[ApiParams],
387
+ ) -> None:
388
+ """
389
+ Set the timeout of the specified sandbox.
390
+ After the timeout expires, the sandbox will be automatically killed.
391
+ This method can extend or reduce the sandbox timeout set when creating the sandbox or from the last call to `.set_timeout`.
392
+
393
+ The maximum time a sandbox can be kept alive is 24 hours (86_400 seconds) for Pro users and 1 hour (3_600 seconds) for Hobby users.
394
+
395
+ :param timeout: Timeout for the sandbox in **seconds**
396
+ """
397
+ await SandboxApi._cls_set_timeout(
398
+ sandbox_id=self.sandbox_id,
399
+ timeout=timeout,
400
+ **self.connection_config.get_api_params(**opts),
401
+ )
402
+
403
+ @overload
404
+ async def get_info(
405
+ self,
406
+ **opts: Unpack[ApiParams],
407
+ ) -> SandboxInfo:
408
+ """
409
+ Get sandbox information like sandbox ID, template, metadata, started at/end at date.
410
+
411
+ :return: Sandbox info
412
+ """
413
+ ...
414
+
415
+ @overload
416
+ @staticmethod
417
+ async def get_info(
418
+ sandbox_id: str,
419
+ **opts: Unpack[ApiParams],
420
+ ) -> SandboxInfo:
421
+ """
422
+ Get sandbox information like sandbox ID, template, metadata, started at/end at date.
423
+ :param sandbox_id: Sandbox ID
424
+
425
+ :return: Sandbox info
426
+ """
427
+ ...
428
+
429
+ @class_method_variant("_cls_get_info")
430
+ async def get_info(
431
+ self,
432
+ **opts: Unpack[ApiParams],
433
+ ) -> SandboxInfo:
434
+ """
435
+ Get sandbox information like sandbox ID, template, metadata, started at/end at date.
436
+
437
+ :return: Sandbox info
438
+ """
439
+
440
+ return await SandboxApi._cls_get_info(
441
+ sandbox_id=self.sandbox_id,
442
+ **self.connection_config.get_api_params(**opts),
443
+ )
444
+
445
+ @overload
446
+ async def get_metrics(
447
+ self,
448
+ start: Optional[datetime.datetime] = None,
449
+ end: Optional[datetime.datetime] = None,
450
+ **opts: Unpack[ApiParams],
451
+ ) -> List[SandboxMetrics]:
452
+ """
453
+ Get the metrics of the current sandbox.
454
+
455
+ :param start: Start time for the metrics, defaults to the start of the sandbox
456
+ :param end: End time for the metrics, defaults to the current time
457
+
458
+ :return: List of sandbox metrics containing CPU, memory and disk usage information
459
+ """
460
+ ...
461
+
462
+ @overload
463
+ @staticmethod
464
+ async def get_metrics(
465
+ sandbox_id: str,
466
+ start: Optional[datetime.datetime] = None,
467
+ end: Optional[datetime.datetime] = None,
468
+ **opts: Unpack[ApiParams],
469
+ ) -> List[SandboxMetrics]:
470
+ """
471
+ Get the metrics of the sandbox specified by sandbox ID.
472
+
473
+ :param sandbox_id: Sandbox ID
474
+ :param start: Start time for the metrics, defaults to the start of the sandbox
475
+ :param end: End time for the metrics, defaults to the current time
476
+
477
+ :return: List of sandbox metrics containing CPU, memory and disk usage information
478
+ """
479
+ ...
480
+
481
+ @class_method_variant("_cls_get_metrics")
482
+ async def get_metrics(
483
+ self,
484
+ start: Optional[datetime.datetime] = None,
485
+ end: Optional[datetime.datetime] = None,
486
+ **opts: Unpack[ApiParams],
487
+ ) -> List[SandboxMetrics]:
488
+ """
489
+ Get the metrics of the current sandbox.
490
+
491
+ :param start: Start time for the metrics, defaults to the start of the sandbox
492
+ :param end: End time for the metrics, defaults to the current time
493
+
494
+ :return: List of sandbox metrics containing CPU, memory and disk usage information
495
+ """
496
+ if self._envd_version < Version("0.1.5"):
497
+ raise SandboxException(
498
+ "Metrics are not supported in this version of the sandbox, please rebuild your template."
499
+ )
500
+
501
+ if self._envd_version < Version("0.2.4"):
502
+ logger.warning(
503
+ "Disk metrics are not supported in this version of the sandbox, please rebuild the template to get disk metrics."
504
+ )
505
+
506
+ return await SandboxApi._cls_get_metrics(
507
+ sandbox_id=self.sandbox_id,
508
+ start=start,
509
+ end=end,
510
+ **self.connection_config.get_api_params(**opts),
511
+ )
512
+
513
+ @classmethod
514
+ async def beta_create(
515
+ cls,
516
+ template: Optional[str] = None,
517
+ timeout: Optional[int] = None,
518
+ auto_pause: bool = False,
519
+ metadata: Optional[Dict[str, str]] = None,
520
+ envs: Optional[Dict[str, str]] = None,
521
+ secure: bool = True,
522
+ allow_internet_access: bool = True,
523
+ mcp: Optional[McpServer] = None,
524
+ **opts: Unpack[ApiParams],
525
+ ) -> Self:
526
+ """
527
+ [BETA] This feature is in beta and may change in the future.
528
+
529
+ Create a new sandbox.
530
+
531
+ By default, the sandbox is created from the default `base` sandbox template.
532
+
533
+ :param template: Sandbox template name or ID
534
+ :param timeout: Timeout for the sandbox in **seconds**, default to 300 seconds. The maximum time a sandbox can be kept alive is 24 hours (86_400 seconds) for Pro users and 1 hour (3_600 seconds) for Hobby users.
535
+ :param auto_pause: Automatically pause the sandbox after the timeout expires. Defaults to `False`.
536
+ :param metadata: Custom metadata for the sandbox
537
+ :param envs: Custom environment variables for the sandbox
538
+ :param secure: Envd is secured with access token and cannot be used without it, defaults to `True`.
539
+ :param allow_internet_access: Allow sandbox to access the internet, defaults to `True`.
540
+ :param mcp: MCP server to enable in the sandbox
541
+
542
+ :return: A Sandbox instance for the new sandbox
543
+
544
+ Use this method instead of using the constructor to create a new sandbox.
545
+ """
546
+
547
+ if not template and mcp is not None:
548
+ template = cls.default_mcp_template
549
+ elif not template:
550
+ template = cls.default_template
551
+
552
+ sandbox = await cls._create(
553
+ template=template,
554
+ timeout=timeout,
555
+ auto_pause=auto_pause,
556
+ metadata=metadata,
557
+ envs=envs,
558
+ secure=secure,
559
+ allow_internet_access=allow_internet_access,
560
+ mcp=mcp,
561
+ **opts,
562
+ )
563
+
564
+ if mcp is not None:
565
+ token = str(uuid.uuid4())
566
+ sandbox._mcp_token = token
567
+
568
+ res = await sandbox.commands.run(
569
+ f"mcp-gateway --config '{json.dumps(mcp)}'",
570
+ user="root",
571
+ envs={"GATEWAY_ACCESS_TOKEN": token},
572
+ )
573
+ if res.exit_code != 0:
574
+ raise Exception(f"Failed to start MCP gateway: {res.stderr}")
575
+
576
+ return sandbox
577
+
578
+ @overload
579
+ async def beta_pause(
580
+ self,
581
+ **opts: Unpack[ApiParams],
582
+ ) -> None:
583
+ """
584
+ [BETA] This feature is in beta and may change in the future.
585
+
586
+ Pause the sandbox.
587
+
588
+ :return: Sandbox ID that can be used to resume the sandbox
589
+ """
590
+ ...
591
+
592
+ @overload
593
+ @staticmethod
594
+ async def beta_pause(
595
+ sandbox_id: str,
596
+ **opts: Unpack[ApiParams],
597
+ ) -> None:
598
+ """
599
+ [BETA] This feature is in beta and may change in the future.
600
+
601
+ Pause the sandbox specified by sandbox ID.
602
+
603
+ :param sandbox_id: Sandbox ID
604
+
605
+ :return: Sandbox ID that can be used to resume the sandbox
606
+ """
607
+ ...
608
+
609
+ @class_method_variant("_cls_pause")
610
+ async def beta_pause(
611
+ self,
612
+ **opts: Unpack[ApiParams],
613
+ ) -> None:
614
+ """
615
+ [BETA] This feature is in beta and may change in the future.
616
+
617
+ Pause the sandbox.
618
+
619
+ :return: Sandbox ID that can be used to resume the sandbox
620
+ """
621
+
622
+ await SandboxApi._cls_pause(
623
+ sandbox_id=self.sandbox_id,
624
+ **opts,
625
+ )
626
+
627
+ async def get_mcp_token(self) -> Optional[str]:
628
+ """
629
+ Get the MCP token for the sandbox.
630
+
631
+ :return: MCP token for the sandbox, or None if MCP is not enabled.
632
+ """
633
+ if not self._mcp_token:
634
+ self._mcp_token = await self.files.read(
635
+ "/etc/mcp-gateway/.token", user="root"
636
+ )
637
+ return self._mcp_token
638
+
639
+ @classmethod
640
+ async def _cls_connect(
641
+ cls,
642
+ sandbox_id: str,
643
+ timeout: Optional[int] = None,
644
+ **opts: Unpack[ApiParams],
645
+ ) -> Self:
646
+ sandbox = await SandboxApi._cls_connect(
647
+ sandbox_id=sandbox_id,
648
+ timeout=timeout,
649
+ **opts,
650
+ )
651
+
652
+ sandbox_headers = {}
653
+ envd_access_token = sandbox.envd_access_token
654
+ if envd_access_token is not None and not isinstance(envd_access_token, Unset):
655
+ sandbox_headers["X-Access-Token"] = envd_access_token
656
+
657
+ connection_config = ConnectionConfig(
658
+ extra_sandbox_headers=sandbox_headers,
659
+ **opts,
660
+ )
661
+
662
+ return cls(
663
+ sandbox_id=sandbox.sandbox_id,
664
+ sandbox_domain=sandbox.domain,
665
+ envd_version=Version(sandbox.envd_version),
666
+ envd_access_token=envd_access_token,
667
+ traffic_access_token=sandbox.traffic_access_token,
668
+ connection_config=connection_config,
669
+ )
670
+
671
+ @classmethod
672
+ async def _create(
673
+ cls,
674
+ template: Optional[str],
675
+ timeout: Optional[int],
676
+ auto_pause: bool,
677
+ allow_internet_access: bool,
678
+ metadata: Optional[Dict[str, str]],
679
+ envs: Optional[Dict[str, str]],
680
+ secure: bool,
681
+ mcp: Optional[McpServer] = None,
682
+ network: Optional[SandboxNetworkOpts] = None,
683
+ **opts: Unpack[ApiParams],
684
+ ) -> Self:
685
+ extra_sandbox_headers = {}
686
+
687
+ debug = opts.get("debug")
688
+ if debug:
689
+ sandbox_id = "debug_sandbox_id"
690
+ sandbox_domain = None
691
+ envd_version = ENVD_DEBUG_FALLBACK
692
+ envd_access_token = None
693
+ traffic_access_token = None
694
+ else:
695
+ response = await SandboxApi._create_sandbox(
696
+ template=template or cls.default_template,
697
+ timeout=timeout or cls.default_sandbox_timeout,
698
+ auto_pause=auto_pause,
699
+ metadata=metadata,
700
+ env_vars=envs,
701
+ secure=secure,
702
+ allow_internet_access=allow_internet_access,
703
+ mcp=mcp,
704
+ network=network,
705
+ **opts,
706
+ )
707
+
708
+ sandbox_id = response.sandbox_id
709
+ sandbox_domain = response.sandbox_domain
710
+ envd_version = Version(response.envd_version)
711
+ envd_access_token = response.envd_access_token
712
+ traffic_access_token = response.traffic_access_token
713
+
714
+ if envd_access_token is not None and not isinstance(
715
+ envd_access_token, Unset
716
+ ):
717
+ extra_sandbox_headers["X-Access-Token"] = envd_access_token
718
+
719
+ extra_sandbox_headers["Moru-Sandbox-Id"] = sandbox_id
720
+ extra_sandbox_headers["Moru-Sandbox-Port"] = str(ConnectionConfig.envd_port)
721
+
722
+ connection_config = ConnectionConfig(
723
+ extra_sandbox_headers=extra_sandbox_headers,
724
+ **opts,
725
+ )
726
+
727
+ return cls(
728
+ sandbox_id=sandbox_id,
729
+ sandbox_domain=sandbox_domain,
730
+ envd_version=envd_version,
731
+ envd_access_token=envd_access_token,
732
+ traffic_access_token=traffic_access_token,
733
+ connection_config=connection_config,
734
+ )