modAL 1.6.2.dev2__py3-none-any.whl → 1.6.2.dev4__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.
@@ -1,10 +1,7 @@
1
1
  import asyncio.locks
2
2
  import collections.abc
3
- import enum
4
3
  import google.protobuf.message
5
- import modal._image
6
4
  import modal._logs_manager
7
- import modal._object
8
5
  import modal._outbound_policy
9
6
  import modal._supports_logs
10
7
  import modal._tunnel
@@ -19,6 +16,7 @@ import modal.mount
19
16
  import modal.network_file_system
20
17
  import modal.object
21
18
  import modal.proxy
19
+ import modal.sandbox._sandbox
22
20
  import modal.sandbox_fs
23
21
  import modal.secret
24
22
  import modal.snapshot
@@ -32,1357 +30,6 @@ import pathlib
32
30
  import typing
33
31
  import typing_extensions
34
32
 
35
- SandboxRuntime = typing.Literal["gvisor", "vm"]
36
-
37
- async def _gather_load_with_timings(
38
- load_coros: collections.abc.Sequence[collections.abc.Awaitable[typing.Any]],
39
- ) -> list[tuple[str, float]]:
40
- """Await all loader coroutines concurrently and return [(object_id, elapsed_seconds)] per load."""
41
- ...
42
-
43
- def _format_sandbox_create_timing_log(
44
- sandbox_id: str, total_seconds: float, rpc_seconds: float, dep_timings: collections.abc.Sequence[tuple[str, float]]
45
- ) -> str:
46
- """Format the Sandbox create debug log line, listing the slowest deps first."""
47
- ...
48
-
49
- def _validate_sandbox_env(env: dict[str, str]) -> None: ...
50
- def _validate_sandbox_runtime(runtime: typing.Optional[typing.Literal["gvisor", "vm"]]) -> None: ...
51
- def _ttl_to_wire_ttl(ttl: typing.Optional[int]) -> int:
52
- """Convert a TTL value to the wire format, validating the input."""
53
- ...
54
-
55
- def _validate_experimental_encryption_key(key: typing.Optional[bytes]) -> typing.Optional[bytes]: ...
56
- def _image_id_for_mount(image: modal._image._Image, method_name: str) -> str: ...
57
-
58
- class SandboxVersion(enum.Enum):
59
- V1 = 1
60
- V2 = 2
61
-
62
- def _is_v1_sandbox_id(sandbox_id: str) -> bool: ...
63
- def _get_sandbox_version(sandbox_id: str) -> SandboxVersion: ...
64
- def _result_returncode(result: typing.Optional[modal_proto.api_pb2.GenericResult]) -> typing.Optional[int]: ...
65
- def _validate_exec_args(args: collections.abc.Sequence[str]) -> None: ...
66
- def _build_outbound_network_access(
67
- block_network: bool,
68
- outbound_cidr_allowlist: typing.Optional[collections.abc.Sequence[str]],
69
- outbound_domain_allowlist: typing.Optional[collections.abc.Sequence[str]],
70
- ) -> modal_proto.api_pb2.NetworkAccess:
71
- """Build the outbound `NetworkAccess` for the given params.
72
-
73
- Shared by `Sandbox.create` and the sidecar create path. When nothing is
74
- specified, defaults to open network access.
75
- """
76
- ...
77
-
78
- class DefaultSandboxNameOverride(str):
79
- """A singleton class that represents the default sandbox name override.
80
-
81
- It is used to indicate that the sandbox name should not be overridden.
82
- """
83
- def __repr__(self) -> str:
84
- """Return repr(self)."""
85
- ...
86
-
87
- _DEFAULT_SANDBOX_NAME_OVERRIDE: DefaultSandboxNameOverride
88
-
89
- class Probe:
90
- """Probe configuration for the Sandbox Readiness Probe.
91
-
92
- Examples:
93
- ```python notest
94
- # Wait until a file exists.
95
- readiness_probe = modal.Probe.with_exec(
96
- "sh", "-c", "test -f /tmp/ready",
97
- )
98
-
99
- # Wait until a TCP port is accepting connections.
100
- readiness_probe = modal.Probe.with_tcp(8080)
101
-
102
- app = modal.App.lookup('sandbox-readiness-probe', create_if_missing=True)
103
- sandbox = modal.Sandbox.create(
104
- "python3", "-m", "http.server", "8080",
105
- readiness_probe=readiness_probe,
106
- app=app,
107
- )
108
- sandbox.wait_until_ready()
109
- ```
110
- """
111
-
112
- tcp_port: typing.Optional[int]
113
- exec_argv: typing.Optional[tuple[str, ...]]
114
- interval_ms: int
115
-
116
- def __post_init__(self): ...
117
- @classmethod
118
- def with_tcp(cls, port: int, *, interval_ms: int = 100) -> Probe: ...
119
- @classmethod
120
- def with_exec(cls, *argv: str, interval_ms: int = 100) -> Probe: ...
121
- def _to_proto(self) -> modal_proto.api_pb2.Probe: ...
122
- def __init__(
123
- self,
124
- tcp_port: typing.Optional[int] = None,
125
- exec_argv: typing.Optional[tuple[str, ...]] = None,
126
- interval_ms: int = 100,
127
- ) -> None:
128
- """Initialize self. See help(type(self)) for accurate signature."""
129
- ...
130
-
131
- def __repr__(self):
132
- """Return repr(self)."""
133
- ...
134
-
135
- def __eq__(self, other):
136
- """Return self==value."""
137
- ...
138
-
139
- def __setattr__(self, name, value):
140
- """Implement setattr(self, name, value)."""
141
- ...
142
-
143
- def __delattr__(self, name):
144
- """Implement delattr(self, name)."""
145
- ...
146
-
147
- def __hash__(self):
148
- """Return hash(self)."""
149
- ...
150
-
151
- def _resolve_app_id_and_client(
152
- app: typing.Optional[modal.app._App], client: typing.Optional[modal.client._Client]
153
- ) -> tuple[typing.Optional[str], typing.Optional[modal.client._Client]]:
154
- """Resolve the App id and client for Sandbox creation, validating that an App is available."""
155
- ...
156
-
157
- class _Sandbox(modal._object._Object):
158
- """A `Sandbox` object lets you interact with a running sandbox. This API is similar to Python's
159
- [asyncio.subprocess.Process](https://docs.python.org/3/library/asyncio-subprocess.html#asyncio.subprocess.Process).
160
-
161
- Refer to the [guide](https://modal.com/docs/guide/sandbox) on how to spawn and use sandboxes.
162
- """
163
-
164
- _result: typing.Optional[modal_proto.api_pb2.GenericResult]
165
- _stdout: modal.io_streams._StreamReader[str]
166
- _stderr: modal.io_streams._StreamReader[str]
167
- _stdin: modal.io_streams._StreamWriter
168
- _task_id: typing.Optional[str]
169
- _tunnels: typing.Optional[dict[int, modal._tunnel.Tunnel]]
170
- _enable_snapshot: bool
171
- _command_router_client: typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient]
172
- _command_router_lock: typing.Optional[asyncio.locks.Lock]
173
- _init_command_router_access: typing.Optional[modal_proto.api_pb2.CommandRouterAccess]
174
- _attached: bool
175
- _filesystem: typing.Optional[modal.sandbox_fs._SandboxFilesystem]
176
- _is_v2: bool
177
- _app_id: typing.Optional[str]
178
-
179
- @staticmethod
180
- def _default_pty_info() -> modal_proto.api_pb2.PTYInfo: ...
181
- @staticmethod
182
- def _new(
183
- args: collections.abc.Sequence[str],
184
- image: modal._image._Image,
185
- secrets: collections.abc.Collection[modal.secret._Secret],
186
- name: typing.Optional[str] = None,
187
- timeout: int = 300,
188
- idle_timeout: typing.Optional[int] = None,
189
- workdir: typing.Optional[str] = None,
190
- gpu: typing.Optional[str] = None,
191
- cloud: typing.Optional[str] = None,
192
- region: typing.Union[str, collections.abc.Sequence[str], None] = None,
193
- cpu: typing.Union[float, tuple[float, float], None] = None,
194
- memory: typing.Union[int, tuple[int, int], None] = None,
195
- mounts: collections.abc.Sequence[modal.mount._Mount] = (),
196
- network_file_systems: dict[typing.Union[str, os.PathLike], modal.network_file_system._NetworkFileSystem] = {},
197
- block_network: bool = False,
198
- outbound_cidr_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
199
- outbound_domain_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
200
- outbound_policy: typing.Optional[modal._outbound_policy._OutboundPolicy] = None,
201
- inbound_cidr_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
202
- volumes: dict[
203
- typing.Union[str, os.PathLike],
204
- typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
205
- ] = {},
206
- pty: bool = False,
207
- pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
208
- encrypted_ports: collections.abc.Sequence[int] = [],
209
- h2_ports: collections.abc.Sequence[int] = [],
210
- unencrypted_ports: collections.abc.Sequence[int] = [],
211
- proxy: typing.Optional[modal.proxy._Proxy] = None,
212
- readiness_probe: typing.Optional[Probe] = None,
213
- experimental_options: typing.Optional[dict[str, typing.Any]] = None,
214
- tags: typing.Optional[dict[str, str]] = None,
215
- enable_snapshot: bool = False,
216
- verbose: bool = False,
217
- custom_domain: typing.Optional[str] = None,
218
- include_oidc_identity_token: bool = False,
219
- runtime: typing.Optional[typing.Literal["gvisor", "vm"]] = None,
220
- ) -> _Sandbox:
221
- """mdmd:hidden"""
222
- ...
223
-
224
- @staticmethod
225
- async def create(
226
- *args: str,
227
- app: typing.Optional[modal.app._App] = None,
228
- name: typing.Optional[str] = None,
229
- tags: typing.Optional[dict[str, str]] = None,
230
- image: typing.Optional[modal._image._Image] = None,
231
- env: typing.Optional[dict[str, typing.Optional[str]]] = None,
232
- secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
233
- network_file_systems: dict[typing.Union[str, os.PathLike], modal.network_file_system._NetworkFileSystem] = {},
234
- timeout: int = 300,
235
- idle_timeout: typing.Optional[int] = None,
236
- workdir: typing.Optional[str] = None,
237
- gpu: typing.Optional[str] = None,
238
- cloud: typing.Optional[str] = None,
239
- region: typing.Union[str, collections.abc.Sequence[str], None] = None,
240
- cpu: typing.Union[float, tuple[float, float], None] = None,
241
- memory: typing.Union[int, tuple[int, int], None] = None,
242
- runtime: typing.Optional[typing.Literal["gvisor", "vm"]] = None,
243
- block_network: bool = False,
244
- outbound_cidr_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
245
- outbound_domain_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
246
- _experimental_outbound_policy: typing.Optional[modal._outbound_policy._OutboundPolicy] = None,
247
- inbound_cidr_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
248
- volumes: dict[
249
- typing.Union[str, os.PathLike],
250
- typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
251
- ] = {},
252
- pty: bool = False,
253
- encrypted_ports: collections.abc.Sequence[int] = [],
254
- h2_ports: collections.abc.Sequence[int] = [],
255
- unencrypted_ports: collections.abc.Sequence[int] = [],
256
- custom_domain: typing.Optional[str] = None,
257
- proxy: typing.Optional[modal.proxy._Proxy] = None,
258
- include_oidc_identity_token: bool = False,
259
- readiness_probe: typing.Optional[Probe] = None,
260
- verbose: bool = False,
261
- experimental_options: typing.Optional[dict[str, typing.Any]] = None,
262
- _experimental_enable_snapshot: bool = False,
263
- client: typing.Optional[modal.client._Client] = None,
264
- environment_name: typing.Optional[str] = None,
265
- pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
266
- cidr_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
267
- ) -> _Sandbox:
268
- """Create a new Sandbox to run untrusted, arbitrary code.
269
-
270
- The Sandbox's corresponding container will be created asynchronously.
271
-
272
- Args:
273
- *args: Set the CMD of the Sandbox, overriding any CMD of the container image.
274
- app: Associate the sandbox with an app. Required unless creating from a container.
275
- name: Optionally give the sandbox a name. Unique within an app.
276
- tags: Tags to assign to the Sandbox.
277
- image: The image to run as the container for the sandbox.
278
- env: Environment variables to set in the Sandbox.
279
- secrets: Secrets to inject into the Sandbox as environment variables.
280
- network_file_systems: Network file systems to mount into the sandbox.
281
- timeout: Maximum lifetime of the sandbox in seconds.
282
- idle_timeout: The amount of time in seconds that a sandbox can be idle before being terminated.
283
- workdir: Working directory of the sandbox.
284
- gpu: GPU reservation for the sandbox.
285
- cloud: Cloud provider for the sandbox.
286
- region: Region or regions to run the sandbox on.
287
- cpu:
288
- Specify, in fractional CPU cores, how many CPU cores to request. Or, pass (request, limit) to
289
- additionally specify a hard limit in fractional CPU cores. CPU throttling will prevent a container
290
- from exceeding its specified limit.
291
- memory:
292
- Specify, in MiB, a memory request which is the minimum memory required. Or, pass (request, limit) to
293
- additionally specify a hard limit in MiB.
294
- runtime:
295
- Runtime under which the Sandbox executes, or None to let Modal pick.
296
- block_network: Whether to block network access.
297
- outbound_cidr_allowlist: List of CIDRs the sandbox is allowed to access. If None, all CIDRs are allowed.
298
- outbound_domain_allowlist: List of domain names the sandbox is allowed to access. Supports
299
- wildcard prefixes (``*.``); a bare ``"*"`` allows all domains. The outbound policy
300
- can be replaced later via `Sandbox._experimental_set_outbound_network_policy`.
301
- inbound_cidr_allowlist:
302
- List of CIDRs allowed to connect inbound to the sandbox (tunnels and connection tokens). If None,
303
- all CIDRs are allowed.
304
- _experimental_outbound_policy: Configuration for replacing headers in outbound HTTPS requests from
305
- the Sandbox. Secrets referenced by the policy are resolved outside the Sandbox and are never
306
- visible to the workload. See `modal.experimental.OutboundPolicy`. This API is experimental and
307
- may change in the future.
308
- volumes: Mount points for Modal Volumes and CloudBucketMounts.
309
- pty:
310
- Enable a PTY for the Sandbox entrypoint command. When enabled, all output (stdout and stderr from the
311
- process) is multiplexed into stdout, and the stderr stream is effectively empty.
312
- encrypted_ports: List of ports to tunnel into the sandbox. Encrypted ports are tunneled with TLS.
313
- h2_ports: List of encrypted ports to tunnel into the sandbox, using HTTP/2.
314
- unencrypted_ports: List of ports to tunnel into the sandbox without encryption.
315
- custom_domain:
316
- Allow connections to the Sandbox via a subdomain of this parent rather than a default Modal domain.
317
- proxy: Reference to a Modal Proxy to use in front of this Sandbox.
318
- include_oidc_identity_token:
319
- If True, the sandbox will receive a MODAL_IDENTITY_TOKEN env var for OIDC-based auth.
320
- readiness_probe: Probe used to determine when the sandbox has become ready.
321
- verbose: Enable verbose logging for sandbox operations.
322
- experimental_options: Experimental options to pass to the sandbox.
323
- _experimental_enable_snapshot: Enable memory snapshots.
324
- client: Modal Client to use for the sandbox.
325
- environment_name: *DEPRECATED* Optionally override the default environment
326
- pty_info: *DEPRECATED* Use `pty` instead. `pty` will override `pty_info`.
327
- cidr_allowlist: *DEPRECATED* Use outbound_cidr_allowlist instead.
328
-
329
- Returns:
330
- A `Sandbox` object representing the created sandbox which can be used to interact with the sandbox.
331
-
332
- Raises:
333
- AlreadyExistsError: If a sandbox with the same name already exists.
334
-
335
- Examples:
336
- ```python
337
- app = modal.App.lookup('sandbox-hello-world', create_if_missing=True)
338
- sandbox = modal.Sandbox.create("echo", "hello world", app=app)
339
- print(sandbox.stdout.read())
340
- sandbox.wait()
341
- ```
342
- """
343
- ...
344
-
345
- @staticmethod
346
- async def _create(
347
- *args: str,
348
- app: typing.Optional[modal.app._App] = None,
349
- name: typing.Optional[str] = None,
350
- tags: typing.Optional[dict[str, str]] = None,
351
- image: typing.Optional[modal._image._Image] = None,
352
- env: typing.Optional[dict[str, typing.Optional[str]]] = None,
353
- secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
354
- mounts: collections.abc.Sequence[modal.mount._Mount] = (),
355
- network_file_systems: dict[typing.Union[str, os.PathLike], modal.network_file_system._NetworkFileSystem] = {},
356
- timeout: int = 300,
357
- idle_timeout: typing.Optional[int] = None,
358
- workdir: typing.Optional[str] = None,
359
- gpu: typing.Optional[str] = None,
360
- cloud: typing.Optional[str] = None,
361
- region: typing.Union[str, collections.abc.Sequence[str], None] = None,
362
- cpu: typing.Union[float, tuple[float, float], None] = None,
363
- memory: typing.Union[int, tuple[int, int], None] = None,
364
- runtime: typing.Optional[typing.Literal["gvisor", "vm"]] = None,
365
- block_network: bool = False,
366
- outbound_cidr_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
367
- outbound_domain_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
368
- _experimental_outbound_policy: typing.Optional[modal._outbound_policy._OutboundPolicy] = None,
369
- inbound_cidr_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
370
- volumes: dict[
371
- typing.Union[str, os.PathLike],
372
- typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
373
- ] = {},
374
- pty: bool = False,
375
- encrypted_ports: collections.abc.Sequence[int] = [],
376
- h2_ports: collections.abc.Sequence[int] = [],
377
- unencrypted_ports: collections.abc.Sequence[int] = [],
378
- proxy: typing.Optional[modal.proxy._Proxy] = None,
379
- include_oidc_identity_token: bool = False,
380
- readiness_probe: typing.Optional[Probe] = None,
381
- experimental_options: typing.Optional[dict[str, typing.Any]] = None,
382
- _experimental_enable_snapshot: bool = False,
383
- client: typing.Optional[modal.client._Client] = None,
384
- verbose: bool = False,
385
- pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
386
- custom_domain: typing.Optional[str] = None,
387
- ):
388
- """Private method used internally.
389
-
390
- This method exposes some internal arguments (currently `mounts`) which are not in the public API.
391
- `mounts` is currently only used by modal shell (cli) to provide a function's mounts to the
392
- sandbox that runs the shell session.
393
- """
394
- ...
395
-
396
- @staticmethod
397
- async def _experimental_create(
398
- *args: str,
399
- app: typing.Optional[modal.app._App] = None,
400
- name: typing.Optional[str] = None,
401
- tags: typing.Optional[dict[str, str]] = None,
402
- image: typing.Optional[modal._image._Image] = None,
403
- env: typing.Optional[dict[str, typing.Optional[str]]] = None,
404
- secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
405
- mounts: collections.abc.Sequence[modal.mount._Mount] = (),
406
- timeout: int = 300,
407
- idle_timeout: typing.Optional[int] = None,
408
- workdir: typing.Optional[str] = None,
409
- cpu: typing.Union[float, tuple[float, float], None] = None,
410
- memory: typing.Union[int, tuple[int, int], None] = None,
411
- runtime: typing.Optional[typing.Literal["gvisor", "vm"]] = None,
412
- cloud: typing.Optional[str] = None,
413
- region: typing.Union[str, collections.abc.Sequence[str], None] = None,
414
- block_network: bool = False,
415
- outbound_cidr_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
416
- outbound_domain_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
417
- _experimental_outbound_policy: typing.Optional[modal._outbound_policy._OutboundPolicy] = None,
418
- inbound_cidr_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
419
- i6pn: bool = False,
420
- volumes: dict[
421
- typing.Union[str, os.PathLike],
422
- typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
423
- ] = {},
424
- pty: bool = False,
425
- encrypted_ports: collections.abc.Sequence[int] = [],
426
- h2_ports: collections.abc.Sequence[int] = [],
427
- unencrypted_ports: collections.abc.Sequence[int] = [],
428
- proxy: typing.Optional[modal.proxy._Proxy] = None,
429
- readiness_probe: typing.Optional[Probe] = None,
430
- experimental_options: typing.Optional[dict[str, typing.Any]] = None,
431
- include_oidc_identity_token: bool = False,
432
- verbose: bool = False,
433
- custom_domain: typing.Optional[str] = None,
434
- client: typing.Optional[modal.client._Client] = None,
435
- _experimental_enable_snapshot: bool = False,
436
- ) -> _Sandbox:
437
- """Create a sandbox using the V2 backend.
438
-
439
- Supported features include exec, encrypted tunnels, wait/poll/terminate,
440
- CPU and memory configuration, region placement, private IPv6 networking
441
- (i6pn), volumes, cloud bucket mounts (with static credentials via
442
- `secret=...` or `oidc_auth_role_arn`), OIDC identity tokens, proxies,
443
- filesystem snapshots, and custom domains (`custom_domain=...` allows
444
- connections to the sandbox via a subdomain of that parent domain rather
445
- than a default Modal domain; requires prior setup by Modal).
446
-
447
- `cpu` and `memory` accept either a scalar request or a `(request, limit)`
448
- tuple that additionally sets a hard limit (fractional CPU cores; memory
449
- in MiB), matching `Sandbox.create()`.
450
-
451
- Pass `_experimental_enable_snapshot=True` to create a sandbox that can be
452
- snapshotted with `._experimental_snapshot()`. Features like network
453
- file systems and GPUs are not supported.
454
-
455
- Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
456
- workspace can address each other directly at their `i6pn.modal.local`
457
- address. i6pn only connects sandboxes co-located on the same routable
458
- network, so pin every sandbox in the group to the same specific region
459
- (e.g. `region="us-east-1"`).
460
-
461
- V2 sandboxes created with this method are not currently returned by
462
- `Sandbox.list()`. A named sandbox can be looked up with
463
- `Sandbox._experimental_from_name(app_name, name)`; otherwise store
464
- `sandbox.object_id` and use `Sandbox.from_id(sandbox.object_id)` to
465
- reattach.
466
- """
467
- ...
468
-
469
- async def _v2_metadata(self, client: typing.Optional[modal.client._Client] = None) -> list[tuple[str, str]]: ...
470
- def _get_metadata(self) -> modal_proto.api_pb2.SandboxHandleMetadata: ...
471
- def _hydrate_metadata(self, handle_metadata: typing.Optional[google.protobuf.message.Message]) -> None: ...
472
- def _hydrate_metadata_v2(self) -> None:
473
- """Wire up V2 stdio readers that read directly from the worker. Cheap
474
- to call eagerly: the router connection is opened lazily on first read.
475
- """
476
- ...
477
-
478
- def _initialize_from_other(self, other): ...
479
- def _initialize_from_empty(self): ...
480
- async def detach(self):
481
- """Disconnects your client from the sandbox and cleans up resources associated with the connection.
482
-
483
- Be sure to only call `detach` when you are done interacting with the sandbox. After calling `detach`,
484
- any operation using the Sandbox object is not guaranteed to work anymore. If you want to continue interacting
485
- with a running sandbox, use `Sandbox.from_id` to get a new Sandbox object.
486
-
487
- This method does not interrupt or wait for running concurrent operations on the sandbox. Resources are
488
- promptly closed once those operations complete.
489
- """
490
- ...
491
-
492
- @property
493
- def _client(self) -> modal.client._Client: ...
494
- @_client.setter
495
- def _client(self, value): ...
496
- def _ensure_attached(self): ...
497
- def _ensure_v1(self, method_name: str): ...
498
- @staticmethod
499
- async def from_name(
500
- app_name: str,
501
- name: str,
502
- *,
503
- environment_name: typing.Optional[str] = None,
504
- client: typing.Optional[modal.client._Client] = None,
505
- ) -> _Sandbox:
506
- """Get a running Sandbox by name from a deployed App.
507
-
508
- A Sandbox's name is the `name` argument passed to `Sandbox.create`.
509
-
510
- Args:
511
- app_name: Name of the deployed app to look up the sandbox under.
512
- name: Sandbox name to resolve.
513
- environment_name: Optional environment name for the lookup; defaults to the configured environment.
514
- client: Modal client to use for the RPC; defaults to `Client.from_env()` when omitted.
515
-
516
- Returns:
517
- A `Sandbox` handle for the running sandbox.
518
-
519
- Raises:
520
- NotFoundError: If no running sandbox exists with the given name.
521
- """
522
- ...
523
-
524
- @staticmethod
525
- async def _experimental_from_name(
526
- app_name: str,
527
- name: str,
528
- *,
529
- environment_name: typing.Optional[str] = None,
530
- client: typing.Optional[modal.client._Client] = None,
531
- ) -> _Sandbox:
532
- """Get a running V2 Sandbox by name from a deployed App.
533
-
534
- This looks up V2 sandboxes, ie sandboxes created via `modal.Sandbox._experimental_create`.
535
-
536
- Args:
537
- app_name: Name of the deployed app to look up the sandbox under.
538
- name: Sandbox name to resolve.
539
- environment_name: Optional environment name for the lookup; defaults to the configured environment.
540
- client: Modal client to use for the RPC; defaults to `Client.from_env()` when omitted.
541
-
542
- Returns:
543
- A `Sandbox` handle for the running sandbox.
544
-
545
- Raises:
546
- NotFoundError: If no running sandbox exists with the given name.
547
- """
548
- ...
549
-
550
- @staticmethod
551
- async def from_id(sandbox_id: str, client: typing.Optional[modal.client._Client] = None) -> _Sandbox:
552
- """Construct a Sandbox from an id and look up the Sandbox result.
553
-
554
- The ID of a Sandbox object can be accessed using `.object_id`.
555
-
556
- Args:
557
- sandbox_id: Sandbox object ID to attach to.
558
- client: Modal client to use for the lookup; defaults to the environment client when omitted.
559
-
560
- Returns:
561
- A `Sandbox` handle with any available result metadata populated from the server.
562
- """
563
- ...
564
-
565
- async def get_tags(self) -> dict[str, str]:
566
- """Fetches any tags (key-value pairs) currently attached to this Sandbox from the server.
567
-
568
- Returns:
569
- Tags as a map from tag name to tag value.
570
- """
571
- ...
572
-
573
- async def set_tags(self, tags: dict[str, str], *, client: typing.Optional[modal.client._Client] = None) -> None:
574
- """Set tags (key-value pairs) on the Sandbox. Tags can be used to filter results in `Sandbox.list`.
575
-
576
- Setting tags replaces the Sandbox's entire tag set; passing an empty dict clears all tags.
577
-
578
- Args:
579
- tags: Tag names and values to set on this sandbox.
580
- client: Deprecated. Prefer setting the client when creating or re-attaching to the sandbox.
581
- """
582
- ...
583
-
584
- async def _experimental_set_name(self, name: str) -> None:
585
- """Assign a name to a running V2 Sandbox that was created without one.
586
-
587
- This is only supported for V2 sandboxes, ie sandboxes created via
588
- `modal.Sandbox._experimental_create`. A name may only be set once and
589
- only on a Sandbox that has never had one; afterwards the Sandbox can
590
- be looked up with `Sandbox._experimental_from_name(app_name, name)`.
591
- Repeating the Sandbox's current name is idempotent and succeeds, so
592
- the call is safe to retry.
593
-
594
- Args:
595
- name: Name to assign to the Sandbox. Must be unique within the App.
596
-
597
- Raises:
598
- AlreadyExistsError: If another running Sandbox in the App already holds the name.
599
- ConflictError: If the Sandbox already has a different name or is no longer running.
600
- """
601
- ...
602
-
603
- async def _experimental_set_outbound_network_policy(
604
- self,
605
- *,
606
- outbound_cidr_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
607
- outbound_domain_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
608
- ) -> None:
609
- """Replace the outbound network policy of a running Sandbox.
610
-
611
- Established connections that the new policy no longer permits are
612
- terminated.
613
-
614
- Args:
615
- outbound_cidr_allowlist: List of CIDRs the Sandbox is allowed to access. If None, all CIDRs are allowed.
616
- outbound_domain_allowlist: List of domain names the Sandbox is allowed to access. Supports
617
- wildcard prefixes (``*.``); a bare ``"*"`` allows all domains.
618
- """
619
- ...
620
-
621
- async def _experimental_update_outbound_policy(
622
- self, outbound_policy: modal._outbound_policy._OutboundPolicy
623
- ) -> None:
624
- """Replace the outbound policy of a running Sandbox.
625
-
626
- This API is experimental and may change in the future.
627
-
628
- The new policy replaces all existing policy configuration on the
629
- Sandbox; build a policy including any existing rules you want to keep.
630
-
631
- Only Sandboxes created with an `_experimental_outbound_policy` can be
632
- updated this way; for Sandboxes created without one this fails, since
633
- header replacement is only set up at creation time.
634
-
635
- Args:
636
- outbound_policy: The new policy to apply.
637
- """
638
- ...
639
-
640
- async def _experimental_get_exit_snapshot(self, timeout: typing.Optional[float] = None) -> modal._image._Image:
641
- """Get the exit filesystem snapshot image.
642
-
643
- An exit snapshot captures the Sandbox filesystem when the Sandbox exits,
644
- whether its entrypoint finishes gracefully, abruptly, or it is stopped with
645
- `terminate()`. Exit snapshots are opt-in: the Sandbox must have been
646
- created with `experimental_options={"enable_exit_snapshot": True}`.
647
-
648
- Args:
649
- timeout: Total time to wait in seconds, spread across repeated long
650
- polls of at most `_EXIT_SNAPSHOT_LONG_POLL_TIMEOUT` seconds each.
651
- `None` (the default) waits until the snapshot reaches a terminal
652
- state; `0` performs an immediate check without waiting.
653
-
654
- Returns:
655
- The exit snapshot Image.
656
-
657
- Raises:
658
- InvalidError: If `timeout` is negative, or if exit snapshot is not
659
- enabled for the sandbox.
660
- TimeoutError: If `timeout` elapses before the snapshot reaches a
661
- terminal state. This includes `timeout=0` when the snapshot is
662
- still pending.
663
- SnapshotCreationError: Snapshot operation is done and failed.
664
- Polling again will not produce an Image; filesystem state is gone.
665
- NotFoundError: If the sandbox does not exist.
666
- PermissionDeniedError: If the caller cannot access the sandbox.
667
- """
668
- ...
669
-
670
- async def snapshot_filesystem(
671
- self, timeout: int = 55, *, ttl: typing.Optional[int] = 2592000
672
- ) -> modal._image._Image:
673
- """Snapshot the filesystem of the Sandbox.
674
-
675
- Args:
676
- timeout:
677
- Maximum time in seconds to wait for the snapshot operation. If the snapshot does not return within
678
- that window, the call is cancelled and `modal.exception.TimeoutError` is raised.
679
- ttl:
680
- The resulting Image is retained for `ttl` seconds (default: 30 days). Pass `ttl=None` to retain
681
- the image indefinitely.
682
-
683
- Returns:
684
- An [`Image`](https://modal.com/docs/sdk/py/latest/Image) object which can be used to spawn a new
685
- Sandbox with the same filesystem.
686
- """
687
- ...
688
-
689
- async def _snapshot_filesystem(
690
- self, timeout: int, *, ttl: typing.Optional[int], container_id: str = ""
691
- ) -> modal._image._Image: ...
692
- async def _legacy_snapshot_filesystem(self, timeout: int = 55) -> modal._image._Image: ...
693
- async def mount_image(
694
- self,
695
- path: typing.Union[pathlib.PurePosixPath, str],
696
- image: modal._image._Image,
697
- *,
698
- _experimental_encryption_key: typing.Optional[bytes] = None,
699
- ):
700
- """Mount an Image at a specified path in a running Sandbox.
701
-
702
- `path` should be a directory that is **not** the root path (`/`). If the path doesn't exist
703
- it will be created. If it exists and contains data, the previous directory will be replaced
704
- by the mount.
705
-
706
- The `image` argument supports any Image that has an object ID, including:
707
- - Images built using `image.build()`
708
- - Images referenced by ID, e.g. `Image.from_id(...)`
709
- - Filesystem/directory snapshots, e.g. created by `.snapshot_directory()` or `.snapshot_filesystem()`
710
- - Empty images created with `Image.from_scratch()`
711
-
712
- Args:
713
- path: Absolute mount point directory inside the sandbox (not `/`).
714
- image: Image to mount at `path` (must be built, referenced by ID, or snapshot-based as described above).
715
-
716
-
717
- Examples:
718
- ```py notest
719
- user_project_snapshot: Image = sandbox_session_1.snapshot_directory("/user_project")
720
-
721
- # You can later mount this snapshot to another Sandbox:
722
- sandbox_session_2 = modal.Sandbox.create(...)
723
- sandbox_session_2.mount_image("/user_project", user_project_snapshot)
724
- sandbox_session_2.filesystem.list_files("/user_project")
725
- ```
726
- """
727
- ...
728
-
729
- async def unmount_image(self, path: typing.Union[pathlib.PurePosixPath, str]):
730
- """Unmount a previously mounted Image from a running Sandbox.
731
-
732
- `path` must be the exact mount point that was passed to `.mount_image()`.
733
- After unmounting, the underlying Sandbox filesystem at that path becomes
734
- visible again.
735
-
736
- Args:
737
- path: Absolute mount point directory to unmount.
738
- """
739
- ...
740
-
741
- async def snapshot_directory(
742
- self,
743
- path: typing.Union[pathlib.PurePosixPath, str],
744
- *,
745
- timeout: int = 55,
746
- ttl: typing.Optional[int] = 2592000,
747
- _experimental_encryption_key: typing.Optional[bytes] = None,
748
- ) -> modal._image._Image:
749
- """Snapshot a directory in a running Sandbox, creating a new Image with its content.
750
-
751
- `timeout` If the snapshot does not return within that window, the call is cancelled
752
- and `modal.exception.TimeoutError` is raised.
753
-
754
- `ttl` The resulting Image is retained for `ttl` seconds (default: 30 days)
755
- Pass `ttl=None` to retain the image indefinitely.
756
-
757
- Args:
758
- path: Absolute path of the directory inside the sandbox to snapshot.
759
-
760
- Returns:
761
- An `Image` containing the directory contents.
762
-
763
- Examples:
764
- ```py notest
765
- user_project_snapshot: Image = sandbox_session_1.snapshot_directory("/user_project")
766
-
767
- # You can later mount this snapshot to another Sandbox:
768
- sandbox_session_2 = modal.Sandbox.create(...)
769
- sandbox_session_2.mount_image("/user_project", user_project_snapshot)
770
- sandbox_session_2.filesystem.list_files("/user_project")
771
- ```
772
- """
773
- ...
774
-
775
- async def wait(self, raise_on_termination: bool = True):
776
- """Wait for the Sandbox to finish running.
777
-
778
- Args:
779
- raise_on_termination: If True, raise when the sandbox is terminated externally.
780
- """
781
- ...
782
-
783
- async def wait_until_ready(self, *, timeout: int = 300) -> None:
784
- """Wait for the Sandbox readiness probe to report that the Sandbox is ready.
785
-
786
- The Sandbox must be configured with a `readiness_probe` in order to use this method.
787
-
788
- Args:
789
- timeout: Maximum time in seconds to wait for readiness.
790
-
791
-
792
- Examples:
793
- ```py notest
794
- app = modal.App.lookup('sandbox-wait-until-ready', create_if_missing=True)
795
- sandbox = modal.Sandbox.create(
796
- "python3", "-m", "http.server", "8080",
797
- readiness_probe=modal.Probe.with_tcp(8080),
798
- app=app,
799
- )
800
- sandbox.wait_until_ready()
801
- ```
802
- """
803
- ...
804
-
805
- async def tunnels(self, timeout: int = 50) -> dict[int, modal._tunnel.Tunnel]:
806
- """Get Tunnel metadata for the sandbox.
807
-
808
- NOTE: Previous to client [v0.64.153](https://modal.com/docs/sdk/py/changelog#064153-2024-09-30), this
809
- returned a list of `TunnelData` objects.
810
-
811
- Args:
812
- timeout: Maximum time in seconds to wait for tunnel metadata when not already cached.
813
-
814
- Returns:
815
- A dictionary mapping container port to `Tunnel` metadata.
816
-
817
- Raises:
818
- SandboxTimeoutError: If the tunnels are not available after the timeout.
819
- """
820
- ...
821
-
822
- async def create_connect_token(
823
- self, user_metadata: typing.Union[str, dict[str, typing.Any], None] = None, port: int = 8080
824
- ) -> modal.types.SandboxConnectCredentials:
825
- """Create a token for making HTTP connections to the Sandbox.
826
-
827
- Accepts an optional user_metadata string or dict to associate with the token. This metadata
828
- will be added to the headers by the proxy when forwarding requests to the Sandbox.
829
- Also accepts a port that requests will be routed to.
830
-
831
- Args:
832
- user_metadata: Optional JSON-serializable metadata or string stored with the connect token.
833
- port: Optional container port that requests are routed to when using this token.
834
-
835
- Returns:
836
- URL and token credentials for connecting to the sandbox over HTTP.
837
- """
838
- ...
839
-
840
- async def reload_volumes(self, *, timeout: int = 55) -> None:
841
- """Reload all Volumes mounted in the Sandbox.
842
-
843
- Added in v1.1.0.
844
-
845
- Blocks until the reload completes, or raises `modal.exception.TimeoutError` on timeout (the reload
846
- may still complete in the background).
847
-
848
- Args:
849
- timeout: Defaults to 55 seconds.
850
- """
851
- ...
852
-
853
- async def _reload_volumes(self, *, timeout: int, container_id: str = "") -> None: ...
854
- @typing.overload
855
- async def terminate(self, *, wait: typing.Literal[True]) -> int: ...
856
- @typing.overload
857
- async def terminate(self, *, wait: typing.Literal[False] = False) -> None: ...
858
- async def poll(self) -> typing.Optional[int]:
859
- """Check if the Sandbox has finished running.
860
-
861
- Returns:
862
- `None` if the Sandbox is still running, otherwise the exit code.
863
- """
864
- ...
865
-
866
- async def _get_task_id(
867
- self, raise_if_task_complete=False, timeout: typing.Optional[float] = None, retry_transient: bool = False
868
- ) -> str: ...
869
- async def _resolve_task_id_for_logs(self) -> str:
870
- """Try to resolve the task ID in one shot for historical log fetches.
871
-
872
- For a historical log api, we should not wait for the task to be created.
873
- If the Sandbox created logs worth fetching, the task ID should be available.
874
- """
875
- ...
876
-
877
- async def _get_command_router_client(
878
- self, task_id: str
879
- ) -> modal._utils.task_command_router_client.TaskCommandRouterClient: ...
880
- @property
881
- def _experimental_sidecars(self) -> _SidecarManager:
882
- """Manage sidecar containers running in this Sandbox."""
883
- ...
884
-
885
- @typing.overload
886
- async def exec(
887
- self,
888
- *args: str,
889
- stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
890
- stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
891
- timeout: typing.Optional[int] = None,
892
- workdir: typing.Optional[str] = None,
893
- env: typing.Optional[dict[str, typing.Optional[str]]] = None,
894
- secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
895
- text: typing.Literal[True] = True,
896
- bufsize: typing.Literal[-1, 1] = -1,
897
- pty: bool = False,
898
- pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
899
- _pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
900
- ) -> modal.container_process._ContainerProcess[str]: ...
901
- @typing.overload
902
- async def exec(
903
- self,
904
- *args: str,
905
- stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
906
- stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
907
- timeout: typing.Optional[int] = None,
908
- workdir: typing.Optional[str] = None,
909
- env: typing.Optional[dict[str, typing.Optional[str]]] = None,
910
- secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
911
- text: typing.Literal[False] = False,
912
- bufsize: typing.Literal[-1, 1] = -1,
913
- pty: bool = False,
914
- pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
915
- _pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
916
- ) -> modal.container_process._ContainerProcess[bytes]: ...
917
- async def _exec(
918
- self,
919
- *args: str,
920
- pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
921
- stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
922
- stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
923
- timeout: typing.Optional[int] = None,
924
- workdir: typing.Optional[str] = None,
925
- env: typing.Optional[dict[str, typing.Optional[str]]] = None,
926
- secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
927
- text: bool = True,
928
- bufsize: typing.Literal[-1, 1] = -1,
929
- container_id: typing.Optional[str] = None,
930
- ) -> typing.Union[modal.container_process._ContainerProcess[bytes], modal.container_process._ContainerProcess[str]]:
931
- """Private method used internally.
932
-
933
- This method exposes some internal arguments (currently `pty_info`) which are not in the public API.
934
- """
935
- ...
936
-
937
- async def _exec_through_command_router(
938
- self,
939
- *args: str,
940
- task_id: str,
941
- command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient,
942
- pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
943
- stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
944
- stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
945
- timeout: typing.Optional[int] = None,
946
- workdir: typing.Optional[str] = None,
947
- secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
948
- env: typing.Optional[dict[str, str]] = None,
949
- text: bool = True,
950
- bufsize: typing.Literal[-1, 1] = -1,
951
- runtime_debug: bool = False,
952
- container_id: typing.Optional[str] = None,
953
- ) -> typing.Union[modal.container_process._ContainerProcess[bytes], modal.container_process._ContainerProcess[str]]:
954
- """Execute a command through a task command router running on the Modal worker."""
955
- ...
956
-
957
- async def _experimental_snapshot(self) -> modal.snapshot._SandboxSnapshot: ...
958
- @staticmethod
959
- async def _experimental_from_snapshot(
960
- snapshot: modal.snapshot._SandboxSnapshot,
961
- client: typing.Optional[modal.client._Client] = None,
962
- *,
963
- name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
964
- ):
965
- """Restore a Sandbox from a memory snapshot.
966
-
967
- The restore targets the same backend the snapshot was taken from. A V1
968
- snapshot restores as a V1 sandbox, a V2 snapshot as a V2 sandbox.
969
- """
970
- ...
971
-
972
- @staticmethod
973
- async def _experimental_from_snapshot_v2(
974
- snapshot: modal.snapshot._SandboxSnapshot,
975
- client: modal.client._Client,
976
- *,
977
- override_type: int,
978
- name_override: typing.Optional[str],
979
- ) -> _Sandbox: ...
980
- @property
981
- def filesystem(self) -> modal.sandbox_fs._SandboxFilesystem:
982
- """Namespace for Sandbox filesystem APIs."""
983
- ...
984
-
985
- @property
986
- def stdout(self) -> modal.io_streams._StreamReader[str]:
987
- """[`StreamReader`](https://modal.com/docs/sdk/py/latest/io_streams#streamreader)
988
- for the sandbox's stdout stream.
989
-
990
- Returns:
991
- Stream reader for sandbox stdout.
992
- """
993
- ...
994
-
995
- @property
996
- def stderr(self) -> modal.io_streams._StreamReader[str]:
997
- """[`StreamReader`](https://modal.com/docs/sdk/py/latest/io_streams#streamreader)
998
- for the Sandbox's stderr stream.
999
-
1000
- Returns:
1001
- Stream reader for sandbox stderr.
1002
- """
1003
- ...
1004
-
1005
- @property
1006
- def stdin(self) -> modal.io_streams._StreamWriter:
1007
- """[`StreamWriter`](https://modal.com/docs/sdk/py/latest/io_streams#streamwriter)
1008
- for the Sandbox's stdin stream.
1009
-
1010
- Returns:
1011
- Stream writer for sandbox stdin.
1012
- """
1013
- ...
1014
-
1015
- @property
1016
- def returncode(self) -> typing.Optional[int]:
1017
- """Return code of the Sandbox process if it has finished running, else `None`.
1018
-
1019
- Returns:
1020
- Exit code when the sandbox process has completed, otherwise None.
1021
- """
1022
- ...
1023
-
1024
- @staticmethod
1025
- def list(
1026
- *,
1027
- app_id: typing.Optional[str] = None,
1028
- tags: typing.Optional[dict[str, str]] = None,
1029
- client: typing.Optional[modal.client._Client] = None,
1030
- ) -> collections.abc.AsyncGenerator[_Sandbox, None]:
1031
- """List all Sandboxes for the current Environment or App ID (if specified). If tags are specified, only
1032
- Sandboxes that have at least those tags are returned.
1033
-
1034
- Args:
1035
- app_id: If set, restrict results to sandboxes under this app ID.
1036
- tags: If set, only sandboxes containing at least these tags are returned.
1037
- client: Modal client to use for listing; defaults to `Client.from_env()` when omitted.
1038
-
1039
- Returns:
1040
- An async generator yielding `Sandbox` objects.
1041
- """
1042
- ...
1043
-
1044
- @staticmethod
1045
- def _experimental_list(
1046
- *,
1047
- app_id: typing.Optional[str] = None,
1048
- tags: typing.Optional[dict[str, str]] = None,
1049
- client: typing.Optional[modal.client._Client] = None,
1050
- ) -> collections.abc.AsyncGenerator[_Sandbox, None]:
1051
- """List all sandboxes (both v1 and v2).
1052
-
1053
- Pass `app_id` to scope to an App; without it, Sandboxes across the current
1054
- Environment are listed (deprecated — prefer scoping by `app_id`).
1055
-
1056
- Args:
1057
- app_id: If set, restrict results to Sandboxes under this App.
1058
- tags: If set, only sandboxes containing at least these tags are returned.
1059
- client: Optional client to use for the session.
1060
-
1061
- Yields:
1062
- `Sandbox` objects that are currently running.
1063
- """
1064
- ...
1065
-
1066
- async def _get_log_query_data(self) -> modal._supports_logs._LogQueryData: ...
1067
- @property
1068
- def logs(self) -> modal._logs_manager._SandboxLogsManager:
1069
- """Access logs for a `Sandbox` entrypoint.
1070
-
1071
- Useful for inspecting logs after a Sandbox terminates.
1072
- Use [`fetch()`](#logsfetch)
1073
- to read logs from a UTC time range, [`tail()`](#logstail)
1074
- to read the most recent logs.
1075
-
1076
- Note that the logs from executed commands in the sandbox (via `exec()`) are not included in the
1077
- entrypoint logs.
1078
-
1079
- See also:
1080
- - [`modal app logs`](https://modal.com/docs/cli/latest/app#modal-app-logs):
1081
- CLI access to logs for an App.
1082
- """
1083
- ...
1084
-
1085
- class _SidecarContainer:
1086
- """Handle to an additional container running in a Sandbox."""
1087
-
1088
- _result: typing.Optional[modal_proto.api_pb2.GenericResult]
1089
- _filesystem: typing.Optional[modal.sandbox_fs._SandboxFilesystem]
1090
-
1091
- def __init__(
1092
- self,
1093
- sandbox: _Sandbox,
1094
- container_id: str,
1095
- container_name: str,
1096
- result: typing.Optional[modal_proto.api_pb2.GenericResult] = None,
1097
- ) -> None:
1098
- """Initialize self. See help(type(self)) for accurate signature."""
1099
- ...
1100
-
1101
- @property
1102
- def object_id(self) -> str: ...
1103
- @property
1104
- def name(self) -> str: ...
1105
- @staticmethod
1106
- def _from_container_info(
1107
- sandbox: _Sandbox, container_info: modal_proto.task_command_router_pb2.TaskContainerInfo
1108
- ) -> _SidecarContainer: ...
1109
- async def _get_command_router(self) -> tuple[str, modal._utils.task_command_router_client.TaskCommandRouterClient]:
1110
- """Get task ID and command router client."""
1111
- ...
1112
-
1113
- async def snapshot_filesystem(
1114
- self, timeout: int = 55, *, ttl: typing.Optional[int] = 2592000
1115
- ) -> modal._image._Image:
1116
- """Snapshot this Sidecar container's filesystem.
1117
-
1118
- Args:
1119
- timeout:
1120
- Maximum time in seconds to wait for the snapshot operation.
1121
- ttl:
1122
- The resulting Image is retained for `ttl` seconds (default: 30 days). Pass `ttl=None` to retain
1123
- the image indefinitely.
1124
-
1125
- Returns:
1126
- An [`Image`](https://modal.com/docs/sdk/py/latest/Image) containing a snapshot of this Sidecar's
1127
- filesystem.
1128
- """
1129
- ...
1130
-
1131
- @typing.overload
1132
- async def exec(
1133
- self,
1134
- *args: str,
1135
- stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1136
- stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1137
- timeout: typing.Optional[int] = None,
1138
- workdir: typing.Optional[str] = None,
1139
- env: typing.Optional[dict[str, typing.Optional[str]]] = None,
1140
- secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
1141
- text: typing.Literal[True] = True,
1142
- bufsize: typing.Literal[-1, 1] = -1,
1143
- pty: bool = False,
1144
- ) -> modal.container_process._ContainerProcess[str]: ...
1145
- @typing.overload
1146
- async def exec(
1147
- self,
1148
- *args: str,
1149
- stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1150
- stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1151
- timeout: typing.Optional[int] = None,
1152
- workdir: typing.Optional[str] = None,
1153
- env: typing.Optional[dict[str, typing.Optional[str]]] = None,
1154
- secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
1155
- text: typing.Literal[False],
1156
- bufsize: typing.Literal[-1, 1] = -1,
1157
- pty: bool = False,
1158
- ) -> modal.container_process._ContainerProcess[bytes]: ...
1159
- @property
1160
- def filesystem(self) -> modal.sandbox_fs._SandboxFilesystem:
1161
- """Namespace for Sandbox filesystem APIs."""
1162
- ...
1163
-
1164
- async def mount_image(
1165
- self,
1166
- path: typing.Union[pathlib.PurePosixPath, str],
1167
- image: modal._image._Image,
1168
- *,
1169
- _experimental_encryption_key: typing.Optional[bytes] = None,
1170
- ) -> None:
1171
- """Mount an Image at a specified path in this Sidecar container.
1172
-
1173
- `path` should be a directory that is **not** the root path (`/`). If the path doesn't exist,
1174
- it will be created. If it exists and contains data, the previous directory will be replaced
1175
- by the mount.
1176
-
1177
- The `image` argument supports any Image that has an object ID, including:
1178
- - Images built using `image.build()`
1179
- - Images referenced by ID, e.g. `Image.from_id(...)`
1180
- - Filesystem/directory snapshots, e.g. created by `.snapshot_directory()` or `.snapshot_filesystem()`
1181
- - Empty images created with `Image.from_scratch()`
1182
-
1183
- Args:
1184
- path: Absolute mount point directory inside the Sidecar container (not `/`).
1185
- image: Image to mount at `path` (must be built, referenced by ID, or snapshot-based as described above).
1186
-
1187
- Examples:
1188
- ```py notest
1189
- sidecar_1.mount_image("/workspace", modal.Image.from_scratch())
1190
- workspace_snapshot = sidecar_1.snapshot_directory("/workspace")
1191
-
1192
- # You can later mount this snapshot in another Sidecar:
1193
- sidecar_2.mount_image("/workspace", workspace_snapshot)
1194
- sidecar_2.filesystem.list_files("/workspace")
1195
- ```
1196
- """
1197
- ...
1198
-
1199
- async def unmount_image(self, path: typing.Union[pathlib.PurePosixPath, str]) -> None:
1200
- """Unmount a previously mounted Image from this Sidecar container.
1201
-
1202
- `path` must be the exact mount point that was passed to `.mount_image()`.
1203
- After unmounting, the underlying Sidecar filesystem at that path becomes
1204
- visible again.
1205
-
1206
- Args:
1207
- path: Absolute mount point directory to unmount.
1208
- """
1209
- ...
1210
-
1211
- async def snapshot_directory(
1212
- self,
1213
- path: typing.Union[pathlib.PurePosixPath, str],
1214
- *,
1215
- timeout: int = 55,
1216
- ttl: typing.Optional[int] = 2592000,
1217
- _experimental_encryption_key: typing.Optional[bytes] = None,
1218
- ) -> modal._image._Image:
1219
- """Snapshot a directory in this Sidecar container, creating a new Image with its content.
1220
-
1221
- `timeout` If the snapshot does not return within that window, the call is cancelled
1222
- and `modal.exception.TimeoutError` is raised.
1223
-
1224
- `ttl` The resulting Image is retained for `ttl` seconds (default: 30 days).
1225
- Pass `ttl=None` to retain the Image indefinitely.
1226
-
1227
- The returned Image can be used anywhere an Image is accepted, including
1228
- as a mount or as the base filesystem for another container.
1229
-
1230
- Args:
1231
- path: Absolute path of the directory inside the Sidecar container to snapshot.
1232
-
1233
- Returns:
1234
- An `Image` containing the directory contents.
1235
-
1236
- Examples:
1237
- ```py notest
1238
- workspace_snapshot = sidecar_1.snapshot_directory("/workspace")
1239
-
1240
- # You can later mount this snapshot in another Sidecar:
1241
- sidecar_2.mount_image("/workspace", workspace_snapshot)
1242
- sidecar_2.filesystem.list_files("/workspace")
1243
- ```
1244
- """
1245
- ...
1246
-
1247
- async def wait(self, raise_on_termination: bool = True) -> None: ...
1248
- async def poll(self) -> typing.Optional[int]: ...
1249
- @typing.overload
1250
- async def terminate(self, *, wait: typing.Literal[True]) -> int: ...
1251
- @typing.overload
1252
- async def terminate(self, *, wait: typing.Literal[False] = False) -> None: ...
1253
- async def reload_volumes(self, *, timeout: int = 55) -> None:
1254
- """Reload all Volumes mounted in this sidecar container.
1255
-
1256
- EXPERIMENTAL: the API is subject to change.
1257
-
1258
- Blocks until the reload completes, or raises `modal.exception.TimeoutError` on timeout (the reload
1259
- may still complete in the background).
1260
-
1261
- Args:
1262
- timeout: Defaults to 55 seconds.
1263
- """
1264
- ...
1265
-
1266
- def _use_control_plane_sidecar_create(is_v2: bool) -> bool:
1267
- """Whether a sidecar create request goes to the Modal server rather than over the Sandbox connection.
1268
-
1269
- V2 Sandboxes do unless opted out via the `use_control_plane_sidecar_create` config
1270
- setting (`MODAL_USE_CONTROL_PLANE_SIDECAR_CREATE=0`). V1 Sandboxes always create
1271
- sidecars over the Sandbox connection.
1272
- """
1273
- ...
1274
-
1275
- class _SidecarManager:
1276
- """Creates and manages sidecar containers in a Sandbox."""
1277
- def __init__(self, sandbox: _Sandbox) -> None:
1278
- """Initialize self. See help(type(self)) for accurate signature."""
1279
- ...
1280
-
1281
- async def _get_command_router(self) -> tuple[str, modal._utils.task_command_router_client.TaskCommandRouterClient]:
1282
- """Get task ID and command router client."""
1283
- ...
1284
-
1285
- async def create(
1286
- self,
1287
- *args: str,
1288
- name: str,
1289
- image: modal._image._Image,
1290
- env: typing.Optional[dict[str, str]] = None,
1291
- secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
1292
- workdir: typing.Optional[str] = None,
1293
- volumes: typing.Optional[
1294
- dict[
1295
- typing.Union[str, os.PathLike],
1296
- typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
1297
- ]
1298
- ] = None,
1299
- outbound_cidr_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
1300
- outbound_domain_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
1301
- include_oidc_identity_token: bool = False,
1302
- proxy: typing.Optional[modal.proxy._Proxy] = None,
1303
- pty: bool = False,
1304
- experimental_memory_reserve_consume_mib: typing.Optional[int] = None,
1305
- ) -> _SidecarContainer:
1306
- """Create a sidecar container running alongside the Sandbox's main container.
1307
-
1308
- Sidecar containers share the Sandbox's lifecycle but run their own Image and command. They
1309
- can be used to run auxiliary processes, such as a database or a service the main container
1310
- depends on.
1311
-
1312
- The sidecar's outbound network policy is independent of the main container's and defaults to
1313
- open network access. To restrict it, pass `outbound_cidr_allowlist` and/or
1314
- `outbound_domain_allowlist`. To block all external egress while keeping connectivity to the
1315
- main container, pass an empty allowlist (`outbound_cidr_allowlist=[]`); a fully network-blocked
1316
- sidecar is not supported because it would have no IP and could not reach the main container.
1317
-
1318
- Args:
1319
- *args: Command and arguments to run inside the sidecar container.
1320
- name: Unique name for the sidecar container. The name ``"main"`` is reserved.
1321
- image: Image to run the sidecar container with. Must be a pre-built or referenced Image.
1322
- env: Environment variables to set in the sidecar container.
1323
- secrets: Secrets to inject as environment variables in the sidecar container.
1324
- workdir: Working directory for the command; must be absolute if set.
1325
- volumes: Mapping of mount paths to `Volume` or `CloudBucketMount` objects to mount in the
1326
- sidecar container. Cloud bucket mounts are not supported for GPU Sandboxes.
1327
- outbound_cidr_allowlist: If set, restrict the sidecar's outbound traffic to these CIDR
1328
- blocks. An empty list blocks all external egress while preserving connectivity to the
1329
- main container.
1330
- outbound_domain_allowlist: If set, restrict the sidecar's outbound TLS connections (port
1331
- 443) to these SNI domains. Supports wildcards like ``*.example.com``.
1332
- include_oidc_identity_token: If True, the sidecar receives a MODAL_IDENTITY_TOKEN env var for
1333
- OIDC-based auth (e.g. to AWS, GCP). The token identifies the sidecar container itself,
1334
- not the main container. Not supported for GPU Sandboxes.
1335
- proxy: Reference to a Modal Proxy to use in front of this sidecar. Not supported for GPU
1336
- Sandboxes.
1337
- pty: Whether to enable PTY for the sidecar container.
1338
- experimental_memory_reserve_consume_mib: Memory, in MiB, this sidecar consumes from the Sandbox's
1339
- sidecar memory reserve (the experimental `vm_sidecar_memory_reserve_mib` option).
1340
- Unset consumes whatever is left of the reserve; creation fails if the request exceeds
1341
- what is left. Ignored by Sandboxes without a reserve.
1342
-
1343
- Returns:
1344
- A `SidecarContainer` handle for the running container.
1345
- """
1346
- ...
1347
-
1348
- async def get(self, *, name: str, include_terminated: bool = False) -> _SidecarContainer: ...
1349
- async def list(self, include_terminated: bool = False) -> list[_SidecarContainer]: ...
1350
-
1351
- class ___container_exec_spec(typing_extensions.Protocol):
1352
- def __call__(
1353
- self,
1354
- /,
1355
- pty: bool,
1356
- container_id: str = "",
1357
- command: tuple[str, ...] = (),
1358
- sandbox_id_v2: typing.Optional[str] = None,
1359
- ):
1360
- """Execute a command in a container.
1361
-
1362
- For tasks belonging to V2 sandboxes, `sandbox_id_v2` may be set to the
1363
- sandbox ID; otherwise the V2 backend is used whenever the task ID itself
1364
- marks a V2 sandbox task.
1365
- """
1366
- ...
1367
-
1368
- async def aio(
1369
- self,
1370
- /,
1371
- pty: bool,
1372
- container_id: str = "",
1373
- command: tuple[str, ...] = (),
1374
- sandbox_id_v2: typing.Optional[str] = None,
1375
- ):
1376
- """Execute a command in a container.
1377
-
1378
- For tasks belonging to V2 sandboxes, `sandbox_id_v2` may be set to the
1379
- sandbox ID; otherwise the V2 backend is used whenever the task ID itself
1380
- marks a V2 sandbox task.
1381
- """
1382
- ...
1383
-
1384
- _container_exec: ___container_exec_spec
1385
-
1386
33
  class SidecarContainer:
