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 +2 -2
- modal/experimental/__init__.py +84 -1
- modal/image.py +21 -10
- modal/image.pyi +5 -0
- {modal-0.73.143.dist-info → modal-0.73.145.dist-info}/METADATA +1 -1
- {modal-0.73.143.dist-info → modal-0.73.145.dist-info}/RECORD +11 -11
- modal_version/_version_generated.py +1 -1
- {modal-0.73.143.dist-info → modal-0.73.145.dist-info}/LICENSE +0 -0
- {modal-0.73.143.dist-info → modal-0.73.145.dist-info}/WHEEL +0 -0
- {modal-0.73.143.dist-info → modal-0.73.145.dist-info}/entry_points.txt +0 -0
- {modal-0.73.143.dist-info → modal-0.73.145.dist-info}/top_level.txt +0 -0
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.
|
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.
|
96
|
+
version: str = "0.73.145",
|
97
97
|
): ...
|
98
98
|
def is_closed(self) -> bool: ...
|
99
99
|
@property
|
modal/experimental/__init__.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Copyright Modal Labs 2025
|
2
|
+
import os
|
2
3
|
from dataclasses import dataclass
|
3
|
-
from
|
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(
|
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
|
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
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
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
|
-
|
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
|
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,
|
@@ -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=
|
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=
|
46
|
-
modal/image.pyi,sha256=
|
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=
|
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=
|
174
|
-
modal-0.73.
|
175
|
-
modal-0.73.
|
176
|
-
modal-0.73.
|
177
|
-
modal-0.73.
|
178
|
-
modal-0.73.
|
179
|
-
modal-0.73.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|