modal 1.0.6.dev58__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 (147) hide show
  1. modal/__main__.py +3 -4
  2. modal/_billing.py +80 -0
  3. modal/_clustered_functions.py +7 -3
  4. modal/_clustered_functions.pyi +4 -2
  5. modal/_container_entrypoint.py +41 -49
  6. modal/_functions.py +424 -195
  7. modal/_grpc_client.py +171 -0
  8. modal/_load_context.py +105 -0
  9. modal/_object.py +68 -20
  10. modal/_output.py +58 -45
  11. modal/_partial_function.py +36 -11
  12. modal/_pty.py +7 -3
  13. modal/_resolver.py +21 -35
  14. modal/_runtime/asgi.py +4 -3
  15. modal/_runtime/container_io_manager.py +301 -186
  16. modal/_runtime/container_io_manager.pyi +70 -61
  17. modal/_runtime/execution_context.py +18 -2
  18. modal/_runtime/execution_context.pyi +4 -1
  19. modal/_runtime/gpu_memory_snapshot.py +170 -63
  20. modal/_runtime/user_code_imports.py +28 -58
  21. modal/_serialization.py +57 -1
  22. modal/_utils/async_utils.py +33 -12
  23. modal/_utils/auth_token_manager.py +2 -5
  24. modal/_utils/blob_utils.py +110 -53
  25. modal/_utils/function_utils.py +49 -42
  26. modal/_utils/grpc_utils.py +80 -50
  27. modal/_utils/mount_utils.py +26 -1
  28. modal/_utils/name_utils.py +17 -3
  29. modal/_utils/task_command_router_client.py +536 -0
  30. modal/_utils/time_utils.py +34 -6
  31. modal/app.py +219 -83
  32. modal/app.pyi +229 -56
  33. modal/billing.py +5 -0
  34. modal/{requirements → builder}/2025.06.txt +1 -0
  35. modal/{requirements → builder}/PREVIEW.txt +1 -0
  36. modal/cli/_download.py +19 -3
  37. modal/cli/_traceback.py +3 -2
  38. modal/cli/app.py +4 -4
  39. modal/cli/cluster.py +15 -7
  40. modal/cli/config.py +5 -3
  41. modal/cli/container.py +7 -6
  42. modal/cli/dict.py +22 -16
  43. modal/cli/entry_point.py +12 -5
  44. modal/cli/environment.py +5 -4
  45. modal/cli/import_refs.py +3 -3
  46. modal/cli/launch.py +102 -5
  47. modal/cli/network_file_system.py +9 -13
  48. modal/cli/profile.py +3 -2
  49. modal/cli/programs/launch_instance_ssh.py +94 -0
  50. modal/cli/programs/run_jupyter.py +1 -1
  51. modal/cli/programs/run_marimo.py +95 -0
  52. modal/cli/programs/vscode.py +1 -1
  53. modal/cli/queues.py +57 -26
  54. modal/cli/run.py +58 -16
  55. modal/cli/secret.py +48 -22
  56. modal/cli/utils.py +3 -4
  57. modal/cli/volume.py +28 -25
  58. modal/client.py +13 -116
  59. modal/client.pyi +9 -91
  60. modal/cloud_bucket_mount.py +5 -3
  61. modal/cloud_bucket_mount.pyi +5 -1
  62. modal/cls.py +130 -102
  63. modal/cls.pyi +45 -85
  64. modal/config.py +29 -10
  65. modal/container_process.py +291 -13
  66. modal/container_process.pyi +95 -32
  67. modal/dict.py +282 -63
  68. modal/dict.pyi +423 -73
  69. modal/environments.py +15 -27
  70. modal/environments.pyi +5 -15
  71. modal/exception.py +8 -0
  72. modal/experimental/__init__.py +143 -38
  73. modal/experimental/flash.py +247 -78
  74. modal/experimental/flash.pyi +137 -9
  75. modal/file_io.py +14 -28
  76. modal/file_io.pyi +2 -2
  77. modal/file_pattern_matcher.py +25 -16
  78. modal/functions.pyi +134 -61
  79. modal/image.py +255 -86
  80. modal/image.pyi +300 -62
  81. modal/io_streams.py +436 -126
  82. modal/io_streams.pyi +236 -171
  83. modal/mount.py +62 -157
  84. modal/mount.pyi +45 -172
  85. modal/network_file_system.py +30 -53
  86. modal/network_file_system.pyi +16 -76
  87. modal/object.pyi +42 -8
  88. modal/parallel_map.py +821 -113
  89. modal/parallel_map.pyi +134 -0
  90. modal/partial_function.pyi +4 -1
  91. modal/proxy.py +16 -7
  92. modal/proxy.pyi +10 -2
  93. modal/queue.py +263 -61
  94. modal/queue.pyi +409 -66
  95. modal/runner.py +112 -92
  96. modal/runner.pyi +45 -27
  97. modal/sandbox.py +451 -124
  98. modal/sandbox.pyi +513 -67
  99. modal/secret.py +291 -67
  100. modal/secret.pyi +425 -19
  101. modal/serving.py +7 -11
  102. modal/serving.pyi +7 -8
  103. modal/snapshot.py +11 -8
  104. modal/token_flow.py +4 -4
  105. modal/volume.py +344 -98
  106. modal/volume.pyi +464 -68
  107. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/METADATA +9 -8
  108. modal-1.2.3.dev7.dist-info/RECORD +195 -0
  109. modal_docs/mdmd/mdmd.py +11 -1
  110. modal_proto/api.proto +399 -67
  111. modal_proto/api_grpc.py +241 -1
  112. modal_proto/api_pb2.py +1395 -1000
  113. modal_proto/api_pb2.pyi +1239 -79
  114. modal_proto/api_pb2_grpc.py +499 -4
  115. modal_proto/api_pb2_grpc.pyi +162 -14
  116. modal_proto/modal_api_grpc.py +175 -160
  117. modal_proto/sandbox_router.proto +145 -0
  118. modal_proto/sandbox_router_grpc.py +105 -0
  119. modal_proto/sandbox_router_pb2.py +149 -0
  120. modal_proto/sandbox_router_pb2.pyi +333 -0
  121. modal_proto/sandbox_router_pb2_grpc.py +203 -0
  122. modal_proto/sandbox_router_pb2_grpc.pyi +75 -0
  123. modal_proto/task_command_router.proto +144 -0
  124. modal_proto/task_command_router_grpc.py +105 -0
  125. modal_proto/task_command_router_pb2.py +149 -0
  126. modal_proto/task_command_router_pb2.pyi +333 -0
  127. modal_proto/task_command_router_pb2_grpc.py +203 -0
  128. modal_proto/task_command_router_pb2_grpc.pyi +75 -0
  129. modal_version/__init__.py +1 -1
  130. modal-1.0.6.dev58.dist-info/RECORD +0 -183
  131. modal_proto/modal_options_grpc.py +0 -3
  132. modal_proto/options.proto +0 -19
  133. modal_proto/options_grpc.py +0 -3
  134. modal_proto/options_pb2.py +0 -35
  135. modal_proto/options_pb2.pyi +0 -20
  136. modal_proto/options_pb2_grpc.py +0 -4
  137. modal_proto/options_pb2_grpc.pyi +0 -7
  138. /modal/{requirements → builder}/2023.12.312.txt +0 -0
  139. /modal/{requirements → builder}/2023.12.txt +0 -0
  140. /modal/{requirements → builder}/2024.04.txt +0 -0
  141. /modal/{requirements → builder}/2024.10.txt +0 -0
  142. /modal/{requirements → builder}/README.md +0 -0
  143. /modal/{requirements → builder}/base-images.json +0 -0
  144. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/WHEEL +0 -0
  145. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/entry_points.txt +0 -0
  146. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/licenses/LICENSE +0 -0
  147. {modal-1.0.6.dev58.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
@@ -90,8 +91,7 @@ def _create_context_mount_function(
90
91
  ],
91
92
  dockerfile_cmds: list[str] = [],
92
93
  dockerfile_path: typing.Optional[pathlib.Path] = None,
93
- context_mount: typing.Optional[modal.mount._Mount] = None,
94
- context_dir: typing.Union[pathlib.Path, str, None] = None,
94
+ context_dir: typing.Union[str, pathlib.Path, None] = None,
95
95
  ): ...
