modal 1.0.4.dev12__py3-none-any.whl → 1.0.5__py3-none-any.whl

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