modal 0.77.0__py3-none-any.whl → 1.0.0__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.

Files changed (51) hide show
  1. modal/__init__.py +10 -4
  2. modal/_functions.py +15 -90
  3. modal/_object.py +0 -14
  4. modal/_partial_function.py +4 -14
  5. modal/_serialization.py +2 -2
  6. modal/_utils/function_utils.py +3 -6
  7. modal/_utils/grpc_utils.py +6 -1
  8. modal/app.py +1 -104
  9. modal/app.pyi +0 -127
  10. modal/cli/app.py +0 -19
  11. modal/cli/programs/run_jupyter.py +1 -1
  12. modal/cli/programs/vscode.py +1 -1
  13. modal/client.pyi +2 -2
  14. modal/cls.py +9 -14
  15. modal/cls.pyi +2 -17
  16. modal/config.py +5 -16
  17. modal/container_process.py +1 -9
  18. modal/container_process.pyi +3 -3
  19. modal/dict.py +3 -5
  20. modal/environments.py +1 -3
  21. modal/exception.py +1 -1
  22. modal/functions.pyi +8 -29
  23. modal/image.py +12 -36
  24. modal/image.pyi +2 -5
  25. modal/mount.py +2 -65
  26. modal/mount.pyi +0 -1
  27. modal/network_file_system.py +3 -5
  28. modal/object.pyi +0 -6
  29. modal/queue.py +3 -5
  30. modal/runner.py +2 -19
  31. modal/runner.pyi +0 -5
  32. modal/sandbox.py +78 -32
  33. modal/sandbox.pyi +102 -7
  34. modal/secret.py +1 -3
  35. modal/serving.py +0 -6
  36. modal/serving.pyi +0 -3
  37. modal/volume.py +8 -17
  38. {modal-0.77.0.dist-info → modal-1.0.0.dist-info}/METADATA +1 -1
  39. {modal-0.77.0.dist-info → modal-1.0.0.dist-info}/RECORD +51 -51
  40. modal_proto/api.proto +29 -1
  41. modal_proto/api_grpc.py +32 -0
  42. modal_proto/api_pb2.py +788 -756
  43. modal_proto/api_pb2.pyi +86 -9
  44. modal_proto/api_pb2_grpc.py +66 -0
  45. modal_proto/api_pb2_grpc.pyi +20 -0
  46. modal_proto/modal_api_grpc.py +2 -0
  47. modal_version/__init__.py +1 -1
  48. {modal-0.77.0.dist-info → modal-1.0.0.dist-info}/WHEEL +0 -0
  49. {modal-0.77.0.dist-info → modal-1.0.0.dist-info}/entry_points.txt +0 -0
  50. {modal-0.77.0.dist-info → modal-1.0.0.dist-info}/licenses/LICENSE +0 -0
  51. {modal-0.77.0.dist-info → modal-1.0.0.dist-info}/top_level.txt +0 -0
modal/cls.py CHANGED
@@ -25,7 +25,7 @@ from ._serialization import check_valid_cls_constructor_arg
25
25
  from ._traceback import print_server_warnings
26
26
  from ._type_manager import parameter_serde_registry
27
27
  from ._utils.async_utils import synchronize_api, synchronizer
28
- from ._utils.deprecation import deprecation_warning, renamed_parameter, warn_on_renamed_autoscaler_settings
28
+ from ._utils.deprecation import deprecation_warning, warn_on_renamed_autoscaler_settings
29
29
  from ._utils.grpc_utils import retry_transient_errors
30
30
  from ._utils.mount_utils import validate_volumes
31
31
  from .client import _Client
@@ -276,7 +276,8 @@ class _Obj:
276
276
  )
277
277
 
278
278
  async def keep_warm(self, warm_pool_size: int) -> None:
279
- """Set the warm pool size for the class containers
279
+ """mdmd:hidden
280
+ Set the warm pool size for the class containers
280
281
 
281
282
  DEPRECATED: Please adapt your code to use the more general `update_autoscaler` method instead:
282
283
 
@@ -586,7 +587,6 @@ More information on class parameterization can be found here: https://modal.com/
586
587
  return cls
587
588
 
588
589
  @classmethod