96
96
 
97
97
  class _ImageRegistryConfig:
@@ -166,7 +166,7 @@ class _Image(modal._object._Object):
166
166
  [typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"]], DockerfileSpec
167
167
  ]
168
168
  ] = None,
169
- secrets: typing.Optional[collections.abc.Sequence[modal.secret._Secret]] = None,
169
+ secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
170
170
  gpu_config: typing.Optional[modal_proto.api_pb2.GPUConfig] = None,
171
171
  build_function: typing.Optional[modal._functions._Function] = None,
172
172
  build_function_input: typing.Optional[modal_proto.api_pb2.FunctionInput] = None,
@@ -176,6 +176,7 @@ class _Image(modal._object._Object):
176
176
  ] = None,
177
177
  force_build: bool = False,
178
178
  build_args: dict[str, str] = {},
179
+ validated_volumes: typing.Optional[collections.abc.Sequence[tuple[str, modal.volume._Volume]]] = None,
179
180
  _namespace: int = 1,
180
181
  _do_assert_no_mount_layers: bool = True,
181
182
  ): ...
@@ -318,6 +319,59 @@ class _Image(modal._object._Object):
318
319
  """
319
320
  ...
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
+
321
375
  def pip_install(
322
376
  self,
323
377
  *packages: typing.Union[str, list[str]],
@@ -327,7 +381,8 @@ class _Image(modal._object._Object):
327
381
  pre: bool = False,
328
382
  extra_options: str = "",
329
383
  force_build: bool = False,
330
- 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,
331
386
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
332
387
  ) -> _Image:
333
388
  """Install a list of Python packages using pip.
