modal 0.73.143__py3-none-any.whl → 0.73.145__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
modal/client.pyi CHANGED
@@ -31,7 +31,7 @@ class _Client:
31
31
  server_url: str,
32
32
  client_type: int,
33
33
  credentials: typing.Optional[tuple[str, str]],
34
- version: str = "0.73.143",
34
+ version: str = "0.73.145",
35
35
  ): ...
36
36
  def is_closed(self) -> bool: ...
37
37
  @property
@@ -93,7 +93,7 @@ class Client:
93
93
  server_url: str,
94
94
  client_type: int,
95
95
  credentials: typing.Optional[tuple[str, str]],
96
- version: str = "0.73.143",
96
+ version: str = "0.73.145",
97
97
  ): ...
98
98
  def is_closed(self) -> bool: ...
99
99
  @property
@@ -1,6 +1,8 @@
1
1
  # Copyright Modal Labs 2025
2
+ import os
2
3
  from dataclasses import dataclass
3
- from typing import Any, Callable, Optional
4
+ from pathlib import Path
5
+ from typing import Any, Callable, Literal, Optional, Union
4
6
 
5
7
  from modal_proto import api_pb2
6
8
 
@@ -12,6 +14,8 @@ from .._runtime.container_io_manager import _ContainerIOManager
12
14
  from .._utils.async_utils import synchronizer
13
15
  from ..client import _Client
14
16
  from ..exception import InvalidError
17
+ from ..image import DockerfileSpec, ImageBuilderVersion, _Image, _ImageRegistryConfig
18
+ from ..secret import _Secret
15
19
 
16
20
 
17
21
  def stop_fetching_inputs():
@@ -103,3 +107,82 @@ async def list_deployed_apps(environment_name: str = "", client: Optional[_Clien
103
107
  )
104
108
  )
105
109
  return app_infos
