modal 0.73.30__py3-none-any.whl → 0.73.32__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.
modal/app.pyi CHANGED
@@ -161,9 +161,7 @@ class _App:
161
161
  image: typing.Optional[modal.image._Image] = None,
162
162
  schedule: typing.Optional[modal.schedule.Schedule] = None,
163
163
  secrets: collections.abc.Sequence[modal.secret._Secret] = (),
164
- gpu: typing.Union[
165
- None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
166
- ] = None,
164
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
167
165
  serialized: bool = False,
168
166
  mounts: collections.abc.Sequence[modal.mount._Mount] = (),
169
167
  network_file_systems: dict[
@@ -209,9 +207,7 @@ class _App:
209
207
  *,
210
208
  image: typing.Optional[modal.image._Image] = None,
211
209
  secrets: collections.abc.Sequence[modal.secret._Secret] = (),
212
- gpu: typing.Union[
213
- None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
214
- ] = None,
210
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
215
211
  serialized: bool = False,
216
212
  mounts: collections.abc.Sequence[modal.mount._Mount] = (),
217
213
  network_file_systems: dict[
@@ -255,7 +251,7 @@ class _App:
255
251
  ] = {},
256
252
  timeout: typing.Optional[int] = None,
257
253
  workdir: typing.Optional[str] = None,
258
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
254
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
259
255
  cloud: typing.Optional[str] = None,
260
256
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
261
257
  cpu: typing.Union[float, tuple[float, float], None] = None,
@@ -397,9 +393,7 @@ class App:
397
393
  image: typing.Optional[modal.image.Image] = None,
398
394
  schedule: typing.Optional[modal.schedule.Schedule] = None,
399
395
  secrets: collections.abc.Sequence[modal.secret.Secret] = (),
400
- gpu: typing.Union[
401
- None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
402
- ] = None,
396
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
403
397
  serialized: bool = False,
404
398
  mounts: collections.abc.Sequence[modal.mount.Mount] = (),
405
399
  network_file_systems: dict[
@@ -445,9 +439,7 @@ class App:
445
439
  *,
446
440
  image: typing.Optional[modal.image.Image] = None,
447
441
  secrets: collections.abc.Sequence[modal.secret.Secret] = (),
448
- gpu: typing.Union[
449
- None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
450
- ] = None,
442
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
451
443
  serialized: bool = False,
452
444
  mounts: collections.abc.Sequence[modal.mount.Mount] = (),
453
445
  network_file_systems: dict[
@@ -493,7 +485,7 @@ class App:
493
485
  ] = {},
494
486
  timeout: typing.Optional[int] = None,
495
487
  workdir: typing.Optional[str] = None,
496
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
488
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
497
489
  cloud: typing.Optional[str] = None,
498
490
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
499
491
  cpu: typing.Union[float, tuple[float, float], None] = None,
@@ -517,7 +509,7 @@ class App:
517
509
  ] = {},
518
510
  timeout: typing.Optional[int] = None,
519
511
  workdir: typing.Optional[str] = None,
520
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
512
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
521
513
  cloud: typing.Optional[str] = None,
522
514
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
523
515
  cpu: typing.Union[float, tuple[float, float], None] = None,
modal/client.pyi CHANGED
@@ -27,7 +27,7 @@ class _Client:
27
27
  _snapshotted: bool
28
28
 
29
29
  def __init__(
30
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.30"
30
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.32"
31
31
  ): ...
32
32
  def is_closed(self) -> bool: ...
33
33
  @property
@@ -85,7 +85,7 @@ class Client:
85
85
  _snapshotted: bool
86
86
 
87
87
  def __init__(
88
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.30"
88
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.32"
89
89
  ): ...
90
90
  def is_closed(self) -> bool: ...
91
91
  @property
modal/cls.pyi CHANGED
@@ -129,7 +129,7 @@ class _Cls(modal._object._Object):
129
129
  self: _Cls,
130
130
  cpu: typing.Union[float, tuple[float, float], None] = None,
131
131
  memory: typing.Union[int, tuple[int, int], None] = None,
132
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
132
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
133
133
  secrets: collections.abc.Collection[modal.secret._Secret] = (),
134
134
  volumes: dict[typing.Union[str, os.PathLike], modal.volume._Volume] = {},
135
135
  retries: typing.Union[int, modal.retries.Retries, None] = None,
@@ -187,7 +187,7 @@ class Cls(modal.object.Object):
187
187
  self: Cls,
188
188
  cpu: typing.Union[float, tuple[float, float], None] = None,
189
189
  memory: typing.Union[int, tuple[int, int], None] = None,
190
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
190
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
191
191
  secrets: collections.abc.Collection[modal.secret.Secret] = (),
192
192
  volumes: dict[typing.Union[str, os.PathLike], modal.volume.Volume] = {},
193
193
  retries: typing.Union[int, modal.retries.Retries, None] = None,
modal/functions.pyi CHANGED
@@ -64,9 +64,7 @@ class Function(
64
64
  secrets: collections.abc.Sequence[modal.secret.Secret] = (),
65
65
  schedule: typing.Optional[modal.schedule.Schedule] = None,
66
66
  is_generator: bool = False,
67
- gpu: typing.Union[
68
- None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
69
- ] = None,
67
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
70
68
  mounts: collections.abc.Collection[modal.mount.Mount] = (),
71
69
  network_file_systems: dict[
72
70
  typing.Union[str, pathlib.PurePosixPath], modal.network_file_system.NetworkFileSystem
modal/gpu.py CHANGED
@@ -1,17 +1,25 @@
1
1
  # Copyright Modal Labs 2022
2
- from dataclasses import dataclass
3
2
  from typing import Union
4
3
 
5
4
  from modal_proto import api_pb2
6
5
 
6
+ from ._utils.deprecation import deprecation_warning
7
7
  from .exception import InvalidError
8
8
 
9
9
 
10
- @dataclass(frozen=True)
11
10
  class _GPUConfig:
12
11
  gpu_type: str
13
12
  count: int
14
13
 
14
+ def __init__(self, gpu_type: str, count: int):
15
+ name = self.__class__.__name__
16
+ str_value = gpu_type
17
+ if count > 1:
18
+ str_value += f":{count}"
19
+ deprecation_warning((2025, 2, 7), f'`gpu={name}(...)` is deprecated. Use `gpu="{str_value}"` instead.')
20
+ self.gpu_type = gpu_type
21
+ self.count = count
22
+
15
23
  def _to_proto(self) -> api_pb2.GPUConfig:
16
24
  """Convert this GPU config to an internal protobuf representation."""
17
25
  return api_pb2.GPUConfig(
@@ -175,10 +183,19 @@ You can see a list of Modal GPU options in the
175
183
  def my_gpu_function():
176
184
  ... # This will have 4 A100-80GB with each container
177
185
  ```
186
+
187
+ **Deprecation notes**
188
+
189
+ An older deprecated way to configure GPU is also still supported,
190
+ but will be removed in future versions of Modal. Examples:
191
+
192
+ - `gpu=modal.gpu.H100()` will attach 1 H100 GPU to each container
193
+ - `gpu=modal.gpu.T4(count=4)` will attach 4 T4 GPUs to each container
194
+ - `gpu=modal.gpu.A100()` will attach 1 A100-40GB GPUs to each container
195
+ - `gpu=modal.gpu.A100(size="80GB")` will attach 1 A100-80GB GPUs to each container
178
196
  """
179
197
 
180
- # bool will be deprecated in future versions.
181
- GPU_T = Union[None, bool, str, _GPUConfig]
198
+ GPU_T = Union[None, str, _GPUConfig]
182
199
 
183
200
 
184
201
  def parse_gpu_config(value: GPU_T) -> api_pb2.GPUConfig:
@@ -197,7 +214,9 @@ def parse_gpu_config(value: GPU_T) -> api_pb2.GPUConfig:
197
214
  gpu_type=gpu_type,
198
215
  count=count,
199
216
  )
200
- elif value is None or value is False:
217
+ elif value is None:
201
218
  return api_pb2.GPUConfig()
202
219
  else:
203
- raise InvalidError(f"Invalid GPU config: {value}. Value must be a string, a `GPUConfig` object, or `None`.")
220
+ raise InvalidError(
221
+ f"Invalid GPU config: {value}. Value must be a string or `None` (or a deprecated `modal.gpu` object)"
222
+ )
modal/image.pyi CHANGED
@@ -155,7 +155,7 @@ class _Image(modal._object._Object):
155
155
  extra_options: str = "",
156
156
  force_build: bool = False,
157
157
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
158
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
158
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
159
159
  ) -> _Image: ...
160
160
  def pip_install_private_repos(
161
161
  self,
@@ -166,7 +166,7 @@ class _Image(modal._object._Object):
166
166
  extra_index_url: typing.Optional[str] = None,
167
167
  pre: bool = False,
168
168
  extra_options: str = "",
169
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
169
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
170
170
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
171
171
  force_build: bool = False,
172
172
  ) -> _Image: ...
@@ -181,7 +181,7 @@ class _Image(modal._object._Object):
181
181
  extra_options: str = "",
182
182
  force_build: bool = False,
183
183
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
184
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
184
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
185
185
  ) -> _Image: ...
186
186
  def pip_install_from_pyproject(
187
187
  self,
@@ -195,7 +195,7 @@ class _Image(modal._object._Object):
195
195
  extra_options: str = "",
196
196
  force_build: bool = False,
197
197
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
198
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
198
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
199
199
  ) -> _Image: ...
200
200
  def poetry_install_from_file(
201
201
  self,
@@ -209,14 +209,14 @@ class _Image(modal._object._Object):
209
209
  only: list[str] = [],
210
210
  *,
211
211
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
212
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
212
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
213
213
  ) -> _Image: ...
214
214
  def dockerfile_commands(
215
215
  self,
216
216
  *dockerfile_commands: typing.Union[str, list[str]],
217
217
  context_files: dict[str, str] = {},
218
218
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
219
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
219
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
220
220
  context_mount: typing.Optional[modal.mount._Mount] = None,
221
221
  force_build: bool = False,
222
222
  ignore: typing.Union[
@@ -229,7 +229,7 @@ class _Image(modal._object._Object):
229
229
  self,
230
230
  *commands: typing.Union[str, list[str]],
231
231
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
232
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
232
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
233
233
  force_build: bool = False,
234
234
  ) -> _Image: ...
235
235
  @staticmethod
@@ -240,7 +240,7 @@ class _Image(modal._object._Object):
240
240
  channels: list[str] = [],
241
241
  force_build: bool = False,
242
242
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
243
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
243
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
244
244
  ): ...
245
245
  def conda_update_from_environment(
246
246
  self,
@@ -248,7 +248,7 @@ class _Image(modal._object._Object):
248
248
  force_build: bool = False,
249
249
  *,
250
250
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
251
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
251
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
252
252
  ): ...
253
253
  @staticmethod
254
254
  def micromamba(python_version: typing.Optional[str] = None, force_build: bool = False) -> _Image: ...
@@ -259,7 +259,7 @@ class _Image(modal._object._Object):
259
259
  channels: list[str] = [],
260
260
  force_build: bool = False,
261
261
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
262
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
262
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
263
263
  ) -> _Image: ...
264
264
  @staticmethod
265
265
  def _registry_setup_commands(
@@ -305,7 +305,7 @@ class _Image(modal._object._Object):
305
305
  force_build: bool = False,
306
306
  *,
307
307
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
308
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
308
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
309
309
  add_python: typing.Optional[str] = None,
310
310
  ignore: typing.Union[
311
311
  collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
@@ -318,15 +318,13 @@ class _Image(modal._object._Object):
318
318
  *packages: typing.Union[str, list[str]],
319
319
  force_build: bool = False,
320
320
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
321
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
321
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
322
322
  ) -> _Image: ...
323
323
  def run_function(
324
324
  self,
325
325
  raw_f: collections.abc.Callable[..., typing.Any],
326
326
  secrets: collections.abc.Sequence[modal.secret._Secret] = (),
327
- gpu: typing.Union[
328
- None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
329
- ] = None,
327
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
330
328
  mounts: collections.abc.Sequence[modal.mount._Mount] = (),
331
329
  volumes: dict[
332
330
  typing.Union[str, pathlib.PurePosixPath],
@@ -432,7 +430,7 @@ class Image(modal.object.Object):
432
430
  extra_options: str = "",
433
431
  force_build: bool = False,
434
432
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
435
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
433
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
436
434
  ) -> Image: ...
437
435
  def pip_install_private_repos(
438
436
  self,
@@ -443,7 +441,7 @@ class Image(modal.object.Object):
443
441
  extra_index_url: typing.Optional[str] = None,
444
442
  pre: bool = False,
445
443
  extra_options: str = "",
446
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
444
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
447
445
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
448
446
  force_build: bool = False,
449
447
  ) -> Image: ...
@@ -458,7 +456,7 @@ class Image(modal.object.Object):
458
456
  extra_options: str = "",
459
457
  force_build: bool = False,
460
458
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
461
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
459
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
462
460
  ) -> Image: ...
463
461
  def pip_install_from_pyproject(
464
462
  self,
@@ -472,7 +470,7 @@ class Image(modal.object.Object):
472
470
  extra_options: str = "",
473
471
  force_build: bool = False,
474
472
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
475
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
473
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
476
474
  ) -> Image: ...
477
475
  def poetry_install_from_file(
478
476
  self,
@@ -486,14 +484,14 @@ class Image(modal.object.Object):
486
484
  only: list[str] = [],
487
485
  *,
488
486
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
489
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
487
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
490
488
  ) -> Image: ...
491
489
  def dockerfile_commands(
492
490
  self,
493
491
  *dockerfile_commands: typing.Union[str, list[str]],
494
492
  context_files: dict[str, str] = {},
495
493
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
496
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
494
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
497
495
  context_mount: typing.Optional[modal.mount.Mount] = None,
498
496
  force_build: bool = False,
499
497
  ignore: typing.Union[
@@ -506,7 +504,7 @@ class Image(modal.object.Object):
506
504
  self,
507
505
  *commands: typing.Union[str, list[str]],
508
506
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
509
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
507
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
510
508
  force_build: bool = False,
511
509
  ) -> Image: ...
512
510
  @staticmethod
@@ -517,7 +515,7 @@ class Image(modal.object.Object):
517
515
  channels: list[str] = [],
518
516
  force_build: bool = False,
519
517
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
520
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
518
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
521
519
  ): ...
522
520
  def conda_update_from_environment(
523
521
  self,
@@ -525,7 +523,7 @@ class Image(modal.object.Object):
525
523
  force_build: bool = False,
526
524
  *,
527
525
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
528
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
526
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
529
527
  ): ...
530
528
  @staticmethod
531
529
  def micromamba(python_version: typing.Optional[str] = None, force_build: bool = False) -> Image: ...
@@ -536,7 +534,7 @@ class Image(modal.object.Object):
536
534
  channels: list[str] = [],
537
535
  force_build: bool = False,
538
536
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
539
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
537
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
540
538
  ) -> Image: ...
541
539
  @staticmethod
542
540
  def _registry_setup_commands(
@@ -582,7 +580,7 @@ class Image(modal.object.Object):
582
580
  force_build: bool = False,
583
581
  *,
584
582
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
585
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
583
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
586
584
  add_python: typing.Optional[str] = None,
587
585
  ignore: typing.Union[
588
586
  collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
@@ -595,15 +593,13 @@ class Image(modal.object.Object):
595
593
  *packages: typing.Union[str, list[str]],
596
594
  force_build: bool = False,
597
595
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
598
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
596
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
599
597
  ) -> Image: ...
600
598
  def run_function(
601
599
  self,
602
600
  raw_f: collections.abc.Callable[..., typing.Any],
603
601
  secrets: collections.abc.Sequence[modal.secret.Secret] = (),
604
- gpu: typing.Union[
605
- None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
606
- ] = None,
602
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
607
603
  mounts: collections.abc.Sequence[modal.mount.Mount] = (),
608
604
  volumes: dict[
609
605
  typing.Union[str, pathlib.PurePosixPath],
modal/sandbox.py CHANGED
@@ -42,10 +42,28 @@ from .stream_type import StreamType
42
42
  _default_image: _Image = _Image.debian_slim()
43
43
 
44
44
 
45
+ # The maximum number of bytes that can be passed to an exec on Linux.
46
+ # Though this is technically a 'server side' limit, it is unlikely to change.
47
+ # getconf ARG_MAX will show this value on a host.
48
+ ARG_MAX_BYTES = 2_097_152 # 2MiB
49
+
45
50
  if TYPE_CHECKING:
46
51
  import modal.app
47
52
 
48
53
 
54
+ def _validate_exec_args(entrypoint_args: Sequence[str]) -> None:
55
+ # Entrypoint args must be strings.
56
+ if not all(isinstance(arg, str) for arg in entrypoint_args):
57
+ raise InvalidError("All entrypoint arguments must be strings")
58
+ # Avoid "[Errno 7] Argument list too long" errors.
59
+ total_arg_len = sum(len(arg) for arg in entrypoint_args)
60
+ if total_arg_len > ARG_MAX_BYTES:
61
+ raise InvalidError(
62
+ f"Total length of entrypoint arguments must be less than {ARG_MAX_BYTES} bytes (ARG_MAX). "
63
+ f"Got {total_arg_len} bytes."
64
+ )
65
+
66
+
49
67
  class _Sandbox(_Object, type_prefix="sb"):
50
68
  """A `Sandbox` object lets you interact with a running sandbox. This API is similar to Python's
51
69
  [asyncio.subprocess.Process](https://docs.python.org/3/library/asyncio-subprocess.html#asyncio.subprocess.Process).
@@ -245,6 +263,8 @@ class _Sandbox(_Object, type_prefix="sb"):
245
263
  max_sleep_time = 60 * 60 * 24 * 2 # 2 days is plenty since workers roll every 24h
246
264
  entrypoint_args = ("sleep", str(max_sleep_time))
247
265
 
266
+ _validate_exec_args(entrypoint_args)
267
+
248
268
  # TODO(erikbern): Get rid of the `_new` method and create an already-hydrated object
249
269
  obj = _Sandbox._new(
250
270
  entrypoint_args,
@@ -521,6 +541,7 @@ class _Sandbox(_Object, type_prefix="sb"):
521
541
 
522
542
  if workdir is not None and not workdir.startswith("/"):
523
543
  raise InvalidError(f"workdir must be an absolute path, got: {workdir}")
544
+ _validate_exec_args(cmds)
524
545
 
525
546
  # Force secret resolution so we can pass the secret IDs to the backend.
526
547
  secret_coros = [secret.hydrate(client=self._client) for secret in secrets]
modal/sandbox.pyi CHANGED
@@ -25,6 +25,8 @@ import os
25
25
  import typing
26
26
  import typing_extensions
27
27
 
28
+ def _validate_exec_args(entrypoint_args: collections.abc.Sequence[str]) -> None: ...
29
+
28
30
  class _Sandbox(modal._object._Object):
29
31
  _result: typing.Optional[modal_proto.api_pb2.GenericResult]
30
32
  _stdout: modal.io_streams._StreamReader[str]
@@ -42,7 +44,7 @@ class _Sandbox(modal._object._Object):
42
44
  secrets: collections.abc.Sequence[modal.secret._Secret],
43
45
  timeout: typing.Optional[int] = None,
44
46
  workdir: typing.Optional[str] = None,
45
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
47
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
46
48
  cloud: typing.Optional[str] = None,
47
49
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
48
50
  cpu: typing.Optional[float] = None,
@@ -72,7 +74,7 @@ class _Sandbox(modal._object._Object):
72
74
  network_file_systems: dict[typing.Union[str, os.PathLike], modal.network_file_system._NetworkFileSystem] = {},
73
75
  timeout: typing.Optional[int] = None,
74
76
  workdir: typing.Optional[str] = None,
75
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
77
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
76
78
  cloud: typing.Optional[str] = None,
77
79
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
78
80
  cpu: typing.Union[float, tuple[float, float], None] = None,
@@ -184,7 +186,7 @@ class Sandbox(modal.object.Object):
184
186
  secrets: collections.abc.Sequence[modal.secret.Secret],
185
187
  timeout: typing.Optional[int] = None,
186
188
  workdir: typing.Optional[str] = None,
187
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
189
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
188
190
  cloud: typing.Optional[str] = None,
189
191
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
190
192
  cpu: typing.Optional[float] = None,
@@ -217,7 +219,7 @@ class Sandbox(modal.object.Object):
217
219
  ] = {},
218
220
  timeout: typing.Optional[int] = None,
219
221
  workdir: typing.Optional[str] = None,
220
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
222
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
221
223
  cloud: typing.Optional[str] = None,
222
224
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
223
225
  cpu: typing.Union[float, tuple[float, float], None] = None,
@@ -249,7 +251,7 @@ class Sandbox(modal.object.Object):
249
251
  ] = {},
250
252
  timeout: typing.Optional[int] = None,
251
253
  workdir: typing.Optional[str] = None,
252
- gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
254
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
253
255
  cloud: typing.Optional[str] = None,
254
256
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
255
257
  cpu: typing.Union[float, tuple[float, float], None] = None,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.73.30
3
+ Version: 0.73.32
4
4
  Summary: Python client library for Modal
5
5
  Author: Modal Labs
6
6
  Author-email: support@modal.com
@@ -19,14 +19,14 @@ modal/_tunnel.py,sha256=zTBxBiuH1O22tS1OliAJdIsSmaZS8PlnifS_6S5z-mk,6320
19
19
  modal/_tunnel.pyi,sha256=JmmDYAy9F1FpgJ_hWx0xkom2nTOFQjn4mTPYlU3PFo4,1245
20
20
  modal/_watcher.py,sha256=K6LYnlmSGQB4tWWI9JADv-tvSvQ1j522FwT71B51CX8,3584
21
21
  modal/app.py,sha256=rCOPD51gVyow8muyaqMuV65qfTnAZKf_w1OCZdSF_6o,44636
22
- modal/app.pyi,sha256=lxiuWzE_OLb3WHg-H7Pek9DGBuCUzZ55P594VhJL5LA,26113
22
+ modal/app.pyi,sha256=0MMCgskIL4r3eq8oBcfm2lLyeao2gXjS3iXaIfmaJ-o,25959
23
23
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
24
24
  modal/client.py,sha256=8SQawr7P1PNUCq1UmJMUQXG2jIo4Nmdcs311XqrNLRE,15276
25
- modal/client.pyi,sha256=N2SJk_T6wD3P5mjZC2xapuXtU4mojxXyP5wioRPllfg,7593
25
+ modal/client.pyi,sha256=pxFu0T3BrokSyDj5XWlvDeV8k35RQo60dwVV3PQ5jh0,7593
26
26
  modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
27
27
  modal/cloud_bucket_mount.pyi,sha256=30T3K1a89l6wzmEJ_J9iWv9SknoGqaZDx59Xs-ZQcmk,1607
28
28
  modal/cls.py,sha256=5Er9L9tGpLGIrbiHOI7c9266gPG6nhxoJ_BX8op96nU,31096
29
- modal/cls.pyi,sha256=TaQEWPZ-zzAQJJSZem6E4KuyOeEJLignUD1anSRzwBg,8909
29
+ modal/cls.pyi,sha256=QqYoRKVCKhP_HkHittKpEZ6sqXkEdtEl4n1wMqeoe3M,8897
30
30
  modal/config.py,sha256=XT1W4Y9PVkbYMAXjJRshvQEPDhZmnfW_ZRMwl8XKoqA,11149
31
31
  modal/container_process.py,sha256=WTqLn01dJPVkPpwR_0w_JH96ceN5mV4TGtiu1ZR2RRA,6108
32
32
  modal/container_process.pyi,sha256=Hf0J5JyDdCCXBJSKx6gvkPOo0XrztCm78xzxamtzUjQ,2828
@@ -41,10 +41,10 @@ modal/file_io.py,sha256=lcMs_E9Xfm0YX1t9U2wNIBPnqHRxmImqjLW1GHqVmyg,20945
41
41
  modal/file_io.pyi,sha256=NTRft1tbPSWf9TlWVeZmTlgB5AZ_Zhu2srWIrWr7brk,9445
42
42
  modal/file_pattern_matcher.py,sha256=trosX-Bp7dOubudN1bLLhRAoidWy1TcoaR4Pv8CedWw,6497
43
43
  modal/functions.py,sha256=kcNHvqeGBxPI7Cgd57NIBBghkfbeFJzXO44WW0jSmao,325
44
- modal/functions.pyi,sha256=YflJx4BhzmJLJzpVWbuAMv0Qv63Mgb3r9qZqrgBEr1w,14289
45
- modal/gpu.py,sha256=uDluoK3hXyj2YRxGhVDFOifOBCsXFTo5hVueGoJPb8w,6001
44
+ modal/functions.pyi,sha256=B6sPrnpt34Z_DVz_5SpHV7NVpfk2nTXuS215Xcu4kK0,14255
45
+ modal/gpu.py,sha256=Kbhs_u49FaC2Zi0TjCdrpstpRtT5eZgecynmQi5IZVE,6752
46
46
  modal/image.py,sha256=KYc6bg-m9A6wiLF38dWcFBMrEATyR2KOF0sp-6O9uC0,91508
47
- modal/image.pyi,sha256=kdJzy1eaxNPZeCpE0TMYYLhJ6UWmkfRDeb_vzngJUoQ,26462
47
+ modal/image.pyi,sha256=kMkIDHcyyhA7BC2Vrx0RfrLEsqK8Ng2-IqUKL-CJexI,26250
48
48
  modal/io_streams.py,sha256=QkQiizKRzd5bnbKQsap31LJgBYlAnj4-XkV_50xPYX0,15079
49
49
  modal/io_streams.pyi,sha256=bJ7ZLmSmJ0nKoa6r4FJpbqvzdUVa0lEe0Fa-MMpMezU,5071
50
50
  modal/mount.py,sha256=hNoy7J-E2C-CkSmbKldfL_zg8db8nP8cVzRj_35Rsp0,32124
@@ -67,8 +67,8 @@ modal/retries.py,sha256=HKR2Q9aNPWkMjQ5nwobqYTuZaSuw0a8lI2zrtY5IW98,5230
67
67
  modal/runner.py,sha256=fdUyDGN-bWu_aZBvxBO_MIgEuucsA0PgDKDHBn5k8J0,24451
68
68
  modal/runner.pyi,sha256=RYEYsnofrvVroYefWLhWAy8I_uwXV9fRNuJaVgcNzrg,5278
69
69
  modal/running_app.py,sha256=v61mapYNV1-O-Uaho5EfJlryMLvIT9We0amUOSvSGx8,1188
70
- modal/sandbox.py,sha256=fPStBypgDjclm388u5v3q26gAub0mP4c1pZYLlrJcUY,31777
71
- modal/sandbox.pyi,sha256=qncEvzK76h_ehrs03vlroQyLThWiMsjKhD0DnCNc6zI,22663
70
+ modal/sandbox.py,sha256=Vp-LlqbeRz4HNAFQW2oIh9RlfsurUyp5bKKUuKEGlMQ,32670
71
+ modal/sandbox.pyi,sha256=cLmSwI1ab-2DgEuXNf6S1PiK63wfUR9dHtxlZtSOuX8,22719
72
72
  modal/schedule.py,sha256=0ZFpKs1bOxeo5n3HZjoL7OE2ktsb-_oGtq-WJEPO4tY,2615
73
73
  modal/scheduler_placement.py,sha256=BAREdOY5HzHpzSBqt6jDVR6YC_jYfHMVqOzkyqQfngU,1235
74
74
  modal/secret.py,sha256=U2Jivqdb94eI_BrGCMVbCots8F2gDcbXLMia_gVlej0,10455
@@ -172,10 +172,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
172
172
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
173
173
  modal_version/__init__.py,sha256=wiJQ53c-OMs0Xf1UeXOxQ7FwlV1VzIjnX6o-pRYZ_Pk,470
174
174
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
175
- modal_version/_version_generated.py,sha256=m7J9hNWfGj6_B090wtexzuPT3ElFz-zc4beQRp2N5Eg,149
176
- modal-0.73.30.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
177
- modal-0.73.30.dist-info/METADATA,sha256=06SMbqr63TkUj0VTuFXpenZLMH3KVHZtYQyWqd9Fv3A,2330
178
- modal-0.73.30.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
179
- modal-0.73.30.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
180
- modal-0.73.30.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
181
- modal-0.73.30.dist-info/RECORD,,
175
+ modal_version/_version_generated.py,sha256=qB711bhl6L8MqhOCkiWQLqBZrWYWZ-dYKuWSPvtQmec,149
176
+ modal-0.73.32.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
177
+ modal-0.73.32.dist-info/METADATA,sha256=MUHy26_qys8dtiKDfQ_fcHmgDnbWcpgAOWKx0mEqdFw,2330
178
+ modal-0.73.32.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
179
+ modal-0.73.32.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
180
+ modal-0.73.32.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
181
+ modal-0.73.32.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 30 # git: d5a1f0c
4
+ build_number = 32 # git: b3dec0b