@@ -369,7 +424,8 @@ class _Image(modal._object._Object):
369
424
  pre: bool = False,
370
425
  extra_options: str = "",
371
426
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
372
- 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,
373
429
  force_build: bool = False,
374
430
  ) -> _Image:
375
431
  """Install a list of Python packages from private git repositories using pip.
@@ -414,7 +470,8 @@ class _Image(modal._object._Object):
414
470
  pre: bool = False,
415
471
  extra_options: str = "",
416
472
  force_build: bool = False,
417
- 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,
418
475
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
419
476
  ) -> _Image:
420
477
  """Install a list of Python packages from a local `requirements.txt` file."""
@@ -431,7 +488,8 @@ class _Image(modal._object._Object):
431
488
  pre: bool = False,
432
489
  extra_options: str = "",
433
490
  force_build: bool = False,
434
- 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,
435
493
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
436
494
  ) -> _Image:
437
495
  """Install dependencies specified by a local `pyproject.toml` file.
@@ -454,7 +512,8 @@ class _Image(modal._object._Object):
454
512
  extra_options: str = "",
455
513
  force_build: bool = False,
456
514
  uv_version: typing.Optional[str] = None,
457
- secrets: collections.abc.Sequence[modal.secret._Secret] = [],
515
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
516
+ secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
458
517
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
459
518
  ) -> _Image:
460
519
  """Install a list of Python packages using uv pip install.
@@ -470,6 +529,8 @@ class _Image(modal._object._Object):
470
529
  - Python is on the `$PATH` and dependencies are installed with the first Python on the `$PATH`.
471
530
  - Shell supports backticks for substitution
472
531
  - `which` command is on the `$PATH`
532
+
533
+ Added in v1.1.0.
473
534
  """
474
535
  ...
475
536
 
@@ -485,7 +546,8 @@ class _Image(modal._object._Object):
485
546
  only: list[str] = [],
486
547
  poetry_version: typing.Optional[str] = "latest",
487
548
  old_installer: bool = False,
488
- secrets: collections.abc.Sequence[modal.secret._Secret] = [],
549
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
550
+ secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
489
551
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
490
552
  ) -> _Image:
