modal 0.68.24__py3-none-any.whl → 0.68.42__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. modal/_traceback.py +6 -2
  2. modal/_utils/deprecation.py +89 -0
  3. modal/app.py +15 -34
  4. modal/app.pyi +6 -7
  5. modal/cli/app.py +1 -1
  6. modal/cli/dict.py +6 -2
  7. modal/cli/network_file_system.py +1 -1
  8. modal/cli/run.py +1 -0
  9. modal/cli/volume.py +1 -1
  10. modal/client.pyi +2 -2
  11. modal/cls.py +15 -9
  12. modal/cls.pyi +5 -5
  13. modal/config.py +2 -1
  14. modal/container_process.py +2 -1
  15. modal/dict.py +12 -17
  16. modal/dict.pyi +8 -12
  17. modal/environments.py +10 -7
  18. modal/environments.pyi +6 -6
  19. modal/exception.py +0 -54
  20. modal/file_io.py +54 -7
  21. modal/file_io.pyi +18 -8
  22. modal/file_pattern_matcher.py +48 -15
  23. modal/functions.py +11 -13
  24. modal/functions.pyi +18 -12
  25. modal/image.py +21 -20
  26. modal/image.pyi +16 -28
  27. modal/mount.py +7 -4
  28. modal/mount.pyi +4 -4
  29. modal/network_file_system.py +13 -19
  30. modal/network_file_system.pyi +8 -12
  31. modal/partial_function.py +2 -30
  32. modal/queue.py +12 -17
  33. modal/queue.pyi +8 -12
  34. modal/runner.py +2 -7
  35. modal/sandbox.py +25 -13
  36. modal/sandbox.pyi +21 -0
  37. modal/secret.py +7 -4
  38. modal/secret.pyi +5 -5
  39. modal/serving.py +1 -1
  40. modal/volume.py +12 -17
  41. modal/volume.pyi +8 -12
  42. {modal-0.68.24.dist-info → modal-0.68.42.dist-info}/METADATA +2 -2
  43. {modal-0.68.24.dist-info → modal-0.68.42.dist-info}/RECORD +51 -50
  44. modal_proto/api.proto +1 -1
  45. modal_proto/api_pb2.py +750 -750
  46. modal_proto/api_pb2.pyi +4 -4
  47. modal_version/_version_generated.py +1 -1
  48. {modal-0.68.24.dist-info → modal-0.68.42.dist-info}/LICENSE +0 -0
  49. {modal-0.68.24.dist-info → modal-0.68.42.dist-info}/WHEEL +0 -0
  50. {modal-0.68.24.dist-info → modal-0.68.42.dist-info}/entry_points.txt +0 -0
  51. {modal-0.68.24.dist-info → modal-0.68.42.dist-info}/top_level.txt +0 -0
modal/image.pyi CHANGED
@@ -92,19 +92,6 @@ class _Image(modal.object._Object):
92
92
  _namespace: int = 1,
93
93
  _do_assert_no_mount_layers: bool = True,
94
94
  ): ...
