modal 0.68.55__py3-none-any.whl → 0.69.1__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/_runtime/container_io_manager.py +9 -19
- modal/_utils/docker_utils.py +58 -0
- modal/client.pyi +2 -2
- modal/file_pattern_matcher.py +11 -1
- modal/functions.pyi +6 -6
- modal/image.py +95 -39
- modal/image.pyi +11 -2
- modal/mount.py +3 -5
- modal/mount.pyi +4 -4
- modal/runner.py +9 -5
- modal/running_app.py +27 -1
- {modal-0.68.55.dist-info → modal-0.69.1.dist-info}/METADATA +1 -1
- {modal-0.68.55.dist-info → modal-0.69.1.dist-info}/RECORD +19 -18
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +1 -1
- {modal-0.68.55.dist-info → modal-0.69.1.dist-info}/LICENSE +0 -0
- {modal-0.68.55.dist-info → modal-0.69.1.dist-info}/WHEEL +0 -0
- {modal-0.68.55.dist-info → modal-0.69.1.dist-info}/entry_points.txt +0 -0
- {modal-0.68.55.dist-info → modal-0.69.1.dist-info}/top_level.txt +0 -0
@@ -21,7 +21,6 @@ from typing import (
|
|
21
21
|
)
|
22
22
|
|
23
23
|
from google.protobuf.empty_pb2 import Empty
|
24
|
-
from google.protobuf.message import Message
|
25
24
|
from grpclib import Status
|
26
25
|
from synchronicity.async_wrap import asynccontextmanager
|
27
26
|
|
@@ -31,12 +30,12 @@ from modal._traceback import extract_traceback, print_exception
|
|
31
30
|
from modal._utils.async_utils import TaskContext, asyncify, synchronize_api, synchronizer
|
32
31
|
from modal._utils.blob_utils import MAX_OBJECT_SIZE_BYTES, blob_download, blob_upload
|
33
32
|
from modal._utils.function_utils import _stream_function_call_data
|
34
|
-
from modal._utils.grpc_utils import
|
33
|
+
from modal._utils.grpc_utils import retry_transient_errors
|
35
34
|
from modal._utils.package_utils import parse_major_minor_version
|
36
35
|
from modal.client import HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT, _Client
|
37
36
|
from modal.config import config, logger
|
38
37
|
from modal.exception import ClientClosed, InputCancellation, InvalidError, SerializationError
|
39
|
-
from modal.running_app import RunningApp
|
38
|
+
from modal.running_app import RunningApp, running_app_from_layout
|
40
39
|
from modal_proto import api_pb2
|
41
40
|
|
42
41
|
if TYPE_CHECKING:
|
@@ -451,24 +450,15 @@ class _ContainerIOManager:
|
|
451
450
|
await asyncio.sleep(DYNAMIC_CONCURRENCY_INTERVAL_SECS)
|
452
451
|
|
453
452
|
async def get_app_objects(self) -> RunningApp:
|
454
|
-
req = api_pb2.
|
455
|
-
resp = await retry_transient_errors(self._client.stub.
|
456
|
-
logger.debug(f"
|
457
|
-
|
458
|
-
|
459
|
-
object_handle_metadata = {}
|
460
|
-
for item in resp.items:
|
461
|
-
handle_metadata: Optional[Message] = get_proto_oneof(item.object, "handle_metadata_oneof")
|
462
|
-
object_handle_metadata[item.object.object_id] = handle_metadata
|
463
|
-
if item.tag:
|
464
|
-
tag_to_object_id[item.tag] = item.object.object_id
|
465
|
-
|
466
|
-
return RunningApp(
|
453
|
+
req = api_pb2.AppGetLayoutRequest(app_id=self.app_id)
|
454
|
+
resp = await retry_transient_errors(self._client.stub.AppGetLayout, req)
|
455
|
+
logger.debug(f"AppGetLayout received {len(resp.app_layout.objects)} objects for app {self.app_id}")
|
456
|
+
|
457
|
+
return running_app_from_layout(
|
467
458
|
self.app_id,
|
459
|
+
resp.app_layout,
|
460
|
+
self._client,
|
468
461
|
environment_name=self._environment_name,
|
469
|
-
tag_to_object_id=tag_to_object_id,
|
470
|
-
object_handle_metadata=object_handle_metadata,
|
471
|
-
client=self._client,
|
472
462
|
)
|
473
463
|
|
474
464
|
async def get_serialized_function(self) -> tuple[Optional[Any], Optional[Callable[..., Any]]]:
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Copyright Modal Labs 2024
|
2
|
+
import re
|
3
|
+
import shlex
|
4
|
+
from typing import Sequence
|
5
|
+
|
6
|
+
from ..exception import InvalidError
|
7
|
+
|
8
|
+
|
9
|
+
def extract_copy_command_patterns(dockerfile_lines: Sequence[str]) -> list[str]:
|
10
|
+
"""
|
11
|
+
Extract all COPY command sources from a Dockerfile.
|
12
|
+
Combines multiline COPY commands into a single line.
|
13
|
+
"""
|
14
|
+
copy_source_patterns: set[str] = set()
|
15
|
+
current_command = ""
|
16
|
+
copy_pattern = re.compile(r"^\s*COPY\s+(.+)$", re.IGNORECASE)
|
17
|
+
|
18
|
+
# First pass: handle line continuations and collect full commands
|
19
|
+
for line in dockerfile_lines:
|
20
|
+
line = line.strip()
|
21
|
+
if not line or line.startswith("#"):
|
22
|
+
# ignore comments and empty lines
|
23
|
+
continue
|
24
|
+
|
25
|
+
if current_command:
|
26
|
+
# Continue previous line
|
27
|
+
current_command += " " + line.rstrip("\\").strip()
|
28
|
+
else:
|
29
|
+
# Start new command
|
30
|
+
current_command = line.rstrip("\\").strip()
|
31
|
+
|
32
|
+
if not line.endswith("\\"):
|
33
|
+
# Command is complete
|
34
|
+
|
35
|
+
match = copy_pattern.match(current_command)
|
36
|
+
if match:
|
37
|
+
args = match.group(1)
|
38
|
+
parts = shlex.split(args)
|
39
|
+
|
40
|
+
if len(parts) >= 2:
|
41
|
+
# Last part is destination, everything else is a mount source
|
42
|
+
sources = parts[:-1]
|
43
|
+
|
44
|
+
for source in sources:
|
45
|
+
special_pattern = re.compile(r"^\s*--|\$\s*")
|
46
|
+
if special_pattern.match(source):
|
47
|
+
raise InvalidError(
|
48
|
+
f"COPY command: {source} using special flags/arguments/variables are not supported"
|
49
|
+
)
|
50
|
+
|
51
|
+
if source == ".":
|
52
|
+
copy_source_patterns.add("./**")
|
53
|
+
else:
|
54
|
+
copy_source_patterns.add(source)
|
55
|
+
|
56
|
+
current_command = ""
|
57
|
+
|
58
|
+
return list(copy_source_patterns)
|
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.69.1"
|
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.69.1"
|
85
85
|
): ...
|
86
86
|
def is_closed(self) -> bool: ...
|
87
87
|
@property
|
modal/file_pattern_matcher.py
CHANGED
@@ -12,7 +12,7 @@ then asking it whether file paths match any of its patterns.
|
|
12
12
|
import os
|
13
13
|
from abc import abstractmethod
|
14
14
|
from pathlib import Path
|
15
|
-
from typing import Callable, Optional
|
15
|
+
from typing import Callable, Optional, Sequence, Union
|
16
16
|
|
17
17
|
from ._utils.pattern_utils import Pattern
|
18
18
|
|
@@ -152,3 +152,13 @@ class FilePatternMatcher(_AbstractPatternMatcher):
|
|
152
152
|
# with_repr allows us to use this matcher as a default value in a function signature
|
153
153
|
# and get a nice repr in the docs and auto-generated type stubs:
|
154
154
|
NON_PYTHON_FILES = (~FilePatternMatcher("**/*.py")).with_repr(f"{__name__}.NON_PYTHON_FILES")
|
155
|
+
_NOTHING = (~FilePatternMatcher()).with_repr(f"{__name__}._NOTHING") # match everything = ignore nothing
|
156
|
+
|
157
|
+
|
158
|
+
def _ignore_fn(ignore: Union[Sequence[str], Callable[[Path], bool]]) -> Callable[[Path], bool]:
|
159
|
+
# if a callable is passed, return it
|
160
|
+
# otherwise, treat input as a sequence of patterns and return a callable pattern matcher for those
|
161
|
+
if callable(ignore):
|
162
|
+
return ignore
|
163
|
+
|
164
|
+
return FilePatternMatcher(*ignore)
|
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
@@ -31,6 +31,9 @@ from ._serialization import serialize
|
|
31
31
|
from ._utils.async_utils import synchronize_api
|
32
32
|
from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
|
33
33
|
from ._utils.deprecation import deprecation_error, deprecation_warning
|
34
|
+
from ._utils.docker_utils import (
|
35
|
+
extract_copy_command_patterns,
|
36
|
+
)
|
34
37
|
from ._utils.function_utils import FunctionInfo
|
35
38
|
from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES, retry_transient_errors
|
36
39
|
from .client import _Client
|
@@ -38,7 +41,7 @@ from .cloud_bucket_mount import _CloudBucketMount
|
|
38
41
|
from .config import config, logger, user_config_path
|
39
42
|
from .environments import _get_environment_cached
|
40
43
|
from .exception import InvalidError, NotFoundError, RemoteError, VersionError
|
41
|
-
from .file_pattern_matcher import NON_PYTHON_FILES
|
44
|
+
from .file_pattern_matcher import NON_PYTHON_FILES, FilePatternMatcher, _ignore_fn
|
42
45
|
from .gpu import GPU_T, parse_gpu_config
|
43
46
|
from .mount import _Mount, python_standalone_mount_name
|
44
47
|
from .network_file_system import _NetworkFileSystem
|
@@ -236,6 +239,33 @@ def _get_image_builder_version(server_version: ImageBuilderVersion) -> ImageBuil
|
|
236
239
|
return version
|
237
240
|
|
238
241
|
|
242
|
+
def _create_context_mount(
|
243
|
+
docker_commands: Sequence[str],
|
244
|
+
ignore_fn: Callable[[Path], bool],
|
245
|
+
context_dir: Path,
|
246
|
+
) -> Optional[_Mount]:
|
247
|
+
"""
|
248
|
+
Creates a context mount from a list of docker commands.
|
249
|
+
|
250
|
+
1. Paths are evaluated relative to context_dir.
|
251
|
+
2. First selects inclusions based on COPY commands in the list of commands.
|
252
|
+
3. Then ignore any files as per the ignore predicate.
|
253
|
+
"""
|
254
|
+
copy_patterns = extract_copy_command_patterns(docker_commands)
|
255
|
+
if not copy_patterns:
|
256
|
+
return None # no mount needed
|
257
|
+
include_fn = FilePatternMatcher(*copy_patterns)
|
258
|
+
|
259
|
+
def ignore_with_include(source: Path) -> bool:
|
260
|
+
relative_source = source.relative_to(context_dir)
|
261
|
+
if not include_fn(relative_source) or ignore_fn(relative_source):
|
262
|
+
return True
|
263
|
+
|
264
|
+
return False
|
265
|
+
|
266
|
+
return _Mount._add_local_dir(Path("./"), PurePosixPath("/"), ignore=ignore_with_include)
|
267
|
+
|
268
|
+
|
239
269
|
class _ImageRegistryConfig:
|
240
270
|
"""mdmd:hidden"""
|
241
271
|
|
@@ -396,7 +426,7 @@ class _Image(_Object, type_prefix="im"):
|
|
396
426
|
build_function: Optional["modal.functions._Function"] = None,
|
397
427
|
build_function_input: Optional[api_pb2.FunctionInput] = None,
|
398
428
|
image_registry_config: Optional[_ImageRegistryConfig] = None,
|
399
|
-
|
429
|
+
context_mount_function: Optional[Callable[[], Optional[_Mount]]] = None,
|
400
430
|
force_build: bool = False,
|
401
431
|
# For internal use only.
|
402
432
|
_namespace: "api_pb2.DeploymentNamespace.ValueType" = api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
@@ -423,13 +453,15 @@ class _Image(_Object, type_prefix="im"):
|
|
423
453
|
deps = tuple(base_images.values()) + tuple(secrets)
|
424
454
|
if build_function:
|
425
455
|
deps += (build_function,)
|
426
|
-
if context_mount:
|
427
|
-
deps += (context_mount,)
|
428
456
|
if image_registry_config and image_registry_config.secret:
|
429
457
|
deps += (image_registry_config.secret,)
|
430
458
|
return deps
|
431
459
|
|
432
460
|
async def _load(self: _Image, resolver: Resolver, existing_object_id: Optional[str]):
|
461
|
+
context_mount = context_mount_function() if context_mount_function else None
|
462
|
+
if context_mount:
|
463
|
+
await resolver.load(context_mount)
|
464
|
+
|
433
465
|
if _do_assert_no_mount_layers:
|
434
466
|
for image in base_images.values():
|
435
467
|
# base images can't have
|
@@ -596,7 +628,7 @@ class _Image(_Object, type_prefix="im"):
|
|
596
628
|
return _Image._from_args(
|
597
629
|
base_images={"base": self},
|
598
630
|
dockerfile_function=build_dockerfile,
|
599
|
-
|
631
|
+
context_mount_function=lambda: mount,
|
600
632
|
)
|
601
633
|
|
602
634
|
def add_local_file(self, local_path: Union[str, Path], remote_path: str, *, copy: bool = False) -> "_Image":
|
@@ -684,7 +716,7 @@ class _Image(_Object, type_prefix="im"):
|
|
684
716
|
# + make default remote_path="./"
|
685
717
|
raise InvalidError("image.add_local_dir() currently only supports absolute remote_path values")
|
686
718
|
|
687
|
-
mount = _Mount._add_local_dir(Path(local_path),
|
719
|
+
mount = _Mount._add_local_dir(Path(local_path), PurePosixPath(remote_path), ignore=_ignore_fn(ignore))
|
688
720
|
return self._add_mount_layer_or_copy(mount, copy=copy)
|
689
721
|
|
690
722
|
def copy_local_file(self, local_path: Union[str, Path], remote_path: Union[str, Path] = "./") -> "_Image":
|
@@ -695,7 +727,6 @@ class _Image(_Object, type_prefix="im"):
|
|
695
727
|
"""
|
696
728
|
# TODO(elias): add pending deprecation with suggestion to use add_* instead
|
697
729
|
basename = str(Path(local_path).name)
|
698
|
-
mount = _Mount.from_local_file(local_path, remote_path=f"/{basename}")
|
699
730
|
|
700
731
|
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
701
732
|
return DockerfileSpec(commands=["FROM base", f"COPY {basename} {remote_path}"], context_files={})
|
@@ -703,7 +734,7 @@ class _Image(_Object, type_prefix="im"):
|
|
703
734
|
return _Image._from_args(
|
704
735
|
base_images={"base": self},
|
705
736
|
dockerfile_function=build_dockerfile,
|
706
|
-
|
737
|
+
context_mount_function=lambda: _Mount.from_local_file(local_path, remote_path=f"/{basename}"),
|
707
738
|
)
|
708
739
|
|
709
740
|
def add_local_python_source(
|
@@ -790,15 +821,15 @@ class _Image(_Object, type_prefix="im"):
|
|
790
821
|
```
|
791
822
|
"""
|
792
823
|
|
793
|
-
mount = _Mount._add_local_dir(Path(local_path), Path("/"), ignore)
|
794
|
-
|
795
824
|
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
796
825
|
return DockerfileSpec(commands=["FROM base", f"COPY . {remote_path}"], context_files={})
|
797
826
|
|
798
827
|
return _Image._from_args(
|
799
828
|
base_images={"base": self},
|
800
829
|
dockerfile_function=build_dockerfile,
|
801
|
-
|
830
|
+
context_mount_function=lambda: _Mount._add_local_dir(
|
831
|
+
Path(local_path), PurePosixPath("/"), ignore=_ignore_fn(ignore)
|
832
|
+
),
|
802
833
|
)
|
803
834
|
|
804
835
|
def pip_install(
|
@@ -1156,12 +1187,29 @@ class _Image(_Object, type_prefix="im"):
|
|
1156
1187
|
# modal.Mount with local files to supply as build context for COPY commands
|
1157
1188
|
context_mount: Optional[_Mount] = None,
|
1158
1189
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
1190
|
+
ignore: Union[Sequence[str], Callable[[Path], bool]] = (),
|
1159
1191
|
) -> "_Image":
|
1160
1192
|
"""Extend an image with arbitrary Dockerfile-like commands."""
|
1161
1193
|
cmds = _flatten_str_args("dockerfile_commands", "dockerfile_commands", dockerfile_commands)
|
1162
1194
|
if not cmds:
|
1163
1195
|
return self
|
1164
1196
|
|
1197
|
+
if context_mount:
|
1198
|
+
if ignore:
|
1199
|
+
raise InvalidError("Cannot set both `context_mount` and `ignore`")
|
1200
|
+
|
1201
|
+
def identity_context_mount_fn() -> Optional[_Mount]:
|
1202
|
+
return context_mount
|
1203
|
+
|
1204
|
+
context_mount_function = identity_context_mount_fn
|
1205
|
+
else:
|
1206
|
+
|
1207
|
+
def auto_created_context_mount_fn() -> Optional[_Mount]:
|
1208
|
+
# use COPY commands and ignore patterns to construct implicit context mount
|
1209
|
+
return _create_context_mount(cmds, ignore_fn=_ignore_fn(ignore), context_dir=Path.cwd())
|
1210
|
+
|
1211
|
+
context_mount_function = auto_created_context_mount_fn
|
1212
|
+
|
1165
1213
|
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
1166
1214
|
return DockerfileSpec(commands=["FROM base", *cmds], context_files=context_files)
|
1167
1215
|
|
@@ -1170,7 +1218,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1170
1218
|
dockerfile_function=build_dockerfile,
|
1171
1219
|
secrets=secrets,
|
1172
1220
|
gpu_config=parse_gpu_config(gpu),
|
1173
|
-
|
1221
|
+
context_mount_function=context_mount_function,
|
1174
1222
|
force_build=self.force_build or force_build,
|
1175
1223
|
)
|
1176
1224
|
|
@@ -1400,11 +1448,15 @@ class _Image(_Object, type_prefix="im"):
|
|
1400
1448
|
modal.Image.from_registry("nvcr.io/nvidia/pytorch:22.12-py3")
|
1401
1449
|
```
|
1402
1450
|
"""
|
1403
|
-
|
1404
|
-
|
1405
|
-
|
1406
|
-
|
1407
|
-
|
1451
|
+
|
1452
|
+
def context_mount_function() -> Optional[_Mount]:
|
1453
|
+
return (
|
1454
|
+
_Mount.from_name(
|
1455
|
+
python_standalone_mount_name(add_python),
|
1456
|
+
namespace=api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL,
|
1457
|
+
)
|
1458
|
+
if add_python
|
1459
|
+
else None
|
1408
1460
|
)
|
1409
1461
|
|
1410
1462
|
if "image_registry_config" not in kwargs and secret is not None:
|
@@ -1417,7 +1469,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1417
1469
|
|
1418
1470
|
return _Image._from_args(
|
1419
1471
|
dockerfile_function=build_dockerfile,
|
1420
|
-
|
1472
|
+
context_mount_function=context_mount_function,
|
1421
1473
|
force_build=force_build,
|
1422
1474
|
**kwargs,
|
1423
1475
|
)
|
@@ -1531,6 +1583,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1531
1583
|
secrets: Sequence[_Secret] = [],
|
1532
1584
|
gpu: GPU_T = None,
|
1533
1585
|
add_python: Optional[str] = None,
|
1586
|
+
ignore: Union[Sequence[str], Callable[[Path], bool]] = (),
|
1534
1587
|
) -> "_Image":
|
1535
1588
|
"""Build a Modal image from a local Dockerfile.
|
1536
1589
|
|
@@ -1542,22 +1595,23 @@ class _Image(_Object, type_prefix="im"):
|
|
1542
1595
|
```python
|
1543
1596
|
image = modal.Image.from_dockerfile("./Dockerfile", add_python="3.12")
|
1544
1597
|
```
|
1598
|
+
"""
|
1545
1599
|
|
1546
|
-
|
1547
|
-
|
1600
|
+
if context_mount:
|
1601
|
+
if ignore:
|
1602
|
+
raise InvalidError("Cannot set both `context_mount` and `ignore`")
|
1548
1603
|
|
1549
|
-
|
1550
|
-
|
1551
|
-
"./Dockerfile",
|
1552
|
-
context_mount=modal.Mount.from_local_dir(
|
1553
|
-
local_path="src",
|
1554
|
-
remote_path=".", # to current WORKDIR
|
1555
|
-
),
|
1556
|
-
)
|
1557
|
-
```
|
1604
|
+
def identity_context_mount_fn() -> Optional[_Mount]:
|
1605
|
+
return context_mount
|
1558
1606
|
|
1559
|
-
|
1560
|
-
|
1607
|
+
context_mount_function = identity_context_mount_fn
|
1608
|
+
else:
|
1609
|
+
|
1610
|
+
def auto_created_context_mount_fn() -> Optional[_Mount]:
|
1611
|
+
lines = Path(path).read_text("utf8").splitlines()
|
1612
|
+
return _create_context_mount(lines, ignore_fn=_ignore_fn(ignore), context_dir=Path.cwd())
|
1613
|
+
|
1614
|
+
context_mount_function = auto_created_context_mount_fn
|
1561
1615
|
|
1562
1616
|
# --- Build the base dockerfile
|
1563
1617
|
|
@@ -1569,7 +1623,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1569
1623
|
gpu_config = parse_gpu_config(gpu)
|
1570
1624
|
base_image = _Image._from_args(
|
1571
1625
|
dockerfile_function=build_dockerfile_base,
|
1572
|
-
|
1626
|
+
context_mount_function=context_mount_function,
|
1573
1627
|
gpu_config=gpu_config,
|
1574
1628
|
secrets=secrets,
|
1575
1629
|
)
|
@@ -1578,13 +1632,15 @@ class _Image(_Object, type_prefix="im"):
|
|
1578
1632
|
# This happening in two steps is probably a vestigial consequence of previous limitations,
|
1579
1633
|
# but it will be difficult to merge them without forcing rebuilds of images.
|
1580
1634
|
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1635
|
+
def add_python_mount():
|
1636
|
+
return (
|
1637
|
+
_Mount.from_name(
|
1638
|
+
python_standalone_mount_name(add_python),
|
1639
|
+
namespace=api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL,
|
1640
|
+
)
|
1641
|
+
if add_python
|
1642
|
+
else None
|
1585
1643
|
)
|
1586
|
-
else:
|
1587
|
-
context_mount = None
|
1588
1644
|
|
1589
1645
|
def build_dockerfile_python(version: ImageBuilderVersion) -> DockerfileSpec:
|
1590
1646
|
commands = _Image._registry_setup_commands("base", version, [], add_python)
|
@@ -1595,7 +1651,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1595
1651
|
return _Image._from_args(
|
1596
1652
|
base_images={"base": base_image},
|
1597
1653
|
dockerfile_function=build_dockerfile_python,
|
1598
|
-
|
1654
|
+
context_mount_function=add_python_mount,
|
1599
1655
|
force_build=force_build,
|
1600
1656
|
)
|
1601
1657
|
|
modal/image.pyi
CHANGED
@@ -44,6 +44,11 @@ def _make_pip_install_args(
|
|
44
44
|
def _get_image_builder_version(
|
45
45
|
server_version: typing.Literal["2023.12", "2024.04", "2024.10"],
|
46
46
|
) -> typing.Literal["2023.12", "2024.04", "2024.10"]: ...
|
47
|
+
def _create_context_mount(
|
48
|
+
docker_commands: collections.abc.Sequence[str],
|
49
|
+
ignore_fn: typing.Callable[[pathlib.Path], bool],
|
50
|
+
context_dir: pathlib.Path,
|
51
|
+
) -> typing.Optional[modal.mount._Mount]: ...
|
47
52
|
|
48
53
|
class _ImageRegistryConfig:
|
49
54
|
def __init__(self, registry_auth_type: int = 0, secret: typing.Optional[modal.secret._Secret] = None): ...
|
@@ -87,7 +92,7 @@ class _Image(modal.object._Object):
|
|
87
92
|
build_function: typing.Optional[modal.functions._Function] = None,
|
88
93
|
build_function_input: typing.Optional[modal_proto.api_pb2.FunctionInput] = None,
|
89
94
|
image_registry_config: typing.Optional[_ImageRegistryConfig] = None,
|
90
|
-
|
95
|
+
context_mount_function: typing.Optional[typing.Callable[[], typing.Optional[modal.mount._Mount]]] = None,
|
91
96
|
force_build: bool = False,
|
92
97
|
_namespace: int = 1,
|
93
98
|
_do_assert_no_mount_layers: bool = True,
|
@@ -195,6 +200,7 @@ class _Image(modal.object._Object):
|
|
195
200
|
gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
|
196
201
|
context_mount: typing.Optional[modal.mount._Mount] = None,
|
197
202
|
force_build: bool = False,
|
203
|
+
ignore: typing.Union[collections.abc.Sequence[str], typing.Callable[[pathlib.Path], bool]] = (),
|
198
204
|
) -> _Image: ...
|
199
205
|
def entrypoint(self, entrypoint_commands: list[str]) -> _Image: ...
|
200
206
|
def shell(self, shell_commands: list[str]) -> _Image: ...
|
@@ -280,6 +286,7 @@ class _Image(modal.object._Object):
|
|
280
286
|
secrets: collections.abc.Sequence[modal.secret._Secret] = [],
|
281
287
|
gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
|
282
288
|
add_python: typing.Optional[str] = None,
|
289
|
+
ignore: typing.Union[collections.abc.Sequence[str], typing.Callable[[pathlib.Path], bool]] = (),
|
283
290
|
) -> _Image: ...
|
284
291
|
@staticmethod
|
285
292
|
def debian_slim(python_version: typing.Optional[str] = None, force_build: bool = False) -> _Image: ...
|
@@ -346,7 +353,7 @@ class Image(modal.object.Object):
|
|
346
353
|
build_function: typing.Optional[modal.functions.Function] = None,
|
347
354
|
build_function_input: typing.Optional[modal_proto.api_pb2.FunctionInput] = None,
|
348
355
|
image_registry_config: typing.Optional[_ImageRegistryConfig] = None,
|
349
|
-
|
356
|
+
context_mount_function: typing.Optional[typing.Callable[[], typing.Optional[modal.mount.Mount]]] = None,
|
350
357
|
force_build: bool = False,
|
351
358
|
_namespace: int = 1,
|
352
359
|
_do_assert_no_mount_layers: bool = True,
|
@@ -454,6 +461,7 @@ class Image(modal.object.Object):
|
|
454
461
|
gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
|
455
462
|
context_mount: typing.Optional[modal.mount.Mount] = None,
|
456
463
|
force_build: bool = False,
|
464
|
+
ignore: typing.Union[collections.abc.Sequence[str], typing.Callable[[pathlib.Path], bool]] = (),
|
457
465
|
) -> Image: ...
|
458
466
|
def entrypoint(self, entrypoint_commands: list[str]) -> Image: ...
|
459
467
|
def shell(self, shell_commands: list[str]) -> Image: ...
|
@@ -539,6 +547,7 @@ class Image(modal.object.Object):
|
|
539
547
|
secrets: collections.abc.Sequence[modal.secret.Secret] = [],
|
540
548
|
gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
|
541
549
|
add_python: typing.Optional[str] = None,
|
550
|
+
ignore: typing.Union[collections.abc.Sequence[str], typing.Callable[[pathlib.Path], bool]] = (),
|
542
551
|
) -> Image: ...
|
543
552
|
@staticmethod
|
544
553
|
def debian_slim(python_version: typing.Optional[str] = None, force_build: bool = False) -> Image: ...
|
modal/mount.py
CHANGED
@@ -16,6 +16,7 @@ from typing import Callable, Optional, Sequence, Union
|
|
16
16
|
from google.protobuf.message import Message
|
17
17
|
|
18
18
|
import modal.exception
|
19
|
+
import modal.file_pattern_matcher
|
19
20
|
from modal_proto import api_pb2
|
20
21
|
from modal_version import __version__
|
21
22
|
|
@@ -325,12 +326,9 @@ class _Mount(_Object, type_prefix="mo"):
|
|
325
326
|
@staticmethod
|
326
327
|
def _add_local_dir(
|
327
328
|
local_path: Path,
|
328
|
-
remote_path:
|
329
|
-
ignore:
|
329
|
+
remote_path: PurePosixPath,
|
330
|
+
ignore: Callable[[Path], bool] = modal.file_pattern_matcher._NOTHING,
|
330
331
|
):
|
331
|
-
if isinstance(ignore, list):
|
332
|
-
ignore = FilePatternMatcher(*ignore)
|
333
|
-
|
334
332
|
return _Mount._new()._extend(
|
335
333
|
_MountDir(
|
336
334
|
local_dir=local_path,
|
modal/mount.pyi
CHANGED
@@ -94,8 +94,8 @@ class _Mount(modal.object._Object):
|
|
94
94
|
@staticmethod
|
95
95
|
def _add_local_dir(
|
96
96
|
local_path: pathlib.Path,
|
97
|
-
remote_path: pathlib.
|
98
|
-
ignore: typing.
|
97
|
+
remote_path: pathlib.PurePosixPath,
|
98
|
+
ignore: typing.Callable[[pathlib.Path], bool] = modal.file_pattern_matcher._NOTHING,
|
99
99
|
): ...
|
100
100
|
def add_local_dir(
|
101
101
|
self,
|
@@ -176,8 +176,8 @@ class Mount(modal.object.Object):
|
|
176
176
|
@staticmethod
|
177
177
|
def _add_local_dir(
|
178
178
|
local_path: pathlib.Path,
|
179
|
-
remote_path: pathlib.
|
180
|
-
ignore: typing.
|
179
|
+
remote_path: pathlib.PurePosixPath,
|
180
|
+
ignore: typing.Callable[[pathlib.Path], bool] = modal.file_pattern_matcher._NOTHING,
|
181
181
|
): ...
|
182
182
|
def add_local_dir(
|
183
183
|
self,
|
modal/runner.py
CHANGED
@@ -30,7 +30,7 @@ from .exception import InteractiveTimeoutError, InvalidError, RemoteError, _CliU
|
|
30
30
|
from .functions import _Function
|
31
31
|
from .object import _get_environment_name, _Object
|
32
32
|
from .output import _get_output_manager, enable_output
|
33
|
-
from .running_app import RunningApp
|
33
|
+
from .running_app import RunningApp, running_app_from_layout
|
34
34
|
from .sandbox import _Sandbox
|
35
35
|
from .secret import _Secret
|
36
36
|
from .stream_type import StreamType
|
@@ -54,15 +54,19 @@ async def _heartbeat(client: _Client, app_id: str) -> None:
|
|
54
54
|
|
55
55
|
async def _init_local_app_existing(client: _Client, existing_app_id: str, environment_name: str) -> RunningApp:
|
56
56
|
# Get all the objects first
|
57
|
-
obj_req = api_pb2.
|
57
|
+
obj_req = api_pb2.AppGetLayoutRequest(app_id=existing_app_id)
|
58
58
|
obj_resp, _ = await gather_cancel_on_exc(
|
59
|
-
retry_transient_errors(client.stub.
|
59
|
+
retry_transient_errors(client.stub.AppGetLayout, obj_req),
|
60
60
|
# Cache the environment associated with the app now as we will use it later
|
61
61
|
_get_environment_cached(environment_name, client),
|
62
62
|
)
|
63
63
|
app_page_url = f"https://modal.com/apps/{existing_app_id}" # TODO (elias): this should come from the backend
|
64
|
-
|
65
|
-
|
64
|
+
return running_app_from_layout(
|
65
|
+
existing_app_id,
|
66
|
+
obj_resp.app_layout,
|
67
|
+
client,
|
68
|
+
app_page_url=app_page_url,
|
69
|
+
)
|
66
70
|
|
67
71
|
|
68
72
|
async def _init_local_app_new(
|
modal/running_app.py
CHANGED
@@ -4,16 +4,42 @@ from typing import Optional
|
|
4
4
|
|
5
5
|
from google.protobuf.message import Message
|
6
6
|
|
7
|
+
from modal._utils.grpc_utils import get_proto_oneof
|
8
|
+
from modal_proto import api_pb2
|
9
|
+
|
7
10
|
from .client import _Client
|
8
11
|
|
9
12
|
|
10
13
|
@dataclass
|
11
14
|
class RunningApp:
|
12
15
|
app_id: str
|
16
|
+
client: _Client
|
13
17
|
environment_name: Optional[str] = None
|
14
18
|
app_page_url: Optional[str] = None
|
15
19
|
app_logs_url: Optional[str] = None
|
16
20
|
tag_to_object_id: dict[str, str] = field(default_factory=dict)
|
17
21
|
object_handle_metadata: dict[str, Optional[Message]] = field(default_factory=dict)
|
18
22
|
interactive: bool = False
|
19
|
-
|
23
|
+
|
24
|
+
|
25
|
+
def running_app_from_layout(
|
26
|
+
app_id: str,
|
27
|
+
app_layout: api_pb2.AppLayout,
|
28
|
+
client: _Client,
|
29
|
+
environment_name: Optional[str] = None,
|
30
|
+
app_page_url: Optional[str] = None,
|
31
|
+
) -> RunningApp:
|
32
|
+
tag_to_object_id = dict(**app_layout.function_ids, **app_layout.class_ids)
|
33
|
+
object_handle_metadata = {}
|
34
|
+
for obj in app_layout.objects:
|
35
|
+
handle_metadata: Optional[Message] = get_proto_oneof(obj, "handle_metadata_oneof")
|
36
|
+
object_handle_metadata[obj.object_id] = handle_metadata
|
37
|
+
|
38
|
+
return RunningApp(
|
39
|
+
app_id,
|
40
|
+
client,
|
41
|
+
environment_name=environment_name,
|
42
|
+
tag_to_object_id=tag_to_object_id,
|
43
|
+
object_handle_metadata=object_handle_metadata,
|
44
|
+
app_page_url=app_page_url,
|
45
|
+
)
|
@@ -19,7 +19,7 @@ modal/app.py,sha256=JWefPs4yB70BKQwSZejB_4_muhxn63cC9UmnNvpQ9XY,45526
|
|
19
19
|
modal/app.pyi,sha256=FYPCEJNhof4YF6HIuNP_2yG6s2PgZnKW9tO1hFE6sfA,25194
|
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=7U3WKJizZ3WdJRt2qk42I5fnck6KLgjtjS1a3uvRXx8,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,16 +35,16 @@ modal/exception.py,sha256=GEV6xMnVnkle0gsFZVLB4B7cUMyw8HzVDvAvPr34ZV4,5185
|
|
35
35
|
modal/experimental.py,sha256=jFuNbwrNHos47viMB9q-cHJSvf2RDxDdoEcss9plaZE,2302
|
36
36
|
modal/file_io.py,sha256=pDOFNQU5m-x-k3oJauck4fOp3bZ55Vc-_LvSaN5_Bow,16465
|
37
37
|
modal/file_io.pyi,sha256=GMhCCRyMftXYI3HqI9EdGPOx70CbCNi-VC5Sfy5TYnc,7631
|
38
|
-
modal/file_pattern_matcher.py,sha256=
|
38
|
+
modal/file_pattern_matcher.py,sha256=LaI7Paxg0xR9D-D7Tgc60xR0w1KZee22LjGbFie1Vms,5571
|
39
39
|
modal/functions.py,sha256=aXXXr3rk7BCeh5OWMvxGksGm8FQoYCyrBDGV74FPoPE,67827
|
40
|
-
modal/functions.pyi,sha256=
|
40
|
+
modal/functions.pyi,sha256=snttn47K81lKhmrCLWNVZelZTDhNsbxtw4l1DlLDR74,25317
|
41
41
|
modal/gpu.py,sha256=MTxj6ql8EpgfBg8YmZ5a1cLznyuZFssX1qXbEX4LKVM,7503
|
42
|
-
modal/image.py,sha256=
|
43
|
-
modal/image.pyi,sha256=
|
42
|
+
modal/image.py,sha256=Krvcsclomp9YsqSNwFj2FoAyg10OvU47RDnsNCwjGbQ,84550
|
43
|
+
modal/image.pyi,sha256=1fgGvsL5Rb0Sa-_2OCgIyJ_QgHcL0_9MWD_oY7cyFFM,24937
|
44
44
|
modal/io_streams.py,sha256=QkQiizKRzd5bnbKQsap31LJgBYlAnj4-XkV_50xPYX0,15079
|
45
45
|
modal/io_streams.pyi,sha256=bCCVSxkMcosYd8O3PQDDwJw7TQ8JEcnYonLJ5t27TQs,4804
|
46
|
-
modal/mount.py,sha256=
|
47
|
-
modal/mount.pyi,sha256=
|
46
|
+
modal/mount.py,sha256=wOr-2vmKImsE3lHBII8hL2gYy5ng46R58QwId4JultQ,29313
|
47
|
+
modal/mount.pyi,sha256=FiNV1wIKFvd0ZMZ0tm1mz6ZSA5Hjsge-kFSA5tPWfcI,10503
|
48
48
|
modal/network_file_system.py,sha256=INj1TfN_Fsmabmlte7anvey1epodjbMmjBW_TIJSST4,14406
|
49
49
|
modal/network_file_system.pyi,sha256=61M-sdWrtaRjmuNVsvusI6kf1Qw-jUOVXvEAeOkM8Aw,7751
|
50
50
|
modal/object.py,sha256=HZs3N59C6JxlMuPQWJYvrWV1FEEkH9txUovVDorVUbs,9763
|
@@ -60,9 +60,9 @@ modal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
60
|
modal/queue.py,sha256=zMUQtdAyqZzBg-2iAo3c3G54HLP7TEWfVhiQXLjewb4,18556
|
61
61
|
modal/queue.pyi,sha256=gGV97pWelSSYqMV9Bl4ys3mSP7q82fS71oqSWeAwyDE,9818
|
62
62
|
modal/retries.py,sha256=HKR2Q9aNPWkMjQ5nwobqYTuZaSuw0a8lI2zrtY5IW98,5230
|
63
|
-
modal/runner.py,sha256=
|
63
|
+
modal/runner.py,sha256=1nPBsIfef2sOr2ebQ348EmDemvYFDhp1-_Gr3BKsjdM,24542
|
64
64
|
modal/runner.pyi,sha256=BvMS1ZVzWSn8B8q0KnIZOJKPkN5L-i5b-USbV6SWWHQ,5177
|
65
|
-
modal/running_app.py,sha256=
|
65
|
+
modal/running_app.py,sha256=FSr0XoL4mPLPCBMj2TozWuEvcvApdY_t68nUowwf8x4,1372
|
66
66
|
modal/sandbox.py,sha256=c-Qli3QJPN7bBQzsTk4iS51zurNlq--InZ2eRR-B6No,28106
|
67
67
|
modal/sandbox.pyi,sha256=k8_vHjN3oigxSCF13Cm2HfcSHuliGuSb8ryd3CGqwoA,19815
|
68
68
|
modal/schedule.py,sha256=0ZFpKs1bOxeo5n3HZjoL7OE2ktsb-_oGtq-WJEPO4tY,2615
|
@@ -78,7 +78,7 @@ modal/volume.py,sha256=T-pLxCYqmqRO6OolpAXlPxomMu0RWjti2e4kUpaj2cQ,29229
|
|
78
78
|
modal/volume.pyi,sha256=eekb2dnAAwFK_NO9ciAOOTthl8NP1iAmMFrCGgjDA2k,11100
|
79
79
|
modal/_runtime/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
80
80
|
modal/_runtime/asgi.py,sha256=Mjs859pSgOmtZL-YmEsSKN557v1A2Ax_5-ERgPfj55E,21920
|
81
|
-
modal/_runtime/container_io_manager.py,sha256=
|
81
|
+
modal/_runtime/container_io_manager.py,sha256=5d7ikjaejLmJX3axLvxkXkJCWTl8eZIr1NGKkE7m9kA,43698
|
82
82
|
modal/_runtime/execution_context.py,sha256=E6ofm6j1POXGPxS841X3V7JU6NheVb8OkQc7JpLq4Kg,2712
|
83
83
|
modal/_runtime/telemetry.py,sha256=T1RoAGyjBDr1swiM6pPsGRSITm7LI5FDK18oNXxY08U,5163
|
84
84
|
modal/_runtime/user_code_imports.py,sha256=n4CQOojzSdf0jwSKSy6MEnVX3IWl3t3Dq54-x9VS2Ds,14663
|
@@ -88,6 +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=rft2WVGhEgYik2zFRZbX63X57b2jnLyy88byykDH2Xo,1963
|
91
92
|
modal/_utils/function_utils.py,sha256=4LYFbNY5aHc96QitwP4Ty-dBl45SD1HjfZrvBFUF-ko,25343
|
92
93
|
modal/_utils/grpc_testing.py,sha256=H1zHqthv19eGPJz2HKXDyWXWGSqO4BRsxah3L5Xaa8A,8619
|
93
94
|
modal/_utils/grpc_utils.py,sha256=PPB5ay-vXencXNIWPVw5modr3EH7gfq2QPcO5YJ1lMU,7737
|
@@ -162,12 +163,12 @@ modal_proto/options_pb2.pyi,sha256=l7DBrbLO7q3Ir-XDkWsajm0d0TQqqrfuX54i4BMpdQg,1
|
|
162
163
|
modal_proto/options_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
163
164
|
modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0yJSI,247
|
164
165
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
165
|
-
modal_version/__init__.py,sha256=
|
166
|
+
modal_version/__init__.py,sha256=aBl2c-5ZPst-0n_E9DeJEfYYSifFGrDOwo9tpDwhyK8,470
|
166
167
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
167
|
-
modal_version/_version_generated.py,sha256=
|
168
|
-
modal-0.
|
169
|
-
modal-0.
|
170
|
-
modal-0.
|
171
|
-
modal-0.
|
172
|
-
modal-0.
|
173
|
-
modal-0.
|
168
|
+
modal_version/_version_generated.py,sha256=i5tv0oldHWm-ZUND3nzjQGAAW9h0-dhVC8LT02amWOo,148
|
169
|
+
modal-0.69.1.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
170
|
+
modal-0.69.1.dist-info/METADATA,sha256=7lw4rSs7pu1UUYNQ4OGoK14rZjZm3qwKrRm5-hX5CCA,2328
|
171
|
+
modal-0.69.1.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
172
|
+
modal-0.69.1.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
173
|
+
modal-0.69.1.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
|
174
|
+
modal-0.69.1.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 = 69
|
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
|