491
553
  """Install poetry *dependencies* specified by a local `pyproject.toml` file.
@@ -512,7 +574,8 @@ class _Image(modal._object._Object):
512
574
  frozen: bool = True,
513
575
  extra_options: str = "",
514
576
  uv_version: typing.Optional[str] = None,
515
- secrets: collections.abc.Sequence[modal.secret._Secret] = [],
577
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
578
+ secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
516
579
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
517
580
  ) -> _Image:
518
581
  """Creates a virtual environment with the dependencies in a uv managed project with `uv sync`.
@@ -521,6 +584,20 @@ class _Image(modal._object._Object):
521
584
  ```python
522
585
  image = modal.Image.debian_slim().uv_sync()
523
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.
524
601
  """
525
602
  ...
526
603
 
@@ -528,10 +605,10 @@ class _Image(modal._object._Object):
528
605
  self,
529
606
  *dockerfile_commands: typing.Union[str, list[str]],
530
607
  context_files: dict[str, str] = {},
531
- 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,
532
610
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
533
- context_mount: typing.Optional[modal.mount._Mount] = None,
534
- context_dir: typing.Union[pathlib.Path, str, None] = None,
611
+ context_dir: typing.Union[str, pathlib.Path, None] = None,
535
612
  force_build: bool = False,
536
613
  ignore: typing.Union[
537
614
  collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
@@ -580,7 +657,7 @@ class _Image(modal._object._Object):
580
657
  ...
581
658
 
582
659
  def entrypoint(self, entrypoint_commands: list[str]) -> _Image:
583
- """Set the entrypoint for the image."""
660
+ """Set the ENTRYPOINT for the image."""
584
661
  ...
585
662
 
586
663
  def shell(self, shell_commands: list[str]) -> _Image:
@@ -590,7 +667,9 @@ class _Image(modal._object._Object):
590
667
  def run_commands(
591
668
  self,
592
669
  *commands: typing.Union[str, list[str]],
593
- 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,
594
673
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
595
674
  force_build: bool = False,
596
675
  ) -> _Image:
@@ -608,7 +687,8 @@ class _Image(modal._object._Object):
608
687
  spec_file: typing.Optional[str] = None,
609
688
  channels: list[str] = [],
610
689
  force_build: bool = False,
611
- 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,
612
692
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
613
693
  ) -> _Image:
614
694
  """Install a list of additional packages using micromamba."""
@@ -713,12 +793,18 @@ class _Image(modal._object._Object):
713
793
  ) -> _Image:
714
794
  """Build a Modal image from a private image in AWS Elastic Container Registry (ECR).
715
795
 
716
- You will need to pass a `modal.Secret` containing `AWS_ACCESS_KEY_ID`,
717
- `AWS_SECRET_ACCESS_KEY`, and `AWS_REGION` to access the target ECR registry.
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`.
718
802
 
719
803
  IAM configuration details can be found in the AWS documentation for
