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.

Files changed (160) hide show
  1. modal/__init__.py +0 -2
  2. modal/__main__.py +3 -4
  3. modal/_billing.py +80 -0
  4. modal/_clustered_functions.py +7 -3
  5. modal/_clustered_functions.pyi +15 -3
  6. modal/_container_entrypoint.py +51 -69
  7. modal/_functions.py +508 -240
  8. modal/_grpc_client.py +171 -0
  9. modal/_load_context.py +105 -0
  10. modal/_object.py +81 -21
  11. modal/_output.py +58 -45
  12. modal/_partial_function.py +48 -73
  13. modal/_pty.py +7 -3
  14. modal/_resolver.py +26 -46
  15. modal/_runtime/asgi.py +4 -3
  16. modal/_runtime/container_io_manager.py +358 -220
  17. modal/_runtime/container_io_manager.pyi +296 -101
  18. modal/_runtime/execution_context.py +18 -2
  19. modal/_runtime/execution_context.pyi +64 -7
  20. modal/_runtime/gpu_memory_snapshot.py +262 -57
  21. modal/_runtime/user_code_imports.py +28 -58
  22. modal/_serialization.py +90 -6
  23. modal/_traceback.py +42 -1
  24. modal/_tunnel.pyi +380 -12
  25. modal/_utils/async_utils.py +84 -29
  26. modal/_utils/auth_token_manager.py +111 -0
  27. modal/_utils/blob_utils.py +181 -58
  28. modal/_utils/deprecation.py +19 -0
  29. modal/_utils/function_utils.py +91 -47
  30. modal/_utils/grpc_utils.py +89 -66
  31. modal/_utils/mount_utils.py +26 -1
  32. modal/_utils/name_utils.py +17 -3
  33. modal/_utils/task_command_router_client.py +536 -0
  34. modal/_utils/time_utils.py +34 -6
  35. modal/app.py +256 -88
  36. modal/app.pyi +909 -92
  37. modal/billing.py +5 -0
  38. modal/builder/2025.06.txt +18 -0
  39. modal/builder/PREVIEW.txt +18 -0
  40. modal/builder/base-images.json +58 -0
  41. modal/cli/_download.py +19 -3
  42. modal/cli/_traceback.py +3 -2
  43. modal/cli/app.py +4 -4
  44. modal/cli/cluster.py +15 -7
  45. modal/cli/config.py +5 -3
  46. modal/cli/container.py +7 -6
  47. modal/cli/dict.py +22 -16
  48. modal/cli/entry_point.py +12 -5
  49. modal/cli/environment.py +5 -4
  50. modal/cli/import_refs.py +3 -3
  51. modal/cli/launch.py +102 -5
  52. modal/cli/network_file_system.py +11 -12
  53. modal/cli/profile.py +3 -2
  54. modal/cli/programs/launch_instance_ssh.py +94 -0
  55. modal/cli/programs/run_jupyter.py +1 -1
  56. modal/cli/programs/run_marimo.py +95 -0
  57. modal/cli/programs/vscode.py +1 -1
  58. modal/cli/queues.py +57 -26
  59. modal/cli/run.py +91 -23
  60. modal/cli/secret.py +48 -22
  61. modal/cli/token.py +7 -8
  62. modal/cli/utils.py +4 -7
  63. modal/cli/volume.py +31 -25
  64. modal/client.py +15 -85
  65. modal/client.pyi +183 -62
  66. modal/cloud_bucket_mount.py +5 -3
  67. modal/cloud_bucket_mount.pyi +197 -5
  68. modal/cls.py +200 -126
  69. modal/cls.pyi +446 -68
  70. modal/config.py +29 -11
  71. modal/container_process.py +319 -19
  72. modal/container_process.pyi +190 -20
  73. modal/dict.py +290 -71
  74. modal/dict.pyi +835 -83
  75. modal/environments.py +15 -27
  76. modal/environments.pyi +46 -24
  77. modal/exception.py +14 -2
  78. modal/experimental/__init__.py +194 -40
  79. modal/experimental/flash.py +618 -0
  80. modal/experimental/flash.pyi +380 -0
  81. modal/experimental/ipython.py +11 -7
  82. modal/file_io.py +29 -36
  83. modal/file_io.pyi +251 -53
  84. modal/file_pattern_matcher.py +56 -16
  85. modal/functions.pyi +673 -92
  86. modal/gpu.py +1 -1
  87. modal/image.py +528 -176
  88. modal/image.pyi +1572 -145
  89. modal/io_streams.py +458 -128
  90. modal/io_streams.pyi +433 -52
  91. modal/mount.py +216 -151
  92. modal/mount.pyi +225 -78
  93. modal/network_file_system.py +45 -62
  94. modal/network_file_system.pyi +277 -56
  95. modal/object.pyi +93 -17
  96. modal/parallel_map.py +942 -129
  97. modal/parallel_map.pyi +294 -15
  98. modal/partial_function.py +0 -2
  99. modal/partial_function.pyi +234 -19
  100. modal/proxy.py +17 -8
  101. modal/proxy.pyi +36 -3
  102. modal/queue.py +270 -65
  103. modal/queue.pyi +817 -57
  104. modal/runner.py +115 -101
  105. modal/runner.pyi +205 -49
  106. modal/sandbox.py +512 -136
  107. modal/sandbox.pyi +845 -111
  108. modal/schedule.py +1 -1
  109. modal/secret.py +300 -70
  110. modal/secret.pyi +589 -34
  111. modal/serving.py +7 -11
  112. modal/serving.pyi +7 -8
  113. modal/snapshot.py +11 -8
  114. modal/snapshot.pyi +25 -4
  115. modal/token_flow.py +4 -4
  116. modal/token_flow.pyi +28 -8
  117. modal/volume.py +416 -158
  118. modal/volume.pyi +1117 -121
  119. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/METADATA +10 -9
  120. modal-1.2.3.dev7.dist-info/RECORD +195 -0
  121. modal_docs/mdmd/mdmd.py +17 -4
  122. modal_proto/api.proto +534 -79
  123. modal_proto/api_grpc.py +337 -1
  124. modal_proto/api_pb2.py +1522 -968
  125. modal_proto/api_pb2.pyi +1619 -134
  126. modal_proto/api_pb2_grpc.py +699 -4
  127. modal_proto/api_pb2_grpc.pyi +226 -14
  128. modal_proto/modal_api_grpc.py +175 -154
  129. modal_proto/sandbox_router.proto +145 -0
  130. modal_proto/sandbox_router_grpc.py +105 -0
  131. modal_proto/sandbox_router_pb2.py +149 -0
  132. modal_proto/sandbox_router_pb2.pyi +333 -0
  133. modal_proto/sandbox_router_pb2_grpc.py +203 -0
  134. modal_proto/sandbox_router_pb2_grpc.pyi +75 -0
  135. modal_proto/task_command_router.proto +144 -0
  136. modal_proto/task_command_router_grpc.py +105 -0
  137. modal_proto/task_command_router_pb2.py +149 -0
  138. modal_proto/task_command_router_pb2.pyi +333 -0
  139. modal_proto/task_command_router_pb2_grpc.py +203 -0
  140. modal_proto/task_command_router_pb2_grpc.pyi +75 -0
  141. modal_version/__init__.py +1 -1
  142. modal/requirements/PREVIEW.txt +0 -16
  143. modal/requirements/base-images.json +0 -26
  144. modal-1.0.3.dev10.dist-info/RECORD +0 -179
  145. modal_proto/modal_options_grpc.py +0 -3
  146. modal_proto/options.proto +0 -19
  147. modal_proto/options_grpc.py +0 -3
  148. modal_proto/options_pb2.py +0 -35
  149. modal_proto/options_pb2.pyi +0 -20
  150. modal_proto/options_pb2_grpc.py +0 -4
  151. modal_proto/options_pb2_grpc.pyi +0 -7
  152. /modal/{requirements → builder}/2023.12.312.txt +0 -0
  153. /modal/{requirements → builder}/2023.12.txt +0 -0
  154. /modal/{requirements → builder}/2024.04.txt +0 -0
  155. /modal/{requirements → builder}/2024.10.txt +0 -0
  156. /modal/{requirements → builder}/README.md +0 -0
  157. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/WHEEL +0 -0
  158. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/entry_points.txt +0 -0
  159. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/licenses/LICENSE +0 -0
  160. {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
- def __call__(self, _: pathlib.Path) -> bool: ...
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(version: typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"]) -> str: ...
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
- def _validate_packages(packages: list[str]) -> bool: ...
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
- context_mount: typing.Optional[modal.mount._Mount] = None,
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
- def __init__(self, registry_auth_type: int = 0, secret: typing.Optional[modal.secret._Secret] = None): ...
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
- def __repr__(self): ...
83
- def __eq__(self, other): ...
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[[typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"]], DockerfileSpec]
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.Sequence[modal.secret._Secret]] = None,
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
- def copy_local_file(
137
- self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "./"
138
- ) -> _Image: ...
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
- def copy_local_dir(
148
- self,
149
- local_path: typing.Union[str, pathlib.Path],
150
- remote_path: typing.Union[str, pathlib.Path] = ".",
151
- ignore: typing.Union[collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]] = [],
152
- ) -> _Image: ...
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
- secrets: collections.abc.Sequence[modal.secret._Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret._Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret._Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret._Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret._Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret._Secret] = [],
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
- context_mount: typing.Optional[modal.mount._Mount] = None,
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
- def entrypoint(self, entrypoint_commands: list[str]) -> _Image: ...
235
- def shell(self, shell_commands: list[str]) -> _Image: ...
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
- secrets: collections.abc.Sequence[modal.secret._Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret._Secret] = [],
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, str, None] = None,
298
- secrets: collections.abc.Sequence[modal.secret._Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret._Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret._Secret] = (),
319
- gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
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: typing.Optional[int] = 3600,
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: typing.Optional[bool] = None,
336
- ) -> _Image: ...
337
- def env(self, vars: dict[str, str]) -> _Image: ...
338
- def workdir(self, path: typing.Union[str, pathlib.PurePosixPath]) -> _Image: ...
339
- def cmd(self, cmd: list[str]) -> _Image: ...
340
- def imports(self): ...
341
- def _logs(self) -> typing.AsyncGenerator[str, None]: ...
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[[typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"]], DockerfileSpec]
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.Sequence[modal.secret.Secret]] = None,
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
- def copy_local_file(
394
- self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "./"
395
- ) -> Image: ...
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
- def copy_local_dir(
405
- self,
406
- local_path: typing.Union[str, pathlib.Path],
407
- remote_path: typing.Union[str, pathlib.Path] = ".",
408
- ignore: typing.Union[collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]] = [],
409
- ) -> Image: ...
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
- async def aio(self, /, image_id: str, client: typing.Optional[modal.client.Client] = None) -> Image: ...
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
- secrets: collections.abc.Sequence[modal.secret.Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret.Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret.Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret.Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret.Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret.Secret] = [],
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
- context_mount: typing.Optional[modal.mount.Mount] = None,
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
- def entrypoint(self, entrypoint_commands: list[str]) -> Image: ...
497
- def shell(self, shell_commands: list[str]) -> Image: ...
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
- secrets: collections.abc.Sequence[modal.secret.Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret.Secret] = [],
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, str, None] = None,
560
- secrets: collections.abc.Sequence[modal.secret.Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret.Secret] = [],
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
- secrets: collections.abc.Sequence[modal.secret.Secret] = (),
581
- gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
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: typing.Optional[int] = 3600,
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: typing.Optional[bool] = None,
598
- ) -> Image: ...
599
- def env(self, vars: dict[str, str]) -> Image: ...
600
- def workdir(self, path: typing.Union[str, pathlib.PurePosixPath]) -> Image: ...
601
- def cmd(self, cmd: list[str]) -> Image: ...
602
- def imports(self): ...
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
- def aio(self, /) -> typing.AsyncGenerator[str, None]: ...
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
- SUPPORTED_PYTHON_SERIES: dict[typing.Literal["2023.12", "2024.04", "2024.10", "PREVIEW"], list[str]]
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]]