modal 0.73.31__py3-none-any.whl → 0.73.33__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/_functions.py CHANGED
@@ -19,7 +19,6 @@ from synchronicity.exceptions import UserCodeException
19
19
  from modal_proto import api_pb2
20
20
  from modal_proto.modal_api_grpc import ModalClientModal
21
21
 
22
- from ._location import parse_cloud_provider
23
22
  from ._object import _get_environment_name, _Object, live_method, live_method_gen
24
23
  from ._pty import get_pty_info
25
24
  from ._resolver import Resolver
@@ -627,10 +626,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
627
626
 
628
627
  if not cloud and not is_builder_function:
629
628
  cloud = config.get("default_cloud")
630
- if cloud:
631
- cloud_provider = parse_cloud_provider(cloud)
632
- else:
633
- cloud_provider = None
634
629
 
635
630
  if is_generator and webhook_config:
636
631
  if webhook_config.type == api_pb2.WEBHOOK_TYPE_FUNCTION:
@@ -819,8 +814,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
819
814
  task_idle_timeout_secs=container_idle_timeout or 0,
820
815
  concurrency_limit=concurrency_limit or 0,
821
816
  pty_info=pty_info,
822
- cloud_provider=cloud_provider, # Deprecated at some point
823
- cloud_provider_str=cloud.upper() if cloud else "", # Supersedes cloud_provider
817
+ cloud_provider_str=cloud if cloud else "",
824
818
  warm_pool_size=keep_warm or 0,
825
819
  runtime=config.get("function_runtime"),
826
820
  runtime_debug=config.get("function_runtime_debug"),
modal/_location.py CHANGED
@@ -1,35 +1,6 @@
1
1
  # Copyright Modal Labs 2022
2
- from enum import Enum
3
-
4
2
  import modal_proto.api_pb2
5
3
 
6
- from .exception import InvalidError
7
-
8
-
9
- class CloudProvider(Enum):
10
- AWS = modal_proto.api_pb2.CLOUD_PROVIDER_AWS
11
- GCP = modal_proto.api_pb2.CLOUD_PROVIDER_GCP
12
- AUTO = modal_proto.api_pb2.CLOUD_PROVIDER_AUTO
13
- OCI = modal_proto.api_pb2.CLOUD_PROVIDER_OCI
14
-
15
-
16
- def parse_cloud_provider(value: str) -> "modal_proto.api_pb2.CloudProvider.V":
17
- try:
18
- cloud_provider = CloudProvider[value.upper()]
19
- except KeyError:
20
- # provider's int identifier may be directly specified
21
- try:
22
- return int(value) # type: ignore
23
- except ValueError:
24
- pass
25
-
26
- raise InvalidError(
27
- f"Invalid cloud provider: {value}. "
28
- f"Value must be one of {[x.name.lower() for x in CloudProvider]} (case-insensitive)."
29
- )
30
-
31
- return cloud_provider.value
32
-
33
4
 
34
5
  def display_location(cloud_provider: "modal_proto.api_pb2.CloudProvider.V") -> str:
35
6
  if cloud_provider == modal_proto.api_pb2.CLOUD_PROVIDER_GCP:
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.31"
30
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.33"
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.31"
88
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.33"
89
89
  ): ...
90
90
  def is_closed(self) -> bool: ...
91
91
  @property
modal/sandbox.py CHANGED
@@ -15,7 +15,6 @@ from modal.cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_p
15
15
  from modal.volume import _Volume
16
16
  from modal_proto import api_pb2
17
17
 
18
- from ._location import parse_cloud_provider
19
18
  from ._object import _get_environment_name, _Object
20
19
  from ._resolver import Resolver
21
20
  from ._resources import convert_fn_config_to_resources_config
@@ -42,10 +41,28 @@ from .stream_type import StreamType
42
41
  _default_image: _Image = _Image.debian_slim()
43
42
 
44
43
 
44
+ # The maximum number of bytes that can be passed to an exec on Linux.
45
+ # Though this is technically a 'server side' limit, it is unlikely to change.
46
+ # getconf ARG_MAX will show this value on a host.
47
+ ARG_MAX_BYTES = 2_097_152 # 2MiB
48
+
45
49
  if TYPE_CHECKING:
46
50
  import modal.app
47
51
 
48
52
 
53
+ def _validate_exec_args(entrypoint_args: Sequence[str]) -> None:
54
+ # Entrypoint args must be strings.
55
+ if not all(isinstance(arg, str) for arg in entrypoint_args):
56
+ raise InvalidError("All entrypoint arguments must be strings")
57
+ # Avoid "[Errno 7] Argument list too long" errors.
58
+ total_arg_len = sum(len(arg) for arg in entrypoint_args)
59
+ if total_arg_len > ARG_MAX_BYTES:
60
+ raise InvalidError(
61
+ f"Total length of entrypoint arguments must be less than {ARG_MAX_BYTES} bytes (ARG_MAX). "
62
+ f"Got {total_arg_len} bytes."
63
+ )
64
+
65
+
49
66
  class _Sandbox(_Object, type_prefix="sb"):