720
804
  ["Private repository policies"](https://docs.aws.amazon.com/AmazonECR/latest/userguide/repository-policies.html).
721
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
+
722
808
  See `Image.from_registry()` for information about the other parameters.
723
809
 
724
810
  **Example**
@@ -740,10 +826,10 @@ class _Image(modal._object._Object):
740
826
  def from_dockerfile(
741
827
  path: typing.Union[str, pathlib.Path],
742
828
  *,
743
- context_mount: typing.Optional[modal.mount._Mount] = None,
744
829
  force_build: bool = False,
745
- context_dir: typing.Union[pathlib.Path, str, None] = None,
746
- 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,
747
833
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
748
834
  add_python: typing.Optional[str] = None,
749
835
  build_args: dict[str, str] = {},
@@ -811,7 +897,8 @@ class _Image(modal._object._Object):
811
897
  self,
812
898
  *packages: typing.Union[str, list[str]],
813
899
  force_build: bool = False,
814
- 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,
815
902
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
816
903
  ) -> _Image:
817
904
  """Install a list of Debian packages using `apt`.
@@ -828,8 +915,8 @@ class _Image(modal._object._Object):
828
915
  self,
829
916
  raw_f: collections.abc.Callable[..., typing.Any],
830
917
  *,
831
- secrets: collections.abc.Sequence[modal.secret._Secret] = (),
832
- 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,
833
920
  volumes: dict[
834
921
  typing.Union[str, pathlib.PurePosixPath],
835
922
  typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
@@ -837,20 +924,22 @@ class _Image(modal._object._Object):
837
924
  network_file_systems: dict[
838
925
  typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem
839
926
  ] = {},
927
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
840
928
  cpu: typing.Optional[float] = None,
841
929
  memory: typing.Optional[int] = None,
842
- timeout: typing.Optional[int] = 3600,
843
- force_build: bool = False,
930
+ timeout: int = 3600,
844
931
  cloud: typing.Optional[str] = None,
845
932
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
933
+ force_build: bool = False,
846
934
  args: collections.abc.Sequence[typing.Any] = (),
847
935
  kwargs: dict[str, typing.Any] = {},
848
- include_source: typing.Optional[bool] = None,
936
+ include_source: bool = True,
849
937
  ) -> _Image:
850
- """Run user-defined function `raw_f` as an image build step. The function runs just like an ordinary Modal
851
- function, and any kwargs accepted by `@app.function` (such as `Mount`s, `NetworkFileSystem`s,
852
- and resource requests) can be supplied to it.
853
- After it finishes execution, a snapshot of the resulting container file system is saved as an 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.
854
943
 
855
944
  **Note**
856
945
 
@@ -907,7 +996,9 @@ class _Image(modal._object._Object):
907
996
  ...
908
997
 
909
998
  def cmd(self, cmd: list[str]) -> _Image:
910
- """Set the default entrypoint argument (`CMD`) for the 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`.
911
1002
 
912
1003
  **Example**
913
1004
 
@@ -992,7 +1083,7 @@ class Image(modal.object.Object):
992
1083
  [typing.Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVIEW"]], DockerfileSpec
993
1084
  ]
994
1085
  ] = None,
995
- secrets: typing.Optional[collections.abc.Sequence[modal.secret.Secret]] = None,
1086
+ secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
996
1087
  gpu_config: typing.Optional[modal_proto.api_pb2.GPUConfig] = None,
997
1088
  build_function: typing.Optional[modal.functions.Function] = None,
998
1089
  build_function_input: typing.Optional[modal_proto.api_pb2.FunctionInput] = None,
@@ -1002,6 +1093,7 @@ class Image(modal.object.Object):
1002
1093
  ] = None,
1003
1094
  force_build: bool = False,
1004
1095
  build_args: dict[str, str] = {},
1096
+ validated_volumes: typing.Optional[collections.abc.Sequence[tuple[str, modal.volume.Volume]]] = None,
1005
1097
  _namespace: int = 1,
1006
1098
  _do_assert_no_mount_layers: bool = True,
1007
1099
  ): ...
@@ -1153,6 +1245,115 @@ class Image(modal.object.Object):
1153
1245
 
1154
1246
  from_id: __from_id_spec
1155
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
+
1156
1357
  def pip_install(
1157
1358
  self,
1158
1359
  *packages: typing.Union[str, list[str]],
@@ -1162,7 +1363,8 @@ class Image(modal.object.Object):
1162
1363
  pre: bool = False,
1163
1364
  extra_options: str = "",
1164
1365
  force_build: bool = False,
1165
- 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,
1166
1368
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
1167
1369
  ) -> Image:
1168
1370
  """Install a list of Python packages using pip.
@@ -1204,7 +1406,8 @@ class Image(modal.object.Object):
1204
1406
  pre: bool = False,
1205
1407
  extra_options: str = "",
1206
1408
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
1207
- 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,
1208
1411
  force_build: bool = False,
1209
1412
  ) -> Image:
1210
1413
  """Install a list of Python packages from private git repositories using pip.
@@ -1249,7 +1452,8 @@ class Image(modal.object.Object):
1249
1452
  pre: bool = False,
1250
1453
  extra_options: str = "",
1251
1454
  force_build: bool = False,
1252
- 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,
1253
1457
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
1254
1458
  ) -> Image:
1255
1459
  """Install a list of Python packages from a local `requirements.txt` file."""
@@ -1266,7 +1470,8 @@ class Image(modal.object.Object):
1266
1470
  pre: bool = False,
1267
1471
  extra_options: str = "",
1268
1472
  force_build: bool = False,
1269
- 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,
1270
1475
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
1271
1476
  ) -> Image:
1272
1477
  """Install dependencies specified by a local `pyproject.toml` file.
