modal 0.62.115__py3-none-any.whl → 0.72.13__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/__init__.py +13 -9
- modal/__main__.py +41 -3
- modal/_clustered_functions.py +80 -0
- modal/_clustered_functions.pyi +22 -0
- modal/_container_entrypoint.py +402 -398
- modal/_ipython.py +3 -13
- modal/_location.py +17 -10
- modal/_output.py +243 -99
- modal/_pty.py +2 -2
- modal/_resolver.py +55 -60
- modal/_resources.py +26 -7
- modal/_runtime/__init__.py +1 -0
- modal/_runtime/asgi.py +519 -0
- modal/_runtime/container_io_manager.py +1025 -0
- modal/{execution_context.py → _runtime/execution_context.py} +11 -2
- modal/_runtime/telemetry.py +169 -0
- modal/_runtime/user_code_imports.py +356 -0
- modal/_serialization.py +123 -6
- modal/_traceback.py +47 -187
- modal/_tunnel.py +50 -14
- modal/_tunnel.pyi +19 -36
- modal/_utils/app_utils.py +3 -17
- modal/_utils/async_utils.py +386 -104
- modal/_utils/blob_utils.py +157 -186
- modal/_utils/bytes_io_segment_payload.py +97 -0
- modal/_utils/deprecation.py +89 -0
- modal/_utils/docker_utils.py +98 -0
- modal/_utils/function_utils.py +299 -98
- modal/_utils/grpc_testing.py +47 -34
- modal/_utils/grpc_utils.py +54 -21
- modal/_utils/hash_utils.py +51 -10
- modal/_utils/http_utils.py +39 -9
- modal/_utils/logger.py +2 -1
- modal/_utils/mount_utils.py +34 -16
- modal/_utils/name_utils.py +58 -0
- modal/_utils/package_utils.py +14 -1
- modal/_utils/pattern_utils.py +205 -0
- modal/_utils/rand_pb_testing.py +3 -3
- modal/_utils/shell_utils.py +15 -49
- modal/_vendor/a2wsgi_wsgi.py +62 -72
- modal/_vendor/cloudpickle.py +1 -1
- modal/_watcher.py +12 -10
- modal/app.py +561 -323
- modal/app.pyi +474 -262
- modal/call_graph.py +7 -6
- modal/cli/_download.py +22 -6
- modal/cli/_traceback.py +200 -0
- modal/cli/app.py +203 -42
- modal/cli/config.py +12 -5
- modal/cli/container.py +61 -13
- modal/cli/dict.py +128 -0
- modal/cli/entry_point.py +26 -13
- modal/cli/environment.py +40 -9
- modal/cli/import_refs.py +21 -48
- modal/cli/launch.py +28 -14
- modal/cli/network_file_system.py +57 -21
- modal/cli/profile.py +1 -1
- modal/cli/programs/run_jupyter.py +34 -9
- modal/cli/programs/vscode.py +58 -8
- modal/cli/queues.py +131 -0
- modal/cli/run.py +199 -96
- modal/cli/secret.py +5 -4
- modal/cli/token.py +7 -2
- modal/cli/utils.py +74 -8
- modal/cli/volume.py +97 -56
- modal/client.py +248 -144
- modal/client.pyi +156 -124
- modal/cloud_bucket_mount.py +43 -30
- modal/cloud_bucket_mount.pyi +32 -25
- modal/cls.py +528 -141
- modal/cls.pyi +189 -145
- modal/config.py +32 -15
- modal/container_process.py +177 -0
- modal/container_process.pyi +82 -0
- modal/dict.py +50 -54
- modal/dict.pyi +120 -164
- modal/environments.py +106 -5
- modal/environments.pyi +77 -25
- modal/exception.py +30 -43
- modal/experimental.py +62 -2
- modal/file_io.py +537 -0
- modal/file_io.pyi +235 -0
- modal/file_pattern_matcher.py +196 -0
- modal/functions.py +846 -428
- modal/functions.pyi +446 -387
- modal/gpu.py +57 -44
- modal/image.py +943 -417
- modal/image.pyi +584 -245
- modal/io_streams.py +434 -0
- modal/io_streams.pyi +122 -0
- modal/mount.py +223 -90
- modal/mount.pyi +241 -243
- modal/network_file_system.py +85 -86
- modal/network_file_system.pyi +151 -110
- modal/object.py +66 -36
- modal/object.pyi +166 -143
- modal/output.py +63 -0
- modal/parallel_map.py +73 -47
- modal/parallel_map.pyi +51 -63
- modal/partial_function.py +272 -107
- modal/partial_function.pyi +219 -120
- modal/proxy.py +15 -12
- modal/proxy.pyi +3 -8
- modal/queue.py +96 -72
- modal/queue.pyi +210 -135
- modal/requirements/2024.04.txt +2 -1
- modal/requirements/2024.10.txt +16 -0
- modal/requirements/README.md +21 -0
- modal/requirements/base-images.json +22 -0
- modal/retries.py +45 -4
- modal/runner.py +325 -203
- modal/runner.pyi +124 -110
- modal/running_app.py +27 -4
- modal/sandbox.py +509 -231
- modal/sandbox.pyi +396 -169
- modal/schedule.py +2 -2
- modal/scheduler_placement.py +20 -3
- modal/secret.py +41 -25
- modal/secret.pyi +62 -42
- modal/serving.py +39 -49
- modal/serving.pyi +37 -43
- modal/stream_type.py +15 -0
- modal/token_flow.py +5 -3
- modal/token_flow.pyi +37 -32
- modal/volume.py +123 -137
- modal/volume.pyi +228 -221
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/METADATA +5 -5
- modal-0.72.13.dist-info/RECORD +174 -0
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/top_level.txt +0 -1
- modal_docs/gen_reference_docs.py +3 -1
- modal_docs/mdmd/mdmd.py +0 -1
- modal_docs/mdmd/signatures.py +1 -2
- modal_global_objects/images/base_images.py +28 -0
- modal_global_objects/mounts/python_standalone.py +2 -2
- modal_proto/__init__.py +1 -1
- modal_proto/api.proto +1231 -531
- modal_proto/api_grpc.py +750 -430
- modal_proto/api_pb2.py +2102 -1176
- modal_proto/api_pb2.pyi +8859 -0
- modal_proto/api_pb2_grpc.py +1329 -675
- modal_proto/api_pb2_grpc.pyi +1416 -0
- modal_proto/modal_api_grpc.py +149 -0
- modal_proto/modal_options_grpc.py +3 -0
- modal_proto/options_pb2.pyi +20 -0
- modal_proto/options_pb2_grpc.pyi +7 -0
- modal_proto/py.typed +0 -0
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +2 -2
- modal/_asgi.py +0 -370
- modal/_container_exec.py +0 -128
- modal/_container_io_manager.py +0 -646
- modal/_container_io_manager.pyi +0 -412
- modal/_sandbox_shell.py +0 -49
- modal/app_utils.py +0 -20
- modal/app_utils.pyi +0 -17
- modal/execution_context.pyi +0 -37
- modal/shared_volume.py +0 -23
- modal/shared_volume.pyi +0 -24
- modal-0.62.115.dist-info/RECORD +0 -207
- modal_global_objects/images/conda.py +0 -15
- modal_global_objects/images/debian_slim.py +0 -15
- modal_global_objects/images/micromamba.py +0 -15
- test/__init__.py +0 -1
- test/aio_test.py +0 -12
- test/async_utils_test.py +0 -279
- test/blob_test.py +0 -67
- test/cli_imports_test.py +0 -149
- test/cli_test.py +0 -674
- test/client_test.py +0 -203
- test/cloud_bucket_mount_test.py +0 -22
- test/cls_test.py +0 -636
- test/config_test.py +0 -149
- test/conftest.py +0 -1485
- test/container_app_test.py +0 -50
- test/container_test.py +0 -1405
- test/cpu_test.py +0 -23
- test/decorator_test.py +0 -85
- test/deprecation_test.py +0 -34
- test/dict_test.py +0 -51
- test/e2e_test.py +0 -68
- test/error_test.py +0 -7
- test/function_serialization_test.py +0 -32
- test/function_test.py +0 -791
- test/function_utils_test.py +0 -101
- test/gpu_test.py +0 -159
- test/grpc_utils_test.py +0 -82
- test/helpers.py +0 -47
- test/image_test.py +0 -814
- test/live_reload_test.py +0 -80
- test/lookup_test.py +0 -70
- test/mdmd_test.py +0 -329
- test/mount_test.py +0 -162
- test/mounted_files_test.py +0 -327
- test/network_file_system_test.py +0 -188
- test/notebook_test.py +0 -66
- test/object_test.py +0 -41
- test/package_utils_test.py +0 -25
- test/queue_test.py +0 -115
- test/resolver_test.py +0 -59
- test/retries_test.py +0 -67
- test/runner_test.py +0 -85
- test/sandbox_test.py +0 -191
- test/schedule_test.py +0 -15
- test/scheduler_placement_test.py +0 -57
- test/secret_test.py +0 -89
- test/serialization_test.py +0 -50
- test/stub_composition_test.py +0 -10
- test/stub_test.py +0 -361
- test/test_asgi_wrapper.py +0 -234
- test/token_flow_test.py +0 -18
- test/traceback_test.py +0 -135
- test/tunnel_test.py +0 -29
- test/utils_test.py +0 -88
- test/version_test.py +0 -14
- test/volume_test.py +0 -397
- test/watcher_test.py +0 -58
- test/webhook_test.py +0 -145
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/LICENSE +0 -0
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/WHEEL +0 -0
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
# Copyright Modal Labs 2024
|
2
|
+
import functools
|
3
|
+
import sys
|
4
|
+
import warnings
|
5
|
+
from datetime import date
|
6
|
+
from typing import Any, Callable, TypeVar
|
7
|
+
|
8
|
+
from typing_extensions import ParamSpec # Needed for Python 3.9
|
9
|
+
|
10
|
+
from ..exception import DeprecationError, PendingDeprecationError
|
11
|
+
|
12
|
+
_INTERNAL_MODULES = ["modal", "synchronicity"]
|
13
|
+
|
14
|
+
|
15
|
+
def _is_internal_frame(frame):
|
16
|
+
module = frame.f_globals["__name__"].split(".")[0]
|
17
|
+
return module in _INTERNAL_MODULES
|
18
|
+
|
19
|
+
|
20
|
+
def deprecation_error(deprecated_on: tuple[int, int, int], msg: str):
|
21
|
+
raise DeprecationError(f"Deprecated on {date(*deprecated_on)}: {msg}")
|
22
|
+
|
23
|
+
|
24
|
+
def deprecation_warning(
|
25
|
+
deprecated_on: tuple[int, int, int], msg: str, *, pending: bool = False, show_source: bool = True
|
26
|
+
) -> None:
|
27
|
+
"""Issue a Modal deprecation warning with source optionally attributed to user code.
|
28
|
+
|
29
|
+
See the implementation of the built-in [warnings.warn](https://docs.python.org/3/library/warnings.html#available-functions).
|
30
|
+
"""
|
31
|
+
filename, lineno = "<unknown>", 0
|
32
|
+
if show_source:
|
33
|
+
# Find the last non-Modal line that triggered the warning
|
34
|
+
try:
|
35
|
+
frame = sys._getframe()
|
36
|
+
while frame is not None and _is_internal_frame(frame):
|
37
|
+
frame = frame.f_back
|
38
|
+
if frame is not None:
|
39
|
+
filename = frame.f_code.co_filename
|
40
|
+
lineno = frame.f_lineno
|
41
|
+
except ValueError:
|
42
|
+
# Use the defaults from above
|
43
|
+
pass
|
44
|
+
|
45
|
+
warning_cls = PendingDeprecationError if pending else DeprecationError
|
46
|
+
|
47
|
+
# This is a lower-level function that warnings.warn uses
|
48
|
+
warnings.warn_explicit(f"{date(*deprecated_on)}: {msg}", warning_cls, filename, lineno)
|
49
|
+
|
50
|
+
|
51
|
+
P = ParamSpec("P")
|
52
|
+
R = TypeVar("R")
|
53
|
+
|
54
|
+
|
55
|
+
def renamed_parameter(
|
56
|
+
date: tuple[int, int, int],
|
57
|
+
old_name: str,
|
58
|
+
new_name: str,
|
59
|
+
show_source: bool = True,
|
60
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
61
|
+
"""Decorator for semi-gracefully changing a parameter name.
|
62
|
+
|
63
|
+
Functions wrapped with this decorator can be defined using only the `new_name` of the parameter.
|
64
|
+
If the function is invoked with the `old_name`, the wrapper will pass the value as a keyword
|
65
|
+
argument for `new_name` and issue a Modal deprecation warning about the change.
|
66
|
+
|
67
|
+
Note that this only prevents parameter renamings from breaking code at runtime.
|
68
|
+
Type checking will fail when code uses `old_name`. To avoid this, the `old_name` can be
|
69
|
+
preserved in the function signature with an `Annotated` type hint indicating the renaming.
|
70
|
+
"""
|
71
|
+
|
72
|
+
def decorator(func: Callable[P, R]) -> Callable[P, R]:
|
73
|
+
@functools.wraps(func)
|
74
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
75
|
+
mut_kwargs: dict[str, Any] = locals()["kwargs"] # Avoid referencing kwargs directly due to bug in sigtools
|
76
|
+
if old_name in mut_kwargs:
|
77
|
+
mut_kwargs[new_name] = mut_kwargs.pop(old_name)
|
78
|
+
func_name = func.__qualname__.removeprefix("_") # Avoid confusion when synchronicity-wrapped
|
79
|
+
message = (
|
80
|
+
f"The '{old_name}' parameter of `{func_name}` has been renamed to '{new_name}'."
|
81
|
+
"\nUsing the old name will become an error in a future release. Please update your code."
|
82
|
+
)
|
83
|
+
deprecation_warning(date, message, show_source=show_source)
|
84
|
+
|
85
|
+
return func(*args, **kwargs)
|
86
|
+
|
87
|
+
return wrapper
|
88
|
+
|
89
|
+
return decorator
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# Copyright Modal Labs 2024
|
2
|
+
import re
|
3
|
+
import shlex
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Optional, Sequence
|
6
|
+
|
7
|
+
from ..exception import InvalidError
|
8
|
+
|
9
|
+
|
10
|
+
def extract_copy_command_patterns(dockerfile_lines: Sequence[str]) -> list[str]:
|
11
|
+
"""
|
12
|
+
Extract all COPY command sources from a Dockerfile.
|
13
|
+
Combines multiline COPY commands into a single line.
|
14
|
+
"""
|
15
|
+
copy_source_patterns: set[str] = set()
|
16
|
+
current_command = ""
|
17
|
+
copy_pattern = re.compile(r"^\s*COPY\s+(.+)$", re.IGNORECASE)
|
18
|
+
|
19
|
+
# First pass: handle line continuations and collect full commands
|
20
|
+
for line in dockerfile_lines:
|
21
|
+
line = line.strip()
|
22
|
+
if not line or line.startswith("#"):
|
23
|
+
# ignore comments and empty lines
|
24
|
+
continue
|
25
|
+
|
26
|
+
if current_command:
|
27
|
+
# Continue previous line
|
28
|
+
current_command += " " + line.rstrip("\\").strip()
|
29
|
+
else:
|
30
|
+
# Start new command
|
31
|
+
current_command = line.rstrip("\\").strip()
|
32
|
+
|
33
|
+
if not line.endswith("\\"):
|
34
|
+
# Command is complete
|
35
|
+
|
36
|
+
match = copy_pattern.match(current_command)
|
37
|
+
if match:
|
38
|
+
args = match.group(1)
|
39
|
+
parts = shlex.split(args)
|
40
|
+
|
41
|
+
# COPY --from=... commands reference external sources and do not need a context mount.
|
42
|
+
# https://docs.docker.com/reference/dockerfile/#copy---from
|
43
|
+
if parts[0].startswith("--from="):
|
44
|
+
current_command = ""
|
45
|
+
continue
|
46
|
+
|
47
|
+
if len(parts) >= 2:
|
48
|
+
# Last part is destination, everything else is a mount source
|
49
|
+
sources = parts[:-1]
|
50
|
+
|
51
|
+
for source in sources:
|
52
|
+
special_pattern = re.compile(r"^\s*--|\$\s*")
|
53
|
+
if special_pattern.match(source):
|
54
|
+
raise InvalidError(
|
55
|
+
f"COPY command: {source} using special flags/arguments/variables are not supported"
|
56
|
+
)
|
57
|
+
|
58
|
+
if source == ".":
|
59
|
+
copy_source_patterns.add("./**")
|
60
|
+
else:
|
61
|
+
copy_source_patterns.add(source)
|
62
|
+
|
63
|
+
current_command = ""
|
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)
|