1387
34
  """Handle to an additional container running in a Sandbox."""
1388
35
 
@@ -1968,7 +615,7 @@ class Sandbox(modal.object.Object):
1968
615
  h2_ports: collections.abc.Sequence[int] = [],
1969
616
  unencrypted_ports: collections.abc.Sequence[int] = [],
1970
617
  proxy: typing.Optional[modal.proxy.Proxy] = None,
1971
- readiness_probe: typing.Optional[Probe] = None,
618
+ readiness_probe: typing.Optional[modal.sandbox._sandbox.Probe] = None,
1972
619
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
1973
620
  tags: typing.Optional[dict[str, str]] = None,
1974
621
  enable_snapshot: bool = False,
@@ -2019,7 +666,7 @@ class Sandbox(modal.object.Object):
2019
666
  custom_domain: typing.Optional[str] = None,
2020
667
  proxy: typing.Optional[modal.proxy.Proxy] = None,
2021
668
  include_oidc_identity_token: bool = False,
2022
- readiness_probe: typing.Optional[Probe] = None,
669
+ readiness_probe: typing.Optional[modal.sandbox._sandbox.Probe] = None,
2023
670
  verbose: bool = False,
2024
671
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
2025
672
  _experimental_enable_snapshot: bool = False,
@@ -2143,7 +790,7 @@ class Sandbox(modal.object.Object):
2143
790
  custom_domain: typing.Optional[str] = None,