@@ -1289,7 +1494,8 @@ class Image(modal.object.Object):
1289
1494
  extra_options: str = "",
1290
1495
  force_build: bool = False,
1291
1496
  uv_version: typing.Optional[str] = None,
1292
- secrets: collections.abc.Sequence[modal.secret.Secret] = [],
1497
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
1498
+ secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
1293
1499
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
1294
1500
  ) -> Image:
1295
1501
  """Install a list of Python packages using uv pip install.
@@ -1305,6 +1511,8 @@ class Image(modal.object.Object):
1305
1511
  - Python is on the `$PATH` and dependencies are installed with the first Python on the `$PATH`.
1306
1512
  - Shell supports backticks for substitution
1307
1513
  - `which` command is on the `$PATH`
1514
+
1515
+ Added in v1.1.0.
1308
1516
  """
1309
1517
  ...
1310
1518
 
@@ -1320,7 +1528,8 @@ class Image(modal.object.Object):
1320
1528
  only: list[str] = [],
1321
1529
  poetry_version: typing.Optional[str] = "latest",
1322
1530
  old_installer: bool = False,
1323
- secrets: collections.abc.Sequence[modal.secret.Secret] = [],
1531
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
1532
+ secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
1324
1533
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
1325
1534
  ) -> Image:
1326
1535
  """Install poetry *dependencies* specified by a local `pyproject.toml` file.
@@ -1347,7 +1556,8 @@ class Image(modal.object.Object):
1347
1556
  frozen: bool = True,
1348
1557
  extra_options: str = "",
1349
1558
  uv_version: typing.Optional[str] = None,
1350
- secrets: collections.abc.Sequence[modal.secret.Secret] = [],
1559
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
1560
+ secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
1351
1561
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
1352
1562
  ) -> Image:
1353
1563
  """Creates a virtual environment with the dependencies in a uv managed project with `uv sync`.
@@ -1356,6 +1566,20 @@ class Image(modal.object.Object):
1356
1566
  ```python
1357
1567
  image = modal.Image.debian_slim().uv_sync()
1358
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.
1359
1583
  """
1360
1584
  ...
1361
1585
 
@@ -1363,10 +1587,10 @@ class Image(modal.object.Object):
1363
1587
  self,
1364
1588
  *dockerfile_commands: typing.Union[str, list[str]],
1365
1589
  context_files: dict[str, str] = {},
1366
- 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,
1367
1592
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
1368
- context_mount: typing.Optional[modal.mount.Mount] = None,
1369
- context_dir: typing.Union[pathlib.Path, str, None] = None,
1593
+ context_dir: typing.Union[str, pathlib.Path, None] = None,
1370
1594
  force_build: bool = False,
1371
1595
  ignore: typing.Union[
1372
1596
  collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
@@ -1415,7 +1639,7 @@ class Image(modal.object.Object):
1415
1639
  ...
1416
1640
 
1417
1641
  def entrypoint(self, entrypoint_commands: list[str]) -> Image:
1418
- """Set the entrypoint for the image."""
1642
+ """Set the ENTRYPOINT for the image."""
1419
1643
  ...
1420
1644
 
1421
1645
  def shell(self, shell_commands: list[str]) -> Image:
@@ -1425,7 +1649,9 @@ class Image(modal.object.Object):
1425
1649
  def run_commands(
1426
1650
  self,
1427
1651
  *commands: typing.Union[str, list[str]],
1428
- 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,
1429
1655
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
1430
1656
  force_build: bool = False,
1431
1657
  ) -> Image:
@@ -1443,7 +1669,8 @@ class Image(modal.object.Object):
1443
1669
  spec_file: typing.Optional[str] = None,
1444
1670
  channels: list[str] = [],
1445
1671
  force_build: bool = False,
1446
- 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,
1447
1674
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
1448
1675
  ) -> Image:
1449
1676
  """Install a list of additional packages using micromamba."""
@@ -1548,12 +1775,18 @@ class Image(modal.object.Object):
1548
1775
  ) -> Image:
1549
1776
  """Build a Modal image from a private image in AWS Elastic Container Registry (ECR).
1550
1777
 
1551
- You will need to pass a `modal.Secret` containing `AWS_ACCESS_KEY_ID`,
1552
- `AWS_SECRET_ACCESS_KEY`, and `AWS_REGION` to access the target ECR registry.
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`.
1553
1784
 
