modal 0.77.1.dev0__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.
- modal/__init__.py +10 -4
- modal/_functions.py +15 -90
- modal/_object.py +0 -14
- modal/_partial_function.py +4 -14
- modal/_serialization.py +2 -2
- modal/_utils/function_utils.py +3 -6
- modal/_utils/grpc_utils.py +6 -1
- modal/app.py +1 -104
- modal/app.pyi +0 -127
- modal/cli/app.py +0 -19
- modal/cli/programs/run_jupyter.py +1 -1
- modal/cli/programs/vscode.py +1 -1
- modal/client.pyi +2 -10
- modal/cls.py +9 -14
- modal/cls.pyi +2 -17
- modal/config.py +5 -16
- modal/container_process.py +1 -9
- modal/container_process.pyi +3 -3
- modal/dict.py +3 -5
- modal/environments.py +1 -3
- modal/exception.py +1 -1
- modal/functions.pyi +8 -29
- modal/image.py +12 -36
- modal/image.pyi +2 -5
- modal/mount.py +2 -65
- modal/mount.pyi +0 -1
- modal/network_file_system.py +3 -5
- modal/object.pyi +0 -6
- modal/queue.py +3 -5
- modal/runner.py +2 -19
- modal/runner.pyi +0 -5
- modal/sandbox.py +78 -32
- modal/sandbox.pyi +102 -7
- modal/secret.py +1 -3
- modal/serving.py +0 -6
- modal/serving.pyi +0 -3
- modal/volume.py +8 -17
- {modal-0.77.1.dev0.dist-info → modal-1.0.0.dist-info}/METADATA +1 -1
- {modal-0.77.1.dev0.dist-info → modal-1.0.0.dist-info}/RECORD +51 -51
- modal_proto/api.proto +29 -2
- modal_proto/api_grpc.py +32 -0
- modal_proto/api_pb2.py +788 -756
- modal_proto/api_pb2.pyi +86 -12
- modal_proto/api_pb2_grpc.py +66 -0
- modal_proto/api_pb2_grpc.pyi +20 -0
- modal_proto/modal_api_grpc.py +2 -0
- modal_version/__init__.py +1 -1
- {modal-0.77.1.dev0.dist-info → modal-1.0.0.dist-info}/WHEEL +0 -0
- {modal-0.77.1.dev0.dist-info → modal-1.0.0.dist-info}/entry_points.txt +0 -0
- {modal-0.77.1.dev0.dist-info → modal-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {modal-0.77.1.dev0.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,
|
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
|
-
"""
|
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
|
-
"""
|
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,
|
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
|
-
#
|
190
|
-
|
191
|
-
|
192
|
-
""
|
193
|
-
|
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.
|
modal/container_process.py
CHANGED
@@ -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
|
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()
|
modal/container_process.pyi
CHANGED
@@ -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
|
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,
|
84
|
-
async def aio(self,
|
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
|
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
|
-
"""
|
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
|
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
|
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
|
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.
|
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[
|
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.
|
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[
|
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.
|
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.
|
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
|
688
|
-
"""
|
689
|
-
|
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
|
-
"""
|
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
|
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
|
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
|
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
|
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
|