modal 1.0.4.dev12__py3-none-any.whl → 1.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modal/_clustered_functions.pyi +13 -3
- modal/_functions.py +84 -46
- modal/_partial_function.py +1 -1
- modal/_runtime/container_io_manager.pyi +222 -40
- modal/_runtime/execution_context.pyi +60 -6
- modal/_serialization.py +25 -2
- modal/_tunnel.pyi +380 -12
- modal/_utils/async_utils.py +1 -1
- modal/_utils/blob_utils.py +56 -19
- modal/_utils/function_utils.py +33 -7
- modal/_utils/grpc_utils.py +11 -4
- modal/app.py +5 -5
- modal/app.pyi +658 -48
- modal/cli/run.py +2 -1
- modal/client.pyi +224 -36
- modal/cloud_bucket_mount.pyi +192 -4
- modal/cls.py +7 -7
- modal/cls.pyi +442 -35
- modal/container_process.pyi +103 -14
- modal/dict.py +4 -4
- modal/dict.pyi +453 -51
- modal/environments.pyi +41 -9
- modal/exception.py +6 -2
- modal/experimental/__init__.py +90 -0
- modal/experimental/ipython.py +11 -7
- modal/file_io.pyi +236 -45
- modal/functions.pyi +573 -65
- modal/gpu.py +1 -1
- modal/image.py +1 -1
- modal/image.pyi +1256 -74
- modal/io_streams.py +8 -4
- modal/io_streams.pyi +348 -38
- modal/mount.pyi +261 -31
- modal/network_file_system.py +3 -3
- modal/network_file_system.pyi +307 -26
- modal/object.pyi +48 -9
- modal/parallel_map.py +93 -19
- modal/parallel_map.pyi +160 -15
- modal/partial_function.pyi +255 -14
- modal/proxy.py +1 -1
- modal/proxy.pyi +28 -3
- modal/queue.py +4 -4
- modal/queue.pyi +447 -30
- modal/runner.pyi +160 -22
- modal/sandbox.py +8 -7
- modal/sandbox.pyi +310 -50
- modal/schedule.py +1 -1
- modal/secret.py +2 -2
- modal/secret.pyi +164 -15
- modal/snapshot.pyi +25 -4
- modal/token_flow.pyi +28 -8
- modal/volume.py +41 -4
- modal/volume.pyi +693 -59
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/METADATA +3 -3
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/RECORD +67 -67
- modal_proto/api.proto +56 -0
- modal_proto/api_grpc.py +48 -0
- modal_proto/api_pb2.py +874 -780
- modal_proto/api_pb2.pyi +194 -8
- modal_proto/api_pb2_grpc.py +100 -0
- modal_proto/api_pb2_grpc.pyi +32 -0
- modal_proto/modal_api_grpc.py +3 -0
- modal_version/__init__.py +1 -1
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/WHEEL +0 -0
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/entry_points.txt +0 -0
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/licenses/LICENSE +0 -0
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/top_level.txt +0 -0
modal/image.pyi
CHANGED
@@ -19,8 +19,13 @@ import typing_extensions
|
|
19
19
|
ImageBuilderVersion = typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"]
|
20
20
|
|
21
21
|
class _AutoDockerIgnoreSentinel:
|
22
|
-
def __repr__(self) -> str:
|
23
|
-
|
22
|
+
def __repr__(self) -> str:
|
23
|
+
"""Return repr(self)."""
|
24
|
+
...
|
25
|
+
|
26
|
+
def __call__(self, _: pathlib.Path) -> bool:
|
27
|
+
"""Call self as a function."""
|
28
|
+
...
|
24
29
|
|
25
30
|
AUTO_DOCKERIGNORE: _AutoDockerIgnoreSentinel
|
26
31
|
|
@@ -43,8 +48,17 @@ def _get_modal_requirements_path(
|
|
43
48
|
def _get_modal_requirements_command(version: typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"]) -> str: ...
|
44
49
|
def _flatten_str_args(
|
45
50
|
function_name: str, arg_name: str, args: collections.abc.Sequence[typing.Union[str, list[str]]]
|
46
|
-
) -> list[str]:
|
47
|
-
|
51
|
+
) -> list[str]:
|
52
|
+
"""Takes a sequence of strings, or string lists, and flattens it.
|
53
|
+
|
54
|
+
Raises an error if any of the elements are not strings or string lists.
|
55
|
+
"""
|
56
|
+
...
|
57
|
+
|
58
|
+
def _validate_packages(packages: list[str]) -> bool:
|
59
|
+
"""Validates that a list of packages does not contain any command-line options."""
|
60
|
+
...
|
61
|
+
|
48
62
|
def _make_pip_install_args(
|
49
63
|
find_links: typing.Optional[str] = None,
|
50
64
|
index_url: typing.Optional[str] = None,
|
@@ -59,7 +73,15 @@ def _create_context_mount(
|
|
59
73
|
docker_commands: collections.abc.Sequence[str],
|
60
74
|
ignore_fn: collections.abc.Callable[[pathlib.Path], bool],
|
61
75
|
context_dir: pathlib.Path,
|
62
|
-
) -> typing.Optional[modal.mount._Mount]:
|
76
|
+
) -> typing.Optional[modal.mount._Mount]:
|
77
|
+
"""Creates a context mount from a list of docker commands.
|
78
|
+
|
79
|
+
1. Paths are evaluated relative to context_dir.
|
80
|
+
2. First selects inclusions based on COPY commands in the list of commands.
|
81
|
+
3. Then ignore any files as per the ignore predicate.
|
82
|
+
"""
|
83
|
+
...
|
84
|
+
|
63
85
|
def _create_context_mount_function(
|
64
86
|
ignore: typing.Union[
|
65
87
|
collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool], _AutoDockerIgnoreSentinel
|
@@ -71,22 +93,42 @@ def _create_context_mount_function(
|
|
71
93
|
): ...
|
72
94
|
|
73
95
|
class _ImageRegistryConfig:
|
74
|
-
|
96
|
+
"""mdmd:hidden"""
|
97
|
+
def __init__(self, registry_auth_type: int = 0, secret: typing.Optional[modal.secret._Secret] = None):
|
98
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
99
|
+
...
|
100
|
+
|
75
101
|
def get_proto(self) -> modal_proto.api_pb2.ImageRegistryConfig: ...
|
76
102
|
|
77
103
|
class DockerfileSpec:
|
104
|
+
"""DockerfileSpec(commands: list[str], context_files: dict[str, str])"""
|
105
|
+
|
78
106
|
commands: list[str]
|
79
107
|
context_files: dict[str, str]
|
80
108
|
|
81
|
-
def __init__(self, commands: list[str], context_files: dict[str, str]) -> None:
|
82
|
-
|
83
|
-
|
109
|
+
def __init__(self, commands: list[str], context_files: dict[str, str]) -> None:
|
110
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
111
|
+
...
|
112
|
+
|
113
|
+
def __repr__(self):
|
114
|
+
"""Return repr(self)."""
|
115
|
+
...
|
116
|
+
|
117
|
+
def __eq__(self, other):
|
118
|
+
"""Return self==value."""
|
119
|
+
...
|
84
120
|
|
85
121
|
async def _image_await_build_result(
|
86
122
|
image_id: str, client: modal.client._Client
|
87
123
|
) -> modal_proto.api_pb2.ImageJoinStreamingResponse: ...
|
88
124
|
|
89
125
|
class _Image(modal._object._Object):
|
126
|
+
"""Base class for container images to run functions in.
|
127
|
+
|
128
|
+
Do not construct this class directly; instead use one of its static factory methods,
|
129
|
+
such as `modal.Image.debian_slim`, `modal.Image.from_registry`, or `modal.Image.micromamba`.
|
130
|
+
"""
|
131
|
+
|
90
132
|
force_build: bool
|
91
133
|
inside_exceptions: list[Exception]
|
92
134
|
_serve_mounts: frozenset[modal.mount._Mount]
|
@@ -100,7 +142,18 @@ class _Image(modal._object._Object):
|
|
100
142
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
101
143
|
def _add_mount_layer_or_copy(self, mount: modal.mount._Mount, copy: bool = False): ...
|
102
144
|
@property
|
103
|
-
def _mount_layers(self) -> typing.Sequence[modal.mount._Mount]:
|
145
|
+
def _mount_layers(self) -> typing.Sequence[modal.mount._Mount]:
|
146
|
+
"""Non-evaluated mount layers on the image
|
147
|
+
|
148
|
+
When the image is used by a Modal container, these mounts need to be attached as well to
|
149
|
+
represent the full image content, as they haven't yet been represented as a layer in the
|
150
|
+
image.
|
151
|
+
|
152
|
+
When the image is used as a base image for a new layer (that is not itself a mount layer)
|
153
|
+
these mounts need to first be inserted as a copy operation (.copy_mount) into the image.
|
154
|
+
"""
|
155
|
+
...
|
156
|
+
|
104
157
|
def _assert_no_mount_layers(self): ...
|
105
158
|
@staticmethod
|
106
159
|
def _from_args(
|
@@ -121,10 +174,31 @@ class _Image(modal._object._Object):
|
|
121
174
|
_namespace: int = 1,
|
122
175
|
_do_assert_no_mount_layers: bool = True,
|
123
176
|
): ...
|
124
|
-
def _copy_mount(self, mount: modal.mount._Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> _Image:
|
177
|
+
def _copy_mount(self, mount: modal.mount._Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> _Image:
|
178
|
+
"""mdmd:hidden
|
179
|
+
Internal
|
180
|
+
"""
|
181
|
+
...
|
182
|
+
|
125
183
|
def add_local_file(
|
126
184
|
self, local_path: typing.Union[str, pathlib.Path], remote_path: str, *, copy: bool = False
|
127
|
-
) -> _Image:
|
185
|
+
) -> _Image:
|
186
|
+
"""Adds a local file to the image at `remote_path` within the container
|
187
|
+
|
188
|
+
By default (`copy=False`), the files are added to containers on startup and are not built into the actual Image,
|
189
|
+
which speeds up deployment.
|
190
|
+
|
191
|
+
Set `copy=True` to copy the files into an Image layer at build time instead, similar to how
|
192
|
+
[`COPY`](https://docs.docker.com/engine/reference/builder/#copy) works in a `Dockerfile`.
|
193
|
+
|
194
|
+
copy=True can slow down iteration since it requires a rebuild of the Image and any subsequent
|
195
|
+
build steps whenever the included files change, but it is required if you want to run additional
|
196
|
+
build steps after this one.
|
197
|
+
|
198
|
+
*Added in v0.66.40*: This method replaces the deprecated `modal.Image.copy_local_file` method.
|
199
|
+
"""
|
200
|
+
...
|
201
|
+
|
128
202
|
def add_local_dir(
|
129
203
|
self,
|
130
204
|
local_path: typing.Union[str, pathlib.Path],
|
@@ -132,10 +206,72 @@ class _Image(modal._object._Object):
|
|
132
206
|
*,
|
133
207
|
copy: bool = False,
|
134
208
|
ignore: typing.Union[collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]] = [],
|
135
|
-
) -> _Image:
|
209
|
+
) -> _Image:
|
210
|
+
"""Adds a local directory's content to the image at `remote_path` within the container
|
211
|
+
|
212
|
+
By default (`copy=False`), the files are added to containers on startup and are not built into the actual Image,
|
213
|
+
which speeds up deployment.
|
214
|
+
|
215
|
+
Set `copy=True` to copy the files into an Image layer at build time instead, similar to how
|
216
|
+
[`COPY`](https://docs.docker.com/engine/reference/builder/#copy) works in a `Dockerfile`.
|
217
|
+
|
218
|
+
copy=True can slow down iteration since it requires a rebuild of the Image and any subsequent
|
219
|
+
build steps whenever the included files change, but it is required if you want to run additional
|
220
|
+
build steps after this one.
|
221
|
+
|
222
|
+
**Usage:**
|
223
|
+
|
224
|
+
```python
|
225
|
+
from modal import FilePatternMatcher
|
226
|
+
|
227
|
+
image = modal.Image.debian_slim().add_local_dir(
|
228
|
+
"~/assets",
|
229
|
+
remote_path="/assets",
|
230
|
+
ignore=["*.venv"],
|
231
|
+
)
|
232
|
+
|
233
|
+
image = modal.Image.debian_slim().add_local_dir(
|
234
|
+
"~/assets",
|
235
|
+
remote_path="/assets",
|
236
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
237
|
+
)
|
238
|
+
|
239
|
+
image = modal.Image.debian_slim().add_local_dir(
|
240
|
+
"~/assets",
|
241
|
+
remote_path="/assets",
|
242
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
243
|
+
)
|
244
|
+
|
245
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
246
|
+
image = modal.Image.debian_slim().add_local_dir(
|
247
|
+
"~/assets",
|
248
|
+
remote_path="/assets",
|
249
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
250
|
+
)
|
251
|
+
|
252
|
+
# You can also read ignore patterns from a file.
|
253
|
+
image = modal.Image.debian_slim().add_local_dir(
|
254
|
+
"~/assets",
|
255
|
+
remote_path="/assets",
|
256
|
+
ignore=FilePatternMatcher.from_file("/path/to/ignorefile"),
|
257
|
+
)
|
258
|
+
```
|
259
|
+
|
260
|
+
*Added in v0.66.40*: This method replaces the deprecated `modal.Image.copy_local_dir` method.
|
261
|
+
"""
|
262
|
+
...
|
263
|
+
|
136
264
|
def copy_local_file(
|
137
265
|
self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "./"
|
138
|
-
) -> _Image:
|
266
|
+
) -> _Image:
|
267
|
+
"""mdmd:hidden
|
268
|
+
Copy a file into the image as a part of building it.
|
269
|
+
|
270
|
+
This works in a similar way to [`COPY`](https://docs.docker.com/engine/reference/builder/#copy)
|
271
|
+
works in a `Dockerfile`.
|
272
|
+
"""
|
273
|
+
...
|
274
|
+
|
139
275
|
def add_local_python_source(
|
140
276
|
self,
|
141
277
|
*module_names: str,
|
@@ -143,15 +279,106 @@ class _Image(modal._object._Object):
|
|
143
279
|
ignore: typing.Union[
|
144
280
|
collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
|
145
281
|
] = modal.file_pattern_matcher.NON_PYTHON_FILES,
|
146
|
-
) -> _Image:
|
282
|
+
) -> _Image:
|
283
|
+
"""Adds locally available Python packages/modules to containers
|
284
|
+
|
285
|
+
Adds all files from the specified Python package or module to containers running the Image.
|
286
|
+
|
287
|
+
Packages are added to the `/root` directory of containers, which is on the `PYTHONPATH`
|
288
|
+
of any executed Modal Functions, enabling import of the module by that name.
|
289
|
+
|
290
|
+
By default (`copy=False`), the files are added to containers on startup and are not built into the actual Image,
|
291
|
+
which speeds up deployment.
|
292
|
+
|
293
|
+
Set `copy=True` to copy the files into an Image layer at build time instead. This can slow down iteration since
|
294
|
+
it requires a rebuild of the Image and any subsequent build steps whenever the included files change, but it is
|
295
|
+
required if you want to run additional build steps after this one.
|
296
|
+
|
297
|
+
**Note:** This excludes all dot-prefixed subdirectories or files and all `.pyc`/`__pycache__` files.
|
298
|
+
To add full directories with finer control, use `.add_local_dir()` instead and specify `/root` as
|
299
|
+
the destination directory.
|
300
|
+
|
301
|
+
By default only includes `.py`-files in the source modules. Set the `ignore` argument to a list of patterns
|
302
|
+
or a callable to override this behavior, e.g.:
|
303
|
+
|
304
|
+
```py
|
305
|
+
# includes everything except data.json
|
306
|
+
modal.Image.debian_slim().add_local_python_source("mymodule", ignore=["data.json"])
|
307
|
+
|
308
|
+
# exclude large files
|
309
|
+
modal.Image.debian_slim().add_local_python_source(
|
310
|
+
"mymodule",
|
311
|
+
ignore=lambda p: p.stat().st_size > 1e9
|
312
|
+
)
|
313
|
+
```
|
314
|
+
|
315
|
+
*Added in v0.67.28*: This method replaces the deprecated `modal.Mount.from_local_python_packages` pattern.
|
316
|
+
"""
|
317
|
+
...
|
318
|
+
|
147
319
|
def copy_local_dir(
|
148
320
|
self,
|
149
321
|
local_path: typing.Union[str, pathlib.Path],
|
150
322
|
remote_path: typing.Union[str, pathlib.Path] = ".",
|
151
323
|
ignore: typing.Union[collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]] = [],
|
152
|
-
) -> _Image:
|
324
|
+
) -> _Image:
|
325
|
+
"""mdmd:hidden
|
326
|
+
**Deprecated**: Use image.add_local_dir instead
|
327
|
+
|
328
|
+
Copy a directory into the image as a part of building the image.
|
329
|
+
|
330
|
+
This works in a similar way to [`COPY`](https://docs.docker.com/engine/reference/builder/#copy)
|
331
|
+
works in a `Dockerfile`.
|
332
|
+
|
333
|
+
**Usage:**
|
334
|
+
|
335
|
+
```python notest
|
336
|
+
from pathlib import Path
|
337
|
+
from modal import FilePatternMatcher
|
338
|
+
|
339
|
+
image = modal.Image.debian_slim().copy_local_dir(
|
340
|
+
"~/assets",
|
341
|
+
remote_path="/assets",
|
342
|
+
ignore=["**/*.venv"],
|
343
|
+
)
|
344
|
+
|
345
|
+
image = modal.Image.debian_slim().copy_local_dir(
|
346
|
+
"~/assets",
|
347
|
+
remote_path="/assets",
|
348
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
349
|
+
)
|
350
|
+
|
351
|
+
image = modal.Image.debian_slim().copy_local_dir(
|
352
|
+
"~/assets",
|
353
|
+
remote_path="/assets",
|
354
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
355
|
+
)
|
356
|
+
|
357
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
358
|
+
image = modal.Image.debian_slim().copy_local_dir(
|
359
|
+
"~/assets",
|
360
|
+
remote_path="/assets",
|
361
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
362
|
+
)
|
363
|
+
|
364
|
+
# You can also read ignore patterns from a file.
|
365
|
+
image = modal.Image.debian_slim().copy_local_dir(
|
366
|
+
"~/assets",
|
367
|
+
remote_path="/assets",
|
368
|
+
ignore=FilePatternMatcher.from_file("/path/to/ignorefile"),
|
369
|
+
)
|
370
|
+
```
|
371
|
+
"""
|
372
|
+
...
|
373
|
+
|
153
374
|
@staticmethod
|
154
|
-
async def from_id(image_id: str, client: typing.Optional[modal.client._Client] = None) -> _Image:
|
375
|
+
async def from_id(image_id: str, client: typing.Optional[modal.client._Client] = None) -> _Image:
|
376
|
+
"""Construct an Image from an id and look up the Image result.
|
377
|
+
|
378
|
+
The ID of an Image object can be accessed using `.object_id`.
|
379
|
+
"""
|
380
|
+
...
|
381
|
+
|
155
382
|
def pip_install(
|
156
383
|
self,
|
157
384
|
*packages: typing.Union[str, list[str]],
|
@@ -163,7 +390,36 @@ class _Image(modal._object._Object):
|
|
163
390
|
force_build: bool = False,
|
164
391
|
secrets: collections.abc.Sequence[modal.secret._Secret] = [],
|
165
392
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
166
|
-
) -> _Image:
|
393
|
+
) -> _Image:
|
394
|
+
"""Install a list of Python packages using pip.
|
395
|
+
|
396
|
+
**Examples**
|
397
|
+
|
398
|
+
Simple installation:
|
399
|
+
```python
|
400
|
+
image = modal.Image.debian_slim().pip_install("click", "httpx~=0.23.3")
|
401
|
+
```
|
402
|
+
|
403
|
+
More complex installation:
|
404
|
+
```python
|
405
|
+
image = (
|
406
|
+
modal.Image.from_registry(
|
407
|
+
"nvidia/cuda:12.2.0-devel-ubuntu22.04", add_python="3.11"
|
408
|
+
)
|
409
|
+
.pip_install(
|
410
|
+
"ninja",
|
411
|
+
"packaging",
|
412
|
+
"wheel",
|
413
|
+
"transformers==4.40.2",
|
414
|
+
)
|
415
|
+
.pip_install(
|
416
|
+
"flash-attn==2.5.8", extra_options="--no-build-isolation"
|
417
|
+
)
|
418
|
+
)
|
419
|
+
```
|
420
|
+
"""
|
421
|
+
...
|
422
|
+
|
167
423
|
def pip_install_private_repos(
|
168
424
|
self,
|
169
425
|
*repositories: str,
|
@@ -176,7 +432,39 @@ class _Image(modal._object._Object):
|
|
176
432
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
177
433
|
secrets: collections.abc.Sequence[modal.secret._Secret] = [],
|
178
434
|
force_build: bool = False,
|
179
|
-
) -> _Image:
|
435
|
+
) -> _Image:
|
436
|
+
"""Install a list of Python packages from private git repositories using pip.
|
437
|
+
|
438
|
+
This method currently supports Github and Gitlab only.
|
439
|
+
|
440
|
+
- **Github:** Provide a `modal.Secret` that contains a `GITHUB_TOKEN` key-value pair
|
441
|
+
- **Gitlab:** Provide a `modal.Secret` that contains a `GITLAB_TOKEN` key-value pair
|
442
|
+
|
443
|
+
These API tokens should have permissions to read the list of private repositories provided as arguments.
|
444
|
+
|
445
|
+
We recommend using Github's ['fine-grained' access tokens](https://github.blog/2022-10-18-introducing-fine-grained-personal-access-tokens-for-github/).
|
446
|
+
These tokens are repo-scoped, and avoid granting read permission across all of a user's private repos.
|
447
|
+
|
448
|
+
**Example**
|
449
|
+
|
450
|
+
```python
|
451
|
+
image = (
|
452
|
+
modal.Image
|
453
|
+
.debian_slim()
|
454
|
+
.pip_install_private_repos(
|
455
|
+
"github.com/ecorp/private-one@1.0.0",
|
456
|
+
"github.com/ecorp/private-two@main"
|
457
|
+
"github.com/ecorp/private-three@d4776502"
|
458
|
+
# install from 'inner' directory on default branch.
|
459
|
+
"github.com/ecorp/private-four#subdirectory=inner",
|
460
|
+
git_user="erikbern",
|
461
|
+
secrets=[modal.Secret.from_name("github-read-private")],
|
462
|
+
)
|
463
|
+
)
|
464
|
+
```
|
465
|
+
"""
|
466
|
+
...
|
467
|
+
|
180
468
|
def pip_install_from_requirements(
|
181
469
|
self,
|
182
470
|
requirements_txt: str,
|
@@ -189,7 +477,10 @@ class _Image(modal._object._Object):
|
|
189
477
|
force_build: bool = False,
|
190
478
|
secrets: collections.abc.Sequence[modal.secret._Secret] = [],
|
191
479
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
192
|
-
) -> _Image:
|
480
|
+
) -> _Image:
|
481
|
+
"""Install a list of Python packages from a local `requirements.txt` file."""
|
482
|
+
...
|
483
|
+
|
193
484
|
def pip_install_from_pyproject(
|
194
485
|
self,
|
195
486
|
pyproject_toml: str,
|
@@ -203,7 +494,16 @@ class _Image(modal._object._Object):
|
|
203
494
|
force_build: bool = False,
|
204
495
|
secrets: collections.abc.Sequence[modal.secret._Secret] = [],
|
205
496
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
206
|
-
) -> _Image:
|
497
|
+
) -> _Image:
|
498
|
+
"""Install dependencies specified by a local `pyproject.toml` file.
|
499
|
+
|
500
|
+
`optional_dependencies` is a list of the keys of the
|
501
|
+
optional-dependencies section(s) of the `pyproject.toml` file
|
502
|
+
(e.g. test, doc, experiment, etc). When provided,
|
503
|
+
all of the packages in each listed section are installed as well.
|
504
|
+
"""
|
505
|
+
...
|
506
|
+
|
207
507
|
def poetry_install_from_file(
|
208
508
|
self,
|
209
509
|
poetry_pyproject_toml: str,
|
@@ -217,7 +517,17 @@ class _Image(modal._object._Object):
|
|
217
517
|
only: list[str] = [],
|
218
518
|
secrets: collections.abc.Sequence[modal.secret._Secret] = [],
|
219
519
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
220
|
-
) -> _Image:
|
520
|
+
) -> _Image:
|
521
|
+
"""Install poetry *dependencies* specified by a local `pyproject.toml` file.
|
522
|
+
|
523
|
+
If not provided as argument the path to the lockfile is inferred. However, the
|
524
|
+
file has to exist, unless `ignore_lockfile` is set to `True`.
|
525
|
+
|
526
|
+
Note that the root project of the poetry project is not installed, only the dependencies.
|
527
|
+
For including local python source files see `add_local_python_source`
|
528
|
+
"""
|
529
|
+
...
|
530
|
+
|
221
531
|
def dockerfile_commands(
|
222
532
|
self,
|
223
533
|
*dockerfile_commands: typing.Union[str, list[str]],
|
@@ -230,18 +540,72 @@ class _Image(modal._object._Object):
|
|
230
540
|
ignore: typing.Union[
|
231
541
|
collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
|
232
542
|
] = modal.image.AUTO_DOCKERIGNORE,
|
233
|
-
) -> _Image:
|
234
|
-
|
235
|
-
|
543
|
+
) -> _Image:
|
544
|
+
"""Extend an image with arbitrary Dockerfile-like commands.
|
545
|
+
|
546
|
+
**Usage:**
|
547
|
+
|
548
|
+
```python
|
549
|
+
from modal import FilePatternMatcher
|
550
|
+
|
551
|
+
# By default a .dockerignore file is used if present in the current working directory
|
552
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
553
|
+
["COPY data /data"],
|
554
|
+
)
|
555
|
+
|
556
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
557
|
+
["COPY data /data"],
|
558
|
+
ignore=["*.venv"],
|
559
|
+
)
|
560
|
+
|
561
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
562
|
+
["COPY data /data"],
|
563
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
564
|
+
)
|
565
|
+
|
566
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
567
|
+
["COPY data /data"],
|
568
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
569
|
+
)
|
570
|
+
|
571
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
572
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
573
|
+
["COPY data /data"],
|
574
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
575
|
+
)
|
576
|
+
|
577
|
+
# You can also read ignore patterns from a file.
|
578
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
579
|
+
["COPY data /data"],
|
580
|
+
ignore=FilePatternMatcher.from_file("/path/to/dockerignore"),
|
581
|
+
)
|
582
|
+
```
|
583
|
+
"""
|
584
|
+
...
|
585
|
+
|
586
|
+
def entrypoint(self, entrypoint_commands: list[str]) -> _Image:
|
587
|
+
"""Set the entrypoint for the image."""
|
588
|
+
...
|
589
|
+
|
590
|
+
def shell(self, shell_commands: list[str]) -> _Image:
|
591
|
+
"""Overwrite default shell for the image."""
|
592
|
+
...
|
593
|
+
|
236
594
|
def run_commands(
|
237
595
|
self,
|
238
596
|
*commands: typing.Union[str, list[str]],
|
239
597
|
secrets: collections.abc.Sequence[modal.secret._Secret] = [],
|
240
598
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
241
599
|
force_build: bool = False,
|
242
|
-
) -> _Image:
|
600
|
+
) -> _Image:
|
601
|
+
"""Extend an image with a list of shell commands to run."""
|
602
|
+
...
|
603
|
+
|
243
604
|
@staticmethod
|
244
|
-
def micromamba(python_version: typing.Optional[str] = None, force_build: bool = False) -> _Image:
|
605
|
+
def micromamba(python_version: typing.Optional[str] = None, force_build: bool = False) -> _Image:
|
606
|
+
"""A Micromamba base image. Micromamba allows for fast building of small Conda-based containers."""
|
607
|
+
...
|
608
|
+
|
245
609
|
def micromamba_install(
|
246
610
|
self,
|
247
611
|
*packages: typing.Union[str, list[str]],
|
@@ -250,7 +614,10 @@ class _Image(modal._object._Object):
|
|
250
614
|
force_build: bool = False,
|
251
615
|
secrets: collections.abc.Sequence[modal.secret._Secret] = [],
|
252
616
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
253
|
-
) -> _Image:
|
617
|
+
) -> _Image:
|
618
|
+
"""Install a list of additional packages using micromamba."""
|
619
|
+
...
|
620
|
+
|
254
621
|
@staticmethod
|
255
622
|
def _registry_setup_commands(
|
256
623
|
tag: str,
|
@@ -267,7 +634,36 @@ class _Image(modal._object._Object):
|
|
267
634
|
force_build: bool = False,
|
268
635
|
add_python: typing.Optional[str] = None,
|
269
636
|
**kwargs,
|
270
|
-
) -> _Image:
|
637
|
+
) -> _Image:
|
638
|
+
"""Build a Modal Image from a public or private image registry, such as Docker Hub.
|
639
|
+
|
640
|
+
The image must be built for the `linux/amd64` platform.
|
641
|
+
|
642
|
+
If your image does not come with Python installed, you can use the `add_python` parameter
|
643
|
+
to specify a version of Python to add to the image. Otherwise, the image is expected to
|
644
|
+
have Python on PATH as `python`, along with `pip`.
|
645
|
+
|
646
|
+
You may also use `setup_dockerfile_commands` to run Dockerfile commands before the
|
647
|
+
remaining commands run. This might be useful if you want a custom Python installation or to
|
648
|
+
set a `SHELL`. Prefer `run_commands()` when possible though.
|
649
|
+
|
650
|
+
To authenticate against a private registry with static credentials, you must set the `secret` parameter to
|
651
|
+
a `modal.Secret` containing a username (`REGISTRY_USERNAME`) and
|
652
|
+
an access token or password (`REGISTRY_PASSWORD`).
|
653
|
+
|
654
|
+
To authenticate against private registries with credentials from a cloud provider,
|
655
|
+
use `Image.from_gcp_artifact_registry()` or `Image.from_aws_ecr()`.
|
656
|
+
|
657
|
+
**Examples**
|
658
|
+
|
659
|
+
```python
|
660
|
+
modal.Image.from_registry("python:3.11-slim-bookworm")
|
661
|
+
modal.Image.from_registry("ubuntu:22.04", add_python="3.11")
|
662
|
+
modal.Image.from_registry("nvcr.io/nvidia/pytorch:22.12-py3")
|
663
|
+
```
|
664
|
+
"""
|
665
|
+
...
|
666
|
+
|
271
667
|
@staticmethod
|
272
668
|
def from_gcp_artifact_registry(
|
273
669
|
tag: str,
|
@@ -277,7 +673,38 @@ class _Image(modal._object._Object):
|
|
277
673
|
force_build: bool = False,
|
278
674
|
add_python: typing.Optional[str] = None,
|
279
675
|
**kwargs,
|
280
|
-
) -> _Image:
|
676
|
+
) -> _Image:
|
677
|
+
"""Build a Modal image from a private image in Google Cloud Platform (GCP) Artifact Registry.
|
678
|
+
|
679
|
+
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)
|
680
|
+
as `SERVICE_ACCOUNT_JSON`. This can be done from the [Secrets](https://modal.com/secrets) page.
|
681
|
+
Your service account should be granted a specific role depending on the GCP registry used:
|
682
|
+
|
683
|
+
- For Artifact Registry images (`pkg.dev` domains) use
|
684
|
+
the ["Artifact Registry Reader"](https://cloud.google.com/artifact-registry/docs/access-control#roles) role
|
685
|
+
- For Container Registry images (`gcr.io` domains) use
|
686
|
+
the ["Storage Object Viewer"](https://cloud.google.com/artifact-registry/docs/transition/setup-gcr-repo) role
|
687
|
+
|
688
|
+
**Note:** This method does not use `GOOGLE_APPLICATION_CREDENTIALS` as that
|
689
|
+
variable accepts a path to a JSON file, not the actual JSON string.
|
690
|
+
|
691
|
+
See `Image.from_registry()` for information about the other parameters.
|
692
|
+
|
693
|
+
**Example**
|
694
|
+
|
695
|
+
```python
|
696
|
+
modal.Image.from_gcp_artifact_registry(
|
697
|
+
"us-east1-docker.pkg.dev/my-project-1234/my-repo/my-image:my-version",
|
698
|
+
secret=modal.Secret.from_name(
|
699
|
+
"my-gcp-secret",
|
700
|
+
required_keys=["SERVICE_ACCOUNT_JSON"],
|
701
|
+
),
|
702
|
+
add_python="3.11",
|
703
|
+
)
|
704
|
+
```
|
705
|
+
"""
|
706
|
+
...
|
707
|
+
|
281
708
|
@staticmethod
|
282
709
|
def from_aws_ecr(
|
283
710
|
tag: str,
|
@@ -287,7 +714,32 @@ class _Image(modal._object._Object):
|
|
287
714
|
force_build: bool = False,
|
288
715
|
add_python: typing.Optional[str] = None,
|
289
716
|
**kwargs,
|
290
|
-
) -> _Image:
|
717
|
+
) -> _Image:
|
718
|
+
"""Build a Modal image from a private image in AWS Elastic Container Registry (ECR).
|
719
|
+
|
720
|
+
You will need to pass a `modal.Secret` containing `AWS_ACCESS_KEY_ID`,
|
721
|
+
`AWS_SECRET_ACCESS_KEY`, and `AWS_REGION` to access the target ECR registry.
|
722
|
+
|
723
|
+
IAM configuration details can be found in the AWS documentation for
|
724
|
+
["Private repository policies"](https://docs.aws.amazon.com/AmazonECR/latest/userguide/repository-policies.html).
|
725
|
+
|
726
|
+
See `Image.from_registry()` for information about the other parameters.
|
727
|
+
|
728
|
+
**Example**
|
729
|
+
|
730
|
+
```python
|
731
|
+
modal.Image.from_aws_ecr(
|
732
|
+
"000000000000.dkr.ecr.us-east-1.amazonaws.com/my-private-registry:my-version",
|
733
|
+
secret=modal.Secret.from_name(
|
734
|
+
"aws",
|
735
|
+
required_keys=["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"],
|
736
|
+
),
|
737
|
+
add_python="3.11",
|
738
|
+
)
|
739
|
+
```
|
740
|
+
"""
|
741
|
+
...
|
742
|
+
|
291
743
|
@staticmethod
|
292
744
|
def from_dockerfile(
|
293
745
|
path: typing.Union[str, pathlib.Path],
|
@@ -301,16 +753,80 @@ class _Image(modal._object._Object):
|
|
301
753
|
ignore: typing.Union[
|
302
754
|
collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
|
303
755
|
] = modal.image.AUTO_DOCKERIGNORE,
|
304
|
-
) -> _Image:
|
756
|
+
) -> _Image:
|
757
|
+
"""Build a Modal image from a local Dockerfile.
|
758
|
+
|
759
|
+
If your Dockerfile does not have Python installed, you can use the `add_python` parameter
|
760
|
+
to specify a version of Python to add to the image.
|
761
|
+
|
762
|
+
**Usage:**
|
763
|
+
|
764
|
+
```python
|
765
|
+
from modal import FilePatternMatcher
|
766
|
+
|
767
|
+
# By default a .dockerignore file is used if present in the current working directory
|
768
|
+
image = modal.Image.from_dockerfile(
|
769
|
+
"./Dockerfile",
|
770
|
+
add_python="3.12",
|
771
|
+
)
|
772
|
+
|
773
|
+
image = modal.Image.from_dockerfile(
|
774
|
+
"./Dockerfile",
|
775
|
+
add_python="3.12",
|
776
|
+
ignore=["*.venv"],
|
777
|
+
)
|
778
|
+
|
779
|
+
image = modal.Image.from_dockerfile(
|
780
|
+
"./Dockerfile",
|
781
|
+
add_python="3.12",
|
782
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
783
|
+
)
|
784
|
+
|
785
|
+
image = modal.Image.from_dockerfile(
|
786
|
+
"./Dockerfile",
|
787
|
+
add_python="3.12",
|
788
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
789
|
+
)
|
790
|
+
|
791
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
792
|
+
image = modal.Image.from_dockerfile(
|
793
|
+
"./Dockerfile",
|
794
|
+
add_python="3.12",
|
795
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
796
|
+
)
|
797
|
+
|
798
|
+
# You can also read ignore patterns from a file.
|
799
|
+
image = modal.Image.from_dockerfile(
|
800
|
+
"./Dockerfile",
|
801
|
+
add_python="3.12",
|
802
|
+
ignore=FilePatternMatcher.from_file("/path/to/dockerignore"),
|
803
|
+
)
|
804
|
+
```
|
805
|
+
"""
|
806
|
+
...
|
807
|
+
|
305
808
|
@staticmethod
|
306
|
-
def debian_slim(python_version: typing.Optional[str] = None, force_build: bool = False) -> _Image:
|
809
|
+
def debian_slim(python_version: typing.Optional[str] = None, force_build: bool = False) -> _Image:
|
810
|
+
"""Default image, based on the official `python` Docker images."""
|
811
|
+
...
|
812
|
+
|
307
813
|
def apt_install(
|
308
814
|
self,
|
309
815
|
*packages: typing.Union[str, list[str]],
|
310
816
|
force_build: bool = False,
|
311
817
|
secrets: collections.abc.Sequence[modal.secret._Secret] = [],
|
312
818
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
313
|
-
) -> _Image:
|
819
|
+
) -> _Image:
|
820
|
+
"""Install a list of Debian packages using `apt`.
|
821
|
+
|
822
|
+
**Example**
|
823
|
+
|
824
|
+
```python
|
825
|
+
image = modal.Image.debian_slim().apt_install("git")
|
826
|
+
```
|
827
|
+
"""
|
828
|
+
...
|
829
|
+
|
314
830
|
def run_function(
|
315
831
|
self,
|
316
832
|
raw_f: collections.abc.Callable[..., typing.Any],
|
@@ -333,16 +849,109 @@ class _Image(modal._object._Object):
|
|
333
849
|
args: collections.abc.Sequence[typing.Any] = (),
|
334
850
|
kwargs: dict[str, typing.Any] = {},
|
335
851
|
include_source: typing.Optional[bool] = None,
|
336
|
-
) -> _Image:
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
852
|
+
) -> _Image:
|
853
|
+
"""Run user-defined function `raw_f` as an image build step. The function runs just like an ordinary Modal
|
854
|
+
function, and any kwargs accepted by `@app.function` (such as `Mount`s, `NetworkFileSystem`s,
|
855
|
+
and resource requests) can be supplied to it.
|
856
|
+
After it finishes execution, a snapshot of the resulting container file system is saved as an image.
|
857
|
+
|
858
|
+
**Note**
|
859
|
+
|
860
|
+
Only the source code of `raw_f`, the contents of `**kwargs`, and any referenced *global* variables
|
861
|
+
are used to determine whether the image has changed and needs to be rebuilt.
|
862
|
+
If this function references other functions or variables, the image will not be rebuilt if you
|
863
|
+
make changes to them. You can force a rebuild by changing the function's source code itself.
|
864
|
+
|
865
|
+
**Example**
|
866
|
+
|
867
|
+
```python notest
|
868
|
+
|
869
|
+
def my_build_function():
|
870
|
+
open("model.pt", "w").write("parameters!")
|
871
|
+
|
872
|
+
image = (
|
873
|
+
modal.Image
|
874
|
+
.debian_slim()
|
875
|
+
.pip_install("torch")
|
876
|
+
.run_function(my_build_function, secrets=[...], mounts=[...])
|
877
|
+
)
|
878
|
+
```
|
879
|
+
"""
|
880
|
+
...
|
881
|
+
|
882
|
+
def env(self, vars: dict[str, str]) -> _Image:
|
883
|
+
"""Sets the environment variables in an Image.
|
884
|
+
|
885
|
+
**Example**
|
886
|
+
|
887
|
+
```python
|
888
|
+
image = (
|
889
|
+
modal.Image.debian_slim()
|
890
|
+
.env({"HF_HUB_ENABLE_HF_TRANSFER": "1"})
|
891
|
+
)
|
892
|
+
```
|
893
|
+
"""
|
894
|
+
...
|
895
|
+
|
896
|
+
def workdir(self, path: typing.Union[str, pathlib.PurePosixPath]) -> _Image:
|
897
|
+
"""Set the working directory for subsequent image build steps and function execution.
|
898
|
+
|
899
|
+
**Example**
|
900
|
+
|
901
|
+
```python
|
902
|
+
image = (
|
903
|
+
modal.Image.debian_slim()
|
904
|
+
.run_commands("git clone https://xyz app")
|
905
|
+
.workdir("/app")
|
906
|
+
.run_commands("yarn install")
|
907
|
+
)
|
908
|
+
```
|
909
|
+
"""
|
910
|
+
...
|
911
|
+
|
912
|
+
def cmd(self, cmd: list[str]) -> _Image:
|
913
|
+
"""Set the default entrypoint argument (`CMD`) for the image.
|
914
|
+
|
915
|
+
**Example**
|
916
|
+
|
917
|
+
```python
|
918
|
+
image = (
|
919
|
+
modal.Image.debian_slim().cmd(["python", "app.py"])
|
920
|
+
)
|
921
|
+
```
|
922
|
+
"""
|
923
|
+
...
|
924
|
+
|
925
|
+
def imports(self):
|
926
|
+
"""Used to import packages in global scope that are only available when running remotely.
|
927
|
+
By using this context manager you can avoid an `ImportError` due to not having certain
|
928
|
+
packages installed locally.
|
929
|
+
|
930
|
+
**Usage:**
|
931
|
+
|
932
|
+
```python notest
|
933
|
+
with image.imports():
|
934
|
+
import torch
|
935
|
+
```
|
936
|
+
"""
|
937
|
+
...
|
938
|
+
|
939
|
+
def _logs(self) -> typing.AsyncGenerator[str, None]:
|
940
|
+
"""Streams logs from an image, or returns logs from an already completed image.
|
941
|
+
|
942
|
+
This method is considered private since its interface may change - use it at your own risk!
|
943
|
+
"""
|
944
|
+
...
|
342
945
|
|
343
946
|
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
344
947
|
|
345
948
|
class Image(modal.object.Object):
|
949
|
+
"""Base class for container images to run functions in.
|
950
|
+
|
951
|
+
Do not construct this class directly; instead use one of its static factory methods,
|
952
|
+
such as `modal.Image.debian_slim`, `modal.Image.from_registry`, or `modal.Image.micromamba`.
|
953
|
+
"""
|
954
|
+
|
346
955
|
force_build: bool
|
347
956
|
inside_exceptions: list[Exception]
|
348
957
|
_serve_mounts: frozenset[modal.mount.Mount]
|
@@ -350,14 +959,28 @@ class Image(modal.object.Object):
|
|
350
959
|
_added_python_source_set: frozenset[str]
|
351
960
|
_metadata: typing.Optional[modal_proto.api_pb2.ImageMetadata]
|
352
961
|
|
353
|
-
def __init__(self, *args, **kwargs):
|
962
|
+
def __init__(self, *args, **kwargs):
|
963
|
+
"""mdmd:hidden"""
|
964
|
+
...
|
965
|
+
|
354
966
|
def _initialize_from_empty(self): ...
|
355
967
|
def _initialize_from_other(self, other: Image): ...
|
356
968
|
def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
|
357
969
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
358
970
|
def _add_mount_layer_or_copy(self, mount: modal.mount.Mount, copy: bool = False): ...
|
359
971
|
@property
|
360
|
-
def _mount_layers(self) -> typing.Sequence[modal.mount.Mount]:
|
972
|
+
def _mount_layers(self) -> typing.Sequence[modal.mount.Mount]:
|
973
|
+
"""Non-evaluated mount layers on the image
|
974
|
+
|
975
|
+
When the image is used by a Modal container, these mounts need to be attached as well to
|
976
|
+
represent the full image content, as they haven't yet been represented as a layer in the
|
977
|
+
image.
|
978
|
+
|
979
|
+
When the image is used as a base image for a new layer (that is not itself a mount layer)
|
980
|
+
these mounts need to first be inserted as a copy operation (.copy_mount) into the image.
|
981
|
+
"""
|
982
|
+
...
|
983
|
+
|
361
984
|
def _assert_no_mount_layers(self): ...
|
362
985
|
@staticmethod
|
363
986
|
def _from_args(
|
@@ -378,10 +1001,31 @@ class Image(modal.object.Object):
|
|
378
1001
|
_namespace: int = 1,
|
379
1002
|
_do_assert_no_mount_layers: bool = True,
|
380
1003
|
): ...
|
381
|
-
def _copy_mount(self, mount: modal.mount.Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> Image:
|
1004
|
+
def _copy_mount(self, mount: modal.mount.Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> Image:
|
1005
|
+
"""mdmd:hidden
|
1006
|
+
Internal
|
1007
|
+
"""
|
1008
|
+
...
|
1009
|
+
|
382
1010
|
def add_local_file(
|
383
1011
|
self, local_path: typing.Union[str, pathlib.Path], remote_path: str, *, copy: bool = False
|
384
|
-
) -> Image:
|
1012
|
+
) -> Image:
|
1013
|
+
"""Adds a local file to the image at `remote_path` within the container
|
1014
|
+
|
1015
|
+
By default (`copy=False`), the files are added to containers on startup and are not built into the actual Image,
|
1016
|
+
which speeds up deployment.
|
1017
|
+
|
1018
|
+
Set `copy=True` to copy the files into an Image layer at build time instead, similar to how
|
1019
|
+
[`COPY`](https://docs.docker.com/engine/reference/builder/#copy) works in a `Dockerfile`.
|
1020
|
+
|
1021
|
+
copy=True can slow down iteration since it requires a rebuild of the Image and any subsequent
|
1022
|
+
build steps whenever the included files change, but it is required if you want to run additional
|
1023
|
+
build steps after this one.
|
1024
|
+
|
1025
|
+
*Added in v0.66.40*: This method replaces the deprecated `modal.Image.copy_local_file` method.
|
1026
|
+
"""
|
1027
|
+
...
|
1028
|
+
|
385
1029
|
def add_local_dir(
|
386
1030
|
self,
|
387
1031
|
local_path: typing.Union[str, pathlib.Path],
|
@@ -389,10 +1033,72 @@ class Image(modal.object.Object):
|
|
389
1033
|
*,
|
390
1034
|
copy: bool = False,
|
391
1035
|
ignore: typing.Union[collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]] = [],
|
392
|
-
) -> Image:
|
1036
|
+
) -> Image:
|
1037
|
+
"""Adds a local directory's content to the image at `remote_path` within the container
|
1038
|
+
|
1039
|
+
By default (`copy=False`), the files are added to containers on startup and are not built into the actual Image,
|
1040
|
+
which speeds up deployment.
|
1041
|
+
|
1042
|
+
Set `copy=True` to copy the files into an Image layer at build time instead, similar to how
|
1043
|
+
[`COPY`](https://docs.docker.com/engine/reference/builder/#copy) works in a `Dockerfile`.
|
1044
|
+
|
1045
|
+
copy=True can slow down iteration since it requires a rebuild of the Image and any subsequent
|
1046
|
+
build steps whenever the included files change, but it is required if you want to run additional
|
1047
|
+
build steps after this one.
|
1048
|
+
|
1049
|
+
**Usage:**
|
1050
|
+
|
1051
|
+
```python
|
1052
|
+
from modal import FilePatternMatcher
|
1053
|
+
|
1054
|
+
image = modal.Image.debian_slim().add_local_dir(
|
1055
|
+
"~/assets",
|
1056
|
+
remote_path="/assets",
|
1057
|
+
ignore=["*.venv"],
|
1058
|
+
)
|
1059
|
+
|
1060
|
+
image = modal.Image.debian_slim().add_local_dir(
|
1061
|
+
"~/assets",
|
1062
|
+
remote_path="/assets",
|
1063
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
1064
|
+
)
|
1065
|
+
|
1066
|
+
image = modal.Image.debian_slim().add_local_dir(
|
1067
|
+
"~/assets",
|
1068
|
+
remote_path="/assets",
|
1069
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
1070
|
+
)
|
1071
|
+
|
1072
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
1073
|
+
image = modal.Image.debian_slim().add_local_dir(
|
1074
|
+
"~/assets",
|
1075
|
+
remote_path="/assets",
|
1076
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
1077
|
+
)
|
1078
|
+
|
1079
|
+
# You can also read ignore patterns from a file.
|
1080
|
+
image = modal.Image.debian_slim().add_local_dir(
|
1081
|
+
"~/assets",
|
1082
|
+
remote_path="/assets",
|
1083
|
+
ignore=FilePatternMatcher.from_file("/path/to/ignorefile"),
|
1084
|
+
)
|
1085
|
+
```
|
1086
|
+
|
1087
|
+
*Added in v0.66.40*: This method replaces the deprecated `modal.Image.copy_local_dir` method.
|
1088
|
+
"""
|
1089
|
+
...
|
1090
|
+
|
393
1091
|
def copy_local_file(
|
394
1092
|
self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "./"
|
395
|
-
) -> Image:
|
1093
|
+
) -> Image:
|
1094
|
+
"""mdmd:hidden
|
1095
|
+
Copy a file into the image as a part of building it.
|
1096
|
+
|
1097
|
+
This works in a similar way to [`COPY`](https://docs.docker.com/engine/reference/builder/#copy)
|
1098
|
+
works in a `Dockerfile`.
|
1099
|
+
"""
|
1100
|
+
...
|
1101
|
+
|
396
1102
|
def add_local_python_source(
|
397
1103
|
self,
|
398
1104
|
*module_names: str,
|
@@ -400,17 +1106,112 @@ class Image(modal.object.Object):
|
|
400
1106
|
ignore: typing.Union[
|
401
1107
|
collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
|
402
1108
|
] = modal.file_pattern_matcher.NON_PYTHON_FILES,
|
403
|
-
) -> Image:
|
1109
|
+
) -> Image:
|
1110
|
+
"""Adds locally available Python packages/modules to containers
|
1111
|
+
|
1112
|
+
Adds all files from the specified Python package or module to containers running the Image.
|
1113
|
+
|
1114
|
+
Packages are added to the `/root` directory of containers, which is on the `PYTHONPATH`
|
1115
|
+
of any executed Modal Functions, enabling import of the module by that name.
|
1116
|
+
|
1117
|
+
By default (`copy=False`), the files are added to containers on startup and are not built into the actual Image,
|
1118
|
+
which speeds up deployment.
|
1119
|
+
|
1120
|
+
Set `copy=True` to copy the files into an Image layer at build time instead. This can slow down iteration since
|
1121
|
+
it requires a rebuild of the Image and any subsequent build steps whenever the included files change, but it is
|
1122
|
+
required if you want to run additional build steps after this one.
|
1123
|
+
|
1124
|
+
**Note:** This excludes all dot-prefixed subdirectories or files and all `.pyc`/`__pycache__` files.
|
1125
|
+
To add full directories with finer control, use `.add_local_dir()` instead and specify `/root` as
|
1126
|
+
the destination directory.
|
1127
|
+
|
1128
|
+
By default only includes `.py`-files in the source modules. Set the `ignore` argument to a list of patterns
|
1129
|
+
or a callable to override this behavior, e.g.:
|
1130
|
+
|
1131
|
+
```py
|
1132
|
+
# includes everything except data.json
|
1133
|
+
modal.Image.debian_slim().add_local_python_source("mymodule", ignore=["data.json"])
|
1134
|
+
|
1135
|
+
# exclude large files
|
1136
|
+
modal.Image.debian_slim().add_local_python_source(
|
1137
|
+
"mymodule",
|
1138
|
+
ignore=lambda p: p.stat().st_size > 1e9
|
1139
|
+
)
|
1140
|
+
```
|
1141
|
+
|
1142
|
+
*Added in v0.67.28*: This method replaces the deprecated `modal.Mount.from_local_python_packages` pattern.
|
1143
|
+
"""
|
1144
|
+
...
|
1145
|
+
|
404
1146
|
def copy_local_dir(
|
405
1147
|
self,
|
406
1148
|
local_path: typing.Union[str, pathlib.Path],
|
407
1149
|
remote_path: typing.Union[str, pathlib.Path] = ".",
|
408
1150
|
ignore: typing.Union[collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]] = [],
|
409
|
-
) -> Image:
|
1151
|
+
) -> Image:
|
1152
|
+
"""mdmd:hidden
|
1153
|
+
**Deprecated**: Use image.add_local_dir instead
|
1154
|
+
|
1155
|
+
Copy a directory into the image as a part of building the image.
|
1156
|
+
|
1157
|
+
This works in a similar way to [`COPY`](https://docs.docker.com/engine/reference/builder/#copy)
|
1158
|
+
works in a `Dockerfile`.
|
1159
|
+
|
1160
|
+
**Usage:**
|
1161
|
+
|
1162
|
+
```python notest
|
1163
|
+
from pathlib import Path
|
1164
|
+
from modal import FilePatternMatcher
|
1165
|
+
|
1166
|
+
image = modal.Image.debian_slim().copy_local_dir(
|
1167
|
+
"~/assets",
|
1168
|
+
remote_path="/assets",
|
1169
|
+
ignore=["**/*.venv"],
|
1170
|
+
)
|
1171
|
+
|
1172
|
+
image = modal.Image.debian_slim().copy_local_dir(
|
1173
|
+
"~/assets",
|
1174
|
+
remote_path="/assets",
|
1175
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
1176
|
+
)
|
1177
|
+
|
1178
|
+
image = modal.Image.debian_slim().copy_local_dir(
|
1179
|
+
"~/assets",
|
1180
|
+
remote_path="/assets",
|
1181
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
1182
|
+
)
|
1183
|
+
|
1184
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
1185
|
+
image = modal.Image.debian_slim().copy_local_dir(
|
1186
|
+
"~/assets",
|
1187
|
+
remote_path="/assets",
|
1188
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
1189
|
+
)
|
1190
|
+
|
1191
|
+
# You can also read ignore patterns from a file.
|
1192
|
+
image = modal.Image.debian_slim().copy_local_dir(
|
1193
|
+
"~/assets",
|
1194
|
+
remote_path="/assets",
|
1195
|
+
ignore=FilePatternMatcher.from_file("/path/to/ignorefile"),
|
1196
|
+
)
|
1197
|
+
```
|
1198
|
+
"""
|
1199
|
+
...
|
410
1200
|
|
411
1201
|
class __from_id_spec(typing_extensions.Protocol):
|
412
|
-
def __call__(self, /, image_id: str, client: typing.Optional[modal.client.Client] = None) -> Image:
|
413
|
-
|
1202
|
+
def __call__(self, /, image_id: str, client: typing.Optional[modal.client.Client] = None) -> Image:
|
1203
|
+
"""Construct an Image from an id and look up the Image result.
|
1204
|
+
|
1205
|
+
The ID of an Image object can be accessed using `.object_id`.
|
1206
|
+
"""
|
1207
|
+
...
|
1208
|
+
|
1209
|
+
async def aio(self, /, image_id: str, client: typing.Optional[modal.client.Client] = None) -> Image:
|
1210
|
+
"""Construct an Image from an id and look up the Image result.
|
1211
|
+
|
1212
|
+
The ID of an Image object can be accessed using `.object_id`.
|
1213
|
+
"""
|
1214
|
+
...
|
414
1215
|
|
415
1216
|
from_id: __from_id_spec
|
416
1217
|
|
@@ -425,7 +1226,36 @@ class Image(modal.object.Object):
|
|
425
1226
|
force_build: bool = False,
|
426
1227
|
secrets: collections.abc.Sequence[modal.secret.Secret] = [],
|
427
1228
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
428
|
-
) -> Image:
|
1229
|
+
) -> Image:
|
1230
|
+
"""Install a list of Python packages using pip.
|
1231
|
+
|
1232
|
+
**Examples**
|
1233
|
+
|
1234
|
+
Simple installation:
|
1235
|
+
```python
|
1236
|
+
image = modal.Image.debian_slim().pip_install("click", "httpx~=0.23.3")
|
1237
|
+
```
|
1238
|
+
|
1239
|
+
More complex installation:
|
1240
|
+
```python
|
1241
|
+
image = (
|
1242
|
+
modal.Image.from_registry(
|
1243
|
+
"nvidia/cuda:12.2.0-devel-ubuntu22.04", add_python="3.11"
|
1244
|
+
)
|
1245
|
+
.pip_install(
|
1246
|
+
"ninja",
|
1247
|
+
"packaging",
|
1248
|
+
"wheel",
|
1249
|
+
"transformers==4.40.2",
|
1250
|
+
)
|
1251
|
+
.pip_install(
|
1252
|
+
"flash-attn==2.5.8", extra_options="--no-build-isolation"
|
1253
|
+
)
|
1254
|
+
)
|
1255
|
+
```
|
1256
|
+
"""
|
1257
|
+
...
|
1258
|
+
|
429
1259
|
def pip_install_private_repos(
|
430
1260
|
self,
|
431
1261
|
*repositories: str,
|
@@ -438,7 +1268,39 @@ class Image(modal.object.Object):
|
|
438
1268
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
439
1269
|
secrets: collections.abc.Sequence[modal.secret.Secret] = [],
|
440
1270
|
force_build: bool = False,
|
441
|
-
) -> Image:
|
1271
|
+
) -> Image:
|
1272
|
+
"""Install a list of Python packages from private git repositories using pip.
|
1273
|
+
|
1274
|
+
This method currently supports Github and Gitlab only.
|
1275
|
+
|
1276
|
+
- **Github:** Provide a `modal.Secret` that contains a `GITHUB_TOKEN` key-value pair
|
1277
|
+
- **Gitlab:** Provide a `modal.Secret` that contains a `GITLAB_TOKEN` key-value pair
|
1278
|
+
|
1279
|
+
These API tokens should have permissions to read the list of private repositories provided as arguments.
|
1280
|
+
|
1281
|
+
We recommend using Github's ['fine-grained' access tokens](https://github.blog/2022-10-18-introducing-fine-grained-personal-access-tokens-for-github/).
|
1282
|
+
These tokens are repo-scoped, and avoid granting read permission across all of a user's private repos.
|
1283
|
+
|
1284
|
+
**Example**
|
1285
|
+
|
1286
|
+
```python
|
1287
|
+
image = (
|
1288
|
+
modal.Image
|
1289
|
+
.debian_slim()
|
1290
|
+
.pip_install_private_repos(
|
1291
|
+
"github.com/ecorp/private-one@1.0.0",
|
1292
|
+
"github.com/ecorp/private-two@main"
|
1293
|
+
"github.com/ecorp/private-three@d4776502"
|
1294
|
+
# install from 'inner' directory on default branch.
|
1295
|
+
"github.com/ecorp/private-four#subdirectory=inner",
|
1296
|
+
git_user="erikbern",
|
1297
|
+
secrets=[modal.Secret.from_name("github-read-private")],
|
1298
|
+
)
|
1299
|
+
)
|
1300
|
+
```
|
1301
|
+
"""
|
1302
|
+
...
|
1303
|
+
|
442
1304
|
def pip_install_from_requirements(
|
443
1305
|
self,
|
444
1306
|
requirements_txt: str,
|
@@ -451,7 +1313,10 @@ class Image(modal.object.Object):
|
|
451
1313
|
force_build: bool = False,
|
452
1314
|
secrets: collections.abc.Sequence[modal.secret.Secret] = [],
|
453
1315
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
454
|
-
) -> Image:
|
1316
|
+
) -> Image:
|
1317
|
+
"""Install a list of Python packages from a local `requirements.txt` file."""
|
1318
|
+
...
|
1319
|
+
|
455
1320
|
def pip_install_from_pyproject(
|
456
1321
|
self,
|
457
1322
|
pyproject_toml: str,
|
@@ -465,7 +1330,16 @@ class Image(modal.object.Object):
|
|
465
1330
|
force_build: bool = False,
|
466
1331
|
secrets: collections.abc.Sequence[modal.secret.Secret] = [],
|
467
1332
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
468
|
-
) -> Image:
|
1333
|
+
) -> Image:
|
1334
|
+
"""Install dependencies specified by a local `pyproject.toml` file.
|
1335
|
+
|
1336
|
+
`optional_dependencies` is a list of the keys of the
|
1337
|
+
optional-dependencies section(s) of the `pyproject.toml` file
|
1338
|
+
(e.g. test, doc, experiment, etc). When provided,
|
1339
|
+
all of the packages in each listed section are installed as well.
|
1340
|
+
"""
|
1341
|
+
...
|
1342
|
+
|
469
1343
|
def poetry_install_from_file(
|
470
1344
|
self,
|
471
1345
|
poetry_pyproject_toml: str,
|
@@ -479,7 +1353,17 @@ class Image(modal.object.Object):
|
|
479
1353
|
only: list[str] = [],
|
480
1354
|
secrets: collections.abc.Sequence[modal.secret.Secret] = [],
|
481
1355
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
482
|
-
) -> Image:
|
1356
|
+
) -> Image:
|
1357
|
+
"""Install poetry *dependencies* specified by a local `pyproject.toml` file.
|
1358
|
+
|
1359
|
+
If not provided as argument the path to the lockfile is inferred. However, the
|
1360
|
+
file has to exist, unless `ignore_lockfile` is set to `True`.
|
1361
|
+
|
1362
|
+
Note that the root project of the poetry project is not installed, only the dependencies.
|
1363
|
+
For including local python source files see `add_local_python_source`
|
1364
|
+
"""
|
1365
|
+
...
|
1366
|
+
|
483
1367
|
def dockerfile_commands(
|
484
1368
|
self,
|
485
1369
|
*dockerfile_commands: typing.Union[str, list[str]],
|
@@ -492,18 +1376,72 @@ class Image(modal.object.Object):
|
|
492
1376
|
ignore: typing.Union[
|
493
1377
|
collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
|
494
1378
|
] = modal.image.AUTO_DOCKERIGNORE,
|
495
|
-
) -> Image:
|
496
|
-
|
497
|
-
|
1379
|
+
) -> Image:
|
1380
|
+
"""Extend an image with arbitrary Dockerfile-like commands.
|
1381
|
+
|
1382
|
+
**Usage:**
|
1383
|
+
|
1384
|
+
```python
|
1385
|
+
from modal import FilePatternMatcher
|
1386
|
+
|
1387
|
+
# By default a .dockerignore file is used if present in the current working directory
|
1388
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
1389
|
+
["COPY data /data"],
|
1390
|
+
)
|
1391
|
+
|
1392
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
1393
|
+
["COPY data /data"],
|
1394
|
+
ignore=["*.venv"],
|
1395
|
+
)
|
1396
|
+
|
1397
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
1398
|
+
["COPY data /data"],
|
1399
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
1400
|
+
)
|
1401
|
+
|
1402
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
1403
|
+
["COPY data /data"],
|
1404
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
1405
|
+
)
|
1406
|
+
|
1407
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
1408
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
1409
|
+
["COPY data /data"],
|
1410
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
1411
|
+
)
|
1412
|
+
|
1413
|
+
# You can also read ignore patterns from a file.
|
1414
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
1415
|
+
["COPY data /data"],
|
1416
|
+
ignore=FilePatternMatcher.from_file("/path/to/dockerignore"),
|
1417
|
+
)
|
1418
|
+
```
|
1419
|
+
"""
|
1420
|
+
...
|
1421
|
+
|
1422
|
+
def entrypoint(self, entrypoint_commands: list[str]) -> Image:
|
1423
|
+
"""Set the entrypoint for the image."""
|
1424
|
+
...
|
1425
|
+
|
1426
|
+
def shell(self, shell_commands: list[str]) -> Image:
|
1427
|
+
"""Overwrite default shell for the image."""
|
1428
|
+
...
|
1429
|
+
|
498
1430
|
def run_commands(
|
499
1431
|
self,
|
500
1432
|
*commands: typing.Union[str, list[str]],
|
501
1433
|
secrets: collections.abc.Sequence[modal.secret.Secret] = [],
|
502
1434
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
503
1435
|
force_build: bool = False,
|
504
|
-
) -> Image:
|
1436
|
+
) -> Image:
|
1437
|
+
"""Extend an image with a list of shell commands to run."""
|
1438
|
+
...
|
1439
|
+
|
505
1440
|
@staticmethod
|
506
|
-
def micromamba(python_version: typing.Optional[str] = None, force_build: bool = False) -> Image:
|
1441
|
+
def micromamba(python_version: typing.Optional[str] = None, force_build: bool = False) -> Image:
|
1442
|
+
"""A Micromamba base image. Micromamba allows for fast building of small Conda-based containers."""
|
1443
|
+
...
|
1444
|
+
|
507
1445
|
def micromamba_install(
|
508
1446
|
self,
|
509
1447
|
*packages: typing.Union[str, list[str]],
|
@@ -512,7 +1450,10 @@ class Image(modal.object.Object):
|
|
512
1450
|
force_build: bool = False,
|
513
1451
|
secrets: collections.abc.Sequence[modal.secret.Secret] = [],
|
514
1452
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
515
|
-
) -> Image:
|
1453
|
+
) -> Image:
|
1454
|
+
"""Install a list of additional packages using micromamba."""
|
1455
|
+
...
|
1456
|
+
|
516
1457
|
@staticmethod
|
517
1458
|
def _registry_setup_commands(
|
518
1459
|
tag: str,
|
@@ -529,7 +1470,36 @@ class Image(modal.object.Object):
|
|
529
1470
|
force_build: bool = False,
|
530
1471
|
add_python: typing.Optional[str] = None,
|
531
1472
|
**kwargs,
|
532
|
-
) -> Image:
|
1473
|
+
) -> Image:
|
1474
|
+
"""Build a Modal Image from a public or private image registry, such as Docker Hub.
|
1475
|
+
|
1476
|
+
The image must be built for the `linux/amd64` platform.
|
1477
|
+
|
1478
|
+
If your image does not come with Python installed, you can use the `add_python` parameter
|
1479
|
+
to specify a version of Python to add to the image. Otherwise, the image is expected to
|
1480
|
+
have Python on PATH as `python`, along with `pip`.
|
1481
|
+
|
1482
|
+
You may also use `setup_dockerfile_commands` to run Dockerfile commands before the
|
1483
|
+
remaining commands run. This might be useful if you want a custom Python installation or to
|
1484
|
+
set a `SHELL`. Prefer `run_commands()` when possible though.
|
1485
|
+
|
1486
|
+
To authenticate against a private registry with static credentials, you must set the `secret` parameter to
|
1487
|
+
a `modal.Secret` containing a username (`REGISTRY_USERNAME`) and
|
1488
|
+
an access token or password (`REGISTRY_PASSWORD`).
|
1489
|
+
|
1490
|
+
To authenticate against private registries with credentials from a cloud provider,
|
1491
|
+
use `Image.from_gcp_artifact_registry()` or `Image.from_aws_ecr()`.
|
1492
|
+
|
1493
|
+
**Examples**
|
1494
|
+
|
1495
|
+
```python
|
1496
|
+
modal.Image.from_registry("python:3.11-slim-bookworm")
|
1497
|
+
modal.Image.from_registry("ubuntu:22.04", add_python="3.11")
|
1498
|
+
modal.Image.from_registry("nvcr.io/nvidia/pytorch:22.12-py3")
|
1499
|
+
```
|
1500
|
+
"""
|
1501
|
+
...
|
1502
|
+
|
533
1503
|
@staticmethod
|
534
1504
|
def from_gcp_artifact_registry(
|
535
1505
|
tag: str,
|
@@ -539,7 +1509,38 @@ class Image(modal.object.Object):
|
|
539
1509
|
force_build: bool = False,
|
540
1510
|
add_python: typing.Optional[str] = None,
|
541
1511
|
**kwargs,
|
542
|
-
) -> Image:
|
1512
|
+
) -> Image:
|
1513
|
+
"""Build a Modal image from a private image in Google Cloud Platform (GCP) Artifact Registry.
|
1514
|
+
|
1515
|
+
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)
|
1516
|
+
as `SERVICE_ACCOUNT_JSON`. This can be done from the [Secrets](https://modal.com/secrets) page.
|
1517
|
+
Your service account should be granted a specific role depending on the GCP registry used:
|
1518
|
+
|
1519
|
+
- For Artifact Registry images (`pkg.dev` domains) use
|
1520
|
+
the ["Artifact Registry Reader"](https://cloud.google.com/artifact-registry/docs/access-control#roles) role
|
1521
|
+
- For Container Registry images (`gcr.io` domains) use
|
1522
|
+
the ["Storage Object Viewer"](https://cloud.google.com/artifact-registry/docs/transition/setup-gcr-repo) role
|
1523
|
+
|
1524
|
+
**Note:** This method does not use `GOOGLE_APPLICATION_CREDENTIALS` as that
|
1525
|
+
variable accepts a path to a JSON file, not the actual JSON string.
|
1526
|
+
|
1527
|
+
See `Image.from_registry()` for information about the other parameters.
|
1528
|
+
|
1529
|
+
**Example**
|
1530
|
+
|
1531
|
+
```python
|
1532
|
+
modal.Image.from_gcp_artifact_registry(
|
1533
|
+
"us-east1-docker.pkg.dev/my-project-1234/my-repo/my-image:my-version",
|
1534
|
+
secret=modal.Secret.from_name(
|
1535
|
+
"my-gcp-secret",
|
1536
|
+
required_keys=["SERVICE_ACCOUNT_JSON"],
|
1537
|
+
),
|
1538
|
+
add_python="3.11",
|
1539
|
+
)
|
1540
|
+
```
|
1541
|
+
"""
|
1542
|
+
...
|
1543
|
+
|
543
1544
|
@staticmethod
|
544
1545
|
def from_aws_ecr(
|
545
1546
|
tag: str,
|
@@ -549,7 +1550,32 @@ class Image(modal.object.Object):
|
|
549
1550
|
force_build: bool = False,
|
550
1551
|
add_python: typing.Optional[str] = None,
|
551
1552
|
**kwargs,
|
552
|
-
) -> Image:
|
1553
|
+
) -> Image:
|
1554
|
+
"""Build a Modal image from a private image in AWS Elastic Container Registry (ECR).
|
1555
|
+
|
1556
|
+
You will need to pass a `modal.Secret` containing `AWS_ACCESS_KEY_ID`,
|
1557
|
+
`AWS_SECRET_ACCESS_KEY`, and `AWS_REGION` to access the target ECR registry.
|
1558
|
+
|
1559
|
+
IAM configuration details can be found in the AWS documentation for
|
1560
|
+
["Private repository policies"](https://docs.aws.amazon.com/AmazonECR/latest/userguide/repository-policies.html).
|
1561
|
+
|
1562
|
+
See `Image.from_registry()` for information about the other parameters.
|
1563
|
+
|
1564
|
+
**Example**
|
1565
|
+
|
1566
|
+
```python
|
1567
|
+
modal.Image.from_aws_ecr(
|
1568
|
+
"000000000000.dkr.ecr.us-east-1.amazonaws.com/my-private-registry:my-version",
|
1569
|
+
secret=modal.Secret.from_name(
|
1570
|
+
"aws",
|
1571
|
+
required_keys=["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"],
|
1572
|
+
),
|
1573
|
+
add_python="3.11",
|
1574
|
+
)
|
1575
|
+
```
|
1576
|
+
"""
|
1577
|
+
...
|
1578
|
+
|
553
1579
|
@staticmethod
|
554
1580
|
def from_dockerfile(
|
555
1581
|
path: typing.Union[str, pathlib.Path],
|
@@ -563,16 +1589,80 @@ class Image(modal.object.Object):
|
|
563
1589
|
ignore: typing.Union[
|
564
1590
|
collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
|
565
1591
|
] = modal.image.AUTO_DOCKERIGNORE,
|
566
|
-
) -> Image:
|
1592
|
+
) -> Image:
|
1593
|
+
"""Build a Modal image from a local Dockerfile.
|
1594
|
+
|
1595
|
+
If your Dockerfile does not have Python installed, you can use the `add_python` parameter
|
1596
|
+
to specify a version of Python to add to the image.
|
1597
|
+
|
1598
|
+
**Usage:**
|
1599
|
+
|
1600
|
+
```python
|
1601
|
+
from modal import FilePatternMatcher
|
1602
|
+
|
1603
|
+
# By default a .dockerignore file is used if present in the current working directory
|
1604
|
+
image = modal.Image.from_dockerfile(
|
1605
|
+
"./Dockerfile",
|
1606
|
+
add_python="3.12",
|
1607
|
+
)
|
1608
|
+
|
1609
|
+
image = modal.Image.from_dockerfile(
|
1610
|
+
"./Dockerfile",
|
1611
|
+
add_python="3.12",
|
1612
|
+
ignore=["*.venv"],
|
1613
|
+
)
|
1614
|
+
|
1615
|
+
image = modal.Image.from_dockerfile(
|
1616
|
+
"./Dockerfile",
|
1617
|
+
add_python="3.12",
|
1618
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
1619
|
+
)
|
1620
|
+
|
1621
|
+
image = modal.Image.from_dockerfile(
|
1622
|
+
"./Dockerfile",
|
1623
|
+
add_python="3.12",
|
1624
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
1625
|
+
)
|
1626
|
+
|
1627
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
1628
|
+
image = modal.Image.from_dockerfile(
|
1629
|
+
"./Dockerfile",
|
1630
|
+
add_python="3.12",
|
1631
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
1632
|
+
)
|
1633
|
+
|
1634
|
+
# You can also read ignore patterns from a file.
|
1635
|
+
image = modal.Image.from_dockerfile(
|
1636
|
+
"./Dockerfile",
|
1637
|
+
add_python="3.12",
|
1638
|
+
ignore=FilePatternMatcher.from_file("/path/to/dockerignore"),
|
1639
|
+
)
|
1640
|
+
```
|
1641
|
+
"""
|
1642
|
+
...
|
1643
|
+
|
567
1644
|
@staticmethod
|
568
|
-
def debian_slim(python_version: typing.Optional[str] = None, force_build: bool = False) -> Image:
|
1645
|
+
def debian_slim(python_version: typing.Optional[str] = None, force_build: bool = False) -> Image:
|
1646
|
+
"""Default image, based on the official `python` Docker images."""
|
1647
|
+
...
|
1648
|
+
|
569
1649
|
def apt_install(
|
570
1650
|
self,
|
571
1651
|
*packages: typing.Union[str, list[str]],
|
572
1652
|
force_build: bool = False,
|
573
1653
|
secrets: collections.abc.Sequence[modal.secret.Secret] = [],
|
574
1654
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
575
|
-
) -> Image:
|
1655
|
+
) -> Image:
|
1656
|
+
"""Install a list of Debian packages using `apt`.
|
1657
|
+
|
1658
|
+
**Example**
|
1659
|
+
|
1660
|
+
```python
|
1661
|
+
image = modal.Image.debian_slim().apt_install("git")
|
1662
|
+
```
|
1663
|
+
"""
|
1664
|
+
...
|
1665
|
+
|
576
1666
|
def run_function(
|
577
1667
|
self,
|
578
1668
|
raw_f: collections.abc.Callable[..., typing.Any],
|
@@ -595,15 +1685,107 @@ class Image(modal.object.Object):
|
|
595
1685
|
args: collections.abc.Sequence[typing.Any] = (),
|
596
1686
|
kwargs: dict[str, typing.Any] = {},
|
597
1687
|
include_source: typing.Optional[bool] = None,
|
598
|
-
) -> Image:
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
1688
|
+
) -> Image:
|
1689
|
+
"""Run user-defined function `raw_f` as an image build step. The function runs just like an ordinary Modal
|
1690
|
+
function, and any kwargs accepted by `@app.function` (such as `Mount`s, `NetworkFileSystem`s,
|
1691
|
+
and resource requests) can be supplied to it.
|
1692
|
+
After it finishes execution, a snapshot of the resulting container file system is saved as an image.
|
1693
|
+
|
1694
|
+
**Note**
|
1695
|
+
|
1696
|
+
Only the source code of `raw_f`, the contents of `**kwargs`, and any referenced *global* variables
|
1697
|
+
are used to determine whether the image has changed and needs to be rebuilt.
|
1698
|
+
If this function references other functions or variables, the image will not be rebuilt if you
|
1699
|
+
make changes to them. You can force a rebuild by changing the function's source code itself.
|
1700
|
+
|
1701
|
+
**Example**
|
1702
|
+
|
1703
|
+
```python notest
|
1704
|
+
|
1705
|
+
def my_build_function():
|
1706
|
+
open("model.pt", "w").write("parameters!")
|
1707
|
+
|
1708
|
+
image = (
|
1709
|
+
modal.Image
|
1710
|
+
.debian_slim()
|
1711
|
+
.pip_install("torch")
|
1712
|
+
.run_function(my_build_function, secrets=[...], mounts=[...])
|
1713
|
+
)
|
1714
|
+
```
|
1715
|
+
"""
|
1716
|
+
...
|
1717
|
+
|
1718
|
+
def env(self, vars: dict[str, str]) -> Image:
|
1719
|
+
"""Sets the environment variables in an Image.
|
1720
|
+
|
1721
|
+
**Example**
|
1722
|
+
|
1723
|
+
```python
|
1724
|
+
image = (
|
1725
|
+
modal.Image.debian_slim()
|
1726
|
+
.env({"HF_HUB_ENABLE_HF_TRANSFER": "1"})
|
1727
|
+
)
|
1728
|
+
```
|
1729
|
+
"""
|
1730
|
+
...
|
1731
|
+
|
1732
|
+
def workdir(self, path: typing.Union[str, pathlib.PurePosixPath]) -> Image:
|
1733
|
+
"""Set the working directory for subsequent image build steps and function execution.
|
1734
|
+
|
1735
|
+
**Example**
|
1736
|
+
|
1737
|
+
```python
|
1738
|
+
image = (
|
1739
|
+
modal.Image.debian_slim()
|
1740
|
+
.run_commands("git clone https://xyz app")
|
1741
|
+
.workdir("/app")
|
1742
|
+
.run_commands("yarn install")
|
1743
|
+
)
|
1744
|
+
```
|
1745
|
+
"""
|
1746
|
+
...
|
1747
|
+
|
1748
|
+
def cmd(self, cmd: list[str]) -> Image:
|
1749
|
+
"""Set the default entrypoint argument (`CMD`) for the image.
|
1750
|
+
|
1751
|
+
**Example**
|
1752
|
+
|
1753
|
+
```python
|
1754
|
+
image = (
|
1755
|
+
modal.Image.debian_slim().cmd(["python", "app.py"])
|
1756
|
+
)
|
1757
|
+
```
|
1758
|
+
"""
|
1759
|
+
...
|
1760
|
+
|
1761
|
+
def imports(self):
|
1762
|
+
"""Used to import packages in global scope that are only available when running remotely.
|
1763
|
+
By using this context manager you can avoid an `ImportError` due to not having certain
|
1764
|
+
packages installed locally.
|
1765
|
+
|
1766
|
+
**Usage:**
|
1767
|
+
|
1768
|
+
```python notest
|
1769
|
+
with image.imports():
|
1770
|
+
import torch
|
1771
|
+
```
|
1772
|
+
"""
|
1773
|
+
...
|
603
1774
|
|
604
1775
|
class ___logs_spec(typing_extensions.Protocol[SUPERSELF]):
|
605
|
-
def __call__(self, /) -> typing.Generator[str, None, None]:
|
606
|
-
|
1776
|
+
def __call__(self, /) -> typing.Generator[str, None, None]:
|
1777
|
+
"""Streams logs from an image, or returns logs from an already completed image.
|
1778
|
+
|
1779
|
+
This method is considered private since its interface may change - use it at your own risk!
|
1780
|
+
"""
|
1781
|
+
...
|
1782
|
+
|
1783
|
+
def aio(self, /) -> typing.AsyncGenerator[str, None]:
|
1784
|
+
"""Streams logs from an image, or returns logs from an already completed image.
|
1785
|
+
|
1786
|
+
This method is considered private since its interface may change - use it at your own risk!
|
1787
|
+
"""
|
1788
|
+
...
|
607
1789
|
|
608
1790
|
_logs: ___logs_spec[typing_extensions.Self]
|
609
1791
|
|