modal 1.0.3.dev10__py3-none-any.whl → 1.2.3.dev7__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 +0 -2
- modal/__main__.py +3 -4
- modal/_billing.py +80 -0
- modal/_clustered_functions.py +7 -3
- modal/_clustered_functions.pyi +15 -3
- modal/_container_entrypoint.py +51 -69
- modal/_functions.py +508 -240
- modal/_grpc_client.py +171 -0
- modal/_load_context.py +105 -0
- modal/_object.py +81 -21
- modal/_output.py +58 -45
- modal/_partial_function.py +48 -73
- modal/_pty.py +7 -3
- modal/_resolver.py +26 -46
- modal/_runtime/asgi.py +4 -3
- modal/_runtime/container_io_manager.py +358 -220
- modal/_runtime/container_io_manager.pyi +296 -101
- modal/_runtime/execution_context.py +18 -2
- modal/_runtime/execution_context.pyi +64 -7
- modal/_runtime/gpu_memory_snapshot.py +262 -57
- modal/_runtime/user_code_imports.py +28 -58
- modal/_serialization.py +90 -6
- modal/_traceback.py +42 -1
- modal/_tunnel.pyi +380 -12
- modal/_utils/async_utils.py +84 -29
- modal/_utils/auth_token_manager.py +111 -0
- modal/_utils/blob_utils.py +181 -58
- modal/_utils/deprecation.py +19 -0
- modal/_utils/function_utils.py +91 -47
- modal/_utils/grpc_utils.py +89 -66
- modal/_utils/mount_utils.py +26 -1
- modal/_utils/name_utils.py +17 -3
- modal/_utils/task_command_router_client.py +536 -0
- modal/_utils/time_utils.py +34 -6
- modal/app.py +256 -88
- modal/app.pyi +909 -92
- modal/billing.py +5 -0
- modal/builder/2025.06.txt +18 -0
- modal/builder/PREVIEW.txt +18 -0
- modal/builder/base-images.json +58 -0
- modal/cli/_download.py +19 -3
- modal/cli/_traceback.py +3 -2
- modal/cli/app.py +4 -4
- modal/cli/cluster.py +15 -7
- modal/cli/config.py +5 -3
- modal/cli/container.py +7 -6
- modal/cli/dict.py +22 -16
- modal/cli/entry_point.py +12 -5
- modal/cli/environment.py +5 -4
- modal/cli/import_refs.py +3 -3
- modal/cli/launch.py +102 -5
- modal/cli/network_file_system.py +11 -12
- modal/cli/profile.py +3 -2
- modal/cli/programs/launch_instance_ssh.py +94 -0
- modal/cli/programs/run_jupyter.py +1 -1
- modal/cli/programs/run_marimo.py +95 -0
- modal/cli/programs/vscode.py +1 -1
- modal/cli/queues.py +57 -26
- modal/cli/run.py +91 -23
- modal/cli/secret.py +48 -22
- modal/cli/token.py +7 -8
- modal/cli/utils.py +4 -7
- modal/cli/volume.py +31 -25
- modal/client.py +15 -85
- modal/client.pyi +183 -62
- modal/cloud_bucket_mount.py +5 -3
- modal/cloud_bucket_mount.pyi +197 -5
- modal/cls.py +200 -126
- modal/cls.pyi +446 -68
- modal/config.py +29 -11
- modal/container_process.py +319 -19
- modal/container_process.pyi +190 -20
- modal/dict.py +290 -71
- modal/dict.pyi +835 -83
- modal/environments.py +15 -27
- modal/environments.pyi +46 -24
- modal/exception.py +14 -2
- modal/experimental/__init__.py +194 -40
- modal/experimental/flash.py +618 -0
- modal/experimental/flash.pyi +380 -0
- modal/experimental/ipython.py +11 -7
- modal/file_io.py +29 -36
- modal/file_io.pyi +251 -53
- modal/file_pattern_matcher.py +56 -16
- modal/functions.pyi +673 -92
- modal/gpu.py +1 -1
- modal/image.py +528 -176
- modal/image.pyi +1572 -145
- modal/io_streams.py +458 -128
- modal/io_streams.pyi +433 -52
- modal/mount.py +216 -151
- modal/mount.pyi +225 -78
- modal/network_file_system.py +45 -62
- modal/network_file_system.pyi +277 -56
- modal/object.pyi +93 -17
- modal/parallel_map.py +942 -129
- modal/parallel_map.pyi +294 -15
- modal/partial_function.py +0 -2
- modal/partial_function.pyi +234 -19
- modal/proxy.py +17 -8
- modal/proxy.pyi +36 -3
- modal/queue.py +270 -65
- modal/queue.pyi +817 -57
- modal/runner.py +115 -101
- modal/runner.pyi +205 -49
- modal/sandbox.py +512 -136
- modal/sandbox.pyi +845 -111
- modal/schedule.py +1 -1
- modal/secret.py +300 -70
- modal/secret.pyi +589 -34
- modal/serving.py +7 -11
- modal/serving.pyi +7 -8
- modal/snapshot.py +11 -8
- modal/snapshot.pyi +25 -4
- modal/token_flow.py +4 -4
- modal/token_flow.pyi +28 -8
- modal/volume.py +416 -158
- modal/volume.pyi +1117 -121
- {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/METADATA +10 -9
- modal-1.2.3.dev7.dist-info/RECORD +195 -0
- modal_docs/mdmd/mdmd.py +17 -4
- modal_proto/api.proto +534 -79
- modal_proto/api_grpc.py +337 -1
- modal_proto/api_pb2.py +1522 -968
- modal_proto/api_pb2.pyi +1619 -134
- modal_proto/api_pb2_grpc.py +699 -4
- modal_proto/api_pb2_grpc.pyi +226 -14
- modal_proto/modal_api_grpc.py +175 -154
- modal_proto/sandbox_router.proto +145 -0
- modal_proto/sandbox_router_grpc.py +105 -0
- modal_proto/sandbox_router_pb2.py +149 -0
- modal_proto/sandbox_router_pb2.pyi +333 -0
- modal_proto/sandbox_router_pb2_grpc.py +203 -0
- modal_proto/sandbox_router_pb2_grpc.pyi +75 -0
- modal_proto/task_command_router.proto +144 -0
- modal_proto/task_command_router_grpc.py +105 -0
- modal_proto/task_command_router_pb2.py +149 -0
- modal_proto/task_command_router_pb2.pyi +333 -0
- modal_proto/task_command_router_pb2_grpc.py +203 -0
- modal_proto/task_command_router_pb2_grpc.pyi +75 -0
- modal_version/__init__.py +1 -1
- modal/requirements/PREVIEW.txt +0 -16
- modal/requirements/base-images.json +0 -26
- modal-1.0.3.dev10.dist-info/RECORD +0 -179
- modal_proto/modal_options_grpc.py +0 -3
- modal_proto/options.proto +0 -19
- modal_proto/options_grpc.py +0 -3
- modal_proto/options_pb2.py +0 -35
- modal_proto/options_pb2.pyi +0 -20
- modal_proto/options_pb2_grpc.py +0 -4
- modal_proto/options_pb2_grpc.pyi +0 -7
- /modal/{requirements → builder}/2023.12.312.txt +0 -0
- /modal/{requirements → builder}/2023.12.txt +0 -0
- /modal/{requirements → builder}/2024.04.txt +0 -0
- /modal/{requirements → builder}/2024.10.txt +0 -0
- /modal/{requirements → builder}/README.md +0 -0
- {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/WHEEL +0 -0
- {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/entry_points.txt +0 -0
- {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/licenses/LICENSE +0 -0
- {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/top_level.txt +0 -0
modal/image.pyi
CHANGED
|
@@ -2,6 +2,7 @@ import collections.abc
|
|
|
2
2
|
import google.protobuf.message
|
|
3
3
|
import modal._functions
|
|
4
4
|
import modal._object
|
|
5
|
+
import modal.app
|
|
5
6
|
import modal.client
|
|
6
7
|
import modal.cloud_bucket_mount
|
|
7
8
|
import modal.functions
|
|
@@ -16,35 +17,51 @@ import pathlib
|
|
|
16
17
|
import typing
|
|
17
18
|
import typing_extensions
|
|
18
19
|
|
|
19
|
-
ImageBuilderVersion = typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"]
|
|
20
|
+
ImageBuilderVersion = typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"]
|
|
20
21
|
|
|
21
22
|
class _AutoDockerIgnoreSentinel:
|
|
22
|
-
def __repr__(self) -> str:
|
|
23
|
-
|
|
23
|
+
def __repr__(self) -> str:
|
|
24
|
+
"""Return repr(self)."""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
def __call__(self, _: pathlib.Path) -> bool:
|
|
28
|
+
"""Call self as a function."""
|
|
29
|
+
...
|
|
24
30
|
|
|
25
31
|
AUTO_DOCKERIGNORE: _AutoDockerIgnoreSentinel
|
|
26
32
|
|
|
27
33
|
def _validate_python_version(
|
|
28
34
|
python_version: typing.Optional[str],
|
|
29
|
-
builder_version: typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"],
|
|
35
|
+
builder_version: typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"],
|
|
30
36
|
allow_micro_granularity: bool = True,
|
|
31
37
|
) -> str: ...
|
|
32
38
|
def _dockerhub_python_version(
|
|
33
|
-
builder_version: typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"],
|
|
39
|
+
builder_version: typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"],
|
|
34
40
|
python_version: typing.Optional[str] = None,
|
|
35
41
|
) -> str: ...
|
|
36
42
|
def _base_image_config(
|
|
37
|
-
group: str, builder_version: typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"]
|
|
43
|
+
group: str, builder_version: typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"]
|
|
38
44
|
) -> typing.Any: ...
|
|
39
45
|
def _get_modal_requirements_path(
|
|
40
|
-
builder_version: typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"],
|
|
46
|
+
builder_version: typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"],
|
|
41
47
|
python_version: typing.Optional[str] = None,
|
|
42
48
|
) -> str: ...
|
|
43
|
-
def _get_modal_requirements_command(
|
|
49
|
+
def _get_modal_requirements_command(
|
|
50
|
+
version: typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"],
|
|
51
|
+
) -> str: ...
|
|
44
52
|
def _flatten_str_args(
|
|
45
53
|
function_name: str, arg_name: str, args: collections.abc.Sequence[typing.Union[str, list[str]]]
|
|
46
|
-
) -> list[str]:
|
|
47
|
-
|
|
54
|
+
) -> list[str]:
|
|
55
|
+
"""Takes a sequence of strings, or string lists, and flattens it.
|
|
56
|
+
|
|
57
|
+
Raises an error if any of the elements are not strings or string lists.
|
|
58
|
+
"""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
def _validate_packages(packages: list[str]) -> bool:
|
|
62
|
+
"""Validates that a list of packages does not contain any command-line options."""
|
|
63
|
+
...
|
|
64
|
+
|
|
48
65
|
def _make_pip_install_args(
|
|
49
66
|
find_links: typing.Optional[str] = None,
|
|
50
67
|
index_url: typing.Optional[str] = None,
|
|
@@ -53,40 +70,67 @@ def _make_pip_install_args(
|
|
|
53
70
|
extra_options: str = "",
|
|
54
71
|
) -> str: ...
|
|
55
72
|
def _get_image_builder_version(
|
|
56
|
-
server_version: typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"],
|
|
57
|
-
) -> typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"]: ...
|
|
73
|
+
server_version: typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"],
|
|
74
|
+
) -> typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"]: ...
|
|
58
75
|
def _create_context_mount(
|
|
59
76
|
docker_commands: collections.abc.Sequence[str],
|
|
60
77
|
ignore_fn: collections.abc.Callable[[pathlib.Path], bool],
|
|
61
78
|
context_dir: pathlib.Path,
|
|
62
|
-
) -> typing.Optional[modal.mount._Mount]:
|
|
79
|
+
) -> typing.Optional[modal.mount._Mount]:
|
|
80
|
+
"""Creates a context mount from a list of docker commands.
|
|
81
|
+
|
|
82
|
+
1. Paths are evaluated relative to context_dir.
|
|
83
|
+
2. First selects inclusions based on COPY commands in the list of commands.
|
|
84
|
+
3. Then ignore any files as per the ignore predicate.
|
|
85
|
+
"""
|
|
86
|
+
...
|
|
87
|
+
|
|
63
88
|
def _create_context_mount_function(
|
|
64
89
|
ignore: typing.Union[
|
|
65
90
|
collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool], _AutoDockerIgnoreSentinel
|
|
66
91
|
],
|
|
67
92
|
dockerfile_cmds: list[str] = [],
|
|
68
93
|
dockerfile_path: typing.Optional[pathlib.Path] = None,
|
|
69
|
-
|
|
70
|
-
context_dir: typing.Union[pathlib.Path, str, None] = None,
|
|
94
|
+
context_dir: typing.Union[str, pathlib.Path, None] = None,
|
|
71
95
|
): ...
|
|
72
96
|
|
|
73
97
|
class _ImageRegistryConfig:
|
|
74
|
-
|
|
98
|
+
"""mdmd:hidden"""
|
|
99
|
+
def __init__(self, registry_auth_type: int = 0, secret: typing.Optional[modal.secret._Secret] = None):
|
|
100
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
101
|
+
...
|
|
102
|
+
|
|
75
103
|
def get_proto(self) -> modal_proto.api_pb2.ImageRegistryConfig: ...
|
|
76
104
|
|
|
77
105
|
class DockerfileSpec:
|
|
106
|
+
"""DockerfileSpec(commands: list[str], context_files: dict[str, str])"""
|
|
107
|
+
|
|
78
108
|
commands: list[str]
|
|
79
109
|
context_files: dict[str, str]
|
|
80
110
|
|
|
81
|
-
def __init__(self, commands: list[str], context_files: dict[str, str]) -> None:
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
def __init__(self, commands: list[str], context_files: dict[str, str]) -> None:
|
|
112
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
def __repr__(self):
|
|
116
|
+
"""Return repr(self)."""
|
|
117
|
+
...
|
|
118
|
+
|
|
119
|
+
def __eq__(self, other):
|
|
120
|
+
"""Return self==value."""
|
|
121
|
+
...
|
|
84
122
|
|
|
85
123
|
async def _image_await_build_result(
|
|
86
124
|
image_id: str, client: modal.client._Client
|
|
87
125
|
) -> modal_proto.api_pb2.ImageJoinStreamingResponse: ...
|
|
88
126
|
|
|
89
127
|
class _Image(modal._object._Object):
|
|
128
|
+
"""Base class for container images to run functions in.
|
|
129
|
+
|
|
130
|
+
Do not construct this class directly; instead use one of its static factory methods,
|
|
131
|
+
such as `modal.Image.debian_slim`, `modal.Image.from_registry`, or `modal.Image.micromamba`.
|
|
132
|
+
"""
|
|
133
|
+
|
|
90
134
|
force_build: bool
|
|
91
135
|
inside_exceptions: list[Exception]
|
|
92
136
|
_serve_mounts: frozenset[modal.mount._Mount]
|
|
@@ -100,16 +144,29 @@ class _Image(modal._object._Object):
|
|
|
100
144
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
101
145
|
def _add_mount_layer_or_copy(self, mount: modal.mount._Mount, copy: bool = False): ...
|
|
102
146
|
@property
|
|
103
|
-
def _mount_layers(self) -> typing.Sequence[modal.mount._Mount]:
|
|
147
|
+
def _mount_layers(self) -> typing.Sequence[modal.mount._Mount]:
|
|
148
|
+
"""Non-evaluated mount layers on the image
|
|
149
|
+
|
|
150
|
+
When the image is used by a Modal container, these mounts need to be attached as well to
|
|
151
|
+
represent the full image content, as they haven't yet been represented as a layer in the
|
|
152
|
+
image.
|
|
153
|
+
|
|
154
|
+
When the image is used as a base image for a new layer (that is not itself a mount layer)
|
|
155
|
+
these mounts need to first be inserted as a copy operation (.copy_mount) into the image.
|
|
156
|
+
"""
|
|
157
|
+
...
|
|
158
|
+
|
|
104
159
|
def _assert_no_mount_layers(self): ...
|
|
105
160
|
@staticmethod
|
|
106
161
|
def _from_args(
|
|
107
162
|
*,
|
|
108
163
|
base_images: typing.Optional[dict[str, _Image]] = None,
|
|
109
164
|
dockerfile_function: typing.Optional[
|
|
110
|
-
collections.abc.Callable[
|
|
165
|
+
collections.abc.Callable[
|
|
166
|
+
[typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"]], DockerfileSpec
|
|
167
|
+
]
|
|
111
168
|
] = None,
|
|
112
|
-
secrets: typing.Optional[collections.abc.
|
|
169
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
113
170
|
gpu_config: typing.Optional[modal_proto.api_pb2.GPUConfig] = None,
|
|
114
171
|
build_function: typing.Optional[modal._functions._Function] = None,
|
|
115
172
|
build_function_input: typing.Optional[modal_proto.api_pb2.FunctionInput] = None,
|
|
@@ -118,13 +175,36 @@ class _Image(modal._object._Object):
|
|
|
118
175
|
collections.abc.Callable[[], typing.Optional[modal.mount._Mount]]
|
|
119
176
|
] = None,
|
|
120
177
|
force_build: bool = False,
|
|
178
|
+
build_args: dict[str, str] = {},
|
|
179
|
+
validated_volumes: typing.Optional[collections.abc.Sequence[tuple[str, modal.volume._Volume]]] = None,
|
|
121
180
|
_namespace: int = 1,
|
|
122
181
|
_do_assert_no_mount_layers: bool = True,
|
|
123
182
|
): ...
|
|
124
|
-
def _copy_mount(self, mount: modal.mount._Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> _Image:
|
|
183
|
+
def _copy_mount(self, mount: modal.mount._Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> _Image:
|
|
184
|
+
"""mdmd:hidden
|
|
185
|
+
Internal
|
|
186
|
+
"""
|
|
187
|
+
...
|
|
188
|
+
|
|
125
189
|
def add_local_file(
|
|
126
190
|
self, local_path: typing.Union[str, pathlib.Path], remote_path: str, *, copy: bool = False
|
|
127
|
-
) -> _Image:
|
|
191
|
+
) -> _Image:
|
|
192
|
+
"""Adds a local file to the image at `remote_path` within the container
|
|
193
|
+
|
|
194
|
+
By default (`copy=False`), the files are added to containers on startup and are not built into the actual Image,
|
|
195
|
+
which speeds up deployment.
|
|
196
|
+
|
|
197
|
+
Set `copy=True` to copy the files into an Image layer at build time instead, similar to how
|
|
198
|
+
[`COPY`](https://docs.docker.com/engine/reference/builder/#copy) works in a `Dockerfile`.
|
|
199
|
+
|
|
200
|
+
copy=True can slow down iteration since it requires a rebuild of the Image and any subsequent
|
|
201
|
+
build steps whenever the included files change, but it is required if you want to run additional
|
|
202
|
+
build steps after this one.
|
|
203
|
+
|
|
204
|
+
*Added in v0.66.40*: This method replaces the deprecated `modal.Image.copy_local_file` method.
|
|
205
|
+
"""
|
|
206
|
+
...
|
|
207
|
+
|
|
128
208
|
def add_local_dir(
|
|
129
209
|
self,
|
|
130
210
|
local_path: typing.Union[str, pathlib.Path],
|
|
@@ -132,10 +212,61 @@ class _Image(modal._object._Object):
|
|
|
132
212
|
*,
|
|
133
213
|
copy: bool = False,
|
|
134
214
|
ignore: typing.Union[collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]] = [],
|
|
135
|
-
) -> _Image:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
215
|
+
) -> _Image:
|
|
216
|
+
"""Adds a local directory's content to the image at `remote_path` within the container
|
|
217
|
+
|
|
218
|
+
By default (`copy=False`), the files are added to containers on startup and are not built into the actual Image,
|
|
219
|
+
which speeds up deployment.
|
|
220
|
+
|
|
221
|
+
Set `copy=True` to copy the files into an Image layer at build time instead, similar to how
|
|
222
|
+
[`COPY`](https://docs.docker.com/engine/reference/builder/#copy) works in a `Dockerfile`.
|
|
223
|
+
|
|
224
|
+
copy=True can slow down iteration since it requires a rebuild of the Image and any subsequent
|
|
225
|
+
build steps whenever the included files change, but it is required if you want to run additional
|
|
226
|
+
build steps after this one.
|
|
227
|
+
|
|
228
|
+
**Usage:**
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
from modal import FilePatternMatcher
|
|
232
|
+
|
|
233
|
+
image = modal.Image.debian_slim().add_local_dir(
|
|
234
|
+
"~/assets",
|
|
235
|
+
remote_path="/assets",
|
|
236
|
+
ignore=["*.venv"],
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
image = modal.Image.debian_slim().add_local_dir(
|
|
240
|
+
"~/assets",
|
|
241
|
+
remote_path="/assets",
|
|
242
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
image = modal.Image.debian_slim().add_local_dir(
|
|
246
|
+
"~/assets",
|
|
247
|
+
remote_path="/assets",
|
|
248
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
|
252
|
+
image = modal.Image.debian_slim().add_local_dir(
|
|
253
|
+
"~/assets",
|
|
254
|
+
remote_path="/assets",
|
|
255
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# You can also read ignore patterns from a file.
|
|
259
|
+
image = modal.Image.debian_slim().add_local_dir(
|
|
260
|
+
"~/assets",
|
|
261
|
+
remote_path="/assets",
|
|
262
|
+
ignore=FilePatternMatcher.from_file("/path/to/ignorefile"),
|
|
263
|
+
)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
*Added in v0.66.40*: This method replaces the deprecated `modal.Image.copy_local_dir` method.
|
|
267
|
+
"""
|
|
268
|
+
...
|
|
269
|
+
|
|
139
270
|
def add_local_python_source(
|
|
140
271
|
self,
|
|
141
272
|
*module_names: str,
|
|
@@ -143,15 +274,104 @@ class _Image(modal._object._Object):
|
|
|
143
274
|
ignore: typing.Union[
|
|
144
275
|
collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
|
|
145
276
|
] = modal.file_pattern_matcher.NON_PYTHON_FILES,
|
|
146
|
-
) -> _Image:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
277
|
+
) -> _Image:
|
|
278
|
+
"""Adds locally available Python packages/modules to containers
|
|
279
|
+
|
|
280
|
+
Adds all files from the specified Python package or module to containers running the Image.
|
|
281
|
+
|
|
282
|
+
Packages are added to the `/root` directory of containers, which is on the `PYTHONPATH`
|
|
283
|
+
of any executed Modal Functions, enabling import of the module by that name.
|
|
284
|
+
|
|
285
|
+
By default (`copy=False`), the files are added to containers on startup and are not built into the actual Image,
|
|
286
|
+
which speeds up deployment.
|
|
287
|
+
|
|
288
|
+
Set `copy=True` to copy the files into an Image layer at build time instead. This can slow down iteration since
|
|
289
|
+
it requires a rebuild of the Image and any subsequent build steps whenever the included files change, but it is
|
|
290
|
+
required if you want to run additional build steps after this one.
|
|
291
|
+
|
|
292
|
+
**Note:** This excludes all dot-prefixed subdirectories or files and all `.pyc`/`__pycache__` files.
|
|
293
|
+
To add full directories with finer control, use `.add_local_dir()` instead and specify `/root` as
|
|
294
|
+
the destination directory.
|
|
295
|
+
|
|
296
|
+
By default only includes `.py`-files in the source modules. Set the `ignore` argument to a list of patterns
|
|
297
|
+
or a callable to override this behavior, e.g.:
|
|
298
|
+
|
|
299
|
+
```py
|
|
300
|
+
# includes everything except data.json
|
|
301
|
+
modal.Image.debian_slim().add_local_python_source("mymodule", ignore=["data.json"])
|
|
302
|
+
|
|
303
|
+
# exclude large files
|
|
304
|
+
modal.Image.debian_slim().add_local_python_source(
|
|
305
|
+
"mymodule",
|
|
306
|
+
ignore=lambda p: p.stat().st_size > 1e9
|
|
307
|
+
)
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
*Added in v0.67.28*: This method replaces the deprecated `modal.Mount.from_local_python_packages` pattern.
|
|
311
|
+
"""
|
|
312
|
+
...
|
|
313
|
+
|
|
153
314
|
@staticmethod
|
|
154
|
-
async def from_id(image_id: str, client: typing.Optional[modal.client._Client] = None) -> _Image:
|
|
315
|
+
async def from_id(image_id: str, client: typing.Optional[modal.client._Client] = None) -> _Image:
|
|
316
|
+
"""Construct an Image from an id and look up the Image result.
|
|
317
|
+
|
|
318
|
+
The ID of an Image object can be accessed using `.object_id`.
|
|
319
|
+
"""
|
|
320
|
+
...
|
|
321
|
+
|
|
322
|
+
async def build(self, app: modal.app._App) -> _Image:
|
|
323
|
+
"""Eagerly build an image.
|
|
324
|
+
|
|
325
|
+
If your image was previously built, then this method will not rebuild your image
|
|
326
|
+
and your cached image is returned.
|
|
327
|
+
|
|
328
|
+
**Examples**
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
image = modal.Image.debian_slim().uv_pip_install("scipy", "numpy")
|
|
332
|
+
|
|
333
|
+
app = modal.App.lookup("build-image", create_if_missing=True)
|
|
334
|
+
with modal.enable_output(): # To see logs in your local terminal
|
|
335
|
+
image.build(app)
|
|
336
|
+
|
|
337
|
+
# Save the image id
|
|
338
|
+
my_image_id = image.object_id
|
|
339
|
+
|
|
340
|
+
# Reference the image with the id or uses it another context.
|
|
341
|
+
built_image = modal.Image.from_id(my_image_id)
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Alternatively, you can pre-build a image and use it in a sandbox.
|
|
345
|
+
|
|
346
|
+
```python notest
|
|
347
|
+
app = modal.App.lookup("sandbox-example", create_if_missing=True)
|
|
348
|
+
|
|
349
|
+
with modal.enable_output():
|
|
350
|
+
image = modal.Image.debian_slim().uv_pip_install("scipy")
|
|
351
|
+
image.build(app)
|
|
352
|
+
|
|
353
|
+
sb = modal.Sandbox.create("python", "-c", "import scipy; print(scipy)", app=app, image=image)
|
|
354
|
+
print(sb.stdout.read())
|
|
355
|
+
sb.terminate()
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**Note**
|
|
359
|
+
|
|
360
|
+
For defining Modal functions, images are built automatically when deploying or running an App.
|
|
361
|
+
You do not need to built the image explicitly:
|
|
362
|
+
|
|
363
|
+
```python notest
|
|
364
|
+
app = modal.App()
|
|
365
|
+
image = modal.Image.debian_slim()
|
|
366
|
+
|
|
367
|
+
# No need to explicitly build the image for defining a function.
|
|
368
|
+
@app.function(image=image)
|
|
369
|
+
def f():
|
|
370
|
+
...
|
|
371
|
+
```
|
|
372
|
+
"""
|
|
373
|
+
...
|
|
374
|
+
|
|
155
375
|
def pip_install(
|
|
156
376
|
self,
|
|
157
377
|
*packages: typing.Union[str, list[str]],
|
|
@@ -161,9 +381,39 @@ class _Image(modal._object._Object):
|
|
|
161
381
|
pre: bool = False,
|
|
162
382
|
extra_options: str = "",
|
|
163
383
|
force_build: bool = False,
|
|
164
|
-
|
|
384
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
385
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
165
386
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
166
|
-
) -> _Image:
|
|
387
|
+
) -> _Image:
|
|
388
|
+
"""Install a list of Python packages using pip.
|
|
389
|
+
|
|
390
|
+
**Examples**
|
|
391
|
+
|
|
392
|
+
Simple installation:
|
|
393
|
+
```python
|
|
394
|
+
image = modal.Image.debian_slim().pip_install("click", "httpx~=0.23.3")
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
More complex installation:
|
|
398
|
+
```python
|
|
399
|
+
image = (
|
|
400
|
+
modal.Image.from_registry(
|
|
401
|
+
"nvidia/cuda:12.2.0-devel-ubuntu22.04", add_python="3.11"
|
|
402
|
+
)
|
|
403
|
+
.pip_install(
|
|
404
|
+
"ninja",
|
|
405
|
+
"packaging",
|
|
406
|
+
"wheel",
|
|
407
|
+
"transformers==4.40.2",
|
|
408
|
+
)
|
|
409
|
+
.pip_install(
|
|
410
|
+
"flash-attn==2.5.8", extra_options="--no-build-isolation"
|
|
411
|
+
)
|
|
412
|
+
)
|
|
413
|
+
```
|
|
414
|
+
"""
|
|
415
|
+
...
|
|
416
|
+
|
|
167
417
|
def pip_install_private_repos(
|
|
168
418
|
self,
|
|
169
419
|
*repositories: str,
|
|
@@ -174,9 +424,42 @@ class _Image(modal._object._Object):
|
|
|
174
424
|
pre: bool = False,
|
|
175
425
|
extra_options: str = "",
|
|
176
426
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
177
|
-
|
|
427
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
428
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
178
429
|
force_build: bool = False,
|
|
179
|
-
) -> _Image:
|
|
430
|
+
) -> _Image:
|
|
431
|
+
"""Install a list of Python packages from private git repositories using pip.
|
|
432
|
+
|
|
433
|
+
This method currently supports Github and Gitlab only.
|
|
434
|
+
|
|
435
|
+
- **Github:** Provide a `modal.Secret` that contains a `GITHUB_TOKEN` key-value pair
|
|
436
|
+
- **Gitlab:** Provide a `modal.Secret` that contains a `GITLAB_TOKEN` key-value pair
|
|
437
|
+
|
|
438
|
+
These API tokens should have permissions to read the list of private repositories provided as arguments.
|
|
439
|
+
|
|
440
|
+
We recommend using Github's ['fine-grained' access tokens](https://github.blog/2022-10-18-introducing-fine-grained-personal-access-tokens-for-github/).
|
|
441
|
+
These tokens are repo-scoped, and avoid granting read permission across all of a user's private repos.
|
|
442
|
+
|
|
443
|
+
**Example**
|
|
444
|
+
|
|
445
|
+
```python
|
|
446
|
+
image = (
|
|
447
|
+
modal.Image
|
|
448
|
+
.debian_slim()
|
|
449
|
+
.pip_install_private_repos(
|
|
450
|
+
"github.com/ecorp/private-one@1.0.0",
|
|
451
|
+
"github.com/ecorp/private-two@main"
|
|
452
|
+
"github.com/ecorp/private-three@d4776502"
|
|
453
|
+
# install from 'inner' directory on default branch.
|
|
454
|
+
"github.com/ecorp/private-four#subdirectory=inner",
|
|
455
|
+
git_user="erikbern",
|
|
456
|
+
secrets=[modal.Secret.from_name("github-read-private")],
|
|
457
|
+
)
|
|
458
|
+
)
|
|
459
|
+
```
|
|
460
|
+
"""
|
|
461
|
+
...
|
|
462
|
+
|
|
180
463
|
def pip_install_from_requirements(
|
|
181
464
|
self,
|
|
182
465
|
requirements_txt: str,
|
|
@@ -187,9 +470,13 @@ class _Image(modal._object._Object):
|
|
|
187
470
|
pre: bool = False,
|
|
188
471
|
extra_options: str = "",
|
|
189
472
|
force_build: bool = False,
|
|
190
|
-
|
|
473
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
474
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
191
475
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
192
|
-
) -> _Image:
|
|
476
|
+
) -> _Image:
|
|
477
|
+
"""Install a list of Python packages from a local `requirements.txt` file."""
|
|
478
|
+
...
|
|
479
|
+
|
|
193
480
|
def pip_install_from_pyproject(
|
|
194
481
|
self,
|
|
195
482
|
pyproject_toml: str,
|
|
@@ -201,60 +488,216 @@ class _Image(modal._object._Object):
|
|
|
201
488
|
pre: bool = False,
|
|
202
489
|
extra_options: str = "",
|
|
203
490
|
force_build: bool = False,
|
|
204
|
-
|
|
491
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
492
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
493
|
+
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
494
|
+
) -> _Image:
|
|
495
|
+
"""Install dependencies specified by a local `pyproject.toml` file.
|
|
496
|
+
|
|
497
|
+
`optional_dependencies` is a list of the keys of the
|
|
498
|
+
optional-dependencies section(s) of the `pyproject.toml` file
|
|
499
|
+
(e.g. test, doc, experiment, etc). When provided,
|
|
500
|
+
all of the packages in each listed section are installed as well.
|
|
501
|
+
"""
|
|
502
|
+
...
|
|
503
|
+
|
|
504
|
+
def uv_pip_install(
|
|
505
|
+
self,
|
|
506
|
+
*packages: typing.Union[str, list[str]],
|
|
507
|
+
requirements: typing.Optional[list[str]] = None,
|
|
508
|
+
find_links: typing.Optional[str] = None,
|
|
509
|
+
index_url: typing.Optional[str] = None,
|
|
510
|
+
extra_index_url: typing.Optional[str] = None,
|
|
511
|
+
pre: bool = False,
|
|
512
|
+
extra_options: str = "",
|
|
513
|
+
force_build: bool = False,
|
|
514
|
+
uv_version: typing.Optional[str] = None,
|
|
515
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
516
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
205
517
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
206
|
-
) -> _Image:
|
|
518
|
+
) -> _Image:
|
|
519
|
+
"""Install a list of Python packages using uv pip install.
|
|
520
|
+
|
|
521
|
+
**Examples**
|
|
522
|
+
|
|
523
|
+
Simple installation:
|
|
524
|
+
```python
|
|
525
|
+
image = modal.Image.debian_slim().uv_pip_install("torch==2.7.1", "numpy")
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
This method assumes that:
|
|
529
|
+
- Python is on the `$PATH` and dependencies are installed with the first Python on the `$PATH`.
|
|
530
|
+
- Shell supports backticks for substitution
|
|
531
|
+
- `which` command is on the `$PATH`
|
|
532
|
+
|
|
533
|
+
Added in v1.1.0.
|
|
534
|
+
"""
|
|
535
|
+
...
|
|
536
|
+
|
|
207
537
|
def poetry_install_from_file(
|
|
208
538
|
self,
|
|
209
539
|
poetry_pyproject_toml: str,
|
|
210
540
|
poetry_lockfile: typing.Optional[str] = None,
|
|
211
541
|
*,
|
|
212
542
|
ignore_lockfile: bool = False,
|
|
213
|
-
old_installer: bool = False,
|
|
214
543
|
force_build: bool = False,
|
|
215
544
|
with_: list[str] = [],
|
|
216
545
|
without: list[str] = [],
|
|
217
546
|
only: list[str] = [],
|
|
218
|
-
|
|
547
|
+
poetry_version: typing.Optional[str] = "latest",
|
|
548
|
+
old_installer: bool = False,
|
|
549
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
550
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
551
|
+
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
552
|
+
) -> _Image:
|
|
553
|
+
"""Install poetry *dependencies* specified by a local `pyproject.toml` file.
|
|
554
|
+
|
|
555
|
+
If not provided as argument the path to the lockfile is inferred. However, the
|
|
556
|
+
file has to exist, unless `ignore_lockfile` is set to `True`.
|
|
557
|
+
|
|
558
|
+
Note that the root project of the poetry project is not installed, only the dependencies.
|
|
559
|
+
For including local python source files see `add_local_python_source`
|
|
560
|
+
|
|
561
|
+
Poetry will be installed to the Image (using pip) unless `poetry_version` is set to None.
|
|
562
|
+
Note that the interpretation of `poetry_version="latest"` depends on the Modal Image Builder
|
|
563
|
+
version, with versions 2024.10 and earlier limiting poetry to 1.x.
|
|
564
|
+
"""
|
|
565
|
+
...
|
|
566
|
+
|
|
567
|
+
def uv_sync(
|
|
568
|
+
self,
|
|
569
|
+
uv_project_dir: str = "./",
|
|
570
|
+
*,
|
|
571
|
+
force_build: bool = False,
|
|
572
|
+
groups: typing.Optional[list[str]] = None,
|
|
573
|
+
extras: typing.Optional[list[str]] = None,
|
|
574
|
+
frozen: bool = True,
|
|
575
|
+
extra_options: str = "",
|
|
576
|
+
uv_version: typing.Optional[str] = None,
|
|
577
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
578
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
219
579
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
220
|
-
) -> _Image:
|
|
580
|
+
) -> _Image:
|
|
581
|
+
"""Creates a virtual environment with the dependencies in a uv managed project with `uv sync`.
|
|
582
|
+
|
|
583
|
+
**Examples**
|
|
584
|
+
```python
|
|
585
|
+
image = modal.Image.debian_slim().uv_sync()
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
The `pyproject.toml` and `uv.lock` in `uv_project_dir` are automatically added to the build context. The
|
|
589
|
+
`uv_project_dir` is relative to the current working directory of where `modal` is called.
|
|
590
|
+
|
|
591
|
+
NOTE: This does *not* install the project itself into the environment (this is equivalent to the
|
|
592
|
+
`--no-install-project` flag in the `uv sync` command) and you would be expected to add any local python source
|
|
593
|
+
files using `Image.add_local_python_source` or similar methods after this call.
|
|
594
|
+
|
|
595
|
+
This ensures that updates to your project code wouldn't require reinstalling third-party dependencies
|
|
596
|
+
after every change.
|
|
597
|
+
|
|
598
|
+
uv workspaces are currently not supported.
|
|
599
|
+
|
|
600
|
+
Added in v1.1.0.
|
|
601
|
+
"""
|
|
602
|
+
...
|
|
603
|
+
|
|
221
604
|
def dockerfile_commands(
|
|
222
605
|
self,
|
|
223
606
|
*dockerfile_commands: typing.Union[str, list[str]],
|
|
224
607
|
context_files: dict[str, str] = {},
|
|
225
|
-
|
|
608
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
609
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
226
610
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
227
|
-
|
|
228
|
-
context_dir: typing.Union[pathlib.Path, str, None] = None,
|
|
611
|
+
context_dir: typing.Union[str, pathlib.Path, None] = None,
|
|
229
612
|
force_build: bool = False,
|
|
230
613
|
ignore: typing.Union[
|
|
231
614
|
collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
|
|
232
615
|
] = modal.image.AUTO_DOCKERIGNORE,
|
|
233
|
-
) -> _Image:
|
|
234
|
-
|
|
235
|
-
|
|
616
|
+
) -> _Image:
|
|
617
|
+
"""Extend an image with arbitrary Dockerfile-like commands.
|
|
618
|
+
|
|
619
|
+
**Usage:**
|
|
620
|
+
|
|
621
|
+
```python
|
|
622
|
+
from modal import FilePatternMatcher
|
|
623
|
+
|
|
624
|
+
# By default a .dockerignore file is used if present in the current working directory
|
|
625
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
|
626
|
+
["COPY data /data"],
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
|
630
|
+
["COPY data /data"],
|
|
631
|
+
ignore=["*.venv"],
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
|
635
|
+
["COPY data /data"],
|
|
636
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
|
640
|
+
["COPY data /data"],
|
|
641
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
|
645
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
|
646
|
+
["COPY data /data"],
|
|
647
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
# You can also read ignore patterns from a file.
|
|
651
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
|
652
|
+
["COPY data /data"],
|
|
653
|
+
ignore=FilePatternMatcher.from_file("/path/to/dockerignore"),
|
|
654
|
+
)
|
|
655
|
+
```
|
|
656
|
+
"""
|
|
657
|
+
...
|
|
658
|
+
|
|
659
|
+
def entrypoint(self, entrypoint_commands: list[str]) -> _Image:
|
|
660
|
+
"""Set the ENTRYPOINT for the image."""
|
|
661
|
+
...
|
|
662
|
+
|
|
663
|
+
def shell(self, shell_commands: list[str]) -> _Image:
|
|
664
|
+
"""Overwrite default shell for the image."""
|
|
665
|
+
...
|
|
666
|
+
|
|
236
667
|
def run_commands(
|
|
237
668
|
self,
|
|
238
669
|
*commands: typing.Union[str, list[str]],
|
|
239
|
-
|
|
670
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
671
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
672
|
+
volumes: typing.Optional[dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume]] = None,
|
|
240
673
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
241
674
|
force_build: bool = False,
|
|
242
|
-
) -> _Image:
|
|
675
|
+
) -> _Image:
|
|
676
|
+
"""Extend an image with a list of shell commands to run."""
|
|
677
|
+
...
|
|
678
|
+
|
|
243
679
|
@staticmethod
|
|
244
|
-
def micromamba(python_version: typing.Optional[str] = None, force_build: bool = False) -> _Image:
|
|
680
|
+
def micromamba(python_version: typing.Optional[str] = None, force_build: bool = False) -> _Image:
|
|
681
|
+
"""A Micromamba base image. Micromamba allows for fast building of small Conda-based containers."""
|
|
682
|
+
...
|
|
683
|
+
|
|
245
684
|
def micromamba_install(
|
|
246
685
|
self,
|
|
247
686
|
*packages: typing.Union[str, list[str]],
|
|
248
687
|
spec_file: typing.Optional[str] = None,
|
|
249
688
|
channels: list[str] = [],
|
|
250
689
|
force_build: bool = False,
|
|
251
|
-
|
|
690
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
691
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
252
692
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
253
|
-
) -> _Image:
|
|
693
|
+
) -> _Image:
|
|
694
|
+
"""Install a list of additional packages using micromamba."""
|
|
695
|
+
...
|
|
696
|
+
|
|
254
697
|
@staticmethod
|
|
255
698
|
def _registry_setup_commands(
|
|
256
699
|
tag: str,
|
|
257
|
-
builder_version: typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"],
|
|
700
|
+
builder_version: typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"],
|
|
258
701
|
setup_commands: list[str],
|
|
259
702
|
add_python: typing.Optional[str] = None,
|
|
260
703
|
) -> list[str]: ...
|
|
@@ -267,7 +710,36 @@ class _Image(modal._object._Object):
|
|
|
267
710
|
force_build: bool = False,
|
|
268
711
|
add_python: typing.Optional[str] = None,
|
|
269
712
|
**kwargs,
|
|
270
|
-
) -> _Image:
|
|
713
|
+
) -> _Image:
|
|
714
|
+
"""Build a Modal Image from a public or private image registry, such as Docker Hub.
|
|
715
|
+
|
|
716
|
+
The image must be built for the `linux/amd64` platform.
|
|
717
|
+
|
|
718
|
+
If your image does not come with Python installed, you can use the `add_python` parameter
|
|
719
|
+
to specify a version of Python to add to the image. Otherwise, the image is expected to
|
|
720
|
+
have Python on PATH as `python`, along with `pip`.
|
|
721
|
+
|
|
722
|
+
You may also use `setup_dockerfile_commands` to run Dockerfile commands before the
|
|
723
|
+
remaining commands run. This might be useful if you want a custom Python installation or to
|
|
724
|
+
set a `SHELL`. Prefer `run_commands()` when possible though.
|
|
725
|
+
|
|
726
|
+
To authenticate against a private registry with static credentials, you must set the `secret` parameter to
|
|
727
|
+
a `modal.Secret` containing a username (`REGISTRY_USERNAME`) and
|
|
728
|
+
an access token or password (`REGISTRY_PASSWORD`).
|
|
729
|
+
|
|
730
|
+
To authenticate against private registries with credentials from a cloud provider,
|
|
731
|
+
use `Image.from_gcp_artifact_registry()` or `Image.from_aws_ecr()`.
|
|
732
|
+
|
|
733
|
+
**Examples**
|
|
734
|
+
|
|
735
|
+
```python
|
|
736
|
+
modal.Image.from_registry("python:3.11-slim-bookworm")
|
|
737
|
+
modal.Image.from_registry("ubuntu:22.04", add_python="3.11")
|
|
738
|
+
modal.Image.from_registry("nvcr.io/nvidia/pytorch:22.12-py3")
|
|
739
|
+
```
|
|
740
|
+
"""
|
|
741
|
+
...
|
|
742
|
+
|
|
271
743
|
@staticmethod
|
|
272
744
|
def from_gcp_artifact_registry(
|
|
273
745
|
tag: str,
|
|
@@ -277,7 +749,38 @@ class _Image(modal._object._Object):
|
|
|
277
749
|
force_build: bool = False,
|
|
278
750
|
add_python: typing.Optional[str] = None,
|
|
279
751
|
**kwargs,
|
|
280
|
-
) -> _Image:
|
|
752
|
+
) -> _Image:
|
|
753
|
+
"""Build a Modal image from a private image in Google Cloud Platform (GCP) Artifact Registry.
|
|
754
|
+
|
|
755
|
+
You will need to pass a `modal.Secret` containing [your GCP service account key data](https://cloud.google.com/iam/docs/keys-create-delete#creating)
|
|
756
|
+
as `SERVICE_ACCOUNT_JSON`. This can be done from the [Secrets](https://modal.com/secrets) page.
|
|
757
|
+
Your service account should be granted a specific role depending on the GCP registry used:
|
|
758
|
+
|
|
759
|
+
- For Artifact Registry images (`pkg.dev` domains) use
|
|
760
|
+
the ["Artifact Registry Reader"](https://cloud.google.com/artifact-registry/docs/access-control#roles) role
|
|
761
|
+
- For Container Registry images (`gcr.io` domains) use
|
|
762
|
+
the ["Storage Object Viewer"](https://cloud.google.com/artifact-registry/docs/transition/setup-gcr-repo) role
|
|
763
|
+
|
|
764
|
+
**Note:** This method does not use `GOOGLE_APPLICATION_CREDENTIALS` as that
|
|
765
|
+
variable accepts a path to a JSON file, not the actual JSON string.
|
|
766
|
+
|
|
767
|
+
See `Image.from_registry()` for information about the other parameters.
|
|
768
|
+
|
|
769
|
+
**Example**
|
|
770
|
+
|
|
771
|
+
```python
|
|
772
|
+
modal.Image.from_gcp_artifact_registry(
|
|
773
|
+
"us-east1-docker.pkg.dev/my-project-1234/my-repo/my-image:my-version",
|
|
774
|
+
secret=modal.Secret.from_name(
|
|
775
|
+
"my-gcp-secret",
|
|
776
|
+
required_keys=["SERVICE_ACCOUNT_JSON"],
|
|
777
|
+
),
|
|
778
|
+
add_python="3.11",
|
|
779
|
+
)
|
|
780
|
+
```
|
|
781
|
+
"""
|
|
782
|
+
...
|
|
783
|
+
|
|
281
784
|
@staticmethod
|
|
282
785
|
def from_aws_ecr(
|
|
283
786
|
tag: str,
|
|
@@ -287,36 +790,133 @@ class _Image(modal._object._Object):
|
|
|
287
790
|
force_build: bool = False,
|
|
288
791
|
add_python: typing.Optional[str] = None,
|
|
289
792
|
**kwargs,
|
|
290
|
-
) -> _Image:
|
|
793
|
+
) -> _Image:
|
|
794
|
+
"""Build a Modal image from a private image in AWS Elastic Container Registry (ECR).
|
|
795
|
+
|
|
796
|
+
You will need to pass a `modal.Secret` containing either IAM user credentials or OIDC
|
|
797
|
+
configuration to access the target ECR registry.
|
|
798
|
+
|
|
799
|
+
For IAM user authentication, set `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_REGION`.
|
|
800
|
+
|
|
801
|
+
For OIDC authentication, set `AWS_ROLE_ARN` and `AWS_REGION`.
|
|
802
|
+
|
|
803
|
+
IAM configuration details can be found in the AWS documentation for
|
|
804
|
+
["Private repository policies"](https://docs.aws.amazon.com/AmazonECR/latest/userguide/repository-policies.html).
|
|
805
|
+
|
|
806
|
+
For more details on using an AWS role to access ECR, see the [OIDC integration guide](https://modal.com/docs/guide/oidc-integration).
|
|
807
|
+
|
|
808
|
+
See `Image.from_registry()` for information about the other parameters.
|
|
809
|
+
|
|
810
|
+
**Example**
|
|
811
|
+
|
|
812
|
+
```python
|
|
813
|
+
modal.Image.from_aws_ecr(
|
|
814
|
+
"000000000000.dkr.ecr.us-east-1.amazonaws.com/my-private-registry:my-version",
|
|
815
|
+
secret=modal.Secret.from_name(
|
|
816
|
+
"aws",
|
|
817
|
+
required_keys=["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"],
|
|
818
|
+
),
|
|
819
|
+
add_python="3.11",
|
|
820
|
+
)
|
|
821
|
+
```
|
|
822
|
+
"""
|
|
823
|
+
...
|
|
824
|
+
|
|
291
825
|
@staticmethod
|
|
292
826
|
def from_dockerfile(
|
|
293
827
|
path: typing.Union[str, pathlib.Path],
|
|
294
828
|
*,
|
|
295
|
-
context_mount: typing.Optional[modal.mount._Mount] = None,
|
|
296
829
|
force_build: bool = False,
|
|
297
|
-
context_dir: typing.Union[pathlib.Path,
|
|
298
|
-
|
|
830
|
+
context_dir: typing.Union[str, pathlib.Path, None] = None,
|
|
831
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
832
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
299
833
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
300
834
|
add_python: typing.Optional[str] = None,
|
|
835
|
+
build_args: dict[str, str] = {},
|
|
301
836
|
ignore: typing.Union[
|
|
302
837
|
collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
|
|
303
838
|
] = modal.image.AUTO_DOCKERIGNORE,
|
|
304
|
-
) -> _Image:
|
|
839
|
+
) -> _Image:
|
|
840
|
+
"""Build a Modal image from a local Dockerfile.
|
|
841
|
+
|
|
842
|
+
If your Dockerfile does not have Python installed, you can use the `add_python` parameter
|
|
843
|
+
to specify a version of Python to add to the image.
|
|
844
|
+
|
|
845
|
+
**Usage:**
|
|
846
|
+
|
|
847
|
+
```python
|
|
848
|
+
from modal import FilePatternMatcher
|
|
849
|
+
|
|
850
|
+
# By default a .dockerignore file is used if present in the current working directory
|
|
851
|
+
image = modal.Image.from_dockerfile(
|
|
852
|
+
"./Dockerfile",
|
|
853
|
+
add_python="3.12",
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
image = modal.Image.from_dockerfile(
|
|
857
|
+
"./Dockerfile",
|
|
858
|
+
add_python="3.12",
|
|
859
|
+
ignore=["*.venv"],
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
image = modal.Image.from_dockerfile(
|
|
863
|
+
"./Dockerfile",
|
|
864
|
+
add_python="3.12",
|
|
865
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
|
866
|
+
)
|
|
867
|
+
|
|
868
|
+
image = modal.Image.from_dockerfile(
|
|
869
|
+
"./Dockerfile",
|
|
870
|
+
add_python="3.12",
|
|
871
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
|
875
|
+
image = modal.Image.from_dockerfile(
|
|
876
|
+
"./Dockerfile",
|
|
877
|
+
add_python="3.12",
|
|
878
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
# You can also read ignore patterns from a file.
|
|
882
|
+
image = modal.Image.from_dockerfile(
|
|
883
|
+
"./Dockerfile",
|
|
884
|
+
add_python="3.12",
|
|
885
|
+
ignore=FilePatternMatcher.from_file("/path/to/dockerignore"),
|
|
886
|
+
)
|
|
887
|
+
```
|
|
888
|
+
"""
|
|
889
|
+
...
|
|
890
|
+
|
|
305
891
|
@staticmethod
|
|
306
|
-
def debian_slim(python_version: typing.Optional[str] = None, force_build: bool = False) -> _Image:
|
|
892
|
+
def debian_slim(python_version: typing.Optional[str] = None, force_build: bool = False) -> _Image:
|
|
893
|
+
"""Default image, based on the official `python` Docker images."""
|
|
894
|
+
...
|
|
895
|
+
|
|
307
896
|
def apt_install(
|
|
308
897
|
self,
|
|
309
898
|
*packages: typing.Union[str, list[str]],
|
|
310
899
|
force_build: bool = False,
|
|
311
|
-
|
|
900
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
901
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
312
902
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
313
|
-
) -> _Image:
|
|
903
|
+
) -> _Image:
|
|
904
|
+
"""Install a list of Debian packages using `apt`.
|
|
905
|
+
|
|
906
|
+
**Example**
|
|
907
|
+
|
|
908
|
+
```python
|
|
909
|
+
image = modal.Image.debian_slim().apt_install("git")
|
|
910
|
+
```
|
|
911
|
+
"""
|
|
912
|
+
...
|
|
913
|
+
|
|
314
914
|
def run_function(
|
|
315
915
|
self,
|
|
316
916
|
raw_f: collections.abc.Callable[..., typing.Any],
|
|
317
917
|
*,
|
|
318
|
-
|
|
319
|
-
|
|
918
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
919
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
320
920
|
volumes: dict[
|
|
321
921
|
typing.Union[str, pathlib.PurePosixPath],
|
|
322
922
|
typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
|
|
@@ -324,25 +924,126 @@ class _Image(modal._object._Object):
|
|
|
324
924
|
network_file_systems: dict[
|
|
325
925
|
typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem
|
|
326
926
|
] = {},
|
|
927
|
+
gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
|
|
327
928
|
cpu: typing.Optional[float] = None,
|
|
328
929
|
memory: typing.Optional[int] = None,
|
|
329
|
-
timeout:
|
|
330
|
-
force_build: bool = False,
|
|
930
|
+
timeout: int = 3600,
|
|
331
931
|
cloud: typing.Optional[str] = None,
|
|
332
932
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
933
|
+
force_build: bool = False,
|
|
333
934
|
args: collections.abc.Sequence[typing.Any] = (),
|
|
334
935
|
kwargs: dict[str, typing.Any] = {},
|
|
335
|
-
include_source:
|
|
336
|
-
) -> _Image:
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
936
|
+
include_source: bool = True,
|
|
937
|
+
) -> _Image:
|
|
938
|
+
"""Run user-defined function `raw_f` as an image build step.
|
|
939
|
+
|
|
940
|
+
The function runs like an ordinary Modal Function, accepting a resource configuration and integrating
|
|
941
|
+
with Modal features like Secrets and Volumes. Unlike ordinary Modal Functions, any changes to the
|
|
942
|
+
filesystem state will be captured on container exit and saved as a new Image.
|
|
943
|
+
|
|
944
|
+
**Note**
|
|
945
|
+
|
|
946
|
+
Only the source code of `raw_f`, the contents of `**kwargs`, and any referenced *global* variables
|
|
947
|
+
are used to determine whether the image has changed and needs to be rebuilt.
|
|
948
|
+
If this function references other functions or variables, the image will not be rebuilt if you
|
|
949
|
+
make changes to them. You can force a rebuild by changing the function's source code itself.
|
|
950
|
+
|
|
951
|
+
**Example**
|
|
952
|
+
|
|
953
|
+
```python notest
|
|
954
|
+
|
|
955
|
+
def my_build_function():
|
|
956
|
+
open("model.pt", "w").write("parameters!")
|
|
957
|
+
|
|
958
|
+
image = (
|
|
959
|
+
modal.Image
|
|
960
|
+
.debian_slim()
|
|
961
|
+
.pip_install("torch")
|
|
962
|
+
.run_function(my_build_function, secrets=[...], mounts=[...])
|
|
963
|
+
)
|
|
964
|
+
```
|
|
965
|
+
"""
|
|
966
|
+
...
|
|
967
|
+
|
|
968
|
+
def env(self, vars: dict[str, str]) -> _Image:
|
|
969
|
+
"""Sets the environment variables in an Image.
|
|
970
|
+
|
|
971
|
+
**Example**
|
|
972
|
+
|
|
973
|
+
```python
|
|
974
|
+
image = (
|
|
975
|
+
modal.Image.debian_slim()
|
|
976
|
+
.env({"HF_HUB_ENABLE_HF_TRANSFER": "1"})
|
|
977
|
+
)
|
|
978
|
+
```
|
|
979
|
+
"""
|
|
980
|
+
...
|
|
981
|
+
|
|
982
|
+
def workdir(self, path: typing.Union[str, pathlib.PurePosixPath]) -> _Image:
|
|
983
|
+
"""Set the working directory for subsequent image build steps and function execution.
|
|
984
|
+
|
|
985
|
+
**Example**
|
|
986
|
+
|
|
987
|
+
```python
|
|
988
|
+
image = (
|
|
989
|
+
modal.Image.debian_slim()
|
|
990
|
+
.run_commands("git clone https://xyz app")
|
|
991
|
+
.workdir("/app")
|
|
992
|
+
.run_commands("yarn install")
|
|
993
|
+
)
|
|
994
|
+
```
|
|
995
|
+
"""
|
|
996
|
+
...
|
|
997
|
+
|
|
998
|
+
def cmd(self, cmd: list[str]) -> _Image:
|
|
999
|
+
"""Set the default command (`CMD`) to run when a container is started.
|
|
1000
|
+
|
|
1001
|
+
Used with `modal.Sandbox`. Has no effect on `modal.Function`.
|
|
1002
|
+
|
|
1003
|
+
**Example**
|
|
1004
|
+
|
|
1005
|
+
```python
|
|
1006
|
+
image = (
|
|
1007
|
+
modal.Image.debian_slim().cmd(["python", "app.py"])
|
|
1008
|
+
)
|
|
1009
|
+
```
|
|
1010
|
+
"""
|
|
1011
|
+
...
|
|
1012
|
+
|
|
1013
|
+
def imports(self):
|
|
1014
|
+
"""Used to import packages in global scope that are only available when running remotely.
|
|
1015
|
+
By using this context manager you can avoid an `ImportError` due to not having certain
|
|
1016
|
+
packages installed locally.
|
|
1017
|
+
|
|
1018
|
+
**Usage:**
|
|
1019
|
+
|
|
1020
|
+
```python notest
|
|
1021
|
+
with image.imports():
|
|
1022
|
+
import torch
|
|
1023
|
+
```
|
|
1024
|
+
"""
|
|
1025
|
+
...
|
|
1026
|
+
|
|
1027
|
+
def _logs(self) -> typing.AsyncGenerator[str, None]:
|
|
1028
|
+
"""Streams logs from an image, or returns logs from an already completed image.
|
|
1029
|
+
|
|
1030
|
+
This method is considered private since its interface may change - use it at your own risk!
|
|
1031
|
+
"""
|
|
1032
|
+
...
|
|
1033
|
+
|
|
1034
|
+
async def hydrate(self, client: typing.Optional[modal.client._Client] = None) -> typing_extensions.Self:
|
|
1035
|
+
"""mdmd:hidden"""
|
|
1036
|
+
...
|
|
342
1037
|
|
|
343
1038
|
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
|
344
1039
|
|
|
345
1040
|
class Image(modal.object.Object):
|
|
1041
|
+
"""Base class for container images to run functions in.
|
|
1042
|
+
|
|
1043
|
+
Do not construct this class directly; instead use one of its static factory methods,
|
|
1044
|
+
such as `modal.Image.debian_slim`, `modal.Image.from_registry`, or `modal.Image.micromamba`.
|
|
1045
|
+
"""
|
|
1046
|
+
|
|
346
1047
|
force_build: bool
|
|
347
1048
|
inside_exceptions: list[Exception]
|
|
348
1049
|
_serve_mounts: frozenset[modal.mount.Mount]
|
|
@@ -350,23 +1051,39 @@ class Image(modal.object.Object):
|
|
|
350
1051
|
_added_python_source_set: frozenset[str]
|
|
351
1052
|
_metadata: typing.Optional[modal_proto.api_pb2.ImageMetadata]
|
|
352
1053
|
|
|
353
|
-
def __init__(self, *args, **kwargs):
|
|
1054
|
+
def __init__(self, *args, **kwargs):
|
|
1055
|
+
"""mdmd:hidden"""
|
|
1056
|
+
...
|
|
1057
|
+
|
|
354
1058
|
def _initialize_from_empty(self): ...
|
|
355
1059
|
def _initialize_from_other(self, other: Image): ...
|
|
356
1060
|
def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
|
|
357
1061
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
358
1062
|
def _add_mount_layer_or_copy(self, mount: modal.mount.Mount, copy: bool = False): ...
|
|
359
1063
|
@property
|
|
360
|
-
def _mount_layers(self) -> typing.Sequence[modal.mount.Mount]:
|
|
1064
|
+
def _mount_layers(self) -> typing.Sequence[modal.mount.Mount]:
|
|
1065
|
+
"""Non-evaluated mount layers on the image
|
|
1066
|
+
|
|
1067
|
+
When the image is used by a Modal container, these mounts need to be attached as well to
|
|
1068
|
+
represent the full image content, as they haven't yet been represented as a layer in the
|
|
1069
|
+
image.
|
|
1070
|
+
|
|
1071
|
+
When the image is used as a base image for a new layer (that is not itself a mount layer)
|
|
1072
|
+
these mounts need to first be inserted as a copy operation (.copy_mount) into the image.
|
|
1073
|
+
"""
|
|
1074
|
+
...
|
|
1075
|
+
|
|
361
1076
|
def _assert_no_mount_layers(self): ...
|
|
362
1077
|
@staticmethod
|
|
363
1078
|
def _from_args(
|
|
364
1079
|
*,
|
|
365
1080
|
base_images: typing.Optional[dict[str, Image]] = None,
|
|
366
1081
|
dockerfile_function: typing.Optional[
|
|
367
|
-
collections.abc.Callable[
|
|
1082
|
+
collections.abc.Callable[
|
|
1083
|
+
[typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"]], DockerfileSpec
|
|
1084
|
+
]
|
|
368
1085
|
] = None,
|
|
369
|
-
secrets: typing.Optional[collections.abc.
|
|
1086
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
370
1087
|
gpu_config: typing.Optional[modal_proto.api_pb2.GPUConfig] = None,
|
|
371
1088
|
build_function: typing.Optional[modal.functions.Function] = None,
|
|
372
1089
|
build_function_input: typing.Optional[modal_proto.api_pb2.FunctionInput] = None,
|
|
@@ -375,13 +1092,36 @@ class Image(modal.object.Object):
|
|
|
375
1092
|
collections.abc.Callable[[], typing.Optional[modal.mount.Mount]]
|
|
376
1093
|
] = None,
|
|
377
1094
|
force_build: bool = False,
|
|
1095
|
+
build_args: dict[str, str] = {},
|
|
1096
|
+
validated_volumes: typing.Optional[collections.abc.Sequence[tuple[str, modal.volume.Volume]]] = None,
|
|
378
1097
|
_namespace: int = 1,
|
|
379
1098
|
_do_assert_no_mount_layers: bool = True,
|
|
380
1099
|
): ...
|
|
381
|
-
def _copy_mount(self, mount: modal.mount.Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> Image:
|
|
1100
|
+
def _copy_mount(self, mount: modal.mount.Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> Image:
|
|
1101
|
+
"""mdmd:hidden
|
|
1102
|
+
Internal
|
|
1103
|
+
"""
|
|
1104
|
+
...
|
|
1105
|
+
|
|
382
1106
|
def add_local_file(
|
|
383
1107
|
self, local_path: typing.Union[str, pathlib.Path], remote_path: str, *, copy: bool = False
|
|
384
|
-
) -> Image:
|
|
1108
|
+
) -> Image:
|
|
1109
|
+
"""Adds a local file to the image at `remote_path` within the container
|
|
1110
|
+
|
|
1111
|
+
By default (`copy=False`), the files are added to containers on startup and are not built into the actual Image,
|
|
1112
|
+
which speeds up deployment.
|
|
1113
|
+
|
|
1114
|
+
Set `copy=True` to copy the files into an Image layer at build time instead, similar to how
|
|
1115
|
+
[`COPY`](https://docs.docker.com/engine/reference/builder/#copy) works in a `Dockerfile`.
|
|
1116
|
+
|
|
1117
|
+
copy=True can slow down iteration since it requires a rebuild of the Image and any subsequent
|
|
1118
|
+
build steps whenever the included files change, but it is required if you want to run additional
|
|
1119
|
+
build steps after this one.
|
|
1120
|
+
|
|
1121
|
+
*Added in v0.66.40*: This method replaces the deprecated `modal.Image.copy_local_file` method.
|
|
1122
|
+
"""
|
|
1123
|
+
...
|
|
1124
|
+
|
|
385
1125
|
def add_local_dir(
|
|
386
1126
|
self,
|
|
387
1127
|
local_path: typing.Union[str, pathlib.Path],
|
|
@@ -389,10 +1129,61 @@ class Image(modal.object.Object):
|
|
|
389
1129
|
*,
|
|
390
1130
|
copy: bool = False,
|
|
391
1131
|
ignore: typing.Union[collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]] = [],
|
|
392
|
-
) -> Image:
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
1132
|
+
) -> Image:
|
|
1133
|
+
"""Adds a local directory's content to the image at `remote_path` within the container
|
|
1134
|
+
|
|
1135
|
+
By default (`copy=False`), the files are added to containers on startup and are not built into the actual Image,
|
|
1136
|
+
which speeds up deployment.
|
|
1137
|
+
|
|
1138
|
+
Set `copy=True` to copy the files into an Image layer at build time instead, similar to how
|
|
1139
|
+
[`COPY`](https://docs.docker.com/engine/reference/builder/#copy) works in a `Dockerfile`.
|
|
1140
|
+
|
|
1141
|
+
copy=True can slow down iteration since it requires a rebuild of the Image and any subsequent
|
|
1142
|
+
build steps whenever the included files change, but it is required if you want to run additional
|
|
1143
|
+
build steps after this one.
|
|
1144
|
+
|
|
1145
|
+
**Usage:**
|
|
1146
|
+
|
|
1147
|
+
```python
|
|
1148
|
+
from modal import FilePatternMatcher
|
|
1149
|
+
|
|
1150
|
+
image = modal.Image.debian_slim().add_local_dir(
|
|
1151
|
+
"~/assets",
|
|
1152
|
+
remote_path="/assets",
|
|
1153
|
+
ignore=["*.venv"],
|
|
1154
|
+
)
|
|
1155
|
+
|
|
1156
|
+
image = modal.Image.debian_slim().add_local_dir(
|
|
1157
|
+
"~/assets",
|
|
1158
|
+
remote_path="/assets",
|
|
1159
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
|
1160
|
+
)
|
|
1161
|
+
|
|
1162
|
+
image = modal.Image.debian_slim().add_local_dir(
|
|
1163
|
+
"~/assets",
|
|
1164
|
+
remote_path="/assets",
|
|
1165
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
|
1166
|
+
)
|
|
1167
|
+
|
|
1168
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
|
1169
|
+
image = modal.Image.debian_slim().add_local_dir(
|
|
1170
|
+
"~/assets",
|
|
1171
|
+
remote_path="/assets",
|
|
1172
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
|
1173
|
+
)
|
|
1174
|
+
|
|
1175
|
+
# You can also read ignore patterns from a file.
|
|
1176
|
+
image = modal.Image.debian_slim().add_local_dir(
|
|
1177
|
+
"~/assets",
|
|
1178
|
+
remote_path="/assets",
|
|
1179
|
+
ignore=FilePatternMatcher.from_file("/path/to/ignorefile"),
|
|
1180
|
+
)
|
|
1181
|
+
```
|
|
1182
|
+
|
|
1183
|
+
*Added in v0.66.40*: This method replaces the deprecated `modal.Image.copy_local_dir` method.
|
|
1184
|
+
"""
|
|
1185
|
+
...
|
|
1186
|
+
|
|
396
1187
|
def add_local_python_source(
|
|
397
1188
|
self,
|
|
398
1189
|
*module_names: str,
|
|
@@ -400,20 +1191,169 @@ class Image(modal.object.Object):
|
|
|
400
1191
|
ignore: typing.Union[
|
|
401
1192
|
collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
|
|
402
1193
|
] = modal.file_pattern_matcher.NON_PYTHON_FILES,
|
|
403
|
-
) -> Image:
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
1194
|
+
) -> Image:
|
|
1195
|
+
"""Adds locally available Python packages/modules to containers
|
|
1196
|
+
|
|
1197
|
+
Adds all files from the specified Python package or module to containers running the Image.
|
|
1198
|
+
|
|
1199
|
+
Packages are added to the `/root` directory of containers, which is on the `PYTHONPATH`
|
|
1200
|
+
of any executed Modal Functions, enabling import of the module by that name.
|
|
1201
|
+
|
|
1202
|
+
By default (`copy=False`), the files are added to containers on startup and are not built into the actual Image,
|
|
1203
|
+
which speeds up deployment.
|
|
1204
|
+
|
|
1205
|
+
Set `copy=True` to copy the files into an Image layer at build time instead. This can slow down iteration since
|
|
1206
|
+
it requires a rebuild of the Image and any subsequent build steps whenever the included files change, but it is
|
|
1207
|
+
required if you want to run additional build steps after this one.
|
|
1208
|
+
|
|
1209
|
+
**Note:** This excludes all dot-prefixed subdirectories or files and all `.pyc`/`__pycache__` files.
|
|
1210
|
+
To add full directories with finer control, use `.add_local_dir()` instead and specify `/root` as
|
|
1211
|
+
the destination directory.
|
|
1212
|
+
|
|
1213
|
+
By default only includes `.py`-files in the source modules. Set the `ignore` argument to a list of patterns
|
|
1214
|
+
or a callable to override this behavior, e.g.:
|
|
1215
|
+
|
|
1216
|
+
```py
|
|
1217
|
+
# includes everything except data.json
|
|
1218
|
+
modal.Image.debian_slim().add_local_python_source("mymodule", ignore=["data.json"])
|
|
1219
|
+
|
|
1220
|
+
# exclude large files
|
|
1221
|
+
modal.Image.debian_slim().add_local_python_source(
|
|
1222
|
+
"mymodule",
|
|
1223
|
+
ignore=lambda p: p.stat().st_size > 1e9
|
|
1224
|
+
)
|
|
1225
|
+
```
|
|
1226
|
+
|
|
1227
|
+
*Added in v0.67.28*: This method replaces the deprecated `modal.Mount.from_local_python_packages` pattern.
|
|
1228
|
+
"""
|
|
1229
|
+
...
|
|
410
1230
|
|
|
411
1231
|
class __from_id_spec(typing_extensions.Protocol):
|
|
412
|
-
def __call__(self, /, image_id: str, client: typing.Optional[modal.client.Client] = None) -> Image:
|
|
413
|
-
|
|
1232
|
+
def __call__(self, /, image_id: str, client: typing.Optional[modal.client.Client] = None) -> Image:
|
|
1233
|
+
"""Construct an Image from an id and look up the Image result.
|
|
1234
|
+
|
|
1235
|
+
The ID of an Image object can be accessed using `.object_id`.
|
|
1236
|
+
"""
|
|
1237
|
+
...
|
|
1238
|
+
|
|
1239
|
+
async def aio(self, /, image_id: str, client: typing.Optional[modal.client.Client] = None) -> Image:
|
|
1240
|
+
"""Construct an Image from an id and look up the Image result.
|
|
1241
|
+
|
|
1242
|
+
The ID of an Image object can be accessed using `.object_id`.
|
|
1243
|
+
"""
|
|
1244
|
+
...
|
|
414
1245
|
|
|
415
1246
|
from_id: __from_id_spec
|
|
416
1247
|
|
|
1248
|
+
class __build_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
1249
|
+
def __call__(self, /, app: modal.app.App) -> Image:
|
|
1250
|
+
"""Eagerly build an image.
|
|
1251
|
+
|
|
1252
|
+
If your image was previously built, then this method will not rebuild your image
|
|
1253
|
+
and your cached image is returned.
|
|
1254
|
+
|
|
1255
|
+
**Examples**
|
|
1256
|
+
|
|
1257
|
+
```python
|
|
1258
|
+
image = modal.Image.debian_slim().uv_pip_install("scipy", "numpy")
|
|
1259
|
+
|
|
1260
|
+
app = modal.App.lookup("build-image", create_if_missing=True)
|
|
1261
|
+
with modal.enable_output(): # To see logs in your local terminal
|
|
1262
|
+
image.build(app)
|
|
1263
|
+
|
|
1264
|
+
# Save the image id
|
|
1265
|
+
my_image_id = image.object_id
|
|
1266
|
+
|
|
1267
|
+
# Reference the image with the id or uses it another context.
|
|
1268
|
+
built_image = modal.Image.from_id(my_image_id)
|
|
1269
|
+
```
|
|
1270
|
+
|
|
1271
|
+
Alternatively, you can pre-build a image and use it in a sandbox.
|
|
1272
|
+
|
|
1273
|
+
```python notest
|
|
1274
|
+
app = modal.App.lookup("sandbox-example", create_if_missing=True)
|
|
1275
|
+
|
|
1276
|
+
with modal.enable_output():
|
|
1277
|
+
image = modal.Image.debian_slim().uv_pip_install("scipy")
|
|
1278
|
+
image.build(app)
|
|
1279
|
+
|
|
1280
|
+
sb = modal.Sandbox.create("python", "-c", "import scipy; print(scipy)", app=app, image=image)
|
|
1281
|
+
print(sb.stdout.read())
|
|
1282
|
+
sb.terminate()
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
**Note**
|
|
1286
|
+
|
|
1287
|
+
For defining Modal functions, images are built automatically when deploying or running an App.
|
|
1288
|
+
You do not need to built the image explicitly:
|
|
1289
|
+
|
|
1290
|
+
```python notest
|
|
1291
|
+
app = modal.App()
|
|
1292
|
+
image = modal.Image.debian_slim()
|
|
1293
|
+
|
|
1294
|
+
# No need to explicitly build the image for defining a function.
|
|
1295
|
+
@app.function(image=image)
|
|
1296
|
+
def f():
|
|
1297
|
+
...
|
|
1298
|
+
```
|
|
1299
|
+
"""
|
|
1300
|
+
...
|
|
1301
|
+
|
|
1302
|
+
async def aio(self, /, app: modal.app.App) -> Image:
|
|
1303
|
+
"""Eagerly build an image.
|
|
1304
|
+
|
|
1305
|
+
If your image was previously built, then this method will not rebuild your image
|
|
1306
|
+
and your cached image is returned.
|
|
1307
|
+
|
|
1308
|
+
**Examples**
|
|
1309
|
+
|
|
1310
|
+
```python
|
|
1311
|
+
image = modal.Image.debian_slim().uv_pip_install("scipy", "numpy")
|
|
1312
|
+
|
|
1313
|
+
app = modal.App.lookup("build-image", create_if_missing=True)
|
|
1314
|
+
with modal.enable_output(): # To see logs in your local terminal
|
|
1315
|
+
image.build(app)
|
|
1316
|
+
|
|
1317
|
+
# Save the image id
|
|
1318
|
+
my_image_id = image.object_id
|
|
1319
|
+
|
|
1320
|
+
# Reference the image with the id or uses it another context.
|
|
1321
|
+
built_image = modal.Image.from_id(my_image_id)
|
|
1322
|
+
```
|
|
1323
|
+
|
|
1324
|
+
Alternatively, you can pre-build a image and use it in a sandbox.
|
|
1325
|
+
|
|
1326
|
+
```python notest
|
|
1327
|
+
app = modal.App.lookup("sandbox-example", create_if_missing=True)
|
|
1328
|
+
|
|
1329
|
+
with modal.enable_output():
|
|
1330
|
+
image = modal.Image.debian_slim().uv_pip_install("scipy")
|
|
1331
|
+
image.build(app)
|
|
1332
|
+
|
|
1333
|
+
sb = modal.Sandbox.create("python", "-c", "import scipy; print(scipy)", app=app, image=image)
|
|
1334
|
+
print(sb.stdout.read())
|
|
1335
|
+
sb.terminate()
|
|
1336
|
+
```
|
|
1337
|
+
|
|
1338
|
+
**Note**
|
|
1339
|
+
|
|
1340
|
+
For defining Modal functions, images are built automatically when deploying or running an App.
|
|
1341
|
+
You do not need to built the image explicitly:
|
|
1342
|
+
|
|
1343
|
+
```python notest
|
|
1344
|
+
app = modal.App()
|
|
1345
|
+
image = modal.Image.debian_slim()
|
|
1346
|
+
|
|
1347
|
+
# No need to explicitly build the image for defining a function.
|
|
1348
|
+
@app.function(image=image)
|
|
1349
|
+
def f():
|
|
1350
|
+
...
|
|
1351
|
+
```
|
|
1352
|
+
"""
|
|
1353
|
+
...
|
|
1354
|
+
|
|
1355
|
+
build: __build_spec[typing_extensions.Self]
|
|
1356
|
+
|
|
417
1357
|
def pip_install(
|
|
418
1358
|
self,
|
|
419
1359
|
*packages: typing.Union[str, list[str]],
|
|
@@ -423,9 +1363,39 @@ class Image(modal.object.Object):
|
|
|
423
1363
|
pre: bool = False,
|
|
424
1364
|
extra_options: str = "",
|
|
425
1365
|
force_build: bool = False,
|
|
426
|
-
|
|
1366
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1367
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
427
1368
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
428
|
-
) -> Image:
|
|
1369
|
+
) -> Image:
|
|
1370
|
+
"""Install a list of Python packages using pip.
|
|
1371
|
+
|
|
1372
|
+
**Examples**
|
|
1373
|
+
|
|
1374
|
+
Simple installation:
|
|
1375
|
+
```python
|
|
1376
|
+
image = modal.Image.debian_slim().pip_install("click", "httpx~=0.23.3")
|
|
1377
|
+
```
|
|
1378
|
+
|
|
1379
|
+
More complex installation:
|
|
1380
|
+
```python
|
|
1381
|
+
image = (
|
|
1382
|
+
modal.Image.from_registry(
|
|
1383
|
+
"nvidia/cuda:12.2.0-devel-ubuntu22.04", add_python="3.11"
|
|
1384
|
+
)
|
|
1385
|
+
.pip_install(
|
|
1386
|
+
"ninja",
|
|
1387
|
+
"packaging",
|
|
1388
|
+
"wheel",
|
|
1389
|
+
"transformers==4.40.2",
|
|
1390
|
+
)
|
|
1391
|
+
.pip_install(
|
|
1392
|
+
"flash-attn==2.5.8", extra_options="--no-build-isolation"
|
|
1393
|
+
)
|
|
1394
|
+
)
|
|
1395
|
+
```
|
|
1396
|
+
"""
|
|
1397
|
+
...
|
|
1398
|
+
|
|
429
1399
|
def pip_install_private_repos(
|
|
430
1400
|
self,
|
|
431
1401
|
*repositories: str,
|
|
@@ -436,9 +1406,42 @@ class Image(modal.object.Object):
|
|
|
436
1406
|
pre: bool = False,
|
|
437
1407
|
extra_options: str = "",
|
|
438
1408
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
439
|
-
|
|
1409
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1410
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
440
1411
|
force_build: bool = False,
|
|
441
|
-
) -> Image:
|
|
1412
|
+
) -> Image:
|
|
1413
|
+
"""Install a list of Python packages from private git repositories using pip.
|
|
1414
|
+
|
|
1415
|
+
This method currently supports Github and Gitlab only.
|
|
1416
|
+
|
|
1417
|
+
- **Github:** Provide a `modal.Secret` that contains a `GITHUB_TOKEN` key-value pair
|
|
1418
|
+
- **Gitlab:** Provide a `modal.Secret` that contains a `GITLAB_TOKEN` key-value pair
|
|
1419
|
+
|
|
1420
|
+
These API tokens should have permissions to read the list of private repositories provided as arguments.
|
|
1421
|
+
|
|
1422
|
+
We recommend using Github's ['fine-grained' access tokens](https://github.blog/2022-10-18-introducing-fine-grained-personal-access-tokens-for-github/).
|
|
1423
|
+
These tokens are repo-scoped, and avoid granting read permission across all of a user's private repos.
|
|
1424
|
+
|
|
1425
|
+
**Example**
|
|
1426
|
+
|
|
1427
|
+
```python
|
|
1428
|
+
image = (
|
|
1429
|
+
modal.Image
|
|
1430
|
+
.debian_slim()
|
|
1431
|
+
.pip_install_private_repos(
|
|
1432
|
+
"github.com/ecorp/private-one@1.0.0",
|
|
1433
|
+
"github.com/ecorp/private-two@main"
|
|
1434
|
+
"github.com/ecorp/private-three@d4776502"
|
|
1435
|
+
# install from 'inner' directory on default branch.
|
|
1436
|
+
"github.com/ecorp/private-four#subdirectory=inner",
|
|
1437
|
+
git_user="erikbern",
|
|
1438
|
+
secrets=[modal.Secret.from_name("github-read-private")],
|
|
1439
|
+
)
|
|
1440
|
+
)
|
|
1441
|
+
```
|
|
1442
|
+
"""
|
|
1443
|
+
...
|
|
1444
|
+
|
|
442
1445
|
def pip_install_from_requirements(
|
|
443
1446
|
self,
|
|
444
1447
|
requirements_txt: str,
|
|
@@ -449,9 +1452,13 @@ class Image(modal.object.Object):
|
|
|
449
1452
|
pre: bool = False,
|
|
450
1453
|
extra_options: str = "",
|
|
451
1454
|
force_build: bool = False,
|
|
452
|
-
|
|
1455
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1456
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
453
1457
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
454
|
-
) -> Image:
|
|
1458
|
+
) -> Image:
|
|
1459
|
+
"""Install a list of Python packages from a local `requirements.txt` file."""
|
|
1460
|
+
...
|
|
1461
|
+
|
|
455
1462
|
def pip_install_from_pyproject(
|
|
456
1463
|
self,
|
|
457
1464
|
pyproject_toml: str,
|
|
@@ -463,60 +1470,216 @@ class Image(modal.object.Object):
|
|
|
463
1470
|
pre: bool = False,
|
|
464
1471
|
extra_options: str = "",
|
|
465
1472
|
force_build: bool = False,
|
|
466
|
-
|
|
1473
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1474
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1475
|
+
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
1476
|
+
) -> Image:
|
|
1477
|
+
"""Install dependencies specified by a local `pyproject.toml` file.
|
|
1478
|
+
|
|
1479
|
+
`optional_dependencies` is a list of the keys of the
|
|
1480
|
+
optional-dependencies section(s) of the `pyproject.toml` file
|
|
1481
|
+
(e.g. test, doc, experiment, etc). When provided,
|
|
1482
|
+
all of the packages in each listed section are installed as well.
|
|
1483
|
+
"""
|
|
1484
|
+
...
|
|
1485
|
+
|
|
1486
|
+
def uv_pip_install(
|
|
1487
|
+
self,
|
|
1488
|
+
*packages: typing.Union[str, list[str]],
|
|
1489
|
+
requirements: typing.Optional[list[str]] = None,
|
|
1490
|
+
find_links: typing.Optional[str] = None,
|
|
1491
|
+
index_url: typing.Optional[str] = None,
|
|
1492
|
+
extra_index_url: typing.Optional[str] = None,
|
|
1493
|
+
pre: bool = False,
|
|
1494
|
+
extra_options: str = "",
|
|
1495
|
+
force_build: bool = False,
|
|
1496
|
+
uv_version: typing.Optional[str] = None,
|
|
1497
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1498
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
467
1499
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
468
|
-
) -> Image:
|
|
1500
|
+
) -> Image:
|
|
1501
|
+
"""Install a list of Python packages using uv pip install.
|
|
1502
|
+
|
|
1503
|
+
**Examples**
|
|
1504
|
+
|
|
1505
|
+
Simple installation:
|
|
1506
|
+
```python
|
|
1507
|
+
image = modal.Image.debian_slim().uv_pip_install("torch==2.7.1", "numpy")
|
|
1508
|
+
```
|
|
1509
|
+
|
|
1510
|
+
This method assumes that:
|
|
1511
|
+
- Python is on the `$PATH` and dependencies are installed with the first Python on the `$PATH`.
|
|
1512
|
+
- Shell supports backticks for substitution
|
|
1513
|
+
- `which` command is on the `$PATH`
|
|
1514
|
+
|
|
1515
|
+
Added in v1.1.0.
|
|
1516
|
+
"""
|
|
1517
|
+
...
|
|
1518
|
+
|
|
469
1519
|
def poetry_install_from_file(
|
|
470
1520
|
self,
|
|
471
1521
|
poetry_pyproject_toml: str,
|
|
472
1522
|
poetry_lockfile: typing.Optional[str] = None,
|
|
473
1523
|
*,
|
|
474
1524
|
ignore_lockfile: bool = False,
|
|
475
|
-
old_installer: bool = False,
|
|
476
1525
|
force_build: bool = False,
|
|
477
1526
|
with_: list[str] = [],
|
|
478
1527
|
without: list[str] = [],
|
|
479
1528
|
only: list[str] = [],
|
|
480
|
-
|
|
1529
|
+
poetry_version: typing.Optional[str] = "latest",
|
|
1530
|
+
old_installer: bool = False,
|
|
1531
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1532
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
481
1533
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
482
|
-
) -> Image:
|
|
1534
|
+
) -> Image:
|
|
1535
|
+
"""Install poetry *dependencies* specified by a local `pyproject.toml` file.
|
|
1536
|
+
|
|
1537
|
+
If not provided as argument the path to the lockfile is inferred. However, the
|
|
1538
|
+
file has to exist, unless `ignore_lockfile` is set to `True`.
|
|
1539
|
+
|
|
1540
|
+
Note that the root project of the poetry project is not installed, only the dependencies.
|
|
1541
|
+
For including local python source files see `add_local_python_source`
|
|
1542
|
+
|
|
1543
|
+
Poetry will be installed to the Image (using pip) unless `poetry_version` is set to None.
|
|
1544
|
+
Note that the interpretation of `poetry_version="latest"` depends on the Modal Image Builder
|
|
1545
|
+
version, with versions 2024.10 and earlier limiting poetry to 1.x.
|
|
1546
|
+
"""
|
|
1547
|
+
...
|
|
1548
|
+
|
|
1549
|
+
def uv_sync(
|
|
1550
|
+
self,
|
|
1551
|
+
uv_project_dir: str = "./",
|
|
1552
|
+
*,
|
|
1553
|
+
force_build: bool = False,
|
|
1554
|
+
groups: typing.Optional[list[str]] = None,
|
|
1555
|
+
extras: typing.Optional[list[str]] = None,
|
|
1556
|
+
frozen: bool = True,
|
|
1557
|
+
extra_options: str = "",
|
|
1558
|
+
uv_version: typing.Optional[str] = None,
|
|
1559
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1560
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1561
|
+
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
1562
|
+
) -> Image:
|
|
1563
|
+
"""Creates a virtual environment with the dependencies in a uv managed project with `uv sync`.
|
|
1564
|
+
|
|
1565
|
+
**Examples**
|
|
1566
|
+
```python
|
|
1567
|
+
image = modal.Image.debian_slim().uv_sync()
|
|
1568
|
+
```
|
|
1569
|
+
|
|
1570
|
+
The `pyproject.toml` and `uv.lock` in `uv_project_dir` are automatically added to the build context. The
|
|
1571
|
+
`uv_project_dir` is relative to the current working directory of where `modal` is called.
|
|
1572
|
+
|
|
1573
|
+
NOTE: This does *not* install the project itself into the environment (this is equivalent to the
|
|
1574
|
+
`--no-install-project` flag in the `uv sync` command) and you would be expected to add any local python source
|
|
1575
|
+
files using `Image.add_local_python_source` or similar methods after this call.
|
|
1576
|
+
|
|
1577
|
+
This ensures that updates to your project code wouldn't require reinstalling third-party dependencies
|
|
1578
|
+
after every change.
|
|
1579
|
+
|
|
1580
|
+
uv workspaces are currently not supported.
|
|
1581
|
+
|
|
1582
|
+
Added in v1.1.0.
|
|
1583
|
+
"""
|
|
1584
|
+
...
|
|
1585
|
+
|
|
483
1586
|
def dockerfile_commands(
|
|
484
1587
|
self,
|
|
485
1588
|
*dockerfile_commands: typing.Union[str, list[str]],
|
|
486
1589
|
context_files: dict[str, str] = {},
|
|
487
|
-
|
|
1590
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1591
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
488
1592
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
489
|
-
|
|
490
|
-
context_dir: typing.Union[pathlib.Path, str, None] = None,
|
|
1593
|
+
context_dir: typing.Union[str, pathlib.Path, None] = None,
|
|
491
1594
|
force_build: bool = False,
|
|
492
1595
|
ignore: typing.Union[
|
|
493
1596
|
collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
|
|
494
1597
|
] = modal.image.AUTO_DOCKERIGNORE,
|
|
495
|
-
) -> Image:
|
|
496
|
-
|
|
497
|
-
|
|
1598
|
+
) -> Image:
|
|
1599
|
+
"""Extend an image with arbitrary Dockerfile-like commands.
|
|
1600
|
+
|
|
1601
|
+
**Usage:**
|
|
1602
|
+
|
|
1603
|
+
```python
|
|
1604
|
+
from modal import FilePatternMatcher
|
|
1605
|
+
|
|
1606
|
+
# By default a .dockerignore file is used if present in the current working directory
|
|
1607
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
|
1608
|
+
["COPY data /data"],
|
|
1609
|
+
)
|
|
1610
|
+
|
|
1611
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
|
1612
|
+
["COPY data /data"],
|
|
1613
|
+
ignore=["*.venv"],
|
|
1614
|
+
)
|
|
1615
|
+
|
|
1616
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
|
1617
|
+
["COPY data /data"],
|
|
1618
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
|
1619
|
+
)
|
|
1620
|
+
|
|
1621
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
|
1622
|
+
["COPY data /data"],
|
|
1623
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
|
1624
|
+
)
|
|
1625
|
+
|
|
1626
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
|
1627
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
|
1628
|
+
["COPY data /data"],
|
|
1629
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
|
1630
|
+
)
|
|
1631
|
+
|
|
1632
|
+
# You can also read ignore patterns from a file.
|
|
1633
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
|
1634
|
+
["COPY data /data"],
|
|
1635
|
+
ignore=FilePatternMatcher.from_file("/path/to/dockerignore"),
|
|
1636
|
+
)
|
|
1637
|
+
```
|
|
1638
|
+
"""
|
|
1639
|
+
...
|
|
1640
|
+
|
|
1641
|
+
def entrypoint(self, entrypoint_commands: list[str]) -> Image:
|
|
1642
|
+
"""Set the ENTRYPOINT for the image."""
|
|
1643
|
+
...
|
|
1644
|
+
|
|
1645
|
+
def shell(self, shell_commands: list[str]) -> Image:
|
|
1646
|
+
"""Overwrite default shell for the image."""
|
|
1647
|
+
...
|
|
1648
|
+
|
|
498
1649
|
def run_commands(
|
|
499
1650
|
self,
|
|
500
1651
|
*commands: typing.Union[str, list[str]],
|
|
501
|
-
|
|
1652
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1653
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
1654
|
+
volumes: typing.Optional[dict[typing.Union[str, pathlib.PurePosixPath], modal.volume.Volume]] = None,
|
|
502
1655
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
503
1656
|
force_build: bool = False,
|
|
504
|
-
) -> Image:
|
|
1657
|
+
) -> Image:
|
|
1658
|
+
"""Extend an image with a list of shell commands to run."""
|
|
1659
|
+
...
|
|
1660
|
+
|
|
505
1661
|
@staticmethod
|
|
506
|
-
def micromamba(python_version: typing.Optional[str] = None, force_build: bool = False) -> Image:
|
|
1662
|
+
def micromamba(python_version: typing.Optional[str] = None, force_build: bool = False) -> Image:
|
|
1663
|
+
"""A Micromamba base image. Micromamba allows for fast building of small Conda-based containers."""
|
|
1664
|
+
...
|
|
1665
|
+
|
|
507
1666
|
def micromamba_install(
|
|
508
1667
|
self,
|
|
509
1668
|
*packages: typing.Union[str, list[str]],
|
|
510
1669
|
spec_file: typing.Optional[str] = None,
|
|
511
1670
|
channels: list[str] = [],
|
|
512
1671
|
force_build: bool = False,
|
|
513
|
-
|
|
1672
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1673
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
514
1674
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
515
|
-
) -> Image:
|
|
1675
|
+
) -> Image:
|
|
1676
|
+
"""Install a list of additional packages using micromamba."""
|
|
1677
|
+
...
|
|
1678
|
+
|
|
516
1679
|
@staticmethod
|
|
517
1680
|
def _registry_setup_commands(
|
|
518
1681
|
tag: str,
|
|
519
|
-
builder_version: typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"],
|
|
1682
|
+
builder_version: typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"],
|
|
520
1683
|
setup_commands: list[str],
|
|
521
1684
|
add_python: typing.Optional[str] = None,
|
|
522
1685
|
) -> list[str]: ...
|
|
@@ -529,7 +1692,36 @@ class Image(modal.object.Object):
|
|
|
529
1692
|
force_build: bool = False,
|
|
530
1693
|
add_python: typing.Optional[str] = None,
|
|
531
1694
|
**kwargs,
|
|
532
|
-
) -> Image:
|
|
1695
|
+
) -> Image:
|
|
1696
|
+
"""Build a Modal Image from a public or private image registry, such as Docker Hub.
|
|
1697
|
+
|
|
1698
|
+
The image must be built for the `linux/amd64` platform.
|
|
1699
|
+
|
|
1700
|
+
If your image does not come with Python installed, you can use the `add_python` parameter
|
|
1701
|
+
to specify a version of Python to add to the image. Otherwise, the image is expected to
|
|
1702
|
+
have Python on PATH as `python`, along with `pip`.
|
|
1703
|
+
|
|
1704
|
+
You may also use `setup_dockerfile_commands` to run Dockerfile commands before the
|
|
1705
|
+
remaining commands run. This might be useful if you want a custom Python installation or to
|
|
1706
|
+
set a `SHELL`. Prefer `run_commands()` when possible though.
|
|
1707
|
+
|
|
1708
|
+
To authenticate against a private registry with static credentials, you must set the `secret` parameter to
|
|
1709
|
+
a `modal.Secret` containing a username (`REGISTRY_USERNAME`) and
|
|
1710
|
+
an access token or password (`REGISTRY_PASSWORD`).
|
|
1711
|
+
|
|
1712
|
+
To authenticate against private registries with credentials from a cloud provider,
|
|
1713
|
+
use `Image.from_gcp_artifact_registry()` or `Image.from_aws_ecr()`.
|
|
1714
|
+
|
|
1715
|
+
**Examples**
|
|
1716
|
+
|
|
1717
|
+
```python
|
|
1718
|
+
modal.Image.from_registry("python:3.11-slim-bookworm")
|
|
1719
|
+
modal.Image.from_registry("ubuntu:22.04", add_python="3.11")
|
|
1720
|
+
modal.Image.from_registry("nvcr.io/nvidia/pytorch:22.12-py3")
|
|
1721
|
+
```
|
|
1722
|
+
"""
|
|
1723
|
+
...
|
|
1724
|
+
|
|
533
1725
|
@staticmethod
|
|
534
1726
|
def from_gcp_artifact_registry(
|
|
535
1727
|
tag: str,
|
|
@@ -539,7 +1731,38 @@ class Image(modal.object.Object):
|
|
|
539
1731
|
force_build: bool = False,
|
|
540
1732
|
add_python: typing.Optional[str] = None,
|
|
541
1733
|
**kwargs,
|
|
542
|
-
) -> Image:
|
|
1734
|
+
) -> Image:
|
|
1735
|
+
"""Build a Modal image from a private image in Google Cloud Platform (GCP) Artifact Registry.
|
|
1736
|
+
|
|
1737
|
+
You will need to pass a `modal.Secret` containing [your GCP service account key data](https://cloud.google.com/iam/docs/keys-create-delete#creating)
|
|
1738
|
+
as `SERVICE_ACCOUNT_JSON`. This can be done from the [Secrets](https://modal.com/secrets) page.
|
|
1739
|
+
Your service account should be granted a specific role depending on the GCP registry used:
|
|
1740
|
+
|
|
1741
|
+
- For Artifact Registry images (`pkg.dev` domains) use
|
|
1742
|
+
the ["Artifact Registry Reader"](https://cloud.google.com/artifact-registry/docs/access-control#roles) role
|
|
1743
|
+
- For Container Registry images (`gcr.io` domains) use
|
|
1744
|
+
the ["Storage Object Viewer"](https://cloud.google.com/artifact-registry/docs/transition/setup-gcr-repo) role
|
|
1745
|
+
|
|
1746
|
+
**Note:** This method does not use `GOOGLE_APPLICATION_CREDENTIALS` as that
|
|
1747
|
+
variable accepts a path to a JSON file, not the actual JSON string.
|
|
1748
|
+
|
|
1749
|
+
See `Image.from_registry()` for information about the other parameters.
|
|
1750
|
+
|
|
1751
|
+
**Example**
|
|
1752
|
+
|
|
1753
|
+
```python
|
|
1754
|
+
modal.Image.from_gcp_artifact_registry(
|
|
1755
|
+
"us-east1-docker.pkg.dev/my-project-1234/my-repo/my-image:my-version",
|
|
1756
|
+
secret=modal.Secret.from_name(
|
|
1757
|
+
"my-gcp-secret",
|
|
1758
|
+
required_keys=["SERVICE_ACCOUNT_JSON"],
|
|
1759
|
+
),
|
|
1760
|
+
add_python="3.11",
|
|
1761
|
+
)
|
|
1762
|
+
```
|
|
1763
|
+
"""
|
|
1764
|
+
...
|
|
1765
|
+
|
|
543
1766
|
@staticmethod
|
|
544
1767
|
def from_aws_ecr(
|
|
545
1768
|
tag: str,
|
|
@@ -549,36 +1772,133 @@ class Image(modal.object.Object):
|
|
|
549
1772
|
force_build: bool = False,
|
|
550
1773
|
add_python: typing.Optional[str] = None,
|
|
551
1774
|
**kwargs,
|
|
552
|
-
) -> Image:
|
|
1775
|
+
) -> Image:
|
|
1776
|
+
"""Build a Modal image from a private image in AWS Elastic Container Registry (ECR).
|
|
1777
|
+
|
|
1778
|
+
You will need to pass a `modal.Secret` containing either IAM user credentials or OIDC
|
|
1779
|
+
configuration to access the target ECR registry.
|
|
1780
|
+
|
|
1781
|
+
For IAM user authentication, set `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_REGION`.
|
|
1782
|
+
|
|
1783
|
+
For OIDC authentication, set `AWS_ROLE_ARN` and `AWS_REGION`.
|
|
1784
|
+
|
|
1785
|
+
IAM configuration details can be found in the AWS documentation for
|
|
1786
|
+
["Private repository policies"](https://docs.aws.amazon.com/AmazonECR/latest/userguide/repository-policies.html).
|
|
1787
|
+
|
|
1788
|
+
For more details on using an AWS role to access ECR, see the [OIDC integration guide](https://modal.com/docs/guide/oidc-integration).
|
|
1789
|
+
|
|
1790
|
+
See `Image.from_registry()` for information about the other parameters.
|
|
1791
|
+
|
|
1792
|
+
**Example**
|
|
1793
|
+
|
|
1794
|
+
```python
|
|
1795
|
+
modal.Image.from_aws_ecr(
|
|
1796
|
+
"000000000000.dkr.ecr.us-east-1.amazonaws.com/my-private-registry:my-version",
|
|
1797
|
+
secret=modal.Secret.from_name(
|
|
1798
|
+
"aws",
|
|
1799
|
+
required_keys=["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"],
|
|
1800
|
+
),
|
|
1801
|
+
add_python="3.11",
|
|
1802
|
+
)
|
|
1803
|
+
```
|
|
1804
|
+
"""
|
|
1805
|
+
...
|
|
1806
|
+
|
|
553
1807
|
@staticmethod
|
|
554
1808
|
def from_dockerfile(
|
|
555
1809
|
path: typing.Union[str, pathlib.Path],
|
|
556
1810
|
*,
|
|
557
|
-
context_mount: typing.Optional[modal.mount.Mount] = None,
|
|
558
1811
|
force_build: bool = False,
|
|
559
|
-
context_dir: typing.Union[pathlib.Path,
|
|
560
|
-
|
|
1812
|
+
context_dir: typing.Union[str, pathlib.Path, None] = None,
|
|
1813
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1814
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
561
1815
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
562
1816
|
add_python: typing.Optional[str] = None,
|
|
1817
|
+
build_args: dict[str, str] = {},
|
|
563
1818
|
ignore: typing.Union[
|
|
564
1819
|
collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
|
|
565
1820
|
] = modal.image.AUTO_DOCKERIGNORE,
|
|
566
|
-
) -> Image:
|
|
1821
|
+
) -> Image:
|
|
1822
|
+
"""Build a Modal image from a local Dockerfile.
|
|
1823
|
+
|
|
1824
|
+
If your Dockerfile does not have Python installed, you can use the `add_python` parameter
|
|
1825
|
+
to specify a version of Python to add to the image.
|
|
1826
|
+
|
|
1827
|
+
**Usage:**
|
|
1828
|
+
|
|
1829
|
+
```python
|
|
1830
|
+
from modal import FilePatternMatcher
|
|
1831
|
+
|
|
1832
|
+
# By default a .dockerignore file is used if present in the current working directory
|
|
1833
|
+
image = modal.Image.from_dockerfile(
|
|
1834
|
+
"./Dockerfile",
|
|
1835
|
+
add_python="3.12",
|
|
1836
|
+
)
|
|
1837
|
+
|
|
1838
|
+
image = modal.Image.from_dockerfile(
|
|
1839
|
+
"./Dockerfile",
|
|
1840
|
+
add_python="3.12",
|
|
1841
|
+
ignore=["*.venv"],
|
|
1842
|
+
)
|
|
1843
|
+
|
|
1844
|
+
image = modal.Image.from_dockerfile(
|
|
1845
|
+
"./Dockerfile",
|
|
1846
|
+
add_python="3.12",
|
|
1847
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
|
1848
|
+
)
|
|
1849
|
+
|
|
1850
|
+
image = modal.Image.from_dockerfile(
|
|
1851
|
+
"./Dockerfile",
|
|
1852
|
+
add_python="3.12",
|
|
1853
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
|
1854
|
+
)
|
|
1855
|
+
|
|
1856
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
|
1857
|
+
image = modal.Image.from_dockerfile(
|
|
1858
|
+
"./Dockerfile",
|
|
1859
|
+
add_python="3.12",
|
|
1860
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
|
1861
|
+
)
|
|
1862
|
+
|
|
1863
|
+
# You can also read ignore patterns from a file.
|
|
1864
|
+
image = modal.Image.from_dockerfile(
|
|
1865
|
+
"./Dockerfile",
|
|
1866
|
+
add_python="3.12",
|
|
1867
|
+
ignore=FilePatternMatcher.from_file("/path/to/dockerignore"),
|
|
1868
|
+
)
|
|
1869
|
+
```
|
|
1870
|
+
"""
|
|
1871
|
+
...
|
|
1872
|
+
|
|
567
1873
|
@staticmethod
|
|
568
|
-
def debian_slim(python_version: typing.Optional[str] = None, force_build: bool = False) -> Image:
|
|
1874
|
+
def debian_slim(python_version: typing.Optional[str] = None, force_build: bool = False) -> Image:
|
|
1875
|
+
"""Default image, based on the official `python` Docker images."""
|
|
1876
|
+
...
|
|
1877
|
+
|
|
569
1878
|
def apt_install(
|
|
570
1879
|
self,
|
|
571
1880
|
*packages: typing.Union[str, list[str]],
|
|
572
1881
|
force_build: bool = False,
|
|
573
|
-
|
|
1882
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1883
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
574
1884
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
575
|
-
) -> Image:
|
|
1885
|
+
) -> Image:
|
|
1886
|
+
"""Install a list of Debian packages using `apt`.
|
|
1887
|
+
|
|
1888
|
+
**Example**
|
|
1889
|
+
|
|
1890
|
+
```python
|
|
1891
|
+
image = modal.Image.debian_slim().apt_install("git")
|
|
1892
|
+
```
|
|
1893
|
+
"""
|
|
1894
|
+
...
|
|
1895
|
+
|
|
576
1896
|
def run_function(
|
|
577
1897
|
self,
|
|
578
1898
|
raw_f: collections.abc.Callable[..., typing.Any],
|
|
579
1899
|
*,
|
|
580
|
-
|
|
581
|
-
|
|
1900
|
+
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1901
|
+
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
582
1902
|
volumes: dict[
|
|
583
1903
|
typing.Union[str, pathlib.PurePosixPath],
|
|
584
1904
|
typing.Union[modal.volume.Volume, modal.cloud_bucket_mount.CloudBucketMount],
|
|
@@ -586,25 +1906,132 @@ class Image(modal.object.Object):
|
|
|
586
1906
|
network_file_systems: dict[
|
|
587
1907
|
typing.Union[str, pathlib.PurePosixPath], modal.network_file_system.NetworkFileSystem
|
|
588
1908
|
] = {},
|
|
1909
|
+
gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
|
|
589
1910
|
cpu: typing.Optional[float] = None,
|
|
590
1911
|
memory: typing.Optional[int] = None,
|
|
591
|
-
timeout:
|
|
592
|
-
force_build: bool = False,
|
|
1912
|
+
timeout: int = 3600,
|
|
593
1913
|
cloud: typing.Optional[str] = None,
|
|
594
1914
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
1915
|
+
force_build: bool = False,
|
|
595
1916
|
args: collections.abc.Sequence[typing.Any] = (),
|
|
596
1917
|
kwargs: dict[str, typing.Any] = {},
|
|
597
|
-
include_source:
|
|
598
|
-
) -> Image:
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
1918
|
+
include_source: bool = True,
|
|
1919
|
+
) -> Image:
|
|
1920
|
+
"""Run user-defined function `raw_f` as an image build step.
|
|
1921
|
+
|
|
1922
|
+
The function runs like an ordinary Modal Function, accepting a resource configuration and integrating
|
|
1923
|
+
with Modal features like Secrets and Volumes. Unlike ordinary Modal Functions, any changes to the
|
|
1924
|
+
filesystem state will be captured on container exit and saved as a new Image.
|
|
1925
|
+
|
|
1926
|
+
**Note**
|
|
1927
|
+
|
|
1928
|
+
Only the source code of `raw_f`, the contents of `**kwargs`, and any referenced *global* variables
|
|
1929
|
+
are used to determine whether the image has changed and needs to be rebuilt.
|
|
1930
|
+
If this function references other functions or variables, the image will not be rebuilt if you
|
|
1931
|
+
make changes to them. You can force a rebuild by changing the function's source code itself.
|
|
1932
|
+
|
|
1933
|
+
**Example**
|
|
1934
|
+
|
|
1935
|
+
```python notest
|
|
1936
|
+
|
|
1937
|
+
def my_build_function():
|
|
1938
|
+
open("model.pt", "w").write("parameters!")
|
|
1939
|
+
|
|
1940
|
+
image = (
|
|
1941
|
+
modal.Image
|
|
1942
|
+
.debian_slim()
|
|
1943
|
+
.pip_install("torch")
|
|
1944
|
+
.run_function(my_build_function, secrets=[...], mounts=[...])
|
|
1945
|
+
)
|
|
1946
|
+
```
|
|
1947
|
+
"""
|
|
1948
|
+
...
|
|
1949
|
+
|
|
1950
|
+
def env(self, vars: dict[str, str]) -> Image:
|
|
1951
|
+
"""Sets the environment variables in an Image.
|
|
1952
|
+
|
|
1953
|
+
**Example**
|
|
1954
|
+
|
|
1955
|
+
```python
|
|
1956
|
+
image = (
|
|
1957
|
+
modal.Image.debian_slim()
|
|
1958
|
+
.env({"HF_HUB_ENABLE_HF_TRANSFER": "1"})
|
|
1959
|
+
)
|
|
1960
|
+
```
|
|
1961
|
+
"""
|
|
1962
|
+
...
|
|
1963
|
+
|
|
1964
|
+
def workdir(self, path: typing.Union[str, pathlib.PurePosixPath]) -> Image:
|
|
1965
|
+
"""Set the working directory for subsequent image build steps and function execution.
|
|
1966
|
+
|
|
1967
|
+
**Example**
|
|
1968
|
+
|
|
1969
|
+
```python
|
|
1970
|
+
image = (
|
|
1971
|
+
modal.Image.debian_slim()
|
|
1972
|
+
.run_commands("git clone https://xyz app")
|
|
1973
|
+
.workdir("/app")
|
|
1974
|
+
.run_commands("yarn install")
|
|
1975
|
+
)
|
|
1976
|
+
```
|
|
1977
|
+
"""
|
|
1978
|
+
...
|
|
1979
|
+
|
|
1980
|
+
def cmd(self, cmd: list[str]) -> Image:
|
|
1981
|
+
"""Set the default command (`CMD`) to run when a container is started.
|
|
1982
|
+
|
|
1983
|
+
Used with `modal.Sandbox`. Has no effect on `modal.Function`.
|
|
1984
|
+
|
|
1985
|
+
**Example**
|
|
1986
|
+
|
|
1987
|
+
```python
|
|
1988
|
+
image = (
|
|
1989
|
+
modal.Image.debian_slim().cmd(["python", "app.py"])
|
|
1990
|
+
)
|
|
1991
|
+
```
|
|
1992
|
+
"""
|
|
1993
|
+
...
|
|
1994
|
+
|
|
1995
|
+
def imports(self):
|
|
1996
|
+
"""Used to import packages in global scope that are only available when running remotely.
|
|
1997
|
+
By using this context manager you can avoid an `ImportError` due to not having certain
|
|
1998
|
+
packages installed locally.
|
|
1999
|
+
|
|
2000
|
+
**Usage:**
|
|
2001
|
+
|
|
2002
|
+
```python notest
|
|
2003
|
+
with image.imports():
|
|
2004
|
+
import torch
|
|
2005
|
+
```
|
|
2006
|
+
"""
|
|
2007
|
+
...
|
|
603
2008
|
|
|
604
2009
|
class ___logs_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
605
|
-
def __call__(self, /) -> typing.Generator[str, None, None]:
|
|
606
|
-
|
|
2010
|
+
def __call__(self, /) -> typing.Generator[str, None, None]:
|
|
2011
|
+
"""Streams logs from an image, or returns logs from an already completed image.
|
|
2012
|
+
|
|
2013
|
+
This method is considered private since its interface may change - use it at your own risk!
|
|
2014
|
+
"""
|
|
2015
|
+
...
|
|
2016
|
+
|
|
2017
|
+
def aio(self, /) -> typing.AsyncGenerator[str, None]:
|
|
2018
|
+
"""Streams logs from an image, or returns logs from an already completed image.
|
|
2019
|
+
|
|
2020
|
+
This method is considered private since its interface may change - use it at your own risk!
|
|
2021
|
+
"""
|
|
2022
|
+
...
|
|
607
2023
|
|
|
608
2024
|
_logs: ___logs_spec[typing_extensions.Self]
|
|
609
2025
|
|
|
610
|
-
|
|
2026
|
+
class __hydrate_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
2027
|
+
def __call__(self, /, client: typing.Optional[modal.client.Client] = None) -> SUPERSELF:
|
|
2028
|
+
"""mdmd:hidden"""
|
|
2029
|
+
...
|
|
2030
|
+
|
|
2031
|
+
async def aio(self, /, client: typing.Optional[modal.client.Client] = None) -> SUPERSELF:
|
|
2032
|
+
"""mdmd:hidden"""
|
|
2033
|
+
...
|
|
2034
|
+
|
|
2035
|
+
hydrate: __hydrate_spec[typing_extensions.Self]
|
|
2036
|
+
|
|
2037
|
+
SUPPORTED_PYTHON_SERIES: dict[typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"], list[str]]
|