2144
791
  proxy: typing.Optional[modal.proxy.Proxy] = None,
2145
792
  include_oidc_identity_token: bool = False,
2146
- readiness_probe: typing.Optional[Probe] = None,
793
+ readiness_probe: typing.Optional[modal.sandbox._sandbox.Probe] = None,
2147
794
  verbose: bool = False,
2148
795
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
2149
796
  _experimental_enable_snapshot: bool = False,
@@ -2270,7 +917,7 @@ class Sandbox(modal.object.Object):
2270
917
  unencrypted_ports: collections.abc.Sequence[int] = [],
2271
918
  proxy: typing.Optional[modal.proxy.Proxy] = None,
2272
919
  include_oidc_identity_token: bool = False,
2273
- readiness_probe: typing.Optional[Probe] = None,
920
+ readiness_probe: typing.Optional[modal.sandbox._sandbox.Probe] = None,
2274
921
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
2275
922
  _experimental_enable_snapshot: bool = False,
2276
923
  client: typing.Optional[modal.client.Client] = None,
@@ -2324,7 +971,7 @@ class Sandbox(modal.object.Object):
2324
971
  unencrypted_ports: collections.abc.Sequence[int] = [],
2325
972
  proxy: typing.Optional[modal.proxy.Proxy] = None,