1554
1785
  IAM configuration details can be found in the AWS documentation for
1555
1786
  ["Private repository policies"](https://docs.aws.amazon.com/AmazonECR/latest/userguide/repository-policies.html).
1556
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
+
1557
1790
  See `Image.from_registry()` for information about the other parameters.
1558
1791
 
1559
1792
  **Example**
@@ -1575,10 +1808,10 @@ class Image(modal.object.Object):
1575
1808
  def from_dockerfile(
1576
1809
  path: typing.Union[str, pathlib.Path],
1577
1810
  *,
1578
- context_mount: typing.Optional[modal.mount.Mount] = None,
1579
1811
  force_build: bool = False,
1580
- context_dir: typing.Union[pathlib.Path, str, None] = None,
1581
- 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,
1582
1815
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
1583
1816
  add_python: typing.Optional[str] = None,
1584
1817
  build_args: dict[str, str] = {},
@@ -1646,7 +1879,8 @@ class Image(modal.object.Object):
1646
1879
  self,
1647
1880
  *packages: typing.Union[str, list[str]],
1648
1881
  force_build: bool = False,
1649
- 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,
1650
1884
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
1651
1885
  ) -> Image:
1652
1886
  """Install a list of Debian packages using `apt`.
@@ -1663,8 +1897,8 @@ class Image(modal.object.Object):
1663
1897
  self,
1664
1898
  raw_f: collections.abc.Callable[..., typing.Any],
1665
1899
  *,
1666
- secrets: collections.abc.Sequence[modal.secret.Secret] = (),
1667
- 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,
1668
1902
  volumes: dict[
1669
1903
  typing.Union[str, pathlib.PurePosixPath],
1670
1904
  typing.Union[modal.volume.Volume, modal.cloud_bucket_mount.CloudBucketMount],
@@ -1672,20 +1906,22 @@ class Image(modal.object.Object):
1672
1906
  network_file_systems: dict[
1673
1907
  typing.Union[str, pathlib.PurePosixPath], modal.network_file_system.NetworkFileSystem
1674
1908
  ] = {},
1909
+ gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
1675
1910
  cpu: typing.Optional[float] = None,
1676
1911
  memory: typing.Optional[int] = None,
1677
- timeout: typing.Optional[int] = 3600,
1678
- force_build: bool = False,
1912
+ timeout: int = 3600,
1679
1913
  cloud: typing.Optional[str] = None,
1680
1914
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
1915
+ force_build: bool = False,
1681
1916
  args: collections.abc.Sequence[typing.Any] = (),
1682
1917
  kwargs: dict[str, typing.Any] = {},
1683
- include_source: typing.Optional[bool] = None,
1918
+ include_source: bool = True,
1684
1919
  ) -> Image:
1685
- """Run user-defined function `raw_f` as an image build step. The function runs just like an ordinary Modal
1686
- function, and any kwargs accepted by `@app.function` (such as `Mount`s, `NetworkFileSystem`s,
1687
- and resource requests) can be supplied to it.
1688
- After it finishes execution, a snapshot of the resulting container file system is saved as an 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.
1689
1925
 
1690
1926
  **Note**
1691
1927
 
@@ -1742,7 +1978,9 @@ class Image(modal.object.Object):
1742
1978
  ...
1743
1979
 
1744
1980
  def cmd(self, cmd: list[str]) -> Image:
1745
- """Set the default entrypoint argument (`CMD`) for the 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`.
1746
1984
 
1747
1985
  **Example**
1748
1986