modal 0.71.12__py3-none-any.whl → 0.72.0__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/_utils/docker_utils.py +35 -1
- modal/client.pyi +2 -2
- modal/file_pattern_matcher.py +43 -18
- modal/functions.pyi +6 -6
- modal/image.py +157 -36
- modal/image.pyi +24 -4
- {modal-0.71.12.dist-info → modal-0.72.0.dist-info}/METADATA +1 -1
- {modal-0.71.12.dist-info → modal-0.72.0.dist-info}/RECORD +14 -14
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +1 -1
- {modal-0.71.12.dist-info → modal-0.72.0.dist-info}/LICENSE +0 -0
- {modal-0.71.12.dist-info → modal-0.72.0.dist-info}/WHEEL +0 -0
- {modal-0.71.12.dist-info → modal-0.72.0.dist-info}/entry_points.txt +0 -0
- {modal-0.71.12.dist-info → modal-0.72.0.dist-info}/top_level.txt +0 -0
modal/_utils/docker_utils.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# Copyright Modal Labs 2024
|
2
2
|
import re
|
3
3
|
import shlex
|
4
|
-
from
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Optional, Sequence
|
5
6
|
|
6
7
|
from ..exception import InvalidError
|
7
8
|
|
@@ -62,3 +63,36 @@ def extract_copy_command_patterns(dockerfile_lines: Sequence[str]) -> list[str]:
|
|
62
63
|
current_command = ""
|
63
64
|
|
64
65
|
return list(copy_source_patterns)
|
66
|
+
|
67
|
+
|
68
|
+
def find_dockerignore_file(context_directory: Path, dockerfile_path: Optional[Path] = None) -> Optional[Path]:
|
69
|
+
"""
|
70
|
+
Find dockerignore file relative to current context directory
|
71
|
+
and if dockerfile path is provided, check if specific <dockerfile_name>.dockerignore
|
72
|
+
file exists in the same directory as <dockerfile_name>
|
73
|
+
Finds the most specific dockerignore file that exists.
|
74
|
+
"""
|
75
|
+
|
76
|
+
def valid_dockerignore_file(fp):
|
77
|
+
# fp has to exist
|
78
|
+
if not fp.exists():
|
79
|
+
return False
|
80
|
+
# fp has to be subpath to current working directory
|
81
|
+
if not fp.is_relative_to(context_directory):
|
82
|
+
return False
|
83
|
+
|
84
|
+
return True
|
85
|
+
|
86
|
+
generic_name = ".dockerignore"
|
87
|
+
possible_locations = []
|
88
|
+
if dockerfile_path:
|
89
|
+
specific_name = f"{dockerfile_path.name}.dockerignore"
|
90
|
+
# 1. check if specific <dockerfile_name>.dockerignore file exists in the same directory as <dockerfile_name>
|
91
|
+
possible_locations.append(dockerfile_path.parent / specific_name)
|
92
|
+
# 2. check if generic .dockerignore file exists in the same directory as <dockerfile_name>
|
93
|
+
possible_locations.append(dockerfile_path.parent / generic_name)
|
94
|
+
|
95
|
+
# 3. check if generic .dockerignore file exists in current working directory
|
96
|
+
possible_locations.append(context_directory / generic_name)
|
97
|
+
|
98
|
+
return next((e for e in possible_locations if valid_dockerignore_file(e)), None)
|
modal/client.pyi
CHANGED
@@ -26,7 +26,7 @@ class _Client:
|
|
26
26
|
_stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
|
27
27
|
|
28
28
|
def __init__(
|
29
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.
|
29
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.72.0"
|
30
30
|
): ...
|
31
31
|
def is_closed(self) -> bool: ...
|
32
32
|
@property
|
@@ -81,7 +81,7 @@ class Client:
|
|
81
81
|
_stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
|
82
82
|
|
83
83
|
def __init__(
|
84
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.
|
84
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.72.0"
|
85
85
|
): ...
|
86
86
|
def is_closed(self) -> bool: ...
|
87
87
|
@property
|
modal/file_pattern_matcher.py
CHANGED
@@ -62,6 +62,27 @@ class _CustomPatternMatcher(_AbstractPatternMatcher):
|
|
62
62
|
class FilePatternMatcher(_AbstractPatternMatcher):
|
63
63
|
"""Allows matching file paths against a list of patterns."""
|
64
64
|
|
65
|
+
patterns: list[Pattern]
|
66
|
+
_delayed_init: Callable[[], None] = None
|
67
|
+
|
68
|
+
def _set_patterns(self, patterns: Sequence[str]) -> None:
|
69
|
+
self.patterns = []
|
70
|
+
for pattern in list(patterns):
|
71
|
+
pattern = pattern.strip()
|
72
|
+
if not pattern:
|
73
|
+
continue
|
74
|
+
pattern = os.path.normpath(pattern)
|
75
|
+
new_pattern = Pattern()
|
76
|
+
if pattern[0] == "!":
|
77
|
+
if len(pattern) == 1:
|
78
|
+
raise ValueError('Illegal exclusion pattern: "!"')
|
79
|
+
new_pattern.exclusion = True
|
80
|
+
pattern = pattern[1:]
|
81
|
+
# In Python, we can proceed without explicit syntax checking
|
82
|
+
new_pattern.cleaned_pattern = pattern
|
83
|
+
new_pattern.dirs = pattern.split(os.path.sep)
|
84
|
+
self.patterns.append(new_pattern)
|
85
|
+
|
65
86
|
def __init__(self, *pattern: str) -> None:
|
66
87
|
"""Initialize a new FilePatternMatcher instance.
|
67
88
|
|
@@ -71,24 +92,25 @@ class FilePatternMatcher(_AbstractPatternMatcher):
|
|
71
92
|
Raises:
|
72
93
|
ValueError: If an illegal exclusion pattern is provided.
|
73
94
|
"""
|
74
|
-
self.
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
95
|
+
self._set_patterns(pattern)
|
96
|
+
|
97
|
+
@classmethod
|
98
|
+
def from_file(cls, file_path: Path) -> "FilePatternMatcher":
|
99
|
+
"""Initialize a new FilePatternMatcher instance from a file.
|
100
|
+
|
101
|
+
The patterns in the file will be read lazily when the matcher is first used.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
file_path (Path): The path to the file containing patterns.
|
105
|
+
"""
|
106
|
+
uninitialized = cls.__new__(cls)
|
107
|
+
|
108
|
+
def _delayed_init():
|
109
|
+
uninitialized._set_patterns(file_path.read_text("utf8").splitlines())
|
110
|
+
uninitialized._delayed_init = None
|
111
|
+
|
112
|
+
uninitialized._delayed_init = _delayed_init
|
113
|
+
return uninitialized
|
92
114
|
|
93
115
|
def _matches(self, file_path: str) -> bool:
|
94
116
|
"""Check if the file path or any of its parent directories match the patterns.
|
@@ -97,6 +119,7 @@ class FilePatternMatcher(_AbstractPatternMatcher):
|
|
97
119
|
library. The reason is that `Matches()` in the original library is
|
98
120
|
deprecated due to buggy behavior.
|
99
121
|
"""
|
122
|
+
|
100
123
|
matched = False
|
101
124
|
file_path = os.path.normpath(file_path)
|
102
125
|
if file_path == ".":
|
@@ -146,6 +169,8 @@ class FilePatternMatcher(_AbstractPatternMatcher):
|
|
146
169
|
assert matcher(Path("foo.py"))
|
147
170
|
```
|
148
171
|
"""
|
172
|
+
if self._delayed_init:
|
173
|
+
self._delayed_init()
|
149
174
|
return self._matches(str(file_path))
|
150
175
|
|
151
176
|
|
modal/functions.pyi
CHANGED
@@ -462,11 +462,11 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
462
462
|
|
463
463
|
_call_generator_nowait: ___call_generator_nowait_spec
|
464
464
|
|
465
|
-
class __remote_spec(typing_extensions.Protocol[
|
465
|
+
class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
466
466
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
467
467
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
468
468
|
|
469
|
-
remote: __remote_spec[
|
469
|
+
remote: __remote_spec[P, ReturnType]
|
470
470
|
|
471
471
|
class __remote_gen_spec(typing_extensions.Protocol):
|
472
472
|
def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
|
@@ -479,17 +479,17 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
479
479
|
def _get_obj(self) -> typing.Optional[modal.cls.Obj]: ...
|
480
480
|
def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
|
481
481
|
|
482
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
482
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
483
483
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
484
484
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
485
485
|
|
486
|
-
_experimental_spawn: ___experimental_spawn_spec[
|
486
|
+
_experimental_spawn: ___experimental_spawn_spec[P, ReturnType]
|
487
487
|
|
488
|
-
class __spawn_spec(typing_extensions.Protocol[
|
488
|
+
class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
489
489
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
490
490
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
491
491
|
|
492
|
-
spawn: __spawn_spec[
|
492
|
+
spawn: __spawn_spec[P, ReturnType]
|
493
493
|
|
494
494
|
def get_raw_f(self) -> typing.Callable[..., typing.Any]: ...
|
495
495
|
|
modal/image.py
CHANGED
@@ -33,6 +33,7 @@ from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
|
|
33
33
|
from ._utils.deprecation import deprecation_error, deprecation_warning
|
34
34
|
from ._utils.docker_utils import (
|
35
35
|
extract_copy_command_patterns,
|
36
|
+
find_dockerignore_file,
|
36
37
|
)
|
37
38
|
from ._utils.function_utils import FunctionInfo
|
38
39
|
from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES, retry_transient_errors
|
@@ -71,6 +72,17 @@ LOCAL_REQUIREMENTS_DIR = Path(__file__).parent / "requirements"
|
|
71
72
|
CONTAINER_REQUIREMENTS_PATH = "/modal_requirements.txt"
|
72
73
|
|
73
74
|
|
75
|
+
class _AutoDockerIgnoreSentinel:
|
76
|
+
def __repr__(self) -> str:
|
77
|
+
return f"{__name__}.AUTO_DOCKERIGNORE"
|
78
|
+
|
79
|
+
def __call__(self, _: Path) -> bool:
|
80
|
+
raise NotImplementedError("This is only a placeholder. Do not call")
|
81
|
+
|
82
|
+
|
83
|
+
AUTO_DOCKERIGNORE = _AutoDockerIgnoreSentinel()
|
84
|
+
|
85
|
+
|
74
86
|
def _validate_python_version(
|
75
87
|
python_version: Optional[str], builder_version: ImageBuilderVersion, allow_micro_granularity: bool = True
|
76
88
|
) -> str:
|
@@ -266,6 +278,47 @@ def _create_context_mount(
|
|
266
278
|
return _Mount._add_local_dir(Path("./"), PurePosixPath("/"), ignore=ignore_with_include)
|
267
279
|
|
268
280
|
|
281
|
+
def _create_context_mount_function(
|
282
|
+
ignore: Union[Sequence[str], Callable[[Path], bool]],
|
283
|
+
dockerfile_cmds: list[str] = [],
|
284
|
+
dockerfile_path: Optional[Path] = None,
|
285
|
+
context_mount: Optional[_Mount] = None,
|
286
|
+
):
|
287
|
+
if dockerfile_path and dockerfile_cmds:
|
288
|
+
raise InvalidError("Cannot provide both dockerfile and docker commands")
|
289
|
+
|
290
|
+
if context_mount:
|
291
|
+
if ignore is not AUTO_DOCKERIGNORE:
|
292
|
+
raise InvalidError("Cannot set both `context_mount` and `ignore`")
|
293
|
+
|
294
|
+
def identity_context_mount_fn() -> Optional[_Mount]:
|
295
|
+
return context_mount
|
296
|
+
|
297
|
+
return identity_context_mount_fn
|
298
|
+
elif ignore is AUTO_DOCKERIGNORE:
|
299
|
+
|
300
|
+
def auto_created_context_mount_fn() -> Optional[_Mount]:
|
301
|
+
context_dir = Path.cwd()
|
302
|
+
dockerignore_file = find_dockerignore_file(context_dir, dockerfile_path)
|
303
|
+
ignore_fn = (
|
304
|
+
FilePatternMatcher(*dockerignore_file.read_text("utf8").splitlines())
|
305
|
+
if dockerignore_file
|
306
|
+
else _ignore_fn(())
|
307
|
+
)
|
308
|
+
|
309
|
+
cmds = dockerfile_path.read_text("utf8").splitlines() if dockerfile_path else dockerfile_cmds
|
310
|
+
return _create_context_mount(cmds, ignore_fn=ignore_fn, context_dir=context_dir)
|
311
|
+
|
312
|
+
return auto_created_context_mount_fn
|
313
|
+
|
314
|
+
def auto_created_context_mount_fn() -> Optional[_Mount]:
|
315
|
+
# use COPY commands and ignore patterns to construct implicit context mount
|
316
|
+
cmds = dockerfile_path.read_text("utf8").splitlines() if dockerfile_path else dockerfile_cmds
|
317
|
+
return _create_context_mount(cmds, ignore_fn=_ignore_fn(ignore), context_dir=Path.cwd())
|
318
|
+
|
319
|
+
return auto_created_context_mount_fn
|
320
|
+
|
321
|
+
|
269
322
|
class _ImageRegistryConfig:
|
270
323
|
"""mdmd:hidden"""
|
271
324
|
|
@@ -683,6 +736,7 @@ class _Image(_Object, type_prefix="im"):
|
|
683
736
|
**Usage:**
|
684
737
|
|
685
738
|
```python
|
739
|
+
from pathlib import Path
|
686
740
|
from modal import FilePatternMatcher
|
687
741
|
|
688
742
|
image = modal.Image.debian_slim().add_local_dir(
|
@@ -697,18 +751,25 @@ class _Image(_Object, type_prefix="im"):
|
|
697
751
|
ignore=lambda p: p.is_relative_to(".venv"),
|
698
752
|
)
|
699
753
|
|
700
|
-
image = modal.Image.debian_slim().
|
754
|
+
image = modal.Image.debian_slim().add_local_dir(
|
701
755
|
"~/assets",
|
702
756
|
remote_path="/assets",
|
703
757
|
ignore=FilePatternMatcher("**/*.txt"),
|
704
758
|
)
|
705
759
|
|
706
760
|
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
707
|
-
image = modal.Image.debian_slim().
|
761
|
+
image = modal.Image.debian_slim().add_local_dir(
|
708
762
|
"~/assets",
|
709
763
|
remote_path="/assets",
|
710
764
|
ignore=~FilePatternMatcher("**/*.py"),
|
711
765
|
)
|
766
|
+
|
767
|
+
# You can also read ignore patterns from a file.
|
768
|
+
image = modal.Image.debian_slim().add_local_dir(
|
769
|
+
"~/assets",
|
770
|
+
remote_path="/assets",
|
771
|
+
ignore=FilePatternMatcher.from_file(Path("/path/to/ignorefile")),
|
772
|
+
)
|
712
773
|
```
|
713
774
|
"""
|
714
775
|
if not PurePosixPath(remote_path).is_absolute():
|
@@ -792,6 +853,7 @@ class _Image(_Object, type_prefix="im"):
|
|
792
853
|
**Usage:**
|
793
854
|
|
794
855
|
```python
|
856
|
+
from pathlib import Path
|
795
857
|
from modal import FilePatternMatcher
|
796
858
|
|
797
859
|
image = modal.Image.debian_slim().copy_local_dir(
|
@@ -818,6 +880,13 @@ class _Image(_Object, type_prefix="im"):
|
|
818
880
|
remote_path="/assets",
|
819
881
|
ignore=~FilePatternMatcher("**/*.py"),
|
820
882
|
)
|
883
|
+
|
884
|
+
# You can also read ignore patterns from a file.
|
885
|
+
image = modal.Image.debian_slim().copy_local_dir(
|
886
|
+
"~/assets",
|
887
|
+
remote_path="/assets",
|
888
|
+
ignore=FilePatternMatcher.from_file(Path("/path/to/ignorefile")),
|
889
|
+
)
|
821
890
|
```
|
822
891
|
"""
|
823
892
|
|
@@ -1205,28 +1274,53 @@ class _Image(_Object, type_prefix="im"):
|
|
1205
1274
|
# modal.Mount with local files to supply as build context for COPY commands
|
1206
1275
|
context_mount: Optional[_Mount] = None,
|
1207
1276
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
1208
|
-
ignore: Union[Sequence[str], Callable[[Path], bool]] =
|
1277
|
+
ignore: Union[Sequence[str], Callable[[Path], bool]] = AUTO_DOCKERIGNORE,
|
1209
1278
|
) -> "_Image":
|
1210
|
-
"""
|
1211
|
-
|
1212
|
-
if not cmds:
|
1213
|
-
return self
|
1279
|
+
"""
|
1280
|
+
Extend an image with arbitrary Dockerfile-like commands.
|
1214
1281
|
|
1215
|
-
|
1216
|
-
if ignore:
|
1217
|
-
raise InvalidError("Cannot set both `context_mount` and `ignore`")
|
1282
|
+
**Usage:**
|
1218
1283
|
|
1219
|
-
|
1220
|
-
|
1284
|
+
```python
|
1285
|
+
from pathlib import Path
|
1286
|
+
from modal import FilePatternMatcher
|
1221
1287
|
|
1222
|
-
|
1223
|
-
|
1288
|
+
# By default a .dockerignore file is used if present in the current working directory
|
1289
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
1290
|
+
["COPY data /data"],
|
1291
|
+
)
|
1224
1292
|
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1293
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
1294
|
+
["COPY data /data"],
|
1295
|
+
ignore=["*.venv"],
|
1296
|
+
)
|
1228
1297
|
|
1229
|
-
|
1298
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
1299
|
+
["COPY data /data"],
|
1300
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
1301
|
+
)
|
1302
|
+
|
1303
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
1304
|
+
["COPY data /data"],
|
1305
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
1306
|
+
)
|
1307
|
+
|
1308
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
1309
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
1310
|
+
["COPY data /data"],
|
1311
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
1312
|
+
)
|
1313
|
+
|
1314
|
+
# You can also read ignore patterns from a file.
|
1315
|
+
image = modal.Image.debian_slim().dockerfile_commands(
|
1316
|
+
["COPY data /data"],
|
1317
|
+
ignore=FilePatternMatcher.from_file(Path("/path/to/dockerignore")),
|
1318
|
+
)
|
1319
|
+
```
|
1320
|
+
"""
|
1321
|
+
cmds = _flatten_str_args("dockerfile_commands", "dockerfile_commands", dockerfile_commands)
|
1322
|
+
if not cmds:
|
1323
|
+
return self
|
1230
1324
|
|
1231
1325
|
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
1232
1326
|
return DockerfileSpec(commands=["FROM base", *cmds], context_files=context_files)
|
@@ -1236,7 +1330,9 @@ class _Image(_Object, type_prefix="im"):
|
|
1236
1330
|
dockerfile_function=build_dockerfile,
|
1237
1331
|
secrets=secrets,
|
1238
1332
|
gpu_config=parse_gpu_config(gpu),
|
1239
|
-
context_mount_function=
|
1333
|
+
context_mount_function=_create_context_mount_function(
|
1334
|
+
ignore=ignore, dockerfile_cmds=cmds, context_mount=context_mount
|
1335
|
+
),
|
1240
1336
|
force_build=self.force_build or force_build,
|
1241
1337
|
)
|
1242
1338
|
|
@@ -1601,35 +1697,58 @@ class _Image(_Object, type_prefix="im"):
|
|
1601
1697
|
secrets: Sequence[_Secret] = [],
|
1602
1698
|
gpu: GPU_T = None,
|
1603
1699
|
add_python: Optional[str] = None,
|
1604
|
-
ignore: Union[Sequence[str], Callable[[Path], bool]] =
|
1700
|
+
ignore: Union[Sequence[str], Callable[[Path], bool]] = AUTO_DOCKERIGNORE,
|
1605
1701
|
) -> "_Image":
|
1606
1702
|
"""Build a Modal image from a local Dockerfile.
|
1607
1703
|
|
1608
1704
|
If your Dockerfile does not have Python installed, you can use the `add_python` parameter
|
1609
1705
|
to specify a version of Python to add to the image.
|
1610
1706
|
|
1611
|
-
**
|
1707
|
+
**Usage:**
|
1612
1708
|
|
1613
1709
|
```python
|
1614
|
-
|
1615
|
-
|
1616
|
-
"""
|
1710
|
+
from pathlib import Path
|
1711
|
+
from modal import FilePatternMatcher
|
1617
1712
|
|
1618
|
-
if
|
1619
|
-
|
1620
|
-
|
1713
|
+
# By default a .dockerignore file is used if present in the current working directory
|
1714
|
+
image = modal.Image.from_dockerfile(
|
1715
|
+
"./Dockerfile",
|
1716
|
+
add_python="3.12",
|
1717
|
+
)
|
1621
1718
|
|
1622
|
-
|
1623
|
-
|
1719
|
+
image = modal.Image.from_dockerfile(
|
1720
|
+
"./Dockerfile",
|
1721
|
+
add_python="3.12",
|
1722
|
+
ignore=["*.venv"],
|
1723
|
+
)
|
1624
1724
|
|
1625
|
-
|
1626
|
-
|
1725
|
+
image = modal.Image.from_dockerfile(
|
1726
|
+
"./Dockerfile",
|
1727
|
+
add_python="3.12",
|
1728
|
+
ignore=lambda p: p.is_relative_to(".venv"),
|
1729
|
+
)
|
1627
1730
|
|
1628
|
-
|
1629
|
-
|
1630
|
-
|
1731
|
+
image = modal.Image.from_dockerfile(
|
1732
|
+
"./Dockerfile",
|
1733
|
+
add_python="3.12",
|
1734
|
+
ignore=FilePatternMatcher("**/*.txt"),
|
1735
|
+
)
|
1631
1736
|
|
1632
|
-
|
1737
|
+
# When including files is simpler than excluding them, you can use the `~` operator to invert the matcher.
|
1738
|
+
image = modal.Image.from_dockerfile(
|
1739
|
+
"./Dockerfile",
|
1740
|
+
add_python="3.12",
|
1741
|
+
ignore=~FilePatternMatcher("**/*.py"),
|
1742
|
+
)
|
1743
|
+
|
1744
|
+
# You can also read ignore patterns from a file.
|
1745
|
+
image = modal.Image.from_dockerfile(
|
1746
|
+
"./Dockerfile",
|
1747
|
+
add_python="3.12",
|
1748
|
+
ignore=FilePatternMatcher.from_file(Path("/path/to/dockerignore")),
|
1749
|
+
)
|
1750
|
+
```
|
1751
|
+
"""
|
1633
1752
|
|
1634
1753
|
# --- Build the base dockerfile
|
1635
1754
|
|
@@ -1641,7 +1760,9 @@ class _Image(_Object, type_prefix="im"):
|
|
1641
1760
|
gpu_config = parse_gpu_config(gpu)
|
1642
1761
|
base_image = _Image._from_args(
|
1643
1762
|
dockerfile_function=build_dockerfile_base,
|
1644
|
-
context_mount_function=
|
1763
|
+
context_mount_function=_create_context_mount_function(
|
1764
|
+
ignore=ignore, dockerfile_path=Path(path), context_mount=context_mount
|
1765
|
+
),
|
1645
1766
|
gpu_config=gpu_config,
|
1646
1767
|
secrets=secrets,
|
1647
1768
|
)
|
modal/image.pyi
CHANGED
@@ -16,6 +16,12 @@ import typing_extensions
|
|
16
16
|
|
17
17
|
ImageBuilderVersion = typing.Literal["2023.12", "2024.04", "2024.10"]
|
18
18
|
|
19
|
+
class _AutoDockerIgnoreSentinel:
|
20
|
+
def __repr__(self) -> str: ...
|
21
|
+
def __call__(self, _: pathlib.Path) -> bool: ...
|
22
|
+
|
23
|
+
AUTO_DOCKERIGNORE: _AutoDockerIgnoreSentinel
|
24
|
+
|
19
25
|
def _validate_python_version(
|
20
26
|
python_version: typing.Optional[str],
|
21
27
|
builder_version: typing.Literal["2023.12", "2024.04", "2024.10"],
|
@@ -49,6 +55,12 @@ def _create_context_mount(
|
|
49
55
|
ignore_fn: typing.Callable[[pathlib.Path], bool],
|
50
56
|
context_dir: pathlib.Path,
|
51
57
|
) -> typing.Optional[modal.mount._Mount]: ...
|
58
|
+
def _create_context_mount_function(
|
59
|
+
ignore: typing.Union[collections.abc.Sequence[str], typing.Callable[[pathlib.Path], bool]],
|
60
|
+
dockerfile_cmds: list[str] = [],
|
61
|
+
dockerfile_path: typing.Optional[pathlib.Path] = None,
|
62
|
+
context_mount: typing.Optional[modal.mount._Mount] = None,
|
63
|
+
): ...
|
52
64
|
|
53
65
|
class _ImageRegistryConfig:
|
54
66
|
def __init__(self, registry_auth_type: int = 0, secret: typing.Optional[modal.secret._Secret] = None): ...
|
@@ -202,7 +214,9 @@ class _Image(modal.object._Object):
|
|
202
214
|
gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
|
203
215
|
context_mount: typing.Optional[modal.mount._Mount] = None,
|
204
216
|
force_build: bool = False,
|
205
|
-
ignore: typing.Union[
|
217
|
+
ignore: typing.Union[
|
218
|
+
collections.abc.Sequence[str], typing.Callable[[pathlib.Path], bool]
|
219
|
+
] = modal.image.AUTO_DOCKERIGNORE,
|
206
220
|
) -> _Image: ...
|
207
221
|
def entrypoint(self, entrypoint_commands: list[str]) -> _Image: ...
|
208
222
|
def shell(self, shell_commands: list[str]) -> _Image: ...
|
@@ -288,7 +302,9 @@ class _Image(modal.object._Object):
|
|
288
302
|
secrets: collections.abc.Sequence[modal.secret._Secret] = [],
|
289
303
|
gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
|
290
304
|
add_python: typing.Optional[str] = None,
|
291
|
-
ignore: typing.Union[
|
305
|
+
ignore: typing.Union[
|
306
|
+
collections.abc.Sequence[str], typing.Callable[[pathlib.Path], bool]
|
307
|
+
] = modal.image.AUTO_DOCKERIGNORE,
|
292
308
|
) -> _Image: ...
|
293
309
|
@staticmethod
|
294
310
|
def debian_slim(python_version: typing.Optional[str] = None, force_build: bool = False) -> _Image: ...
|
@@ -470,7 +486,9 @@ class Image(modal.object.Object):
|
|
470
486
|
gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
|
471
487
|
context_mount: typing.Optional[modal.mount.Mount] = None,
|
472
488
|
force_build: bool = False,
|
473
|
-
ignore: typing.Union[
|
489
|
+
ignore: typing.Union[
|
490
|
+
collections.abc.Sequence[str], typing.Callable[[pathlib.Path], bool]
|
491
|
+
] = modal.image.AUTO_DOCKERIGNORE,
|
474
492
|
) -> Image: ...
|
475
493
|
def entrypoint(self, entrypoint_commands: list[str]) -> Image: ...
|
476
494
|
def shell(self, shell_commands: list[str]) -> Image: ...
|
@@ -556,7 +574,9 @@ class Image(modal.object.Object):
|
|
556
574
|
secrets: collections.abc.Sequence[modal.secret.Secret] = [],
|
557
575
|
gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
|
558
576
|
add_python: typing.Optional[str] = None,
|
559
|
-
ignore: typing.Union[
|
577
|
+
ignore: typing.Union[
|
578
|
+
collections.abc.Sequence[str], typing.Callable[[pathlib.Path], bool]
|
579
|
+
] = modal.image.AUTO_DOCKERIGNORE,
|
560
580
|
) -> Image: ...
|
561
581
|
@staticmethod
|
562
582
|
def debian_slim(python_version: typing.Optional[str] = None, force_build: bool = False) -> Image: ...
|
@@ -19,7 +19,7 @@ modal/app.py,sha256=vEE0cK5QPF6_cdW5AJvcuWxz5KmeprHwBEtlDkVRHgE,45582
|
|
19
19
|
modal/app.pyi,sha256=Gx7gxjfQ70sxhbwfpx1VjvzEON-ZEMTJ_Vy8qt0oQvo,25302
|
20
20
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
21
21
|
modal/client.py,sha256=JAnd4-GCN093BwkvOFAK5a6iy5ycxofjpUncMxlrIMw,15253
|
22
|
-
modal/client.pyi,sha256=
|
22
|
+
modal/client.pyi,sha256=yhGbIx_ZDcPa1Lo4cm7CWmiSkF8-hiMxea6OG8xvOEA,7278
|
23
23
|
modal/cloud_bucket_mount.py,sha256=G7T7jWLD0QkmrfKR75mSTwdUZ2xNfj7pkVqb4ipmxmI,5735
|
24
24
|
modal/cloud_bucket_mount.pyi,sha256=CEi7vrH3kDUF4LAy4qP6tfImy2UJuFRcRbsgRNM1wo8,1403
|
25
25
|
modal/cls.py,sha256=3hjb0JcoPjxKZNeK22f5rR43bZRBjoRI7_EMZXY7YrE,31172
|
@@ -35,12 +35,12 @@ modal/exception.py,sha256=4JyO-SACaLNDe2QC48EjsK8GMkZ8AgEurZ8j1YdRu8E,5263
|
|
35
35
|
modal/experimental.py,sha256=npfKbyMpI41uZZs9HW_QiB3E4ykWfDXZbACXXbw6qeA,2385
|
36
36
|
modal/file_io.py,sha256=lcMs_E9Xfm0YX1t9U2wNIBPnqHRxmImqjLW1GHqVmyg,20945
|
37
37
|
modal/file_io.pyi,sha256=NrIoB0YjIqZ8MDMe826xAnybT0ww_kxQM3iPLo82REU,8898
|
38
|
-
modal/file_pattern_matcher.py,sha256=
|
38
|
+
modal/file_pattern_matcher.py,sha256=uksEpQG4LSNdW57NQVqP9oOWPpD2-c9QVaAN_dmzKIQ,6415
|
39
39
|
modal/functions.py,sha256=3uJPbrEAWhpFfLfUnoRjGmvEUC-_wVh-8yNJBx8eVeM,68249
|
40
|
-
modal/functions.pyi,sha256=
|
40
|
+
modal/functions.pyi,sha256=LiSDgH-X7jcZ56pAoLMwo3x9Dzdp_3Sd7W5MVAJPoCg,25407
|
41
41
|
modal/gpu.py,sha256=MTxj6ql8EpgfBg8YmZ5a1cLznyuZFssX1qXbEX4LKVM,7503
|
42
|
-
modal/image.py,sha256=
|
43
|
-
modal/image.pyi,sha256=
|
42
|
+
modal/image.py,sha256=BAalPHfJn3TM5RW63AK-TRQjq82K2KLgzXXxWwfYbxI,89366
|
43
|
+
modal/image.pyi,sha256=NfZyLkl4rmxpc5fokaO4mmEeGFOwGn0AndV1vKwBdbs,26027
|
44
44
|
modal/io_streams.py,sha256=QkQiizKRzd5bnbKQsap31LJgBYlAnj4-XkV_50xPYX0,15079
|
45
45
|
modal/io_streams.pyi,sha256=bCCVSxkMcosYd8O3PQDDwJw7TQ8JEcnYonLJ5t27TQs,4804
|
46
46
|
modal/mount.py,sha256=wOr-2vmKImsE3lHBII8hL2gYy5ng46R58QwId4JultQ,29313
|
@@ -88,7 +88,7 @@ modal/_utils/async_utils.py,sha256=9ubwMkwiDB4gzOYG2jL9j7Fs-5dxHjcifZe3r7JRg-k,2
|
|
88
88
|
modal/_utils/blob_utils.py,sha256=N66LtZI8PpCkZ7maA7GLW5CAmYUoNJdG-GjaAUR4_NQ,14509
|
89
89
|
modal/_utils/bytes_io_segment_payload.py,sha256=uunxVJS4PE1LojF_UpURMzVK9GuvmYWRqQo_bxEj5TU,3385
|
90
90
|
modal/_utils/deprecation.py,sha256=dycySRBxyZf3ITzEqPNM6MxXTk9-0VVLA8oCPQ5j_Os,3426
|
91
|
-
modal/_utils/docker_utils.py,sha256=
|
91
|
+
modal/_utils/docker_utils.py,sha256=h1uETghR40mp_y3fSWuZAfbIASH1HMzuphJHghAL6DU,3722
|
92
92
|
modal/_utils/function_utils.py,sha256=q68HhFH16MwhHRnGD8jvIgqDjduRQVp3a_qMWXPyrgU,25518
|
93
93
|
modal/_utils/grpc_testing.py,sha256=H1zHqthv19eGPJz2HKXDyWXWGSqO4BRsxah3L5Xaa8A,8619
|
94
94
|
modal/_utils/grpc_utils.py,sha256=PPB5ay-vXencXNIWPVw5modr3EH7gfq2QPcO5YJ1lMU,7737
|
@@ -163,12 +163,12 @@ modal_proto/options_pb2.pyi,sha256=l7DBrbLO7q3Ir-XDkWsajm0d0TQqqrfuX54i4BMpdQg,1
|
|
163
163
|
modal_proto/options_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
164
164
|
modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0yJSI,247
|
165
165
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
166
|
-
modal_version/__init__.py,sha256=
|
166
|
+
modal_version/__init__.py,sha256=kGya2ZlItX2zB7oHORs-wvP4PG8lg_mtbi1QIK3G6SQ,470
|
167
167
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
168
|
-
modal_version/_version_generated.py,sha256=
|
169
|
-
modal-0.
|
170
|
-
modal-0.
|
171
|
-
modal-0.
|
172
|
-
modal-0.
|
173
|
-
modal-0.
|
174
|
-
modal-0.
|
168
|
+
modal_version/_version_generated.py,sha256=asLMznMvSNVF3O1wHlqhvUWpAwCW2KJ8H_bGT-L07Sc,148
|
169
|
+
modal-0.72.0.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
170
|
+
modal-0.72.0.dist-info/METADATA,sha256=EdsneAri5o5Dm_88RJRdew5jM-Lfib7aLReAaJ5E0hY,2328
|
171
|
+
modal-0.72.0.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
172
|
+
modal-0.72.0.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
173
|
+
modal-0.72.0.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
|
174
|
+
modal-0.72.0.dist-info/RECORD,,
|
modal_version/__init__.py
CHANGED
@@ -7,7 +7,7 @@ from ._version_generated import build_number
|
|
7
7
|
major_number = 0
|
8
8
|
|
9
9
|
# Bump this manually on breaking changes, then reset the number in _version_generated.py
|
10
|
-
minor_number =
|
10
|
+
minor_number = 72
|
11
11
|
|
12
12
|
# Right now, automatically increment the patch number in CI
|
13
13
|
__version__ = f"{major_number}.{minor_number}.{max(build_number, 0)}"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|