2326
973
  include_oidc_identity_token: bool = False,
2327
- readiness_probe: typing.Optional[Probe] = None,
974
+ readiness_probe: typing.Optional[modal.sandbox._sandbox.Probe] = None,
2328
975
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
2329
976
  _experimental_enable_snapshot: bool = False,
2330
977
  client: typing.Optional[modal.client.Client] = None,
@@ -2377,7 +1024,7 @@ class Sandbox(modal.object.Object):
2377
1024
  h2_ports: collections.abc.Sequence[int] = [],
2378
1025
  unencrypted_ports: collections.abc.Sequence[int] = [],
2379
1026
  proxy: typing.Optional[modal.proxy.Proxy] = None,
2380
- readiness_probe: typing.Optional[Probe] = None,
1027
+ readiness_probe: typing.Optional[modal.sandbox._sandbox.Probe] = None,
2381
1028
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
2382
1029
  include_oidc_identity_token: bool = False,
2383
1030
  verbose: bool = False,
@@ -2451,7 +1098,7 @@ class Sandbox(modal.object.Object):
2451
1098
  h2_ports: collections.abc.Sequence[int] = [],
2452
1099
  unencrypted_ports: collections.abc.Sequence[int] = [],
2453
1100
  proxy: typing.Optional[modal.proxy.Proxy] = None,