95
- def extend(
96
- self,
97
- *,
98
- secrets: typing.Optional[collections.abc.Sequence[modal.secret._Secret]] = None,
99
- gpu_config: typing.Optional[modal_proto.api_pb2.GPUConfig] = None,
100
- build_function: typing.Optional[modal.functions._Function] = None,
101
- build_function_input: typing.Optional[modal_proto.api_pb2.FunctionInput] = None,
102
- image_registry_config: typing.Optional[_ImageRegistryConfig] = None,
103
- context_mount: typing.Optional[modal.mount._Mount] = None,
104
- force_build: bool = False,
105
- _namespace: int = 1,
106
- _do_assert_no_mount_layers: bool = True,
107
- ) -> _Image: ...
108
95
  def copy_mount(self, mount: modal.mount._Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> _Image: ...
109
96
  def add_local_file(
110
97
  self, local_path: typing.Union[str, pathlib.Path], remote_path: str, *, copy: bool = False
@@ -120,7 +107,14 @@ class _Image(modal.object._Object):
120
107
  def copy_local_file(
121
108
  self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "./"
122
109
  ) -> _Image: ...
123
- def add_local_python_source(self, *module_names: str, copy: bool = False) -> _Image: ...
110
+ def add_local_python_source(
111
+ self,
112
+ *module_names: str,
113
+ copy: bool = False,
114
+ ignore: typing.Union[
115
+ collections.abc.Sequence[str], typing.Callable[[pathlib.Path], bool]
116
+ ] = modal.file_pattern_matcher.NON_PYTHON_FILES,
117
+ ) -> _Image: ...
124
118
  def copy_local_dir(
125
119
  self,
126
120
  local_path: typing.Union[str, pathlib.Path],
@@ -357,19 +351,6 @@ class Image(modal.object.Object):
357
351
  _namespace: int = 1,
358
352
  _do_assert_no_mount_layers: bool = True,
359
353
  ): ...
360
- def extend(
361
- self,
362
- *,
363
- secrets: typing.Optional[collections.abc.Sequence[modal.secret.Secret]] = None,
364
- gpu_config: typing.Optional[modal_proto.api_pb2.GPUConfig] = None,
365
- build_function: typing.Optional[modal.functions.Function] = None,
366
- build_function_input: typing.Optional[modal_proto.api_pb2.FunctionInput] = None,
367
- image_registry_config: typing.Optional[_ImageRegistryConfig] = None,
368
- context_mount: typing.Optional[modal.mount.Mount] = None,
369
- force_build: bool = False,
370
- _namespace: int = 1,
371
- _do_assert_no_mount_layers: bool = True,
372
- ) -> Image: ...
373
354
  def copy_mount(self, mount: modal.mount.Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> Image: ...
374
355
  def add_local_file(
375
356
  self, local_path: typing.Union[str, pathlib.Path], remote_path: str, *, copy: bool = False
@@ -385,7 +366,14 @@ class Image(modal.object.Object):
385
366
  def copy_local_file(
386
367
  self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "./"
387
368
  ) -> Image: ...
388
- def add_local_python_source(self, *module_names: str, copy: bool = False) -> Image: ...
369
+ def add_local_python_source(
370
+ self,
371
+ *module_names: str,
372
+ copy: bool = False,
373
+ ignore: typing.Union[
374
+ collections.abc.Sequence[str], typing.Callable[[pathlib.Path], bool]
375
+ ] = modal.file_pattern_matcher.NON_PYTHON_FILES,
376
+ ) -> Image: ...
389
377
  def copy_local_dir(
390
378
  self,
391
379
  local_path: typing.Union[str, pathlib.Path],
modal/mount.py CHANGED
@@ -22,6 +22,7 @@ from modal_version import __version__
22
22
  from ._resolver import Resolver
23
23
  from ._utils.async_utils import aclosing, async_map, synchronize_api
24
24
  from ._utils.blob_utils import FileUploadSpec, blob_upload_file, get_file_upload_spec_from_path
25
+ from ._utils.deprecation import renamed_parameter
25
26
  from ._utils.grpc_utils import retry_transient_errors
26
27
  from ._utils.name_utils import check_object_name
27
28
  from ._utils.package_utils import get_module_mount_info
@@ -623,8 +624,9 @@ class _Mount(_Object, type_prefix="mo"):
623
624
  return mount
624
625
 
625
626
  @staticmethod
627
+ @renamed_parameter((2024, 12, 18), "label", "name")
626
628
  def from_name(
627
- label: str,
629
+ name: str,
628
630
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
629
631
  environment_name: Optional[str] = None,
630
632
  ) -> "_Mount":
@@ -632,7 +634,7 @@ class _Mount(_Object, type_prefix="mo"):
632
634
 
633
635
  async def _load(provider: _Mount, resolver: Resolver, existing_object_id: Optional[str]):
634
636
  req = api_pb2.MountGetOrCreateRequest(
635
- deployment_name=label,
637
+ deployment_name=name,
636
638
  namespace=namespace,
637
639
  environment_name=_get_environment_name(environment_name, resolver),
638
640
  )
@@ -642,15 +644,16 @@ class _Mount(_Object, type_prefix="mo"):
642
644
  return _Mount._from_loader(_load, "Mount()")
643
645
 
644
646
  @classmethod
647
+ @renamed_parameter((2024, 12, 18), "label", "name")
645
648
  async def lookup(
646
649
  cls: type["_Mount"],
647
- label: str,
650
+ name: str,
648
651
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
649
652
  client: Optional[_Client] = None,
650
653
  environment_name: Optional[str] = None,
651
654
  ) -> "_Mount":
652
655
  """mdmd:hidden"""
653
- obj = _Mount.from_name(label, namespace=namespace, environment_name=environment_name)
656
+ obj = _Mount.from_name(name, namespace=namespace, environment_name=environment_name)
654
657
  if client is None:
655
658
  client = await _Client.from_env()
656
659
  resolver = Resolver(client=client)
modal/mount.pyi CHANGED
@@ -139,11 +139,11 @@ class _Mount(modal.object._Object):
139
139
  ignore: typing.Union[typing.Sequence[str], typing.Callable[[pathlib.Path], bool], None] = None,
140
140
  ) -> _Mount: ...
141
141
  @staticmethod
142
- def from_name(label: str, namespace=1, environment_name: typing.Optional[str] = None) -> _Mount: ...
142
+ def from_name(name: str, namespace=1, environment_name: typing.Optional[str] = None) -> _Mount: ...
143
143
  @classmethod
144
144
  async def lookup(
145
145
  cls: type[_Mount],
146
- label: str,
146
+ name: str,
147
147
  namespace=1,
148
148
  client: typing.Optional[modal.client._Client] = None,
149
149
  environment_name: typing.Optional[str] = None,
@@ -231,11 +231,11 @@ class Mount(modal.object.Object):
231
231
  ignore: typing.Union[typing.Sequence[str], typing.Callable[[pathlib.Path], bool], None] = None,
232
232
  ) -> Mount: ...
233
233
  @staticmethod
234
- def from_name(label: str, namespace=1, environment_name: typing.Optional[str] = None) -> Mount: ...
234
+ def from_name(name: str, namespace=1, environment_name: typing.Optional[str] = None) -> Mount: ...
235
235
  @classmethod
236
236
  def lookup(
237
237
  cls: type[Mount],
238
- label: str,
238
+ name: str,
239
239
  namespace=1,
240
240
  client: typing.Optional[modal.client.Client] = None,
241
241
  environment_name: typing.Optional[str] = None,
@@ -15,11 +15,12 @@ from modal_proto import api_pb2
15
15
  from ._resolver import Resolver
16
16
  from ._utils.async_utils import TaskContext, aclosing, async_map, sync_or_async_iter, synchronize_api
17
17
  from ._utils.blob_utils import LARGE_FILE_LIMIT, blob_iter, blob_upload_file
18
+ from ._utils.deprecation import renamed_parameter
18
19
  from ._utils.grpc_utils import retry_transient_errors
19
20
  from ._utils.hash_utils import get_sha256_hex
20
21
  from ._utils.name_utils import check_object_name
21
22
  from .client import _Client
22
- from .exception import InvalidError, deprecation_error
23
+ from .exception import InvalidError
23
24
  from .object import (
24
25
  EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
25
26
  _get_environment_name,
@@ -90,18 +91,9 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
90
91
  """
91
92
 
92
93
  @staticmethod
93
- def new(cloud: Optional[str] = None):
94
- """mdmd:hidden"""
95
- message = (
96
- "`NetworkFileSystem.new` is deprecated."
97
- " Please use `NetworkFileSystem.from_name` (for persisted)"
98
- " or `NetworkFileSystem.ephemeral` (for ephemeral) network filesystems instead."
99
- )
100
- deprecation_error((2024, 3, 20), message)
101
-
102
- @staticmethod
94
+ @renamed_parameter((2024, 12, 18), "label", "name")
103
95
  def from_name(
104
- label: str,
96
+ name: str,
105
97
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
106
98
  environment_name: Optional[str] = None,
107
99
  create_if_missing: bool = False,
@@ -120,11 +112,11 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
120
112
  pass
121
113
  ```
122
114
  """
123
- check_object_name(label, "NetworkFileSystem")
115
+ check_object_name(name, "NetworkFileSystem")
124
116
 
125
117
  async def _load(self: _NetworkFileSystem, resolver: Resolver, existing_object_id: Optional[str]):
126
118
  req = api_pb2.SharedVolumeGetOrCreateRequest(
127
- deployment_name=label,
119
+ deployment_name=name,
128
120
  namespace=namespace,
129
121
  environment_name=_get_environment_name(environment_name, resolver),
130
122
  object_creation_type=(api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING if create_if_missing else None),
@@ -135,7 +127,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
135
127
  except GRPCError as exc:
136
128
  if exc.status == Status.NOT_FOUND and exc.message == "App has wrong entity vo":
137
129
  raise InvalidError(
138
- f"Attempted to mount: `{label}` as a NetworkFileSystem " + "which already exists as a Volume"
130
+ f"Attempted to mount: `{name}` as a NetworkFileSystem " + "which already exists as a Volume"
139
131
  )
140
132
  raise
141
133
 
@@ -175,8 +167,9 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
175
167
  yield cls._new_hydrated(response.shared_volume_id, client, None, is_another_app=True)
176
168
 
177
169
  @staticmethod
170
+ @renamed_parameter((2024, 12, 18), "label", "name")
178
171
  async def lookup(
179
- label: str,
172
+ name: str,
180
173
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
181
174
  client: Optional[_Client] = None,
182
175
  environment_name: Optional[str] = None,
@@ -193,7 +186,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
193
186
  ```
194
187
  """
195
188
  obj = _NetworkFileSystem.from_name(
196
- label, namespace=namespace, environment_name=environment_name, create_if_missing=create_if_missing
189
+ name, namespace=namespace, environment_name=environment_name, create_if_missing=create_if_missing
197
190
  )
198
191
  if client is None:
199
192
  client = await _Client.from_env()
@@ -222,8 +215,9 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
222
215
  return resp.shared_volume_id
223
216
 
224
217
  @staticmethod
225
- async def delete(label: str, client: Optional[_Client] = None, environment_name: Optional[str] = None):
226
- obj = await _NetworkFileSystem.lookup(label, client=client, environment_name=environment_name)
218
+ @renamed_parameter((2024, 12, 18), "label", "name")
219
+ async def delete(name: str, client: Optional[_Client] = None, environment_name: Optional[str] = None):
220
+ obj = await _NetworkFileSystem.lookup(name, client=client, environment_name=environment_name)
227
221
  req = api_pb2.SharedVolumeDeleteRequest(shared_volume_id=obj.object_id)
228
222
  await retry_transient_errors(obj._client.stub.SharedVolumeDelete, req)
229
223
 
@@ -13,11 +13,9 @@ def network_file_system_mount_protos(
13
13
  ) -> list[modal_proto.api_pb2.SharedVolumeMount]: ...
14
14
 
15
15
  class _NetworkFileSystem(modal.object._Object):
16
- @staticmethod
17
- def new(cloud: typing.Optional[str] = None): ...
18
16
  @staticmethod
19
17
  def from_name(
20
- label: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
18
+ name: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
21
19
  ) -> _NetworkFileSystem: ...
22
20
  @classmethod
23
21
  def ephemeral(
@@ -28,7 +26,7 @@ class _NetworkFileSystem(modal.object._Object):
28
26
  ) -> typing.AsyncContextManager[_NetworkFileSystem]: ...
29
27
  @staticmethod
30
28
  async def lookup(
31
- label: str,
29
+ name: str,
32
30
  namespace=1,
33
31
  client: typing.Optional[modal.client._Client] = None,
34
32
  environment_name: typing.Optional[str] = None,
@@ -43,7 +41,7 @@ class _NetworkFileSystem(modal.object._Object):
43
41
  ) -> str: ...
44
42
  @staticmethod
45
43
  async def delete(
46
- label: str, client: typing.Optional[modal.client._Client] = None, environment_name: typing.Optional[str] = None
44
+ name: str, client: typing.Optional[modal.client._Client] = None, environment_name: typing.Optional[str] = None
47
45
  ): ...
48
46
  async def write_file(
49
47
  self,
@@ -71,10 +69,8 @@ class _NetworkFileSystem(modal.object._Object):
71
69
  class NetworkFileSystem(modal.object.Object):
72
70
  def __init__(self, *args, **kwargs): ...
73
71
  @staticmethod
74
- def new(cloud: typing.Optional[str] = None): ...
75
- @staticmethod
76
72
  def from_name(
77
- label: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
73
+ name: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
78
74
  ) -> NetworkFileSystem: ...
79
75
  @classmethod
80
76
  def ephemeral(
@@ -87,7 +83,7 @@ class NetworkFileSystem(modal.object.Object):
87
83
  class __lookup_spec(typing_extensions.Protocol):
88
84
  def __call__(
89
85
  self,
90
- label: str,
86
+ name: str,
91
87
  namespace=1,
92
88
  client: typing.Optional[modal.client.Client] = None,
93
89
  environment_name: typing.Optional[str] = None,
@@ -95,7 +91,7 @@ class NetworkFileSystem(modal.object.Object):
95
91
  ) -> NetworkFileSystem: ...
96
92
  async def aio(
97
93
  self,
98
- label: str,
94
+ name: str,
99
95
  namespace=1,
100
96
  client: typing.Optional[modal.client.Client] = None,
101
97
  environment_name: typing.Optional[str] = None,
@@ -125,13 +121,13 @@ class NetworkFileSystem(modal.object.Object):
125
121
  class __delete_spec(typing_extensions.Protocol):
126
122
  def __call__(
127
123
  self,
128
- label: str,
124
+ name: str,
129
125
  client: typing.Optional[modal.client.Client] = None,
130
126
  environment_name: typing.Optional[str] = None,
131
127
  ): ...
132
128
  async def aio(
133
129
  self,
134
- label: str,
130
+ name: str,
135
131
  client: typing.Optional[modal.client.Client] = None,
136
132
  environment_name: typing.Optional[str] = None,
137
133
  ): ...
modal/partial_function.py CHANGED
@@ -15,9 +15,10 @@ import typing_extensions
15
15
  from modal_proto import api_pb2
16
16
 
17
17
  from ._utils.async_utils import synchronize_api, synchronizer
18
+ from ._utils.deprecation import deprecation_error, deprecation_warning
18
19
  from ._utils.function_utils import callable_has_non_self_non_default_params, callable_has_non_self_params
19
20
  from .config import logger
20
- from .exception import InvalidError, deprecation_error, deprecation_warning
21
+ from .exception import InvalidError
21
22
  from .functions import _Function
22
23
 
23
24
  MAX_MAX_BATCH_SIZE = 1000
@@ -137,29 +138,6 @@ PartialFunction = synchronize_api(_PartialFunction)
137
138
  def _find_partial_methods_for_user_cls(user_cls: type[Any], flags: int) -> dict[str, _PartialFunction]:
138
139
  """Grabs all method on a user class, and returns partials. Includes legacy methods."""
139
140
 
140
- # Build up a list of legacy attributes to check
141
- check_attrs: list[str] = []
142
- if flags & _PartialFunctionFlags.BUILD:
143
- check_attrs += ["__build__", "__abuild__"]
144
- if flags & _PartialFunctionFlags.ENTER_POST_SNAPSHOT:
145
- check_attrs += ["__enter__", "__aenter__"]
146
- if flags & _PartialFunctionFlags.EXIT:
147
- check_attrs += ["__exit__", "__aexit__"]
148
-
149
- # Grab legacy lifecycle methods
150
- for attr in check_attrs:
151
- if hasattr(user_cls, attr):
152
- suggested = attr.strip("_")
153
- if is_async := suggested.startswith("a"):
154
- suggested = suggested[1:]
155
- async_suggestion = " (on an async method)" if is_async else ""
156
- message = (
157
- f"Using `{attr}` methods for class lifecycle management is deprecated."
158
- f" Please try using the `modal.{suggested}` decorator{async_suggestion} instead."
159
- " See https://modal.com/docs/guide/lifecycle-functions for more information."
160
- )
161
- deprecation_error((2024, 2, 21), message)
162
-
163
141
  partial_functions: dict[str, _PartialFunction] = {}
164
142
  for parent_cls in reversed(user_cls.mro()):
165
143
  if parent_cls is not object:
@@ -633,12 +611,6 @@ def _exit(_warn_parentheses_missing=None) -> Callable[[ExitHandlerType], _Partia
633
611
  if isinstance(f, _PartialFunction):
634
612
  _disallow_wrapping_method(f, "exit")
635
613
 
636
- if callable_has_non_self_params(f):
637
- message = (
638
- "Support for decorating parameterized methods with `@exit` has been deprecated."
639
- " Please update your code by removing the parameters."
640
- )
641
- deprecation_error((2024, 2, 23), message)
642
614
  return _PartialFunction(f, _PartialFunctionFlags.EXIT)
643
615
 
644
616
  return wrapper
modal/queue.py CHANGED
@@ -13,10 +13,11 @@ from modal_proto import api_pb2
13
13
  from ._resolver import Resolver
14
14
  from ._serialization import deserialize, serialize
15
15
  from ._utils.async_utils import TaskContext, synchronize_api, warn_if_generator_is_not_consumed
16
+ from ._utils.deprecation import renamed_parameter
16
17
  from ._utils.grpc_utils import retry_transient_errors
17
18
  from ._utils.name_utils import check_object_name
18
19
  from .client import _Client
19
- from .exception import InvalidError, RequestSizeError, deprecation_error
20
+ from .exception import InvalidError, RequestSizeError
20
21
  from .object import EPHEMERAL_OBJECT_HEARTBEAT_SLEEP, _get_environment_name, _Object, live_method, live_method_gen
21
22
 
22
23
 
@@ -93,15 +94,6 @@ class _Queue(_Object, type_prefix="qu"):
93
94
  Partition keys must be non-empty and must not exceed 64 bytes.
94
95
  """
95
96
 
96
- @staticmethod
97
- def new():
98
- """mdmd:hidden"""
99
- message = (
100
- "`Queue.new` is deprecated."
101
- " Please use `Queue.from_name` (for persisted) or `Queue.ephemeral` (for ephemeral) queues instead."
102
- )
103
- deprecation_error((2024, 3, 19), message)
104
-
105
97
  def __init__(self):
106
98
  """mdmd:hidden"""
107
99
  raise RuntimeError("Queue() is not allowed. Please use `Queue.from_name(...)` or `Queue.ephemeral()` instead.")
@@ -153,8 +145,9 @@ class _Queue(_Object, type_prefix="qu"):
153
145
  yield cls._new_hydrated(response.queue_id, client, None, is_another_app=True)
154
146
 
155
147
  @staticmethod
148
+ @renamed_parameter((2024, 12, 18), "label", "name")
156
149
  def from_name(
157
- label: str,
150
+ name: str,
158
151
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
159
152
  environment_name: Optional[str] = None,
160
153
  create_if_missing: bool = False,
@@ -170,11 +163,11 @@ class _Queue(_Object, type_prefix="qu"):
170
163
  q.put(123)
171
164
  ```
172
165
  """
173
- check_object_name(label, "Queue")
166
+ check_object_name(name, "Queue")
174
167
 
175
168
  async def _load(self: _Queue, resolver: Resolver, existing_object_id: Optional[str]):
176
169
  req = api_pb2.QueueGetOrCreateRequest(
177
- deployment_name=label,
170
+ deployment_name=name,
178
171
  namespace=namespace,
179
172
  environment_name=_get_environment_name(environment_name, resolver),
180
173
  object_creation_type=(api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING if create_if_missing else None),
@@ -185,8 +178,9 @@ class _Queue(_Object, type_prefix="qu"):
185
178
  return _Queue._from_loader(_load, "Queue()", is_another_app=True, hydrate_lazily=True)
186
179
 
187
180
  @staticmethod
181
+ @renamed_parameter((2024, 12, 18), "label", "name")
188
182
  async def lookup(
189
- label: str,
183
+ name: str,
190
184
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
191
185
  client: Optional[_Client] = None,
192
186
  environment_name: Optional[str] = None,
@@ -203,7 +197,7 @@ class _Queue(_Object, type_prefix="qu"):
203
197
  ```
204
198
  """
205
199
  obj = _Queue.from_name(
206
- label, namespace=namespace, environment_name=environment_name, create_if_missing=create_if_missing
200
+ name, namespace=namespace, environment_name=environment_name, create_if_missing=create_if_missing
207
201
  )
208
202
  if client is None:
209
203
  client = await _Client.from_env()
@@ -212,8 +206,9 @@ class _Queue(_Object, type_prefix="qu"):
212
206
  return obj
213
207
 
214
208
  @staticmethod
215
- async def delete(label: str, *, client: Optional[_Client] = None, environment_name: Optional[str] = None):
216
- obj = await _Queue.lookup(label, client=client, environment_name=environment_name)
209
+ @renamed_parameter((2024, 12, 18), "label", "name")
210
+ async def delete(name: str, *, client: Optional[_Client] = None, environment_name: Optional[str] = None):
211
+ obj = await _Queue.lookup(name, client=client, environment_name=environment_name)
217
212
  req = api_pb2.QueueDeleteRequest(queue_id=obj.object_id)
218
213
  await retry_transient_errors(obj._client.stub.QueueDelete, req)
219
214
 
modal/queue.pyi CHANGED
@@ -6,8 +6,6 @@ import typing
6
6
  import typing_extensions
7
7
 
8
8
  class _Queue(modal.object._Object):
9
- @staticmethod
10
- def new(): ...
11
9
  def __init__(self): ...
12
10
  @staticmethod
13
11
  def validate_partition_key(partition: typing.Optional[str]) -> bytes: ...
@@ -20,11 +18,11 @@ class _Queue(modal.object._Object):
20
18
  ) -> typing.AsyncContextManager[_Queue]: ...
21
19
  @staticmethod
22
20
  def from_name(
23
- label: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
21
+ name: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
24
22
  ) -> _Queue: ...
25
23
  @staticmethod
26
24
  async def lookup(
27
- label: str,
25
+ name: str,
28
26
  namespace=1,
29
27
  client: typing.Optional[modal.client._Client] = None,
30
28
  environment_name: typing.Optional[str] = None,
@@ -32,7 +30,7 @@ class _Queue(modal.object._Object):
32
30
  ) -> _Queue: ...
33
31
  @staticmethod
34
32
  async def delete(
35
- label: str,
33
+ name: str,
36
34
  *,
37
35
  client: typing.Optional[modal.client._Client] = None,
38
36
  environment_name: typing.Optional[str] = None,
@@ -89,8 +87,6 @@ class _Queue(modal.object._Object):
89
87
  class Queue(modal.object.Object):
90
88
  def __init__(self): ...
91
89
  @staticmethod
92
- def new(): ...
93
- @staticmethod
94
90
  def validate_partition_key(partition: typing.Optional[str]) -> bytes: ...
95
91
  @classmethod
96
92
  def ephemeral(
@@ -101,13 +97,13 @@ class Queue(modal.object.Object):
101
97
  ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Queue]: ...
102
98
  @staticmethod
103
99
  def from_name(
104
- label: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
100
+ name: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
105
101
  ) -> Queue: ...
106
102
 
107
103
  class __lookup_spec(typing_extensions.Protocol):
108
104
  def __call__(
109
105
  self,
110
- label: str,
106
+ name: str,
111
107
  namespace=1,
112
108
  client: typing.Optional[modal.client.Client] = None,
113
109
  environment_name: typing.Optional[str] = None,
@@ -115,7 +111,7 @@ class Queue(modal.object.Object):
115
111
  ) -> Queue: ...
116
112
  async def aio(
117
113
  self,
118
- label: str,
114
+ name: str,
119
115
  namespace=1,
120
116
  client: typing.Optional[modal.client.Client] = None,
121
117
  environment_name: typing.Optional[str] = None,
@@ -127,14 +123,14 @@ class Queue(modal.object.Object):
127
123
  class __delete_spec(typing_extensions.Protocol):
128
124
  def __call__(
129
125
  self,
130
- label: str,
126
+ name: str,
131
127
  *,
132
128
  client: typing.Optional[modal.client.Client] = None,
133
129
  environment_name: typing.Optional[str] = None,
134
130
  ): ...
135
131
  async def aio(
136
132
  self,
137
- label: str,
133
+ name: str,
138
134
  *,
139
135
  client: typing.Optional[modal.client.Client] = None,
140
136
  environment_name: typing.Optional[str] = None,
modal/runner.py CHANGED
@@ -19,19 +19,14 @@ from ._resolver import Resolver
19
19
  from ._runtime.execution_context import is_local
20
20
  from ._traceback import print_server_warnings, traceback_contains_remote_call
21
21
  from ._utils.async_utils import TaskContext, gather_cancel_on_exc, synchronize_api
22
+ from ._utils.deprecation import deprecation_error
22
23
  from ._utils.grpc_utils import retry_transient_errors
23
24
  from ._utils.name_utils import check_object_name, is_valid_tag
24
25
  from .client import HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT, _Client
25
26
  from .cls import _Cls
26
27
  from .config import config, logger
27
28
  from .environments import _get_environment_cached
28
- from .exception import (
29
- InteractiveTimeoutError,
30
- InvalidError,
31
- RemoteError,
32
- _CliUserExecutionError,
33
- deprecation_error,
34
- )
29
+ from .exception import InteractiveTimeoutError, InvalidError, RemoteError, _CliUserExecutionError
35
30
  from .functions import _Function
36
31
  from .object import _get_environment_name, _Object
37
32
  from .output import _get_output_manager, enable_output
modal/sandbox.py CHANGED
@@ -19,19 +19,13 @@ from ._location import parse_cloud_provider
19
19
  from ._resolver import Resolver
20
20
  from ._resources import convert_fn_config_to_resources_config
21
21
  from ._utils.async_utils import synchronize_api
22
+ from ._utils.deprecation import deprecation_error
22
23
  from ._utils.grpc_utils import retry_transient_errors
23
24
  from ._utils.mount_utils import validate_network_file_systems, validate_volumes
24
25
  from .client import _Client
25
26
  from .config import config
26
27
  from .container_process import _ContainerProcess
27
- from .exception import (
28
- ExecutionError,
29
- InvalidError,
30
- SandboxTerminatedError,
31
- SandboxTimeoutError,
32
- deprecation_error,
33
- deprecation_warning,
34
- )
28
+ from .exception import ExecutionError, InvalidError, SandboxTerminatedError, SandboxTimeoutError
35
29
  from .file_io import _FileIO
36
30
  from .gpu import GPU_T
37
31
  from .image import _Image
@@ -124,6 +118,8 @@ class _Sandbox(_Object, type_prefix="sb"):
124
118
  for _, cloud_bucket_mount in cloud_bucket_mounts:
125
119
  if cloud_bucket_mount.secret:
126
120
  deps.append(cloud_bucket_mount.secret)
121
+ if proxy:
122
+ deps.append(proxy)
127
123
  return deps
128
124
 
129
125
  async def _load(self: _Sandbox, resolver: Resolver, _existing_object_id: Optional[str]):
@@ -284,13 +280,14 @@ class _Sandbox(_Object, type_prefix="sb"):
284
280
  app_id = _App._container_app.app_id
285
281
  app_client = _App._container_app.client
286
282
  else:
287
- deprecation_warning(
283
+ arglist = ", ".join(repr(s) for s in entrypoint_args)
284
+ deprecation_error(
288
285
  (2024, 9, 14),
289
- "Creating a `Sandbox` without an `App` is deprecated.\n"
290
- "You may pass in an `App` object, or reference one by name with `App.lookup`:\n"
286
+ "Creating a `Sandbox` without an `App` is deprecated.\n\n"
287
+ "You may pass in an `App` object, or reference one by name with `App.lookup`:\n\n"
291
288
  "```\n"
292
- "app = modal.App.lookup('my-app', create_if_missing=True)\n"
293
- "modal.Sandbox.create('echo', 'hi', app=app)\n"
289
+ "app = modal.App.lookup('sandbox-app', create_if_missing=True)\n"
290
+ f"sb = modal.Sandbox.create({arglist}, app=app)\n"
294
291
  "```",
295
292
  )
296
293
 
@@ -567,6 +564,21 @@ class _Sandbox(_Object, type_prefix="sb"):
567
564
  task_id = await self._get_task_id()
568
565
  return await _FileIO.create(path, mode, self._client, task_id)
569
566
 
567
+ async def ls(self, path: str) -> list[str]:
568
+ """List the contents of a directory in the Sandbox."""
569
+ task_id = await self._get_task_id()
570
+ return await _FileIO.ls(path, self._client, task_id)
571
+
572
+ async def mkdir(self, path: str, parents: bool = False) -> None:
573
+ """Create a new directory in the Sandbox."""
574
+ task_id = await self._get_task_id()
575
+ return await _FileIO.mkdir(path, self._client, task_id, parents)
576
+
577
+ async def rm(self, path: str, recursive: bool = False) -> None:
578
+ """Remove a file or directory in the Sandbox."""
579
+ task_id = await self._get_task_id()
580
+ return await _FileIO.rm(path, self._client, task_id, recursive)
581
+
570
582
  @property
571
583
  def stdout(self) -> _StreamReader[str]:
572
584
  """
modal/sandbox.pyi CHANGED
@@ -128,6 +128,9 @@ class _Sandbox(modal.object._Object):
128
128
  async def open(self, path: str, mode: _typeshed.OpenTextMode) -> modal.file_io._FileIO[str]: ...
129
129
  @typing.overload
130
130
  async def open(self, path: str, mode: _typeshed.OpenBinaryMode) -> modal.file_io._FileIO[bytes]: ...
131
+ async def ls(self, path: str) -> list[str]: ...
132
+ async def mkdir(self, path: str, parents: bool = False) -> None: ...
133
+ async def rm(self, path: str, recursive: bool = False) -> None: ...
131
134
  @property
132
135
  def stdout(self) -> modal.io_streams._StreamReader[str]: ...
133
136
  @property
@@ -367,6 +370,24 @@ class Sandbox(modal.object.Object):
367
370
 
368
371
  open: __open_spec
369
372
 
373
+ class __ls_spec(typing_extensions.Protocol):
374
+ def __call__(self, path: str) -> list[str]: ...
375
+ async def aio(self, path: str) -> list[str]: ...
376
+
377
+ ls: __ls_spec
378
+
379
+ class __mkdir_spec(typing_extensions.Protocol):
380
+ def __call__(self, path: str, parents: bool = False) -> None: ...
381
+ async def aio(self, path: str, parents: bool = False) -> None: ...
382
+
383
+ mkdir: __mkdir_spec
384
+
385
+ class __rm_spec(typing_extensions.Protocol):
386
+ def __call__(self, path: str, recursive: bool = False) -> None: ...
387
+ async def aio(self, path: str, recursive: bool = False) -> None: ...
388
+
389
+ rm: __rm_spec
390
+
370
391
  @property
371
392
  def stdout(self) -> modal.io_streams.StreamReader[str]: ...
372
393
  @property