589
- @renamed_parameter((2024, 12, 18), "tag", "name")
590
590
  def from_name(
591
591
  cls: type["_Cls"],
592
592
  app_name: str,
@@ -594,7 +594,6 @@ More information on class parameterization can be found here: https://modal.com/
594
594
  *,
595
595
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
596
596
  environment_name: Optional[str] = None,
597
- workspace: Optional[str] = None, # Deprecated and unused
598
597
  ) -> "_Cls":
599
598
  """Reference a Cls from a deployed App by its name.
600
599
 
@@ -608,18 +607,12 @@ More information on class parameterization can be found here: https://modal.com/
608
607
  """
609
608
  _environment_name = environment_name or config.get("environment")
610
609
 
611
- if workspace is not None:
612
- deprecation_warning(
613
- (2025, 1, 27), "The `workspace` argument is no longer used and will be removed in a future release."
614
- )
615
-
616
610
  async def _load_remote(self: _Cls, resolver: Resolver, existing_object_id: Optional[str]):
617
611
  request = api_pb2.ClassGetRequest(
618
612
  app_name=app_name,
619
613
  object_tag=name,
620
614
  namespace=namespace,
621
615
  environment_name=_environment_name,
622
- lookup_published=workspace is not None,
623
616
  only_class_function=True,
624
617
  )
625
618
  try:
@@ -786,16 +779,15 @@ More information on class parameterization can be found here: https://modal.com/
786
779
  return cls
787
780
 
788
781
  @staticmethod
789
- @renamed_parameter((2024, 12, 18), "tag", "name")
790
782
  async def lookup(
791
783
  app_name: str,
792
784
  name: str,
793
785
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
794
786
  client: Optional[_Client] = None,
795
787
  environment_name: Optional[str] = None,
796
- workspace: Optional[str] = None, # Deprecated and unused
797
788
  ) -> "_Cls":
798
- """Lookup a Cls from a deployed App by its name.
789
+ """mdmd:hidden
790
+ Lookup a Cls from a deployed App by its name.
799
791
 
800
792
  DEPRECATED: This method is deprecated in favor of `modal.Cls.from_name`.
801
793
 
@@ -815,7 +807,10 @@ More information on class parameterization can be found here: https://modal.com/
815
807
  "\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
816
808
  )
817
809
  obj = _Cls.from_name(
818
- app_name, name, namespace=namespace, environment_name=environment_name, workspace=workspace
810
+ app_name,
811
+ name,
812
+ namespace=namespace,
813
+ environment_name=environment_name,
819
814
  )
820
815
  if client is None:
821
816
  client = await _Client.from_env()
modal/cls.pyi CHANGED
@@ -172,13 +172,7 @@ class _Cls(modal._object._Object):
172
172
  def from_local(user_cls, app: modal.app._App, class_service_function: modal._functions._Function) -> _Cls: ...
173
173
  @classmethod
174
174
  def from_name(
175
- cls: type[_Cls],
176
- app_name: str,
177
- name: str,
178
- *,
179
- namespace=1,
180
- environment_name: typing.Optional[str] = None,
181
- workspace: typing.Optional[str] = None,
175
+ cls: type[_Cls], app_name: str, name: str, *, namespace=1, environment_name: typing.Optional[str] = None
182
176
  ) -> _Cls: ...
183
177
  def with_options(
184
178
  self: _Cls,
@@ -206,7 +200,6 @@ class _Cls(modal._object._Object):
206
200
  namespace=1,
207
201
  client: typing.Optional[modal.client._Client] = None,
208
202
  environment_name: typing.Optional[str] = None,
209
- workspace: typing.Optional[str] = None,
210
203
  ) -> _Cls: ...
211
204
  def __call__(self, *args, **kwargs) -> _Obj: ...
212
205
  def __getattr__(self, k): ...
@@ -238,13 +231,7 @@ class Cls(modal.object.Object):
238
231
  def from_local(user_cls, app: modal.app.App, class_service_function: modal.functions.Function) -> Cls: ...
239
232
  @classmethod
240
233
  def from_name(
241
- cls: type[Cls],
242
- app_name: str,
243
- name: str,
244
- *,
245
- namespace=1,
246
- environment_name: typing.Optional[str] = None,
247
- workspace: typing.Optional[str] = None,
234
+ cls: type[Cls], app_name: str, name: str, *, namespace=1, environment_name: typing.Optional[str] = None
248
235
  ) -> Cls: ...
