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/environments.py
CHANGED
@@ -1,14 +1,115 @@
|
|
1
1
|
# Copyright Modal Labs 2023
|
2
|
-
from
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import Optional
|
3
4
|
|
4
5
|
from google.protobuf.empty_pb2 import Empty
|
6
|
+
from google.protobuf.message import Message
|
5
7
|
from google.protobuf.wrappers_pb2 import StringValue
|
6
8
|
|
7
|
-
from modal.client import _Client
|
8
|
-
from modal.config import config
|
9
9
|
from modal_proto import api_pb2
|
10
10
|
|
11
|
-
from .
|
11
|
+
from ._resolver import Resolver
|
12
|
+
from ._utils.async_utils import synchronize_api, synchronizer
|
13
|
+
from ._utils.deprecation import renamed_parameter
|
14
|
+
from ._utils.grpc_utils import retry_transient_errors
|
15
|
+
from ._utils.name_utils import check_object_name
|
16
|
+
from .client import _Client
|
17
|
+
from .config import config, logger
|
18
|
+
from .object import _Object
|
19
|
+
|
20
|
+
|
21
|
+
@dataclass(frozen=True)
|
22
|
+
class EnvironmentSettings:
|
23
|
+
image_builder_version: str # Ideally would be typed with ImageBuilderVersion literal
|
24
|
+
webhook_suffix: str
|
25
|
+
|
26
|
+
|
27
|
+
class _Environment(_Object, type_prefix="en"):
|
28
|
+
_settings: EnvironmentSettings
|
29
|
+
|
30
|
+
def __init__(self):
|
31
|
+
"""mdmd:hidden"""
|
32
|
+
raise RuntimeError(
|
33
|
+
"`Environment(...)` constructor is not allowed."
|
34
|
+
" Please use `Environment.from_name` or `Environment.lookup` instead."
|
35
|
+
)
|
36
|
+
|
37
|
+
# TODO(michael) Keeping this private for now until we decide what else should be in it
|
38
|
+
# And what the rules should be about updates / mutability
|
39
|
+
# @property
|
40
|
+
# def settings(self) -> EnvironmentSettings:
|
41
|
+
# return self._settings
|
42
|
+
|
43
|
+
def _hydrate_metadata(self, metadata: Message):
|
44
|
+
# Overridden concrete implementation of base class method
|
45
|
+
assert metadata and isinstance(metadata, api_pb2.EnvironmentMetadata)
|
46
|
+
# TODO(michael) should probably expose the `name` from the metadata
|
47
|
+
# as the way to discover the name of the "default" environment
|
48
|
+
|
49
|
+
# Is there a simpler way to go Message -> Dataclass?
|
50
|
+
self._settings = EnvironmentSettings(
|
51
|
+
image_builder_version=metadata.settings.image_builder_version,
|
52
|
+
webhook_suffix=metadata.settings.webhook_suffix,
|
53
|
+
)
|
54
|
+
|
55
|
+
@staticmethod
|
56
|
+
@renamed_parameter((2024, 12, 18), "label", "name")
|
57
|
+
async def from_name(
|
58
|
+
name: str,
|
59
|
+
create_if_missing: bool = False,
|
60
|
+
):
|
61
|
+
if name:
|
62
|
+
# Allow null names for the case where we want to look up the "default" environment,
|
63
|
+
# which is defined by the server. It feels messy to have "from_name" without a name, though?
|
64
|
+
# We're adding this mostly for internal use right now. We could consider an environment-only
|
65
|
+
# alternate constructor, like `Environment.get_default`, rather than exposing "unnamed"
|
66
|
+
# environments as part of public API when we make this class more useful.
|
67
|
+
check_object_name(name, "Environment")
|
68
|
+
|
69
|
+
async def _load(self: _Environment, resolver: Resolver, existing_object_id: Optional[str]):
|
70
|
+
request = api_pb2.EnvironmentGetOrCreateRequest(
|
71
|
+
deployment_name=name,
|
72
|
+
object_creation_type=(
|
73
|
+
api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING
|
74
|
+
if create_if_missing
|
75
|
+
else api_pb2.OBJECT_CREATION_TYPE_UNSPECIFIED
|
76
|
+
),
|
77
|
+
)
|
78
|
+
response = await retry_transient_errors(resolver.client.stub.EnvironmentGetOrCreate, request)
|
79
|
+
logger.debug(f"Created environment with id {response.environment_id}")
|
80
|
+
self._hydrate(response.environment_id, resolver.client, response.metadata)
|
81
|
+
|
82
|
+
# TODO environment name (and id?) in the repr? (We should make reprs consistently more useful)
|
83
|
+
return _Environment._from_loader(_load, "Environment()", is_another_app=True, hydrate_lazily=True)
|
84
|
+
|
85
|
+
@staticmethod
|
86
|
+
@renamed_parameter((2024, 12, 18), "label", "name")
|
87
|
+
async def lookup(
|
88
|
+
name: str,
|
89
|
+
client: Optional[_Client] = None,
|
90
|
+
create_if_missing: bool = False,
|
91
|
+
):
|
92
|
+
obj = await _Environment.from_name(name, create_if_missing=create_if_missing)
|
93
|
+
if client is None:
|
94
|
+
client = await _Client.from_env()
|
95
|
+
resolver = Resolver(client=client)
|
96
|
+
await resolver.load(obj)
|
97
|
+
return obj
|
98
|
+
|
99
|
+
|
100
|
+
Environment = synchronize_api(_Environment)
|
101
|
+
|
102
|
+
|
103
|
+
# Needs to be after definition; synchronicity interferes with forward references?
|
104
|
+
ENVIRONMENT_CACHE: dict[str, _Environment] = {}
|
105
|
+
|
106
|
+
|
107
|
+
async def _get_environment_cached(name: str, client: _Client) -> _Environment:
|
108
|
+
if name in ENVIRONMENT_CACHE:
|
109
|
+
return ENVIRONMENT_CACHE[name]
|
110
|
+
environment = await _Environment.lookup(name, client)
|
111
|
+
ENVIRONMENT_CACHE[name] = environment
|
112
|
+
return environment
|
12
113
|
|
13
114
|
|
14
115
|
@synchronizer.create_blocking
|
@@ -53,7 +154,7 @@ async def create_environment(name: str, client: Optional[_Client] = None):
|
|
53
154
|
|
54
155
|
|
55
156
|
@synchronizer.create_blocking
|
56
|
-
async def list_environments(client: Optional[_Client] = None) ->
|
157
|
+
async def list_environments(client: Optional[_Client] = None) -> list[api_pb2.EnvironmentListItem]:
|
57
158
|
if client is None:
|
58
159
|
client = await _Client.from_env()
|
59
160
|
resp = await client.stub.EnvironmentList(Empty())
|
modal/environments.pyi
CHANGED
@@ -1,47 +1,99 @@
|
|
1
|
+
import google.protobuf.message
|
1
2
|
import modal.client
|
3
|
+
import modal.object
|
2
4
|
import modal_proto.api_pb2
|
3
5
|
import typing
|
4
6
|
import typing_extensions
|
5
7
|
|
6
|
-
class
|
7
|
-
|
8
|
-
|
8
|
+
class EnvironmentSettings:
|
9
|
+
image_builder_version: str
|
10
|
+
webhook_suffix: str
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
+
def __init__(self, image_builder_version: str, webhook_suffix: str) -> None: ...
|
13
|
+
def __repr__(self): ...
|
14
|
+
def __eq__(self, other): ...
|
15
|
+
def __setattr__(self, name, value): ...
|
16
|
+
def __delattr__(self, name): ...
|
17
|
+
def __hash__(self): ...
|
12
18
|
|
13
|
-
|
19
|
+
class _Environment(modal.object._Object):
|
20
|
+
_settings: EnvironmentSettings
|
14
21
|
|
22
|
+
def __init__(self): ...
|
23
|
+
def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
|
24
|
+
@staticmethod
|
25
|
+
async def from_name(name: str, create_if_missing: bool = False): ...
|
26
|
+
@staticmethod
|
27
|
+
async def lookup(
|
28
|
+
name: str, client: typing.Optional[modal.client._Client] = None, create_if_missing: bool = False
|
29
|
+
): ...
|
15
30
|
|
16
|
-
class
|
17
|
-
|
18
|
-
...
|
31
|
+
class Environment(modal.object.Object):
|
32
|
+
_settings: EnvironmentSettings
|
19
33
|
|
20
|
-
|
21
|
-
|
34
|
+
def __init__(self): ...
|
35
|
+
def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
|
22
36
|
|
23
|
-
|
37
|
+
class __from_name_spec(typing_extensions.Protocol):
|
38
|
+
def __call__(self, name: str, create_if_missing: bool = False): ...
|
39
|
+
async def aio(self, name: str, create_if_missing: bool = False): ...
|
24
40
|
|
41
|
+
from_name: __from_name_spec
|
25
42
|
|
26
|
-
class
|
27
|
-
|
28
|
-
|
43
|
+
class __lookup_spec(typing_extensions.Protocol):
|
44
|
+
def __call__(
|
45
|
+
self, name: str, client: typing.Optional[modal.client.Client] = None, create_if_missing: bool = False
|
46
|
+
): ...
|
47
|
+
async def aio(
|
48
|
+
self, name: str, client: typing.Optional[modal.client.Client] = None, create_if_missing: bool = False
|
49
|
+
): ...
|
29
50
|
|
30
|
-
|
31
|
-
...
|
51
|
+
lookup: __lookup_spec
|
32
52
|
|
33
|
-
|
53
|
+
async def _get_environment_cached(name: str, client: modal.client._Client) -> _Environment: ...
|
34
54
|
|
55
|
+
class __delete_environment_spec(typing_extensions.Protocol):
|
56
|
+
def __call__(self, name: str, client: typing.Optional[modal.client.Client] = None): ...
|
57
|
+
async def aio(self, name: str, client: typing.Optional[modal.client.Client] = None): ...
|
35
58
|
|
36
|
-
|
37
|
-
def __call__(self, client: typing.Union[modal.client.Client, None] = None) -> typing.List[modal_proto.api_pb2.EnvironmentListItem]:
|
38
|
-
...
|
59
|
+
delete_environment: __delete_environment_spec
|
39
60
|
|
40
|
-
|
41
|
-
|
61
|
+
class __update_environment_spec(typing_extensions.Protocol):
|
62
|
+
def __call__(
|
63
|
+
self,
|
64
|
+
current_name: str,
|
65
|
+
*,
|
66
|
+
new_name: typing.Optional[str] = None,
|
67
|
+
new_web_suffix: typing.Optional[str] = None,
|
68
|
+
client: typing.Optional[modal.client.Client] = None,
|
69
|
+
): ...
|
70
|
+
async def aio(
|
71
|
+
self,
|
72
|
+
current_name: str,
|
73
|
+
*,
|
74
|
+
new_name: typing.Optional[str] = None,
|
75
|
+
new_web_suffix: typing.Optional[str] = None,
|
76
|
+
client: typing.Optional[modal.client.Client] = None,
|
77
|
+
): ...
|
78
|
+
|
79
|
+
update_environment: __update_environment_spec
|
80
|
+
|
81
|
+
class __create_environment_spec(typing_extensions.Protocol):
|
82
|
+
def __call__(self, name: str, client: typing.Optional[modal.client.Client] = None): ...
|
83
|
+
async def aio(self, name: str, client: typing.Optional[modal.client.Client] = None): ...
|
84
|
+
|
85
|
+
create_environment: __create_environment_spec
|
86
|
+
|
87
|
+
class __list_environments_spec(typing_extensions.Protocol):
|
88
|
+
def __call__(
|
89
|
+
self, client: typing.Optional[modal.client.Client] = None
|
90
|
+
) -> list[modal_proto.api_pb2.EnvironmentListItem]: ...
|
91
|
+
async def aio(
|
92
|
+
self, client: typing.Optional[modal.client.Client] = None
|
93
|
+
) -> list[modal_proto.api_pb2.EnvironmentListItem]: ...
|
42
94
|
|
43
95
|
list_environments: __list_environments_spec
|
44
96
|
|
97
|
+
def ensure_env(environment_name: typing.Optional[str] = None) -> str: ...
|
45
98
|
|
46
|
-
|
47
|
-
...
|
99
|
+
ENVIRONMENT_CACHE: dict[str, _Environment]
|
modal/exception.py
CHANGED
@@ -1,10 +1,6 @@
|
|
1
1
|
# Copyright Modal Labs 2022
|
2
2
|
import random
|
3
3
|
import signal
|
4
|
-
import sys
|
5
|
-
import warnings
|
6
|
-
from datetime import date
|
7
|
-
from typing import Tuple
|
8
4
|
|
9
5
|
|
10
6
|
class Error(Exception):
|
@@ -58,6 +54,10 @@ class InteractiveTimeoutError(TimeoutError):
|
|
58
54
|
"""Raised when interactive frontends time out while trying to connect to a container."""
|
59
55
|
|
60
56
|
|
57
|
+
class OutputExpiredError(TimeoutError):
|
58
|
+
"""Raised when the Output exceeds expiration and times out."""
|
59
|
+
|
60
|
+
|
61
61
|
class AuthError(Error):
|
62
62
|
"""Raised when a client has missing or invalid authentication."""
|
63
63
|
|
@@ -86,6 +86,14 @@ class DeserializationError(Error):
|
|
86
86
|
"""Raised to provide more context when an error is encountered during deserialization."""
|
87
87
|
|
88
88
|
|
89
|
+
class SerializationError(Error):
|
90
|
+
"""Raised to provide more context when an error is encountered during serialization."""
|
91
|
+
|
92
|
+
|
93
|
+
class RequestSizeError(Error):
|
94
|
+
"""Raised when an operation produces a gRPC request that is rejected by the server for being too large."""
|
95
|
+
|
96
|
+
|
89
97
|
class DeprecationError(UserWarning):
|
90
98
|
"""UserWarning category emitted when a deprecated Modal feature or API is used."""
|
91
99
|
|
@@ -96,6 +104,16 @@ class PendingDeprecationError(UserWarning):
|
|
96
104
|
"""Soon to be deprecated feature. Only used intermittently because of multi-repo concerns."""
|
97
105
|
|
98
106
|
|
107
|
+
class ServerWarning(UserWarning):
|
108
|
+
"""Warning originating from the Modal server and re-issued in client code."""
|
109
|
+
|
110
|
+
|
111
|
+
class InternalFailure(Error):
|
112
|
+
"""
|
113
|
+
Retriable internal error.
|
114
|
+
"""
|
115
|
+
|
116
|
+
|
99
117
|
class _CliUserExecutionError(Exception):
|
100
118
|
"""mdmd:hidden
|
101
119
|
Private wrapper for exceptions during when importing or running stubs from the CLI.
|
@@ -111,45 +129,6 @@ class _CliUserExecutionError(Exception):
|
|
111
129
|
self.user_source = user_source
|
112
130
|
|
113
131
|
|
114
|
-
# TODO(erikbern): we have something similready in function_utils.py
|
115
|
-
_INTERNAL_MODULES = ["modal", "synchronicity"]
|
116
|
-
|
117
|
-
|
118
|
-
def _is_internal_frame(frame):
|
119
|
-
module = frame.f_globals["__name__"].split(".")[0]
|
120
|
-
return module in _INTERNAL_MODULES
|
121
|
-
|
122
|
-
|
123
|
-
def deprecation_error(deprecated_on: Tuple[int, int, int], msg: str):
|
124
|
-
raise DeprecationError(f"Deprecated on {date(*deprecated_on)}: {msg}")
|
125
|
-
|
126
|
-
|
127
|
-
def deprecation_warning(
|
128
|
-
deprecated_on: Tuple[int, int, int], msg: str, *, pending: bool = False, show_source: bool = True
|
129
|
-
) -> None:
|
130
|
-
"""Utility for getting the proper stack entry.
|
131
|
-
|
132
|
-
See the implementation of the built-in [warnings.warn](https://docs.python.org/3/library/warnings.html#available-functions).
|
133
|
-
"""
|
134
|
-
filename, lineno = "<unknown>", 0
|
135
|
-
if show_source:
|
136
|
-
# Find the last non-Modal line that triggered the warning
|
137
|
-
try:
|
138
|
-
frame = sys._getframe()
|
139
|
-
while frame is not None and _is_internal_frame(frame):
|
140
|
-
frame = frame.f_back
|
141
|
-
filename = frame.f_code.co_filename
|
142
|
-
lineno = frame.f_lineno
|
143
|
-
except ValueError:
|
144
|
-
# Use the defaults from above
|
145
|
-
pass
|
146
|
-
|
147
|
-
warning_cls: type = PendingDeprecationError if pending else DeprecationError
|
148
|
-
|
149
|
-
# This is a lower-level function that warnings.warn uses
|
150
|
-
warnings.warn_explicit(f"{date(*deprecated_on)}: {msg}", warning_cls, filename, lineno)
|
151
|
-
|
152
|
-
|
153
132
|
def _simulate_preemption_interrupt(signum, frame):
|
154
133
|
signal.alarm(30) # simulate a SIGKILL after 30s
|
155
134
|
raise KeyboardInterrupt("Simulated preemption interrupt from modal-client!")
|
@@ -198,3 +177,11 @@ class InputCancellation(BaseException):
|
|
198
177
|
|
199
178
|
class ModuleNotMountable(Exception):
|
200
179
|
pass
|
180
|
+
|
181
|
+
|
182
|
+
class ClientClosed(Error):
|
183
|
+
pass
|
184
|
+
|
185
|
+
|
186
|
+
class FilesystemExecutionError(Error):
|
187
|
+
"""Raised when an unknown error is thrown during a container filesystem operation."""
|
modal/experimental.py
CHANGED
@@ -1,9 +1,69 @@
|
|
1
1
|
# Copyright Modal Labs 2022
|
2
|
-
from
|
2
|
+
from typing import (
|
3
|
+
Any,
|
4
|
+
Callable,
|
5
|
+
)
|
6
|
+
|
7
|
+
import modal._clustered_functions
|
8
|
+
from modal.functions import _Function
|
9
|
+
|
10
|
+
from ._runtime.container_io_manager import _ContainerIOManager
|
11
|
+
from .exception import (
|
12
|
+
InvalidError,
|
13
|
+
)
|
14
|
+
from .partial_function import _PartialFunction, _PartialFunctionFlags
|
3
15
|
|
4
16
|
|
5
17
|
def stop_fetching_inputs():
|
6
18
|
"""Don't fetch any more inputs from the server, after the current one.
|
7
19
|
The container will exit gracefully after the current input is processed."""
|
8
|
-
|
9
20
|
_ContainerIOManager.stop_fetching_inputs()
|
21
|
+
|
22
|
+
|
23
|
+
def get_local_input_concurrency():
|
24
|
+
"""Get the container's local input concurrency.
|
25
|
+
If recently reduced to particular value, it can return a larger number than
|
26
|
+
set due to in-progress inputs."""
|
27
|
+
return _ContainerIOManager.get_input_concurrency()
|
28
|
+
|
29
|
+
|
30
|
+
def set_local_input_concurrency(concurrency: int):
|
31
|
+
"""Set the container's local input concurrency. Dynamic concurrency will be disabled.
|
32
|
+
When setting to a smaller value, this method will not interrupt in-progress inputs.
|
33
|
+
"""
|
34
|
+
_ContainerIOManager.set_input_concurrency(concurrency)
|
35
|
+
|
36
|
+
|
37
|
+
def clustered(size: int, broadcast: bool = True):
|
38
|
+
"""Provision clusters of colocated and networked containers for the Function.
|
39
|
+
|
40
|
+
Parameters:
|
41
|
+
size: int
|
42
|
+
Number of containers spun up to handle each input.
|
43
|
+
broadcast: bool = True
|
44
|
+
If True, inputs will be sent simultaneously to each container. Otherwise,
|
45
|
+
inputs will be sent only to the rank-0 container, which is responsible for
|
46
|
+
delegating to the workers.
|
47
|
+
"""
|
48
|
+
|
49
|
+
assert broadcast, "broadcast=False has not been implemented yet!"
|
50
|
+
|
51
|
+
if size <= 0:
|
52
|
+
raise ValueError("cluster size must be greater than 0")
|
53
|
+
|
54
|
+
def wrapper(raw_f: Callable[..., Any]) -> _PartialFunction:
|
55
|
+
if isinstance(raw_f, _Function):
|
56
|
+
raw_f = raw_f.get_raw_f()
|
57
|
+
raise InvalidError(
|
58
|
+
f"Applying decorators for {raw_f} in the wrong order!\nUsage:\n\n"
|
59
|
+
"@app.function()\n@modal.clustered()\ndef clustered_function():\n ..."
|
60
|
+
)
|
61
|
+
return _PartialFunction(
|
62
|
+
raw_f, _PartialFunctionFlags.FUNCTION | _PartialFunctionFlags.CLUSTERED, cluster_size=size
|
63
|
+
)
|
64
|
+
|
65
|
+
return wrapper
|
66
|
+
|
67
|
+
|
68
|
+
def get_cluster_info() -> modal._clustered_functions.ClusterInfo:
|
69
|
+
return modal._clustered_functions.get_cluster_info()
|