2454
- readiness_probe: typing.Optional[Probe] = None,
1101
+ readiness_probe: typing.Optional[modal.sandbox._sandbox.Probe] = None,
2455
1102
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
2456
1103
  include_oidc_identity_token: bool = False,
2457
1104
  verbose: bool = False,
@@ -3526,8 +2173,7 @@ class Sandbox(modal.object.Object):
3526
2173
  stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
3527
2174
  timeout: typing.Optional[int] = None,
3528
2175
  workdir: typing.Optional[str] = None,
3529
- secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
3530
- env: typing.Optional[dict[str, str]] = None,
2176
+ secret_sources: typing.Optional[collections.abc.Collection[modal_proto.api_pb2.SecretSource]] = None,
3531
2177
  text: bool = True,
3532
2178
  bufsize: typing.Literal[-1, 1] = -1,
3533
2179
  runtime_debug: bool = False,
@@ -3549,8 +2195,7 @@ class Sandbox(modal.object.Object):
3549
2195
  stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
3550
2196
  timeout: typing.Optional[int] = None,
3551
2197
  workdir: typing.Optional[str] = None,
3552
- secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
3553
- env: typing.Optional[dict[str, str]] = None,
2198
+ secret_sources: typing.Optional[collections.abc.Collection[modal_proto.api_pb2.SecretSource]] = None,
3554
2199
  text: bool = True,