249
236
  def with_options(
250
237
  self: Cls,
@@ -275,7 +262,6 @@ class Cls(modal.object.Object):
275
262
  namespace=1,
276
263
  client: typing.Optional[modal.client.Client] = None,
277
264
  environment_name: typing.Optional[str] = None,
278
- workspace: typing.Optional[str] = None,
279
265
  ) -> Cls: ...
280
266
  async def aio(
281
267
  self,
@@ -285,7 +271,6 @@ class Cls(modal.object.Object):
285
271
  namespace=1,
286
272
  client: typing.Optional[modal.client.Client] = None,
287
273
  environment_name: typing.Optional[str] = None,
288
- workspace: typing.Optional[str] = None,
289
274
  ) -> Cls: ...
290
275
 
291
276
  lookup: __lookup_spec
modal/config.py CHANGED
@@ -51,10 +51,6 @@ Other possible configuration options are:
51
51
  Defaults to 10.
52
52
  Number of seconds to wait for logs to drain when closing the session,
53
53
  before giving up.
54
- * `automount` (in the .toml file) / `MODAL_AUTOMOUNT` (as an env var).
55
- Defaults to True.
56
- By default, Modal automatically mounts modules imported in the current scope, that
57
- are deemed to be "local". This can be turned off by setting this to False.
58
54
  * `force_build` (in the .toml file) / `MODAL_FORCE_BUILD` (as an env var).
59
55
  Defaults to False.
60
56
  When set, ignores the Image cache and builds all Image layers. Note that this
@@ -91,14 +87,12 @@ import logging
91
87
  import os
92
88
  import typing
93
89
  import warnings
94
- from textwrap import dedent
95
90
  from typing import Any, Callable, Optional
96
91
 
97
92
  from google.protobuf.empty_pb2 import Empty
98
93
 
99
94
  from modal_proto import api_pb2
100
95
 
101
- from ._utils.deprecation import deprecation_error
102
96
  from ._utils.logger import configure_logger
103
97
  from .exception import InvalidError
104
98
 
@@ -186,16 +180,12 @@ def _check_config() -> None:
186
180
  f"({user_config_path})."
187
181
  )
188
182
  elif num_profiles > 1 and num_active == 0 and _profile == "default":
