modal 1.1.5.dev19__py3-none-any.whl → 1.1.5.dev21__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.
Potentially problematic release.
This version of modal might be problematic. Click here for more details.
- modal/_functions.py +12 -4
- modal/_pty.py +7 -3
- modal/app.py +12 -3
- modal/app.pyi +8 -4
- modal/client.pyi +2 -2
- modal/cls.py +7 -2
- modal/cls.pyi +7 -5
- modal/functions.pyi +6 -2
- modal/image.py +85 -15
- modal/image.pyi +54 -28
- modal/runner.py +2 -2
- modal/sandbox.py +112 -46
- modal/sandbox.pyi +148 -29
- modal/secret.py +1 -1
- {modal-1.1.5.dev19.dist-info → modal-1.1.5.dev21.dist-info}/METADATA +1 -1
- {modal-1.1.5.dev19.dist-info → modal-1.1.5.dev21.dist-info}/RECORD +21 -21
- modal_version/__init__.py +1 -1
- {modal-1.1.5.dev19.dist-info → modal-1.1.5.dev21.dist-info}/WHEEL +0 -0
- {modal-1.1.5.dev19.dist-info → modal-1.1.5.dev21.dist-info}/entry_points.txt +0 -0
- {modal-1.1.5.dev19.dist-info → modal-1.1.5.dev21.dist-info}/licenses/LICENSE +0 -0
- {modal-1.1.5.dev19.dist-info → modal-1.1.5.dev21.dist-info}/top_level.txt +0 -0
modal/image.pyi
CHANGED
|
@@ -166,7 +166,7 @@ class _Image(modal._object._Object):
|
|
|
166
166
|
[typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"]], DockerfileSpec
|
|
167
167
|
]
|
|
168
168
|
] = None,
|
|
169
|
-
secrets: typing.Optional[collections.abc.
|
|
169
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
170
170
|
gpu_config: typing.Optional[modal_proto.api_pb2.GPUConfig] = None,
|
|
171
171
|
build_function: typing.Optional[modal._functions._Function] = None,
|
|
172
172
|
build_function_input: typing.Optional[modal_proto.api_pb2.FunctionInput] = None,
|
|
@@ -327,7 +327,8 @@ class _Image(modal._object._Object):
|
|
|
327
327
|
pre: bool = False,
|
|
328
328
|
extra_options: str = "",
|
|
329
329
|
force_build: bool = False,
|
|
330
|
-
|
|
330
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
331
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
331
332
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
332
333
|
) -> _Image:
|
|
333
334
|
"""Install a list of Python packages using pip.
|
|
@@ -369,7 +370,8 @@ class _Image(modal._object._Object):
|
|
|
369
370
|
pre: bool = False,
|
|
370
371
|
extra_options: str = "",
|
|
371
372
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
372
|
-
|
|
373
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
374
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
373
375
|
force_build: bool = False,
|
|
374
376
|
) -> _Image:
|
|
375
377
|
"""Install a list of Python packages from private git repositories using pip.
|
|
@@ -414,7 +416,8 @@ class _Image(modal._object._Object):
|
|
|
414
416
|
pre: bool = False,
|
|
415
417
|
extra_options: str = "",
|
|
416
418
|
force_build: bool = False,
|
|
417
|
-
|
|
419
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
420
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
418
421
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
419
422
|
) -> _Image:
|
|
420
423
|
"""Install a list of Python packages from a local `requirements.txt` file."""
|
|
@@ -431,7 +434,8 @@ class _Image(modal._object._Object):
|
|
|
431
434
|
pre: bool = False,
|
|
432
435
|
extra_options: str = "",
|
|
433
436
|
force_build: bool = False,
|
|
434
|
-
|
|
437
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
438
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
435
439
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
436
440
|
) -> _Image:
|
|
437
441
|
"""Install dependencies specified by a local `pyproject.toml` file.
|
|
@@ -454,7 +458,8 @@ class _Image(modal._object._Object):
|
|
|
454
458
|
extra_options: str = "",
|
|
455
459
|
force_build: bool = False,
|
|
456
460
|
uv_version: typing.Optional[str] = None,
|
|
457
|
-
|
|
461
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
462
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
458
463
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
459
464
|
) -> _Image:
|
|
460
465
|
"""Install a list of Python packages using uv pip install.
|
|
@@ -487,7 +492,8 @@ class _Image(modal._object._Object):
|
|
|
487
492
|
only: list[str] = [],
|
|
488
493
|
poetry_version: typing.Optional[str] = "latest",
|
|
489
494
|
old_installer: bool = False,
|
|
490
|
-
|
|
495
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
496
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
491
497
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
492
498
|
) -> _Image:
|
|
493
499
|
"""Install poetry *dependencies* specified by a local `pyproject.toml` file.
|
|
@@ -514,7 +520,8 @@ class _Image(modal._object._Object):
|
|
|
514
520
|
frozen: bool = True,
|
|
515
521
|
extra_options: str = "",
|
|
516
522
|
uv_version: typing.Optional[str] = None,
|
|
517
|
-
|
|
523
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
524
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
518
525
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
519
526
|
) -> _Image:
|
|
520
527
|
"""Creates a virtual environment with the dependencies in a uv managed project with `uv sync`.
|
|
@@ -535,7 +542,8 @@ class _Image(modal._object._Object):
|
|
|
535
542
|
self,
|
|
536
543
|
*dockerfile_commands: typing.Union[str, list[str]],
|
|
537
544
|
context_files: dict[str, str] = {},
|
|
538
|
-
|
|
545
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
546
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
539
547
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
540
548
|
context_mount: typing.Optional[modal.mount._Mount] = None,
|
|
541
549
|
context_dir: typing.Union[str, pathlib.Path, None] = None,
|
|
@@ -597,7 +605,8 @@ class _Image(modal._object._Object):
|
|
|
597
605
|
def run_commands(
|
|
598
606
|
self,
|
|
599
607
|
*commands: typing.Union[str, list[str]],
|
|
600
|
-
|
|
608
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
609
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
601
610
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
602
611
|
force_build: bool = False,
|
|
603
612
|
) -> _Image:
|
|
@@ -615,7 +624,8 @@ class _Image(modal._object._Object):
|
|
|
615
624
|
spec_file: typing.Optional[str] = None,
|
|
616
625
|
channels: list[str] = [],
|
|
617
626
|
force_build: bool = False,
|
|
618
|
-
|
|
627
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
628
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
619
629
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
620
630
|
) -> _Image:
|
|
621
631
|
"""Install a list of additional packages using micromamba."""
|
|
@@ -756,7 +766,8 @@ class _Image(modal._object._Object):
|
|
|
756
766
|
context_mount: typing.Optional[modal.mount._Mount] = None,
|
|
757
767
|
force_build: bool = False,
|
|
758
768
|
context_dir: typing.Union[str, pathlib.Path, None] = None,
|
|
759
|
-
|
|
769
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
770
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
760
771
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
761
772
|
add_python: typing.Optional[str] = None,
|
|
762
773
|
build_args: dict[str, str] = {},
|
|
@@ -824,7 +835,8 @@ class _Image(modal._object._Object):
|
|
|
824
835
|
self,
|
|
825
836
|
*packages: typing.Union[str, list[str]],
|
|
826
837
|
force_build: bool = False,
|
|
827
|
-
|
|
838
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
839
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
828
840
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
829
841
|
) -> _Image:
|
|
830
842
|
"""Install a list of Debian packages using `apt`.
|
|
@@ -841,7 +853,8 @@ class _Image(modal._object._Object):
|
|
|
841
853
|
self,
|
|
842
854
|
raw_f: collections.abc.Callable[..., typing.Any],
|
|
843
855
|
*,
|
|
844
|
-
|
|
856
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
857
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
845
858
|
volumes: dict[
|
|
846
859
|
typing.Union[str, pathlib.PurePosixPath],
|
|
847
860
|
typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
|
|
@@ -1008,7 +1021,7 @@ class Image(modal.object.Object):
|
|
|
1008
1021
|
[typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"]], DockerfileSpec
|
|
1009
1022
|
]
|
|
1010
1023
|
] = None,
|
|
1011
|
-
secrets: typing.Optional[collections.abc.
|
|
1024
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1012
1025
|
gpu_config: typing.Optional[modal_proto.api_pb2.GPUConfig] = None,
|
|
1013
1026
|
build_function: typing.Optional[modal.functions.Function] = None,
|
|
1014
1027
|
build_function_input: typing.Optional[modal_proto.api_pb2.FunctionInput] = None,
|
|
@@ -1178,7 +1191,8 @@ class Image(modal.object.Object):
|
|
|
1178
1191
|
pre: bool = False,
|
|
1179
1192
|
extra_options: str = "",
|
|
1180
1193
|
force_build: bool = False,
|
|
1181
|
-
|
|
1194
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1195
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1182
1196
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
1183
1197
|
) -> Image:
|
|
1184
1198
|
"""Install a list of Python packages using pip.
|
|
@@ -1220,7 +1234,8 @@ class Image(modal.object.Object):
|
|
|
1220
1234
|
pre: bool = False,
|
|
1221
1235
|
extra_options: str = "",
|
|
1222
1236
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
1223
|
-
|
|
1237
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1238
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1224
1239
|
force_build: bool = False,
|
|
1225
1240
|
) -> Image:
|
|
1226
1241
|
"""Install a list of Python packages from private git repositories using pip.
|
|
@@ -1265,7 +1280,8 @@ class Image(modal.object.Object):
|
|
|
1265
1280
|
pre: bool = False,
|
|
1266
1281
|
extra_options: str = "",
|
|
1267
1282
|
force_build: bool = False,
|
|
1268
|
-
|
|
1283
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1284
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1269
1285
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
1270
1286
|
) -> Image:
|
|
1271
1287
|
"""Install a list of Python packages from a local `requirements.txt` file."""
|
|
@@ -1282,7 +1298,8 @@ class Image(modal.object.Object):
|
|
|
1282
1298
|
pre: bool = False,
|
|
1283
1299
|
extra_options: str = "",
|
|
1284
1300
|
force_build: bool = False,
|
|
1285
|
-
|
|
1301
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1302
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1286
1303
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
1287
1304
|
) -> Image:
|
|
1288
1305
|
"""Install dependencies specified by a local `pyproject.toml` file.
|
|
@@ -1305,7 +1322,8 @@ class Image(modal.object.Object):
|
|
|
1305
1322
|
extra_options: str = "",
|
|
1306
1323
|
force_build: bool = False,
|
|
1307
1324
|
uv_version: typing.Optional[str] = None,
|
|
1308
|
-
|
|
1325
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1326
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1309
1327
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
1310
1328
|
) -> Image:
|
|
1311
1329
|
"""Install a list of Python packages using uv pip install.
|
|
@@ -1338,7 +1356,8 @@ class Image(modal.object.Object):
|
|
|
1338
1356
|
only: list[str] = [],
|
|
1339
1357
|
poetry_version: typing.Optional[str] = "latest",
|
|
1340
1358
|
old_installer: bool = False,
|
|
1341
|
-
|
|
1359
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1360
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1342
1361
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
1343
1362
|
) -> Image:
|
|
1344
1363
|
"""Install poetry *dependencies* specified by a local `pyproject.toml` file.
|
|
@@ -1365,7 +1384,8 @@ class Image(modal.object.Object):
|
|
|
1365
1384
|
frozen: bool = True,
|
|
1366
1385
|
extra_options: str = "",
|
|
1367
1386
|
uv_version: typing.Optional[str] = None,
|
|
1368
|
-
|
|
1387
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1388
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1369
1389
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
1370
1390
|
) -> Image:
|
|
1371
1391
|
"""Creates a virtual environment with the dependencies in a uv managed project with `uv sync`.
|
|
@@ -1386,7 +1406,8 @@ class Image(modal.object.Object):
|
|
|
1386
1406
|
self,
|
|
1387
1407
|
*dockerfile_commands: typing.Union[str, list[str]],
|
|
1388
1408
|
context_files: dict[str, str] = {},
|
|
1389
|
-
|
|
1409
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1410
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1390
1411
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
1391
1412
|
context_mount: typing.Optional[modal.mount.Mount] = None,
|
|
1392
1413
|
context_dir: typing.Union[str, pathlib.Path, None] = None,
|
|
@@ -1448,7 +1469,8 @@ class Image(modal.object.Object):
|
|
|
1448
1469
|
def run_commands(
|
|
1449
1470
|
self,
|
|
1450
1471
|
*commands: typing.Union[str, list[str]],
|
|
1451
|
-
|
|
1472
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1473
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1452
1474
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
1453
1475
|
force_build: bool = False,
|
|
1454
1476
|
) -> Image:
|
|
@@ -1466,7 +1488,8 @@ class Image(modal.object.Object):
|
|
|
1466
1488
|
spec_file: typing.Optional[str] = None,
|
|
1467
1489
|
channels: list[str] = [],
|
|
1468
1490
|
force_build: bool = False,
|
|
1469
|
-
|
|
1491
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1492
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1470
1493
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
1471
1494
|
) -> Image:
|
|
1472
1495
|
"""Install a list of additional packages using micromamba."""
|
|
@@ -1607,7 +1630,8 @@ class Image(modal.object.Object):
|
|
|
1607
1630
|
context_mount: typing.Optional[modal.mount.Mount] = None,
|
|
1608
1631
|
force_build: bool = False,
|
|
1609
1632
|
context_dir: typing.Union[str, pathlib.Path, None] = None,
|
|
1610
|
-
|
|
1633
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1634
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1611
1635
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
1612
1636
|
add_python: typing.Optional[str] = None,
|
|
1613
1637
|
build_args: dict[str, str] = {},
|
|
@@ -1675,7 +1699,8 @@ class Image(modal.object.Object):
|
|
|
1675
1699
|
self,
|
|
1676
1700
|
*packages: typing.Union[str, list[str]],
|
|
1677
1701
|
force_build: bool = False,
|
|
1678
|
-
|
|
1702
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1703
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1679
1704
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
1680
1705
|
) -> Image:
|
|
1681
1706
|
"""Install a list of Debian packages using `apt`.
|
|
@@ -1692,7 +1717,8 @@ class Image(modal.object.Object):
|
|
|
1692
1717
|
self,
|
|
1693
1718
|
raw_f: collections.abc.Callable[..., typing.Any],
|
|
1694
1719
|
*,
|
|
1695
|
-
|
|
1720
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1721
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1696
1722
|
volumes: dict[
|
|
1697
1723
|
typing.Union[str, pathlib.PurePosixPath],
|
|
1698
1724
|
typing.Union[modal.volume.Volume, modal.cloud_bucket_mount.CloudBucketMount],
|
modal/runner.py
CHANGED
|
@@ -607,12 +607,12 @@ async def _interactive_shell(
|
|
|
607
607
|
|
|
608
608
|
try:
|
|
609
609
|
if pty:
|
|
610
|
-
container_process = await sandbox.
|
|
610
|
+
container_process = await sandbox._exec(
|
|
611
611
|
*sandbox_cmds, pty_info=get_pty_info(shell=True) if pty else None
|
|
612
612
|
)
|
|
613
613
|
await container_process.attach()
|
|
614
614
|
else:
|
|
615
|
-
container_process = await sandbox.
|
|
615
|
+
container_process = await sandbox._exec(
|
|
616
616
|
*sandbox_cmds, stdout=StreamType.STDOUT, stderr=StreamType.STDOUT
|
|
617
617
|
)
|
|
618
618
|
await container_process.wait()
|
modal/sandbox.py
CHANGED
|
@@ -3,10 +3,11 @@ import asyncio
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
5
|
import time
|
|
6
|
-
from collections.abc import AsyncGenerator, Sequence
|
|
6
|
+
from collections.abc import AsyncGenerator, Collection, Sequence
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from typing import TYPE_CHECKING, Any, AsyncIterator, Literal, Optional, Union, overload
|
|
9
9
|
|
|
10
|
+
from ._pty import get_pty_info
|
|
10
11
|
from .config import config, logger
|
|
11
12
|
|
|
12
13
|
if TYPE_CHECKING:
|
|
@@ -124,11 +125,15 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
124
125
|
_tunnels: Optional[dict[int, Tunnel]] = None
|
|
125
126
|
_enable_snapshot: bool = False
|
|
126
127
|
|
|
128
|
+
@staticmethod
|
|
129
|
+
def _default_pty_info() -> api_pb2.PTYInfo:
|
|
130
|
+
return get_pty_info(shell=True, no_terminate_on_idle_stdin=True)
|
|
131
|
+
|
|
127
132
|
@staticmethod
|
|
128
133
|
def _new(
|
|
129
134
|
args: Sequence[str],
|
|
130
135
|
image: _Image,
|
|
131
|
-
secrets:
|
|
136
|
+
secrets: Collection[_Secret],
|
|
132
137
|
name: Optional[str] = None,
|
|
133
138
|
timeout: int = 300,
|
|
134
139
|
idle_timeout: Optional[int] = None,
|
|
@@ -143,7 +148,8 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
143
148
|
block_network: bool = False,
|
|
144
149
|
cidr_allowlist: Optional[Sequence[str]] = None,
|
|
145
150
|
volumes: dict[Union[str, os.PathLike], Union[_Volume, _CloudBucketMount]] = {},
|
|
146
|
-
|
|
151
|
+
pty: bool = False,
|
|
152
|
+
pty_info: Optional[api_pb2.PTYInfo] = None, # deprecated
|
|
147
153
|
encrypted_ports: Sequence[int] = [],
|
|
148
154
|
h2_ports: Sequence[int] = [],
|
|
149
155
|
unencrypted_ports: Sequence[int] = [],
|
|
@@ -177,6 +183,9 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
177
183
|
cloud_bucket_mounts = [(k, v) for k, v in validated_volumes if isinstance(v, _CloudBucketMount)]
|
|
178
184
|
validated_volumes = [(k, v) for k, v in validated_volumes if isinstance(v, _Volume)]
|
|
179
185
|
|
|
186
|
+
if pty:
|
|
187
|
+
pty_info = _Sandbox._default_pty_info()
|
|
188
|
+
|
|
180
189
|
def _deps() -> list[_Object]:
|
|
181
190
|
deps: list[_Object] = [image] + list(mounts) + list(secrets)
|
|
182
191
|
for _, vol in validated_network_file_systems:
|
|
@@ -279,7 +288,8 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
279
288
|
app: Optional["modal.app._App"] = None,
|
|
280
289
|
name: Optional[str] = None, # Optionally give the sandbox a name. Unique within an app.
|
|
281
290
|
image: Optional[_Image] = None, # The image to run as the container for the sandbox.
|
|
282
|
-
|
|
291
|
+
env: Optional[dict[str, Optional[str]]] = None, # Environment variables to set in the Sandbox.
|
|
292
|
+
secrets: Optional[Collection[_Secret]] = None, # Secrets to inject into the Sandbox as environment variables.
|
|
283
293
|
network_file_systems: dict[Union[str, os.PathLike], _NetworkFileSystem] = {},
|
|
284
294
|
timeout: int = 300, # Maximum lifetime of the sandbox in seconds.
|
|
285
295
|
# The amount of time in seconds that a sandbox can be idle before being terminated.
|
|
@@ -301,7 +311,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
301
311
|
volumes: dict[
|
|
302
312
|
Union[str, os.PathLike], Union[_Volume, _CloudBucketMount]
|
|
303
313
|
] = {}, # Mount points for Modal Volumes and CloudBucketMounts
|
|
304
|
-
|
|
314
|
+
pty: bool = False, # Enable a PTY for the Sandbox
|
|
305
315
|
# List of ports to tunnel into the sandbox. Encrypted ports are tunneled with TLS.
|
|
306
316
|
encrypted_ports: Sequence[int] = [],
|
|
307
317
|
# List of encrypted ports to tunnel into the sandbox, using HTTP/2.
|
|
@@ -320,6 +330,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
320
330
|
] = None, # Experimental controls over fine-grained scheduling (alpha).
|
|
321
331
|
client: Optional[_Client] = None,
|
|
322
332
|
environment_name: Optional[str] = None, # *DEPRECATED* Optionally override the default environment
|
|
333
|
+
pty_info: Optional[api_pb2.PTYInfo] = None, # *DEPRECATED* Use `pty` instead. `pty` will override `pty_info`.
|
|
323
334
|
) -> "_Sandbox":
|
|
324
335
|
"""
|
|
325
336
|
Create a new Sandbox to run untrusted, arbitrary code.
|
|
@@ -342,6 +353,17 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
342
353
|
"A sandbox's environment is determined by the app it is associated with.",
|
|
343
354
|
)
|
|
344
355
|
|
|
356
|
+
if pty_info is not None:
|
|
357
|
+
deprecation_warning(
|
|
358
|
+
(2025, 9, 12),
|
|
359
|
+
"The `pty_info` parameter is deprecated and will be removed in a future release. "
|
|
360
|
+
"Set the `pty` parameter to `True` instead.",
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
secrets = secrets or []
|
|
364
|
+
if env:
|
|
365
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
366
|
+
|
|
345
367
|
return await _Sandbox._create(
|
|
346
368
|
*args,
|
|
347
369
|
app=app,
|
|
@@ -360,7 +382,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
360
382
|
block_network=block_network,
|
|
361
383
|
cidr_allowlist=cidr_allowlist,
|
|
362
384
|
volumes=volumes,
|
|
363
|
-
|
|
385
|
+
pty=pty,
|
|
364
386
|
encrypted_ports=encrypted_ports,
|
|
365
387
|
h2_ports=h2_ports,
|
|
366
388
|
unencrypted_ports=unencrypted_ports,
|
|
@@ -370,59 +392,48 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
370
392
|
_experimental_scheduler_placement=_experimental_scheduler_placement,
|
|
371
393
|
client=client,
|
|
372
394
|
verbose=verbose,
|
|
395
|
+
pty_info=pty_info,
|
|
373
396
|
)
|
|
374
397
|
|
|
375
398
|
@staticmethod
|
|
376
399
|
async def _create(
|
|
377
|
-
*args: str,
|
|
378
|
-
# Associate the sandbox with an app. Required unless creating from a container.
|
|
400
|
+
*args: str,
|
|
379
401
|
app: Optional["modal.app._App"] = None,
|
|
380
|
-
name: Optional[str] = None,
|
|
381
|
-
image: Optional[_Image] = None,
|
|
382
|
-
|
|
402
|
+
name: Optional[str] = None,
|
|
403
|
+
image: Optional[_Image] = None,
|
|
404
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
405
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
383
406
|
mounts: Sequence[_Mount] = (),
|
|
384
407
|
network_file_systems: dict[Union[str, os.PathLike], _NetworkFileSystem] = {},
|
|
385
|
-
timeout: int = 300,
|
|
386
|
-
# The amount of time in seconds that a sandbox can be idle before being terminated.
|
|
408
|
+
timeout: int = 300,
|
|
387
409
|
idle_timeout: Optional[int] = None,
|
|
388
|
-
workdir: Optional[str] = None,
|
|
410
|
+
workdir: Optional[str] = None,
|
|
389
411
|
gpu: GPU_T = None,
|
|
390
412
|
cloud: Optional[str] = None,
|
|
391
|
-
region: Optional[Union[str, Sequence[str]]] = None,
|
|
392
|
-
# Specify, in fractional CPU cores, how many CPU cores to request.
|
|
393
|
-
# Or, pass (request, limit) to additionally specify a hard limit in fractional CPU cores.
|
|
394
|
-
# CPU throttling will prevent a container from exceeding its specified limit.
|
|
413
|
+
region: Optional[Union[str, Sequence[str]]] = None,
|
|
395
414
|
cpu: Optional[Union[float, tuple[float, float]]] = None,
|
|
396
|
-
# Specify, in MiB, a memory request which is the minimum memory required.
|
|
397
|
-
# Or, pass (request, limit) to additionally specify a hard limit in MiB.
|
|
398
415
|
memory: Optional[Union[int, tuple[int, int]]] = None,
|
|
399
|
-
block_network: bool = False,
|
|
400
|
-
# List of CIDRs the sandbox is allowed to access. If None, all CIDRs are allowed.
|
|
416
|
+
block_network: bool = False,
|
|
401
417
|
cidr_allowlist: Optional[Sequence[str]] = None,
|
|
402
|
-
volumes: dict[
|
|
403
|
-
|
|
404
|
-
] = {}, # Mount points for Modal Volumes and CloudBucketMounts
|
|
405
|
-
pty_info: Optional[api_pb2.PTYInfo] = None,
|
|
406
|
-
# List of ports to tunnel into the sandbox. Encrypted ports are tunneled with TLS.
|
|
418
|
+
volumes: dict[Union[str, os.PathLike], Union[_Volume, _CloudBucketMount]] = {},
|
|
419
|
+
pty: bool = False,
|
|
407
420
|
encrypted_ports: Sequence[int] = [],
|
|
408
|
-
# List of encrypted ports to tunnel into the sandbox, using HTTP/2.
|
|
409
421
|
h2_ports: Sequence[int] = [],
|
|
410
|
-
# List of ports to tunnel into the sandbox without encryption.
|
|
411
422
|
unencrypted_ports: Sequence[int] = [],
|
|
412
|
-
# Reference to a Modal Proxy to use in front of this Sandbox.
|
|
413
423
|
proxy: Optional[_Proxy] = None,
|
|
414
424
|
experimental_options: Optional[dict[str, bool]] = None,
|
|
415
|
-
# Enable memory snapshots.
|
|
416
425
|
_experimental_enable_snapshot: bool = False,
|
|
417
|
-
_experimental_scheduler_placement: Optional[
|
|
418
|
-
SchedulerPlacement
|
|
419
|
-
] = None, # Experimental controls over fine-grained scheduling (alpha).
|
|
426
|
+
_experimental_scheduler_placement: Optional[SchedulerPlacement] = None,
|
|
420
427
|
client: Optional[_Client] = None,
|
|
421
428
|
verbose: bool = False,
|
|
429
|
+
pty_info: Optional[api_pb2.PTYInfo] = None,
|
|
422
430
|
):
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
431
|
+
"""Private method used internally.
|
|
432
|
+
|
|
433
|
+
This method exposes some internal arguments (currently `mounts`) which are not in the public API.
|
|
434
|
+
`mounts` is currently only used by modal shell (cli) to provide a function's mounts to the
|
|
435
|
+
sandbox that runs the shell session.
|
|
436
|
+
"""
|
|
426
437
|
from .app import _App
|
|
427
438
|
|
|
428
439
|
_validate_exec_args(args)
|
|
@@ -432,6 +443,10 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
432
443
|
if block_network and (encrypted_ports or h2_ports or unencrypted_ports):
|
|
433
444
|
raise InvalidError("Cannot specify open ports when `block_network` is enabled")
|
|
434
445
|
|
|
446
|
+
secrets = secrets or []
|
|
447
|
+
if env:
|
|
448
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
449
|
+
|
|
435
450
|
# TODO(erikbern): Get rid of the `_new` method and create an already-hydrated object
|
|
436
451
|
obj = _Sandbox._new(
|
|
437
452
|
args,
|
|
@@ -451,6 +466,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
451
466
|
block_network=block_network,
|
|
452
467
|
cidr_allowlist=cidr_allowlist,
|
|
453
468
|
volumes=volumes,
|
|
469
|
+
pty=pty,
|
|
454
470
|
pty_info=pty_info,
|
|
455
471
|
encrypted_ports=encrypted_ports,
|
|
456
472
|
h2_ports=h2_ports,
|
|
@@ -703,14 +719,16 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
703
719
|
async def exec(
|
|
704
720
|
self,
|
|
705
721
|
*args: str,
|
|
706
|
-
pty_info: Optional[api_pb2.PTYInfo] = None,
|
|
707
722
|
stdout: StreamType = StreamType.PIPE,
|
|
708
723
|
stderr: StreamType = StreamType.PIPE,
|
|
709
724
|
timeout: Optional[int] = None,
|
|
710
725
|
workdir: Optional[str] = None,
|
|
711
|
-
|
|
726
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
727
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
712
728
|
text: Literal[True] = True,
|
|
713
729
|
bufsize: Literal[-1, 1] = -1,
|
|
730
|
+
pty: bool = False,
|
|
731
|
+
pty_info: Optional[api_pb2.PTYInfo] = None,
|
|
714
732
|
_pty_info: Optional[api_pb2.PTYInfo] = None,
|
|
715
733
|
) -> _ContainerProcess[str]: ...
|
|
716
734
|
|
|
@@ -718,33 +736,38 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
718
736
|
async def exec(
|
|
719
737
|
self,
|
|
720
738
|
*args: str,
|
|
721
|
-
pty_info: Optional[api_pb2.PTYInfo] = None,
|
|
722
739
|
stdout: StreamType = StreamType.PIPE,
|
|
723
740
|
stderr: StreamType = StreamType.PIPE,
|
|
724
741
|
timeout: Optional[int] = None,
|
|
725
742
|
workdir: Optional[str] = None,
|
|
726
|
-
|
|
743
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
744
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
727
745
|
text: Literal[False] = False,
|
|
728
746
|
bufsize: Literal[-1, 1] = -1,
|
|
747
|
+
pty: bool = False,
|
|
748
|
+
pty_info: Optional[api_pb2.PTYInfo] = None,
|
|
729
749
|
_pty_info: Optional[api_pb2.PTYInfo] = None,
|
|
730
750
|
) -> _ContainerProcess[bytes]: ...
|
|
731
751
|
|
|
732
752
|
async def exec(
|
|
733
753
|
self,
|
|
734
754
|
*args: str,
|
|
735
|
-
pty_info: Optional[api_pb2.PTYInfo] = None, # Deprecated: internal use only
|
|
736
755
|
stdout: StreamType = StreamType.PIPE,
|
|
737
756
|
stderr: StreamType = StreamType.PIPE,
|
|
738
757
|
timeout: Optional[int] = None,
|
|
739
758
|
workdir: Optional[str] = None,
|
|
740
|
-
|
|
759
|
+
env: Optional[dict[str, Optional[str]]] = None, # Environment variables to set during command execution.
|
|
760
|
+
secrets: Optional[
|
|
761
|
+
Collection[_Secret]
|
|
762
|
+
] = None, # Secrets to inject as environment variables during command execution.
|
|
741
763
|
# Encode output as text.
|
|
742
764
|
text: bool = True,
|
|
743
765
|
# Control line-buffered output.
|
|
744
766
|
# -1 means unbuffered, 1 means line-buffered (only available if `text=True`).
|
|
745
767
|
bufsize: Literal[-1, 1] = -1,
|
|
746
|
-
|
|
747
|
-
_pty_info: Optional[api_pb2.PTYInfo] = None,
|
|
768
|
+
pty: bool = False, # Enable a PTY for the command
|
|
769
|
+
_pty_info: Optional[api_pb2.PTYInfo] = None, # *DEPRECATED* Use `pty` instead. `pty` will override `pty_info`.
|
|
770
|
+
pty_info: Optional[api_pb2.PTYInfo] = None, # *DEPRECATED* Use `pty` instead. `pty` will override `pty_info`.
|
|
748
771
|
):
|
|
749
772
|
"""Execute a command in the Sandbox and return a ContainerProcess handle.
|
|
750
773
|
|
|
@@ -764,11 +787,54 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
764
787
|
print(line)
|
|
765
788
|
```
|
|
766
789
|
"""
|
|
790
|
+
if pty_info is not None or _pty_info is not None:
|
|
791
|
+
deprecation_warning(
|
|
792
|
+
(2025, 9, 12),
|
|
793
|
+
"The `_pty_info` and `pty_info` parameters are deprecated and will be removed in a future release. "
|
|
794
|
+
"Set the `pty` parameter to `True` instead.",
|
|
795
|
+
)
|
|
796
|
+
pty_info = _pty_info or pty_info
|
|
797
|
+
if pty:
|
|
798
|
+
pty_info = self._default_pty_info()
|
|
799
|
+
|
|
800
|
+
return await self._exec(
|
|
801
|
+
*args,
|
|
802
|
+
pty_info=pty_info,
|
|
803
|
+
stdout=stdout,
|
|
804
|
+
stderr=stderr,
|
|
805
|
+
timeout=timeout,
|
|
806
|
+
workdir=workdir,
|
|
807
|
+
env=env,
|
|
808
|
+
secrets=secrets,
|
|
809
|
+
text=text,
|
|
810
|
+
bufsize=bufsize,
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
async def _exec(
|
|
814
|
+
self,
|
|
815
|
+
*args: str,
|
|
816
|
+
pty_info: Optional[api_pb2.PTYInfo] = None,
|
|
817
|
+
stdout: StreamType = StreamType.PIPE,
|
|
818
|
+
stderr: StreamType = StreamType.PIPE,
|
|
819
|
+
timeout: Optional[int] = None,
|
|
820
|
+
workdir: Optional[str] = None,
|
|
821
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
822
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
823
|
+
text: bool = True,
|
|
824
|
+
bufsize: Literal[-1, 1] = -1,
|
|
825
|
+
) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
|
|
826
|
+
"""Private method used internally.
|
|
767
827
|
|
|
828
|
+
This method exposes some internal arguments (currently `pty_info`) which are not in the public API.
|
|
829
|
+
"""
|
|
768
830
|
if workdir is not None and not workdir.startswith("/"):
|
|
769
831
|
raise InvalidError(f"workdir must be an absolute path, got: {workdir}")
|
|
770
832
|
_validate_exec_args(args)
|
|
771
833
|
|
|
834
|
+
secrets = secrets or []
|
|
835
|
+
if env:
|
|
836
|
+
secrets = [*secrets, _Secret.from_dict(env)]
|
|
837
|
+
|
|
772
838
|
# Force secret resolution so we can pass the secret IDs to the backend.
|
|
773
839
|
secret_coros = [secret.hydrate(client=self._client) for secret in secrets]
|
|
774
840
|
await TaskContext.gather(*secret_coros)
|
|
@@ -777,7 +843,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
777
843
|
req = api_pb2.ContainerExecRequest(
|
|
778
844
|
task_id=task_id,
|
|
779
845
|
command=args,
|
|
780
|
-
pty_info=
|
|
846
|
+
pty_info=pty_info,
|
|
781
847
|
runtime_debug=config.get("function_runtime_debug"),
|
|
782
848
|
timeout_secs=timeout or 0,
|
|
783
849
|
workdir=workdir,
|