50
67
  """A `Sandbox` object lets you interact with a running sandbox. This API is similar to Python's
51
68
  [asyncio.subprocess.Process](https://docs.python.org/3/library/asyncio-subprocess.html#asyncio.subprocess.Process).
@@ -168,8 +185,7 @@ class _Sandbox(_Object, type_prefix="sb"):
168
185
  resources=convert_fn_config_to_resources_config(
169
186
  cpu=cpu, memory=memory, gpu=gpu, ephemeral_disk=ephemeral_disk
170
187
  ),
171
- cloud_provider=parse_cloud_provider(cloud) if cloud else None, # Deprecated at some point
172
- cloud_provider_str=cloud.upper() if cloud else None, # Supersedes cloud_provider
188
+ cloud_provider_str=cloud if cloud else None, # Supersedes cloud_provider
173
189
  nfs_mounts=network_file_system_mount_protos(validated_network_file_systems, False),
174
190
  runtime_debug=config.get("function_runtime_debug"),
175
191
  cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
@@ -245,6 +261,8 @@ class _Sandbox(_Object, type_prefix="sb"):
245
261
  max_sleep_time = 60 * 60 * 24 * 2 # 2 days is plenty since workers roll every 24h
246
262
  entrypoint_args = ("sleep", str(max_sleep_time))
247
263
 
264
+ _validate_exec_args(entrypoint_args)
265
+
248
266
  # TODO(erikbern): Get rid of the `_new` method and create an already-hydrated object
249
267
  obj = _Sandbox._new(
250
268
  entrypoint_args,
@@ -521,6 +539,7 @@ class _Sandbox(_Object, type_prefix="sb"):
521
539
 
522
540
  if workdir is not None and not workdir.startswith("/"):
523
541
  raise InvalidError(f"workdir must be an absolute path, got: {workdir}")
542
+ _validate_exec_args(cmds)
524
543
 
525
544
  # Force secret resolution so we can pass the secret IDs to the backend.
526
545
  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]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.73.31
3
+ Version: 0.73.33
4
4
  Summary: Python client library for Modal
5
5
  Author: Modal Labs
6
6
  Author-email: support@modal.com
@@ -3,9 +3,9 @@ modal/__main__.py,sha256=CgIjP8m1xJjjd4AXc-delmR6LdBCZclw2A_V38CFIio,2870
3
3
  modal/_clustered_functions.py,sha256=kTf-9YBXY88NutC1akI-gCbvf01RhMPCw-zoOI_YIUE,2700
4
4
  modal/_clustered_functions.pyi,sha256=vllkegc99A0jrUOWa8mdlSbdp6uz36TsHhGxysAOpaQ,771
5
5
  modal/_container_entrypoint.py,sha256=YtfJ852XUDtAWBD-yVs99zy933-VBEKQyIngEj36Qcw,29286
6
- modal/_functions.py,sha256=eXqyhxucn3c_V4Y-zqwqqXCDeF6lW1h7N0hcQJNinR8,71463
6
+ modal/_functions.py,sha256=ls_u9YqRFLgL5_vAWcTpBkpRQZX0Wx50kCxrt6Tlb_k,71180
7
7
  modal/_ipython.py,sha256=TW1fkVOmZL3YYqdS2YlM1hqpf654Yf8ZyybHdBnlhSw,301
8
- modal/_location.py,sha256=S3lSxIU3h9HkWpkJ3Pwo0pqjIOSB1fjeSgUsY3x7eec,1202
8
+ modal/_location.py,sha256=joiX-0ZeutEUDTrrqLF1GHXCdVLF-rHzstocbMcd_-k,366
9
9
  modal/_object.py,sha256=ItQcsMNkz9Y3kdTsvfNarbW-paJ2qabDyQ7njaqY0XI,11359
10
10
  modal/_output.py,sha256=Z0nngPh2mKHMQc4MQ92YjVPc3ewOLa3I4dFBlL9nvQY,25656
11
11
  modal/_partial_function.py,sha256=KGv4hWXBboiWFxC733jfOHKdvShTPjF75lVv866Lgyc,29078
@@ -22,7 +22,7 @@ modal/app.py,sha256=rCOPD51gVyow8muyaqMuV65qfTnAZKf_w1OCZdSF_6o,44636
22
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=TT9wOEgIzzEkyCBPOf4iEWhdNqduK3Bg869sRX-lO3Y,7593
25
+ modal/client.pyi,sha256=81qGUSORM6eHl05tz2Ke3yiFWFZkkj-7wSIMBvSkeAo,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
@@ -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=0E8aEejCcI5ySes0sifpRqIkmI584wiOPNp6J3L6Fs0,22633
70
+ modal/sandbox.py,sha256=39MCW0EQ_CCmZgmhcr_PjT-H-7mo_Dycg9oeeNx3L2Q,32511
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=J1oAwbWNkSUn5KRSMgTkUcnTmLQuB4j_XhHlcX7WIuE,149
176
- modal-0.73.31.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
177
- modal-0.73.31.dist-info/METADATA,sha256=40QP2R389PZVjhdg4DKclsWxlXD6G4uA6Xw3jbAV2D0,2330
178
- modal-0.73.31.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
179
- modal-0.73.31.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
180
- modal-0.73.31.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
181
- modal-0.73.31.dist-info/RECORD,,
175
+ modal_version/_version_generated.py,sha256=0z9U6hfE3SIVx8R28D2_YwK_cXr1eX0p9LpyvZ7egEg,149
176
+ modal-0.73.33.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
177
+ modal-0.73.33.dist-info/METADATA,sha256=_6rqwvIii_O7uVb1LuIL3002kt72twfiUaopI3PpV3k,2330
178
+ modal-0.73.33.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
179
+ modal-0.73.33.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
180
+ modal-0.73.33.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
181
+ modal-0.73.33.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 = 31 # git: 08cdea1
4
+ build_number = 33 # git: 41d57c2