189
- # Eventually we plan to have num_profiles > 1 with num_active = 0 be an error
190
- # But we want to give users time to activate one of their profiles without disruption
191
- message = dedent(
192
- """
193
- Support for using an implicit 'default' profile is deprecated.
194
- Please use `modal profile activate` to activate one of your profiles.
195
- (Use `modal profile list` to see the options.)
196
- """
183
+ # TODO: We should get rid of the `_profile = "default"` concept entirely now
184
+ raise InvalidError(
185
+ "No Modal profile is active.\n\n"
186
+ "Please fix by running `modal profile activate` or by editing your Modal config file "
187
+ f"({user_config_path})."
197
188
  )
198
- deprecation_error((2024, 2, 6), message)
199
189
 
200
190
 
201
191
  _profile = os.environ.get("MODAL_PROFILE") or _config_active_profile()
@@ -233,7 +223,6 @@ _SETTINGS = {
233
223
  "sync_entrypoint": _Setting(),
234
224
  "logs_timeout": _Setting(10, float),
235
225
  "image_id": _Setting(),
236
- "automount": _Setting(True, transform=_to_boolean),
237
226
  "heartbeat_interval": _Setting(15, float),
238
227
  "function_runtime": _Setting(),
239
228
  "function_runtime_debug": _Setting(False, transform=_to_boolean), # For internal debugging use.
@@ -6,7 +6,6 @@ from typing import Generic, Optional, TypeVar
6
6
  from modal_proto import api_pb2
7
7
 
8
8
  from ._utils.async_utils import TaskContext, synchronize_api
9
- from ._utils.deprecation import deprecation_error
10
9
  from ._utils.grpc_utils import retry_transient_errors
11
10
  from ._utils.shell_utils import stream_from_stdin, write_to_fd
12
11
  from .client import _Client
@@ -118,18 +117,11 @@ class _ContainerProcess(Generic[T]):
118
117
  self._returncode = resp.exit_code
119
118
  return self._returncode
120
119
 
121
- async def attach(self, *, pty: Optional[bool] = None):
120
+ async def attach(self):
122
121
  if platform.system() == "Windows":
123
122
  print("interactive exec is not currently supported on Windows.")
124
123
  return
125
124
 
126
- if pty is not None:
127
- deprecation_error(
128
- (2024, 12, 9),
129
- "The `pty` argument to `modal.container_process.attach(pty=...)` is deprecated, "
130
- "as only PTY mode is supported. Please remove the argument.",
131
- )
132
-
133
125
  from rich.console import Console
134
126
 
135
127
  console = Console()
@@ -35,7 +35,7 @@ class _ContainerProcess(typing.Generic[T]):
35
35
  def returncode(self) -> int: ...
36
36
  async def poll(self) -> typing.Optional[int]: ...
37
37
  async def wait(self) -> int: ...
38
- async def attach(self, *, pty: typing.Optional[bool] = None): ...
38
+ async def attach(self): ...
39
39
 
40
40
  SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
41
41
 
@@ -80,7 +80,7 @@ class ContainerProcess(typing.Generic[T]):
80
80
  wait: __wait_spec[typing_extensions.Self]
81
81
 
82
82
  class __attach_spec(typing_extensions.Protocol[SUPERSELF]):
83
- def __call__(self, /, *, pty: typing.Optional[bool] = None): ...
84
- async def aio(self, /, *, pty: typing.Optional[bool] = None): ...
83
+ def __call__(self, /): ...
84
+ async def aio(self, /): ...
85
85
 
86
86
  attach: __attach_spec[typing_extensions.Self]
modal/dict.py CHANGED
@@ -11,7 +11,7 @@ from ._object import EPHEMERAL_OBJECT_HEARTBEAT_SLEEP, _get_environment_name, _O
11
11
  from ._resolver import Resolver
12
12
  from ._serialization import deserialize, serialize
13
13
  from ._utils.async_utils import TaskContext, synchronize_api
14
- from ._utils.deprecation import deprecation_warning, renamed_parameter
14
+ from ._utils.deprecation import deprecation_warning
15
15
  from ._utils.grpc_utils import retry_transient_errors
16
16
  from ._utils.name_utils import check_object_name
17
17
  from .client import _Client
@@ -115,7 +115,6 @@ class _Dict(_Object, type_prefix="di"):
115
115
  yield cls._new_hydrated(response.dict_id, client, None, is_another_app=True)
116
116
 
117
117
  @staticmethod
118
- @renamed_parameter((2024, 12, 18), "label", "name")
119
118
  def from_name(
120
119
  name: str,
121
120
  data: Optional[dict] = None, # DEPRECATED
@@ -159,7 +158,6 @@ class _Dict(_Object, type_prefix="di"):
159
158
  return _Dict._from_loader(_load, "Dict()", is_another_app=True, hydrate_lazily=True)
160
159
 
161
160
  @staticmethod
162
- @renamed_parameter((2024, 12, 18), "label", "name")
163
161
  async def lookup(
164
162
  name: str,
165
163
  data: Optional[dict] = None,
@@ -168,7 +166,8 @@ class _Dict(_Object, type_prefix="di"):
168
166
  environment_name: Optional[str] = None,
169
167
  create_if_missing: bool = False,
170
168
  ) -> "_Dict":
171
- """Lookup a named Dict.
169
+ """mdmd:hidden
170
+ Lookup a named Dict.
172
171
 
173
172
  DEPRECATED: This method is deprecated in favor of `modal.Dict.from_name`.
174
173
 
@@ -200,7 +199,6 @@ class _Dict(_Object, type_prefix="di"):
200
199
  return obj
201
200
 
202
201
  @staticmethod
203
- @renamed_parameter((2024, 12, 18), "label", "name")
204
202
  async def delete(
205
203
  name: str,
206
204
  *,
modal/environments.py CHANGED
@@ -11,7 +11,7 @@ from modal_proto import api_pb2
11
11
  from ._object import _Object
12
12
  from ._resolver import Resolver
13
13
  from ._utils.async_utils import synchronize_api, synchronizer
14
- from ._utils.deprecation import deprecation_warning, renamed_parameter
14
+ from ._utils.deprecation import deprecation_warning
15
15
  from ._utils.grpc_utils import retry_transient_errors
16
16
  from ._utils.name_utils import check_object_name
17
17
  from .client import _Client
@@ -50,7 +50,6 @@ class _Environment(_Object, type_prefix="en"):
50
50
  )
51
51
 
52
52
  @staticmethod
53
- @renamed_parameter((2024, 12, 18), "label", "name")
54
53
  def from_name(
55
54
  name: str,
56
55
  *,
@@ -81,7 +80,6 @@ class _Environment(_Object, type_prefix="en"):
81
80
  return _Environment._from_loader(_load, "Environment()", is_another_app=True, hydrate_lazily=True)
82
81
 
83
82
  @staticmethod
84
- @renamed_parameter((2024, 12, 18), "label", "name")
85
83
  async def lookup(
86
84
  name: str,
87
85
  client: Optional[_Client] = None,
modal/exception.py CHANGED
@@ -116,7 +116,7 @@ class InternalFailure(Error):
116
116
 
117
117
  class _CliUserExecutionError(Exception):
118
118
  """mdmd:hidden
119
- Private wrapper for exceptions during when importing or running stubs from the CLI.
119
+ Private wrapper for exceptions during when importing or running Apps from the CLI.
120
120
 
121
121
  This intentionally does not inherit from `modal.exception.Error` because it
122
122
  is a private type that should never bubble up to users. Exceptions raised in
modal/functions.pyi CHANGED
@@ -63,7 +63,6 @@ class Function(
63
63
  schedule: typing.Optional[modal.schedule.Schedule] = None,
64
64
  is_generator: bool = False,
65
65
  gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
66
- mounts: collections.abc.Collection[modal.mount.Mount] = (),
67
66
  network_file_systems: dict[
68
67
  typing.Union[str, pathlib.PurePosixPath], modal.network_file_system.NetworkFileSystem
69
68
  ] = {},
@@ -228,17 +227,11 @@ class Function(
228
227
 
229
228
  _call_generator: ___call_generator_spec[typing_extensions.Self]
230
229
 
231
- class ___call_generator_nowait_spec(typing_extensions.Protocol[SUPERSELF]):
232
- def __call__(self, /, args, kwargs): ...
233
- async def aio(self, /, args, kwargs): ...
234
-
235
- _call_generator_nowait: ___call_generator_nowait_spec[typing_extensions.Self]
236
-
237
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
230
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
238
231
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
239
232
  async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
240
233
 
241
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
234
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
242
235
 
243
236
  class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
244
237
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
@@ -253,12 +246,12 @@ class Function(
253
246
  self, *args: modal._functions.P.args, **kwargs: modal._functions.P.kwargs
254
247
  ) -> modal._functions.OriginalReturnType: ...
255
248
 
256
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
249
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
257
250
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
258
251
  async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
259
252
 
260
253
  _experimental_spawn: ___experimental_spawn_spec[
261
- modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
254
+ modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
262
255
  ]
263
256
 
264
257
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
@@ -267,11 +260,11 @@ class Function(
267
260
 
268
261
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
269
262
 
270
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
263
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
271
264
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
272
265
  async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
273
266
 
274
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
267
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
275
268
 
276
269
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]: ...
277
270
 
@@ -350,12 +343,6 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
350
343
 
351
344
  get: __get_spec[modal._functions.ReturnType, typing_extensions.Self]
352
345
 
353
- class __get_gen_spec(typing_extensions.Protocol[SUPERSELF]):
354
- def __call__(self, /) -> typing.Generator[typing.Any, None, None]: ...
355
- def aio(self, /) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
356
-
357
- get_gen: __get_gen_spec[typing_extensions.Self]
358
-
359
346
  class __get_call_graph_spec(typing_extensions.Protocol[SUPERSELF]):
360
347
  def __call__(self, /) -> list[modal.call_graph.InputInfo]: ...
361
348
  async def aio(self, /) -> list[modal.call_graph.InputInfo]: ...
@@ -370,18 +357,10 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
370
357
 
371
358
  class __from_id_spec(typing_extensions.Protocol):
372
359
  def __call__(
373
- self,
374
- /,
375
- function_call_id: str,
376
- client: typing.Optional[modal.client.Client] = None,
377
- is_generator: bool = False,
360
+ self, /, function_call_id: str, client: typing.Optional[modal.client.Client] = None
378
361
  ) -> FunctionCall[typing.Any]: ...
379
362
  async def aio(
380
- self,
381
- /,
382
- function_call_id: str,
383
- client: typing.Optional[modal.client.Client] = None,
384
- is_generator: bool = False,
363
+ self, /, function_call_id: str, client: typing.Optional[modal.client.Client] = None
385
364
  ) -> FunctionCall[typing.Any]: ...
386
365
 
387
366
  from_id: __from_id_spec
modal/image.py CHANGED
@@ -193,18 +193,6 @@ def _validate_packages(packages: list[str]) -> bool:
193
193
  return not any(pkg.startswith("-") for pkg in packages)
194
194
 
195
195
 
196
- def _warn_invalid_packages(old_command: str) -> None:
197
- deprecation_warning(
198
- (2024, 7, 3),
199
- "Passing flags to `pip` via the `packages` argument of `pip_install` is deprecated."
200
- " Please pass flags via the `extra_options` argument instead."
201
- "\nNote that this will cause a rebuild of this image layer."
202
- " To avoid rebuilding, you can pass the following to `run_commands` instead:"
203
- f'\n`image.run_commands("{old_command}")`',
204
- show_source=False,
205
- )
206
-
207
-
208
196
  def _make_pip_install_args(
209
197
  find_links: Optional[str] = None, # Passes -f (--find-links) pip install
210
198
  index_url: Optional[str] = None, # Passes -i (--index-url) to pip install
@@ -449,7 +437,7 @@ class _Image(_Object, type_prefix="im"):
449
437
 
450
438
  def _add_mount_layer_or_copy(self, mount: _Mount, copy: bool = False):
451
439
  if copy:
452
- return self.copy_mount(mount, remote_path="/")
440
+ return self._copy_mount(mount, remote_path="/")
453
441
 
454
442
  base_image = self
455
443
 
@@ -684,23 +672,9 @@ class _Image(_Object, type_prefix="im"):
684
672
  )
685
673
  return obj
686
674
 
687
- def copy_mount(self, mount: _Mount, remote_path: Union[str, Path] = ".") -> "_Image":
688
- """
689
- **Deprecated**: Use image.add_local_dir(..., copy=True) or similar instead.
690
-
691
- Copy the entire contents of a `modal.Mount` into an image.
692
- Useful when files only available locally are required during the image
693
- build process.
694
-
695
- **Example**
696
-
697
- ```python notest
698
- static_images_dir = "./static"
699
- # place all static images in root of mount
700
- mount = modal.Mount.from_local_dir(static_images_dir, remote_path="/")
701
- # place mount's contents into /static directory of image.
702
- image = modal.Image.debian_slim().copy_mount(mount, remote_path="/static")
703
- ```
675
+ def _copy_mount(self, mount: _Mount, remote_path: Union[str, Path] = ".") -> "_Image":
676
+ """mdmd:hidden
677
+ Internal
704
678
  """
705
679
  if not isinstance(mount, _Mount):
706
680
  raise InvalidError("The mount argument to copy has to be a Modal Mount object")
@@ -815,7 +789,8 @@ class _Image(_Object, type_prefix="im"):
815
789
  return self._add_mount_layer_or_copy(mount, copy=copy)
816
790
 
817
791
  def copy_local_file(self, local_path: Union[str, Path], remote_path: Union[str, Path] = "./") -> "_Image":
818
- """Copy a file into the image as a part of building it.
792
+ """mdmd:hidden
793
+ Copy a file into the image as a part of building it.
819
794
 
820
795
  This works in a similar way to [`COPY`](https://docs.docker.com/engine/reference/builder/#copy)
821
796
  works in a `Dockerfile`.
@@ -888,7 +863,7 @@ class _Image(_Object, type_prefix="im"):
888
863
  # Which follows dockerignore syntax.
889
864
  ignore: Union[Sequence[str], Callable[[Path], bool]] = [],
890
865
  ) -> "_Image":
891
- """
866
+ """mdmd:hidden
892
867
  **Deprecated**: Use image.add_local_dir instead
893
868
 
894
869
  Copy a directory into the image as a part of building the image.
@@ -1011,13 +986,16 @@ class _Image(_Object, type_prefix="im"):
1011
986
  pkgs = _flatten_str_args("pip_install", "packages", packages)
1012
987
  if not pkgs:
1013
988
  return self
989
+ elif not _validate_packages(pkgs):
990
+ raise InvalidError(
991
+ "Package list for `Image.pip_install` cannot contain other arguments;"
992
+ " try the `extra_options` parameter instead."
993
+ )
1014
994
 
1015
995
  def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
1016
996
  package_args = shlex.join(sorted(pkgs))
1017
997
  extra_args = _make_pip_install_args(find_links, index_url, extra_index_url, pre, extra_options)
1018
998
  commands = ["FROM base", f"RUN python -m pip install {package_args} {extra_args}"]
1019
- if not _validate_packages(pkgs):
1020
- _warn_invalid_packages(commands[-1].split("RUN ")[-1])
1021
999
  if version > "2023.12": # Back-compat for legacy trailing space with empty extra_args
1022
1000
  commands = [cmd.strip() for cmd in commands]
1023
1001
  return DockerfileSpec(commands=commands, context_files={})
@@ -1900,7 +1878,6 @@ class _Image(_Object, type_prefix="im"):
1900
1878
  *,
1901
1879
  secrets: Sequence[_Secret] = (), # Optional Modal Secret objects with environment variables for the container
1902
1880
  gpu: Union[GPU_T, list[GPU_T]] = None, # Requested GPU or or list of acceptable GPUs( e.g. ["A10", "A100"])
1903
- mounts: Sequence[_Mount] = (), # Mounts attached to the function
1904
1881
  volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {}, # Volume mount paths
1905
1882
  network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem] = {}, # NFS mount paths
1906
1883
  cpu: Optional[float] = None, # How many CPU cores to request. This is a soft limit.
@@ -1958,7 +1935,6 @@ class _Image(_Object, type_prefix="im"):
1958
1935
  image=self, # type: ignore[reportArgumentType] # TODO: probably conflict with type stub?
1959
1936
  secrets=secrets,
1960
1937
  gpu=gpu,
1961
- mounts=mounts,
1962
1938
  volumes=volumes,
1963
1939
  network_file_systems=network_file_systems,
1964
1940
  cloud=cloud,
modal/image.pyi CHANGED
@@ -45,7 +45,6 @@ def _flatten_str_args(
45
45
  function_name: str, arg_name: str, args: collections.abc.Sequence[typing.Union[str, list[str]]]
46
46
  ) -> list[str]: ...
47
47
  def _validate_packages(packages: list[str]) -> bool: ...
48
- def _warn_invalid_packages(old_command: str) -> None: ...
49
48
  def _make_pip_install_args(
50
49
  find_links: typing.Optional[str] = None,
51
50
  index_url: typing.Optional[str] = None,
@@ -121,7 +120,7 @@ class _Image(modal._object._Object):
121
120
  _namespace: int = 1,
122
121
  _do_assert_no_mount_layers: bool = True,
123
122
  ): ...
124
- def copy_mount(self, mount: modal.mount._Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> _Image: ...
123
+ def _copy_mount(self, mount: modal.mount._Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> _Image: ...
125
124
  def add_local_file(
126
125
  self, local_path: typing.Union[str, pathlib.Path], remote_path: str, *, copy: bool = False
127
126
  ) -> _Image: ...
@@ -317,7 +316,6 @@ class _Image(modal._object._Object):
317
316
  *,
318
317
  secrets: collections.abc.Sequence[modal.secret._Secret] = (),
319
318
  gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
320
- mounts: collections.abc.Sequence[modal.mount._Mount] = (),
321
319
  volumes: dict[
322
320
  typing.Union[str, pathlib.PurePosixPath],
323
321
  typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
@@ -378,7 +376,7 @@ class Image(modal.object.Object):
378
376
  _namespace: int = 1,
379
377
  _do_assert_no_mount_layers: bool = True,
380
378
  ): ...
381
- def copy_mount(self, mount: modal.mount.Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> Image: ...
379
+ def _copy_mount(self, mount: modal.mount.Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> Image: ...
382
380
  def add_local_file(
383
381
  self, local_path: typing.Union[str, pathlib.Path], remote_path: str, *, copy: bool = False
384
382
  ) -> Image: ...
@@ -579,7 +577,6 @@ class Image(modal.object.Object):
579
577
  *,
580
578
  secrets: collections.abc.Sequence[modal.secret.Secret] = (),
581
579
  gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
582
- mounts: collections.abc.Sequence[modal.mount.Mount] = (),
583
580
  volumes: dict[
584
581
  typing.Union[str, pathlib.PurePosixPath],
585
582
  typing.Union[modal.volume.Volume, modal.cloud_bucket_mount.CloudBucketMount],
modal/mount.py CHANGED
@@ -5,9 +5,6 @@ import concurrent.futures
5
5
  import dataclasses
6
6
  import os
7
7
  import re
8
- import site
9
- import sys
10
- import sysconfig
11
8
  import time
12
9
  import typing
13
10
  import warnings
@@ -26,13 +23,13 @@ from ._object import _get_environment_name, _Object
26
23
  from ._resolver import Resolver
27
24
  from ._utils.async_utils import aclosing, async_map, synchronize_api
28
25
  from ._utils.blob_utils import FileUploadSpec, blob_upload_file, get_file_upload_spec_from_path
29
- from ._utils.deprecation import deprecation_warning, renamed_parameter
26
+ from ._utils.deprecation import deprecation_warning
30
27
  from ._utils.grpc_utils import retry_transient_errors
31
28
  from ._utils.name_utils import check_object_name
32
29
  from ._utils.package_utils import get_module_mount_info
33
30
  from .client import _Client
34
31
  from .config import config, logger
35
- from .exception import ExecutionError, InvalidError, ModuleNotMountable
32
+ from .exception import ExecutionError, InvalidError
36
33
  from .file_pattern_matcher import FilePatternMatcher
37
34
 
38
35
  ROOT_DIR: PurePosixPath = PurePosixPath("/root")
@@ -691,7 +688,6 @@ class _Mount(_Object, type_prefix="mo"):
691
688
  return mount
692
689
 
693
690
  @staticmethod
694
- @renamed_parameter((2024, 12, 18), "label", "name")
695
691
  def from_name(
696
692
  name: str,
697
693
  *,
@@ -712,7 +708,6 @@ class _Mount(_Object, type_prefix="mo"):
712
708
  return _Mount._from_loader(_load, "Mount()", hydrate_lazily=True)
713
709
 
714
710
  @classmethod
715
- @renamed_parameter((2024, 12, 18), "label", "name")
716
711
  async def lookup(
717
712
  cls: type["_Mount"],
718
713
  name: str,
@@ -802,22 +797,6 @@ def _get_client_mount():
802
797
  return _Mount.from_name(client_mount_name(), namespace=api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL)
803
798
 
804
799
 
805
- SYS_PREFIXES = {
806
- Path(p)
807
- for p in (
808
- sys.prefix,
809
- sys.base_prefix,
810
- sys.exec_prefix,
811
- sys.base_exec_prefix,
812
- *sysconfig.get_paths().values(),
813
- *site.getsitepackages(),
814
- site.getusersitepackages(),
815
- )
816
- }
817
-
818
-
819
- SYS_PREFIXES |= {p.resolve() for p in SYS_PREFIXES}
820
-
821
800
  MODAL_PACKAGES = ["modal", "modal_proto", "modal_version"]
822
801
 
823
802
 
@@ -831,45 +810,3 @@ def _is_modal_path(remote_path: PurePosixPath):
831
810
  if is_modal_path:
832
811
  return True
833
812
  return False
834
-
835
-
836
- def get_sys_modules_mounts() -> dict[str, _Mount]:
837
- """mdmd:hidden
838
-
839
- Auto-mount local modules that have been imported in global scope.
840
- This may or may not include the "entrypoint" of the function as well, depending on how modal is invoked
841
- Note: sys.modules may change during the iteration
842
- """
843
- auto_mounts = {}
844
- top_level_modules = []
845
- skip_prefixes = set()
846
- for name, module in sorted(sys.modules.items(), key=lambda kv: len(kv[0])):
847
- parent = name.rsplit(".")[0]
848
- if parent and parent in skip_prefixes:
849
- skip_prefixes.add(name)
850
- continue
851
- skip_prefixes.add(name)
852
- top_level_modules.append((name, module))
853
-
854
- for module_name, module in top_level_modules:
855
- if module_name.startswith("__"):
856
- # skip "built in" modules like __main__ and __mp_main__
857
- # the running function's main file should be included anyway
858
- continue
859
-
860
- try:
861
- # at this point we don't know if the sys.modules module should be mounted or not
862
- potential_mount = _Mount._from_local_python_packages(module_name)
863
- mount_paths = potential_mount._top_level_paths()
864
- except ModuleNotMountable:
865
- # this typically happens if the module is a built-in, has binary components or doesn't exist
866
- continue
867
-
868
- for local_path, remote_path in mount_paths:
869
- if any(local_path.is_relative_to(p) for p in SYS_PREFIXES) or _is_modal_path(remote_path):
870
- # skip any module that has paths in SYS_PREFIXES, or would overwrite the modal Package in the container
871
- break
872
- else:
873
- auto_mounts[module_name] = potential_mount
874
-
875
- return auto_mounts