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
modal/__init__.py
CHANGED
@@ -1,27 +1,29 @@
|
|
1
1
|
# Copyright Modal Labs 2022
|
2
2
|
import sys
|
3
3
|
|
4
|
-
if sys.version_info[:2] < (3,
|
5
|
-
raise RuntimeError("This version of Modal requires at least Python 3.
|
6
|
-
if sys.version_info[:2] >= (3,
|
7
|
-
raise RuntimeError("This version of Modal does not support Python 3.
|
4
|
+
if sys.version_info[:2] < (3, 9):
|
5
|
+
raise RuntimeError("This version of Modal requires at least Python 3.9")
|
6
|
+
if sys.version_info[:2] >= (3, 14):
|
7
|
+
raise RuntimeError("This version of Modal does not support Python 3.14+")
|
8
8
|
|
9
9
|
from modal_version import __version__
|
10
10
|
|
11
11
|
try:
|
12
|
+
from ._runtime.execution_context import current_function_call_id, current_input_id, interact, is_local
|
12
13
|
from ._tunnel import Tunnel, forward
|
13
14
|
from .app import App, Stub
|
14
15
|
from .client import Client
|
15
16
|
from .cloud_bucket_mount import CloudBucketMount
|
16
|
-
from .cls import Cls
|
17
|
+
from .cls import Cls, parameter
|
17
18
|
from .dict import Dict
|
18
19
|
from .exception import Error
|
19
|
-
from .
|
20
|
+
from .file_pattern_matcher import FilePatternMatcher
|
20
21
|
from .functions import Function
|
21
22
|
from .image import Image
|
22
23
|
from .mount import Mount
|
23
24
|
from .network_file_system import NetworkFileSystem
|
24
|
-
from .
|
25
|
+
from .output import enable_output
|
26
|
+
from .partial_function import asgi_app, batched, build, enter, exit, method, web_endpoint, web_server, wsgi_app
|
25
27
|
from .proxy import Proxy
|
26
28
|
from .queue import Queue
|
27
29
|
from .retries import Retries
|
@@ -29,7 +31,6 @@ try:
|
|
29
31
|
from .schedule import Cron, Period
|
30
32
|
from .scheduler_placement import SchedulerPlacement
|
31
33
|
from .secret import Secret
|
32
|
-
from .shared_volume import SharedVolume
|
33
34
|
from .volume import Volume
|
34
35
|
except Exception:
|
35
36
|
print()
|
@@ -48,6 +49,7 @@ __all__ = [
|
|
48
49
|
"Cron",
|
49
50
|
"Dict",
|
50
51
|
"Error",
|
52
|
+
"FilePatternMatcher",
|
51
53
|
"Function",
|
52
54
|
"Image",
|
53
55
|
"Mount",
|
@@ -60,20 +62,22 @@ __all__ = [
|
|
60
62
|
"Sandbox",
|
61
63
|
"SchedulerPlacement",
|
62
64
|
"Secret",
|
63
|
-
"SharedVolume",
|
64
65
|
"Stub",
|
65
66
|
"Tunnel",
|
66
67
|
"Volume",
|
67
68
|
"asgi_app",
|
69
|
+
"batched",
|
68
70
|
"build",
|
69
71
|
"current_function_call_id",
|
70
72
|
"current_input_id",
|
73
|
+
"enable_output",
|
71
74
|
"enter",
|
72
75
|
"exit",
|
73
76
|
"forward",
|
74
77
|
"is_local",
|
75
78
|
"interact",
|
76
79
|
"method",
|
80
|
+
"parameter",
|
77
81
|
"web_endpoint",
|
78
82
|
"web_server",
|
79
83
|
"wsgi_app",
|
modal/__main__.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# Copyright Modal Labs 2022
|
2
2
|
import sys
|
3
3
|
|
4
|
-
from ._traceback import
|
4
|
+
from ._traceback import reduce_traceback_to_user_code
|
5
|
+
from .cli._traceback import highlight_modal_deprecation_warnings, setup_rich_traceback
|
5
6
|
from .cli.entry_point import entrypoint_cli
|
6
7
|
from .cli.import_refs import _CliUserExecutionError
|
7
8
|
from .config import config
|
@@ -19,19 +20,56 @@ def main():
|
|
19
20
|
if config.get("traceback"):
|
20
21
|
raise
|
21
22
|
|
23
|
+
assert exc.__cause__ # We should always raise this class from another error
|
22
24
|
tb = reduce_traceback_to_user_code(exc.__cause__.__traceback__, exc.user_source)
|
23
25
|
sys.excepthook(type(exc.__cause__), exc.__cause__, tb)
|
24
26
|
sys.exit(1)
|
25
27
|
|
26
28
|
except Exception as exc:
|
27
|
-
if
|
29
|
+
if (
|
30
|
+
# User has asked to alway see full tracebacks
|
31
|
+
config.get("traceback")
|
32
|
+
# The exception message is empty, so we need to provide _some_ actionable information
|
33
|
+
or not str(exc)
|
34
|
+
):
|
28
35
|
raise
|
29
36
|
|
37
|
+
from grpclib import GRPCError, Status
|
30
38
|
from rich.console import Console
|
31
39
|
from rich.panel import Panel
|
40
|
+
from rich.text import Text
|
41
|
+
|
42
|
+
if isinstance(exc, GRPCError):
|
43
|
+
status_map = {
|
44
|
+
Status.ABORTED: "Aborted",
|
45
|
+
Status.ALREADY_EXISTS: "Already exists",
|
46
|
+
Status.CANCELLED: "Cancelled",
|
47
|
+
Status.DATA_LOSS: "Data loss",
|
48
|
+
Status.DEADLINE_EXCEEDED: "Deadline exceeded",
|
49
|
+
Status.FAILED_PRECONDITION: "Failed precondition",
|
50
|
+
Status.INTERNAL: "Internal",
|
51
|
+
Status.INVALID_ARGUMENT: "Invalid",
|
52
|
+
Status.NOT_FOUND: "Not found",
|
53
|
+
Status.OUT_OF_RANGE: "Out of range",
|
54
|
+
Status.PERMISSION_DENIED: "Permission denied",
|
55
|
+
Status.RESOURCE_EXHAUSTED: "Resource exhausted",
|
56
|
+
Status.UNAUTHENTICATED: "Unauthenticaed",
|
57
|
+
Status.UNAVAILABLE: "Unavailable",
|
58
|
+
Status.UNIMPLEMENTED: "Unimplemented",
|
59
|
+
Status.UNKNOWN: "Unknown",
|
60
|
+
}
|
61
|
+
title = f"Error: {status_map.get(exc.status, 'Unknown')}"
|
62
|
+
content = str(exc.message)
|
63
|
+
if exc.details:
|
64
|
+
content += f"\n\nDetails: {exc.details}"
|
65
|
+
else:
|
66
|
+
title = "Error"
|
67
|
+
content = str(exc)
|
68
|
+
if notes := getattr(exc, "__notes__", []):
|
69
|
+
content = f"{content}\n\nNote: {' ' .join(notes)}"
|
32
70
|
|
33
71
|
console = Console(stderr=True)
|
34
|
-
panel = Panel(
|
72
|
+
panel = Panel(Text(content), title=title, title_align="left", border_style="red")
|
35
73
|
console.print(panel, highlight=False)
|
36
74
|
sys.exit(1)
|
37
75
|
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# Copyright Modal Labs 2024
|
2
|
+
import os
|
3
|
+
import socket
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from typing import Optional
|
6
|
+
|
7
|
+
from modal._utils.async_utils import synchronize_api
|
8
|
+
from modal._utils.grpc_utils import retry_transient_errors
|
9
|
+
from modal.client import _Client
|
10
|
+
from modal.exception import InvalidError
|
11
|
+
from modal_proto import api_pb2
|
12
|
+
|
13
|
+
|
14
|
+
@dataclass
|
15
|
+
class ClusterInfo:
|
16
|
+
rank: int
|
17
|
+
container_ips: list[str]
|
18
|
+
|
19
|
+
|
20
|
+
cluster_info: Optional[ClusterInfo] = None
|
21
|
+
|
22
|
+
|
23
|
+
def get_cluster_info() -> ClusterInfo:
|
24
|
+
if cluster_info is None:
|
25
|
+
raise InvalidError(
|
26
|
+
"Cluster info not initialized. Please ensure that you are "
|
27
|
+
"calling get_cluster_info() from a clustered function."
|
28
|
+
)
|
29
|
+
return cluster_info
|
30
|
+
|
31
|
+
|
32
|
+
async def _initialize_clustered_function(client: _Client, task_id: str, world_size: int):
|
33
|
+
global cluster_info
|
34
|
+
|
35
|
+
def get_i6pn():
|
36
|
+
"""Returns the ipv6 address assigned to this container."""
|
37
|
+
return socket.getaddrinfo("i6pn.modal.local", None, socket.AF_INET6)[0][4][0]
|
38
|
+
|
39
|
+
hostname = socket.gethostname()
|
40
|
+
container_ip = get_i6pn()
|
41
|
+
|
42
|
+
# nccl's default host ID is $(hostname)$(cat /proc/sys/kernel/random/boot_id).
|
43
|
+
# on runc, if two i6pn-linked containers get scheduled on the same worker,
|
44
|
+
# their boot ID and hostname will both be identical, causing nccl to break.
|
45
|
+
# As a workaround, we can explicitly specify a unique host ID here.
|
46
|
+
# See MOD-4067.
|
47
|
+
os.environ["NCCL_HOSTID"] = f"{hostname}{container_ip}"
|
48
|
+
|
49
|
+
# We found these settings to work well in most cases. You may be able to achieve
|
50
|
+
# better performance by tuning these settings.
|
51
|
+
if os.environ["MODAL_CLOUD_PROVIDER"] in ("CLOUD_PROVIDER_GCP", "CLOUD_PROVIDER_OCI"):
|
52
|
+
os.environ["NCCL_SOCKET_NTHREADS"] = "4"
|
53
|
+
os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
|
54
|
+
elif os.environ["MODAL_CLOUD_PROVIDER"] == "CLOUD_PROVIDER_AWS":
|
55
|
+
os.environ["NCCL_SOCKET_NTHREADS"] = "2"
|
56
|
+
os.environ["NCCL_NSOCKS_PERTHREAD"] = "8"
|
57
|
+
else:
|
58
|
+
os.environ["NCCL_SOCKET_NTHREADS"] = "1"
|
59
|
+
os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
|
60
|
+
|
61
|
+
if world_size > 1:
|
62
|
+
resp: api_pb2.TaskClusterHelloResponse = await retry_transient_errors(
|
63
|
+
client.stub.TaskClusterHello,
|
64
|
+
api_pb2.TaskClusterHelloRequest(
|
65
|
+
task_id=task_id,
|
66
|
+
container_ip=container_ip,
|
67
|
+
),
|
68
|
+
)
|
69
|
+
cluster_info = ClusterInfo(
|
70
|
+
rank=resp.cluster_rank,
|
71
|
+
container_ips=resp.container_ips,
|
72
|
+
)
|
73
|
+
else:
|
74
|
+
cluster_info = ClusterInfo(
|
75
|
+
rank=0,
|
76
|
+
container_ips=[container_ip],
|
77
|
+
)
|
78
|
+
|
79
|
+
|
80
|
+
initialize_clustered_function = synchronize_api(_initialize_clustered_function)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import modal.client
|
2
|
+
import typing
|
3
|
+
import typing_extensions
|
4
|
+
|
5
|
+
class ClusterInfo:
|
6
|
+
rank: int
|
7
|
+
container_ips: list[str]
|
8
|
+
|
9
|
+
def __init__(self, rank: int, container_ips: list[str]) -> None: ...
|
10
|
+
def __repr__(self): ...
|
11
|
+
def __eq__(self, other): ...
|
12
|
+
|
13
|
+
def get_cluster_info() -> ClusterInfo: ...
|
14
|
+
async def _initialize_clustered_function(client: modal.client._Client, task_id: str, world_size: int): ...
|
15
|
+
|
16
|
+
class __initialize_clustered_function_spec(typing_extensions.Protocol):
|
17
|
+
def __call__(self, client: modal.client.Client, task_id: str, world_size: int): ...
|
18
|
+
async def aio(self, client: modal.client.Client, task_id: str, world_size: int): ...
|
19
|
+
|
20
|
+
initialize_clustered_function: __initialize_clustered_function_spec
|
21
|
+
|
22
|
+
cluster_info: typing.Optional[ClusterInfo]
|