110
+
111
+
112
+ @synchronizer.create_blocking
113
+ async def raw_dockerfile_image(
114
+ path: Union[str, Path],
115
+ force_build: bool = False,
116
+ ) -> _Image:
117
+ """
118
+ Build a Modal Image from a local Dockerfile recipe without any changes.
119
+
120
+ Unlike for `modal.Image.from_dockerfile`, the provided recipe will not be embellished with
121
+ steps to install dependencies for the Modal client package. As a consequence, the resulting
122
+ Image cannot be used with a modal Function unless those dependencies are added in a subsequent
123
+ layer. It _can_ be directly used with a modal Sandbox, which does not need the Modal client.
124
+
125
+ We expect to support this experimental function until the `2025.04` Modal Image Builder is
126
+ stable, at which point Modal Image recipes will no longer install the client dependencies
127
+ by default. At that point, users can upgrade their Image Builder Version and migrate to
128
+ `modal.Image.from_dockerfile` for usecases supported by this function.
129
+
130
+ """
131
+
132
+ def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
133
+ with open(os.path.expanduser(path)) as f:
134
+ commands = f.read().split("\n")
135
+ return DockerfileSpec(commands=commands, context_files={})
136
+
137
+ return _Image._from_args(
138
+ dockerfile_function=build_dockerfile,
139
+ force_build=force_build,
140
+ )
141
+
142
+
143
+ @synchronizer.create_blocking
144
+ async def raw_registry_image(
145
+ tag: str,
146
+ registry_secret: Optional[_Secret] = None,
147
+ credential_type: Literal["static", "aws", "gcp", None] = None,
148
+ force_build: bool = False,
149
+ ) -> _Image:
150
+ """
151
+ Build a Modal Image from a public or private image registry without any changes.
152
+
153
+ Unlike for `modal.Image.from_registry`, the provided recipe will not be embellished with
154
+ steps to install dependencies for the Modal client package. As a consequence, the resulting
155
+ Image cannot be used with a modal Function unless those dependencies are added in a subsequent
156
+ layer. It _can_ be directly used with a modal Sandbox, which does not need the Modal client.
157
+
158
+ We expect to support this experimental function until the `2025.04` Modal Image Builder is
159
+ stable, at which point Modal Image recipes will no longer install the client dependencies
160
+ by default. At that point, users can upgrade their Image Builder Version and migrate to
161
+ `modal.Image.from_registry` for usecases supported by this function.
162
+
163
+ """
164
+
165
+ def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
166
+ commands = [f"FROM {tag}"]
167
+ return DockerfileSpec(commands=commands, context_files={})
168
+
169
+ if registry_secret:
170
+ if credential_type is None:
171
+ raise InvalidError("credential_type must be provided when using a registry_secret")
172
+ elif credential_type == "static":
173
+ auth_type = api_pb2.REGISTRY_AUTH_TYPE_STATIC_CREDS
174
+ elif credential_type == "aws":
175
+ auth_type = api_pb2.REGISTRY_AUTH_TYPE_AWS
176
+ elif credential_type == "gcp":
177
+ auth_type = api_pb2.REGISTRY_AUTH_TYPE_GCP
178
+ else:
179
+ raise InvalidError(f"Invalid credential_type: {credential_type!r}")
180
+ registry_config = _ImageRegistryConfig(auth_type, registry_secret)
181
+ else:
182
+ registry_config = None
183
+
184
+ return _Image._from_args(
185
+ dockerfile_function=build_dockerfile,
186
+ image_registry_config=registry_config,
187
+ force_build=force_build,
188
+ )
modal/image.py CHANGED
@@ -285,7 +285,7 @@ def _create_context_mount(
285
285
 
286
286
  return False
287
287
 
288
- return _Mount._add_local_dir(Path("./"), PurePosixPath("/"), ignore=ignore_with_include)
288
+ return _Mount._add_local_dir(context_dir, PurePosixPath("/"), ignore=ignore_with_include)
289
289
 
290
290
 
291
291
  def _create_context_mount_function(
@@ -293,6 +293,7 @@ def _create_context_mount_function(
293
293
  dockerfile_cmds: list[str] = [],
294
294
  dockerfile_path: Optional[Path] = None,
295
295
  context_mount: Optional[_Mount] = None,
296
+ context_dir: Optional[Union[Path, str]] = None,
296
297
  ):
297
298
  if dockerfile_path and dockerfile_cmds:
298
299
  raise InvalidError("Cannot provide both dockerfile and docker commands")
@@ -300,15 +301,19 @@ def _create_context_mount_function(
300
301
  if context_mount:
301
302
  if ignore is not AUTO_DOCKERIGNORE:
302
303
  raise InvalidError("Cannot set both `context_mount` and `ignore`")
304
+ if context_dir is not None:
305
+ raise InvalidError("Cannot set both `context_mount` and `context_dir`")
303
306
 
304
307
  def identity_context_mount_fn() -> Optional[_Mount]:
305
308
  return context_mount
306
309
 
307
310
  return identity_context_mount_fn
311
+
308
312
  elif ignore is AUTO_DOCKERIGNORE:
309
313
 
310
314
  def auto_created_context_mount_fn() -> Optional[_Mount]:
311
- context_dir = Path.cwd()
315
+ nonlocal context_dir
316
+ context_dir = Path.cwd() if context_dir is None else Path(context_dir).absolute()
312
317
  dockerignore_file = find_dockerignore_file(context_dir, dockerfile_path)
313
318
  ignore_fn = (
314
319
  FilePatternMatcher(*dockerignore_file.read_text("utf8").splitlines())
@@ -321,12 +326,16 @@ def _create_context_mount_function(
321
326
 
322
327
  return auto_created_context_mount_fn
323
328
 
324
- def auto_created_context_mount_fn() -> Optional[_Mount]:
325
- # use COPY commands and ignore patterns to construct implicit context mount
326
- cmds = dockerfile_path.read_text("utf8").splitlines() if dockerfile_path else dockerfile_cmds
327
- return _create_context_mount(cmds, ignore_fn=_ignore_fn(ignore), context_dir=Path.cwd())
329
+ else:
330
+
331
+ def auto_created_context_mount_fn() -> Optional[_Mount]:
332
+ # use COPY commands and ignore patterns to construct implicit context mount
333
+ nonlocal context_dir
334
+ context_dir = Path.cwd() if context_dir is None else Path(context_dir).absolute()
335
+ cmds = dockerfile_path.read_text("utf8").splitlines() if dockerfile_path else dockerfile_cmds
336
+ return _create_context_mount(cmds, ignore_fn=_ignore_fn(ignore), context_dir=context_dir)
328
337
 
329
- return auto_created_context_mount_fn
338
+ return auto_created_context_mount_fn
330
339
 
331
340
 
332
341
  class _ImageRegistryConfig:
@@ -1310,6 +1319,7 @@ class _Image(_Object, type_prefix="im"):
1310
1319
  secrets: Sequence[_Secret] = [],
1311
1320
  gpu: GPU_T = None,
1312
1321
  context_mount: Optional[_Mount] = None, # Deprecated: the context is now inferred
1322
+ context_dir: Optional[Union[Path, str]] = None, # Context for relative COPY commands
1313
1323
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1314
1324
  ignore: Union[Sequence[str], Callable[[Path], bool]] = AUTO_DOCKERIGNORE,
1315
1325
  ) -> "_Image":
@@ -1374,7 +1384,7 @@ class _Image(_Object, type_prefix="im"):
1374
1384
  secrets=secrets,
1375
1385
  gpu_config=parse_gpu_config(gpu),
1376
1386
  context_mount_function=_create_context_mount_function(
1377
- ignore=ignore, dockerfile_cmds=cmds, context_mount=context_mount
1387
+ ignore=ignore, dockerfile_cmds=cmds, context_mount=context_mount, context_dir=context_dir
1378
1388
  ),
1379
1389
  force_build=self.force_build or force_build,
1380
1390
  )
@@ -1552,7 +1562,7 @@ class _Image(_Object, type_prefix="im"):
1552
1562
  add_python: Optional[str] = None,
1553
1563
  **kwargs,
1554
1564
  ) -> "_Image":
1555
- """Build a Modal image from a public or private image registry, such as Docker Hub.
1565
+ """Build a Modal Image from a public or private image registry, such as Docker Hub.
1556
1566
 
1557
1567
  The image must be built for the `linux/amd64` platform.
1558
1568
 
@@ -1709,6 +1719,7 @@ class _Image(_Object, type_prefix="im"):
1709
1719
  # Ignore cached builds, similar to 'docker build --no-cache'
1710
1720
  force_build: bool = False,
1711
1721
  *,
1722
+ context_dir: Optional[Union[Path, str]] = None, # Context for relative COPY commands
1712
1723
  secrets: Sequence[_Secret] = [],
1713
1724
  gpu: GPU_T = None,
1714
1725
  add_python: Optional[str] = None,
@@ -1782,7 +1793,7 @@ class _Image(_Object, type_prefix="im"):
1782
1793
  base_image = _Image._from_args(
1783
1794
  dockerfile_function=build_dockerfile_base,
1784
1795
  context_mount_function=_create_context_mount_function(
1785
- ignore=ignore, dockerfile_path=Path(path), context_mount=context_mount
1796
+ ignore=ignore, dockerfile_path=Path(path), context_mount=context_mount, context_dir=context_dir
1786
1797
  ),
1787
1798
  gpu_config=gpu_config,
1788
1799
  secrets=secrets,
modal/image.pyi CHANGED
@@ -66,6 +66,7 @@ def _create_context_mount_function(
66
66
  dockerfile_cmds: list[str] = [],
67
67
  dockerfile_path: typing.Optional[pathlib.Path] = None,
68
68
  context_mount: typing.Optional[modal.mount._Mount] = None,
69
+ context_dir: typing.Union[pathlib.Path, str, None] = None,
69
70
  ): ...
70
71
 
71
72
  class _ImageRegistryConfig:
@@ -222,6 +223,7 @@ class _Image(modal._object._Object):
222
223
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
223
224
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
224
225
  context_mount: typing.Optional[modal.mount._Mount] = None,
226
+ context_dir: typing.Union[pathlib.Path, str, None] = None,
225
227
  force_build: bool = False,
226
228
  ignore: typing.Union[
227
229
  collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
@@ -290,6 +292,7 @@ class _Image(modal._object._Object):
290
292
  context_mount: typing.Optional[modal.mount._Mount] = None,
291
293
  force_build: bool = False,
292
294
  *,
295
+ context_dir: typing.Union[pathlib.Path, str, None] = None,
293
296
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
294
297
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
295
298
  add_python: typing.Optional[str] = None,
@@ -482,6 +485,7 @@ class Image(modal.object.Object):
482
485
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
483
486
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
484
487
  context_mount: typing.Optional[modal.mount.Mount] = None,
488
+ context_dir: typing.Union[pathlib.Path, str, None] = None,
485
489
  force_build: bool = False,
486
490
  ignore: typing.Union[
487
491
  collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
@@ -550,6 +554,7 @@ class Image(modal.object.Object):
550
554
  context_mount: typing.Optional[modal.mount.Mount] = None,
551
555
  force_build: bool = False,
552
556
  *,
557
+ context_dir: typing.Union[pathlib.Path, str, None] = None,
553
558
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
554
559
  gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
555
560
  add_python: typing.Optional[str] = None,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: modal
3
- Version: 0.73.143
3
+ Version: 0.73.145
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -23,7 +23,7 @@ modal/app.py,sha256=NKH7Cw1M6eyyrMXFbhWfdo3uRd28-8kv0Pcw56kPiPU,47312
23
23
  modal/app.pyi,sha256=pUEqciyGZ446sc_QoG8XcQ_oc6oU-U4dqjkxjhgOX98,26968
24
24
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
25
25
  modal/client.py,sha256=j9D3hNis1lfhnz9lVFGgJgowbH3PaGUzNKgHPWYG778,15372
26
- modal/client.pyi,sha256=CfDyx-Nfu0CC1bs8CypPA7qvaTvjCabL-FpRgUm6cog,7661
26
+ modal/client.pyi,sha256=J7eXuFOFX-QEUY-bFll_4B9V3SqdWS9iuFrslnty0Sc,7661
27
27
  modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
28
28
  modal/cloud_bucket_mount.pyi,sha256=30T3K1a89l6wzmEJ_J9iWv9SknoGqaZDx59Xs-ZQcmk,1607
29
29
  modal/cls.py,sha256=PJimWA9q_sbQJNLbYy7fzjZGBm_hdfXuuZ7O_pKLXdk,31586
@@ -42,8 +42,8 @@ modal/file_pattern_matcher.py,sha256=trosX-Bp7dOubudN1bLLhRAoidWy1TcoaR4Pv8CedWw
42
42
  modal/functions.py,sha256=kcNHvqeGBxPI7Cgd57NIBBghkfbeFJzXO44WW0jSmao,325
43
43
  modal/functions.pyi,sha256=0Au1n37DimTZVvxCIR7HWALuNGxfJ_fcNR-or37eo5k,14438
44
44
  modal/gpu.py,sha256=Kbhs_u49FaC2Zi0TjCdrpstpRtT5eZgecynmQi5IZVE,6752
45
- modal/image.py,sha256=JVnSSAm7wmull3H_plU-bdPkc4Dx_2EPG_229YJd81U,92174
46
- modal/image.pyi,sha256=DQ4DLOCPr6_yV7z4LS0bTY0rOyvQP9-dQOrzaW7pPG8,25260
45
+ modal/image.py,sha256=HtkKomhX4inozqSRi7lf5Vt9IEqCnVHn5bEo59hD64A,92835
46
+ modal/image.pyi,sha256=iWclz2rxaP-LSsYMgU0X3ZcN5mEFvpyKzIPKJbohmsg,25591
47
47
  modal/io_streams.py,sha256=h5O2LmbRoT9l777z3TQhCAm-JF1r7avZ2ykXlejztDs,15163
48
48
  modal/io_streams.pyi,sha256=bJ7ZLmSmJ0nKoa6r4FJpbqvzdUVa0lEe0Fa-MMpMezU,5071
49
49
  modal/mount.py,sha256=JII0zTS1fPCcCbZgO18okkOuTDqYCxY1DIVa6i1E9cI,32196
@@ -137,7 +137,7 @@ modal/cli/volume.py,sha256=c2IuVNO2yJVaXmZkRh3xwQmznlRTgFoJr_BIzzqtVv0,10251
137
137
  modal/cli/programs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
138
138
  modal/cli/programs/run_jupyter.py,sha256=MX6YQ6zRyRk1xo8tYZFiGam0p5KETwax81L6TpaS9I0,2778
139
139
  modal/cli/programs/vscode.py,sha256=kfvhZQ4bJwtVm3MgC1V7AlygZOlKT1a33alr_uwrewA,3473
140
- modal/experimental/__init__.py,sha256=6VY98HfgoQU-65ELJ4Re_PB3VBCjDMZywuK1oOvljkg,4025
140
+ modal/experimental/__init__.py,sha256=DXXQnYEjaRPcr1b4pbKk4DckHKtQ8T8PcWTUuXG4_ug,7595
141
141
  modal/experimental/ipython.py,sha256=epLUZeDSdE226TH_tU3igRKCiVuQi99mUOrIJ4SemOE,2792
142
142
  modal/requirements/2023.12.312.txt,sha256=zWWUVgVQ92GXBKNYYr2-5vn9rlnXcmkqlwlX5u1eTYw,400
143
143
  modal/requirements/2023.12.txt,sha256=OjsbXFkCSdkzzryZP82Q73osr5wxQ6EUzmGcK7twfkA,502
@@ -170,10 +170,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
170
170
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
171
171
  modal_version/__init__.py,sha256=wiJQ53c-OMs0Xf1UeXOxQ7FwlV1VzIjnX6o-pRYZ_Pk,470
172
172
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
173
- modal_version/_version_generated.py,sha256=Swo3V9IveICUpIFVpIVccS8muTG9yF2vONXjcIswZwg,150
174
- modal-0.73.143.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
175
- modal-0.73.143.dist-info/METADATA,sha256=RIQlD3ToI9VKKHLrS0I7OnNSeHbBVL6WfzoINs9kuZA,2453
176
- modal-0.73.143.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
177
- modal-0.73.143.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
178
- modal-0.73.143.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
179
- modal-0.73.143.dist-info/RECORD,,
173
+ modal_version/_version_generated.py,sha256=YQBstI23mB655VZiiHXuwQBeJvkuthZrz2UWbWaboK0,150
174
+ modal-0.73.145.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
175
+ modal-0.73.145.dist-info/METADATA,sha256=TN9rgxptFzQz0ByXCsWGymktdRwq5gSl7bpKDvxwDrw,2453
176
+ modal-0.73.145.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
177
+ modal-0.73.145.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
178
+ modal-0.73.145.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
179
+ modal-0.73.145.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 143 # git: 1eee221
4
+ build_number = 145 # git: d6f7843