3555
2200
  bufsize: typing.Literal[-1, 1] = -1,
3556
2201
  runtime_debug: bool = False,
@@ -3576,7 +2221,7 @@ class Sandbox(modal.object.Object):
3576
2221
  snapshot: modal.snapshot.SandboxSnapshot,
3577
2222
  client: typing.Optional[modal.client.Client] = None,
3578
2223
  *,
3579
- name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
2224
+ name: typing.Optional[str] = modal.sandbox._sandbox._DEFAULT_SANDBOX_NAME_OVERRIDE,
3580
2225
  ):
3581
2226
  """Restore a Sandbox from a memory snapshot.
3582
2227
 
@@ -3591,7 +2236,7 @@ class Sandbox(modal.object.Object):
3591
2236
  snapshot: modal.snapshot.SandboxSnapshot,
3592
2237
  client: typing.Optional[modal.client.Client] = None,
3593
2238
  *,
3594
- name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
2239
+ name: typing.Optional[str] = modal.sandbox._sandbox._DEFAULT_SANDBOX_NAME_OVERRIDE,
3595
2240
  ):
3596
2241
  """Restore a Sandbox from a memory snapshot.
3597
2242
 
@@ -3786,10 +2431,37 @@ class Sandbox(modal.object.Object):
3786
2431
  """
3787
2432
  ...
3788
2433
 
3789
- _default_image: modal._image._Image
2434
+ class ___container_exec_spec(typing_extensions.Protocol):
2435
+ def __call__(
2436
+ self,
2437
+ /,
2438
+ pty: bool,
2439
+ container_id: str = "",
2440
+ command: tuple[str, ...] = (),
2441
+ sandbox_id_v2: typing.Optional[str] = None,
2442
+ ):
2443
+ """Execute a command in a container.
2444
+
2445
+ For tasks belonging to V2 sandboxes, `sandbox_id_v2` may be set to the
2446
+ sandbox ID; otherwise the V2 backend is used whenever the task ID itself
2447
+ marks a V2 sandbox task.
2448
+ """
2449
+ ...
3790
2450
 
3791
- _SANDBOX_SCHEDULING_TIMEOUT: float
2451
+ async def aio(
2452
+ self,
2453
+ /,
2454
+ pty: bool,
2455
+ container_id: str = "",
2456
+ command: tuple[str, ...] = (),
2457
+ sandbox_id_v2: typing.Optional[str] = None,
2458
+ ):
2459
+ """Execute a command in a container.
3792
2460
 
3793
- _TASK_ID_POLL_INTERVAL: float
2461
+ For tasks belonging to V2 sandboxes, `sandbox_id_v2` may be set to the
2462
+ sandbox ID; otherwise the V2 backend is used whenever the task ID itself
2463
+ marks a V2 sandbox task.
2464
+ """
2465
+ ...
3794
2466
 
3795
- _MAIN_CONTAINER_NAME: str
2467
+ _container_exec: ___container_exec_spec