modAL 1.0.0__tar.gz → 1.0.0.dev2__tar.gz
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-1.0.0 → modal-1.0.0.dev2}/PKG-INFO +1 -1
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/__init__.py +4 -10
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_functions.py +88 -15
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_object.py +14 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_partial_function.py +14 -4
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_serialization.py +2 -2
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/function_utils.py +6 -3
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/grpc_utils.py +1 -6
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/app.py +102 -1
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/app.pyi +127 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/app.py +19 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/programs/run_jupyter.py +1 -1
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/programs/vscode.py +1 -1
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/client.pyi +10 -2
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cls.py +11 -8
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cls.pyi +17 -2
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/config.py +16 -5
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/container_process.py +9 -1
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/container_process.pyi +3 -3
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/dict.py +1 -2
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/exception.py +1 -1
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/functions.pyi +29 -8
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/image.py +36 -12
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/image.pyi +5 -2
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/mount.py +62 -1
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/mount.pyi +1 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/network_file_system.py +1 -2
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/object.pyi +6 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/queue.py +1 -2
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/runner.py +19 -2
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/runner.pyi +5 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/sandbox.py +32 -78
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/sandbox.pyi +7 -102
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/serving.py +6 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/serving.pyi +3 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/volume.py +14 -8
- {modal-1.0.0 → modal-1.0.0.dev2}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/api.proto +2 -29
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/api_grpc.py +0 -32
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/api_pb2.py +756 -788
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/api_pb2.pyi +12 -86
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/api_pb2_grpc.py +0 -66
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/api_pb2_grpc.pyi +0 -20
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/modal_api_grpc.py +0 -2
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_version/__init__.py +1 -1
- {modal-1.0.0 → modal-1.0.0.dev2}/LICENSE +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/README.md +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/__main__.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_clustered_functions.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_clustered_functions.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_container_entrypoint.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_ipython.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_location.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_output.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_pty.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_resolver.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_resources.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_runtime/__init__.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_runtime/asgi.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_runtime/execution_context.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_runtime/telemetry.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_traceback.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_tunnel.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_tunnel.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_type_manager.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/__init__.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/app_utils.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/async_utils.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/blob_utils.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/deprecation.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/docker_utils.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/git_utils.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/hash_utils.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/http_utils.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/logger.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/mount_utils.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/name_utils.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/package_utils.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_utils/shell_utils.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_vendor/__init__.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_vendor/tblib.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/_watcher.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/call_graph.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/__init__.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/_download.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/_traceback.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/cluster.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/config.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/container.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/dict.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/entry_point.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/environment.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/import_refs.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/launch.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/network_file_system.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/profile.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/programs/__init__.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/queues.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/run.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/secret.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/token.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/utils.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cli/volume.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/client.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/dict.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/environments.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/environments.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/experimental/__init__.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/experimental/ipython.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/file_io.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/file_io.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/file_pattern_matcher.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/functions.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/gpu.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/io_streams.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/io_streams.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/network_file_system.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/object.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/output.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/parallel_map.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/parallel_map.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/partial_function.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/partial_function.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/proxy.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/proxy.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/py.typed +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/queue.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/requirements/2023.12.312.txt +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/requirements/2023.12.txt +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/requirements/2024.04.txt +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/requirements/2024.10.txt +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/requirements/PREVIEW.txt +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/requirements/README.md +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/requirements/base-images.json +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/retries.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/running_app.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/schedule.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/scheduler_placement.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/secret.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/secret.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/snapshot.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/snapshot.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/stream_type.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/token_flow.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/token_flow.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal/volume.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal.egg-info/requires.txt +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal.egg-info/top_level.txt +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_docs/__init__.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/__init__.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/modal_options_grpc.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/options.proto +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/options_grpc.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/options_pb2.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/options_pb2.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/options_pb2_grpc.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/options_pb2_grpc.pyi +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_proto/py.typed +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/modal_version/__main__.py +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/pyproject.toml +0 -0
- {modal-1.0.0 → modal-1.0.0.dev2}/setup.cfg +0 -0
|
@@ -11,7 +11,7 @@ from modal_version import __version__
|
|
|
11
11
|
try:
|
|
12
12
|
from ._runtime.execution_context import current_function_call_id, current_input_id, interact, is_local
|
|
13
13
|
from ._tunnel import Tunnel, forward
|
|
14
|
-
from .app import App
|
|
14
|
+
from .app import App, Stub
|
|
15
15
|
from .client import Client
|
|
16
16
|
from .cloud_bucket_mount import CloudBucketMount
|
|
17
17
|
from .cls import Cls, parameter
|
|
@@ -20,6 +20,7 @@ try:
|
|
|
20
20
|
from .file_pattern_matcher import FilePatternMatcher
|
|
21
21
|
from .functions import Function, FunctionCall
|
|
22
22
|
from .image import Image
|
|
23
|
+
from .mount import Mount
|
|
23
24
|
from .network_file_system import NetworkFileSystem
|
|
24
25
|
from .output import enable_output
|
|
25
26
|
from .partial_function import (
|
|
@@ -53,7 +54,6 @@ except Exception:
|
|
|
53
54
|
print()
|
|
54
55
|
raise
|
|
55
56
|
|
|
56
|
-
|
|
57
57
|
__all__ = [
|
|
58
58
|
"__version__",
|
|
59
59
|
"App",
|
|
@@ -66,6 +66,7 @@ __all__ = [
|
|
|
66
66
|
"Function",
|
|
67
67
|
"FunctionCall",
|
|
68
68
|
"Image",
|
|
69
|
+
"Mount",
|
|
69
70
|
"NetworkFileSystem",
|
|
70
71
|
"Period",
|
|
71
72
|
"Proxy",
|
|
@@ -76,6 +77,7 @@ __all__ = [
|
|
|
76
77
|
"SandboxSnapshot",
|
|
77
78
|
"SchedulerPlacement",
|
|
78
79
|
"Secret",
|
|
80
|
+
"Stub",
|
|
79
81
|
"Tunnel",
|
|
80
82
|
"Volume",
|
|
81
83
|
"asgi_app",
|
|
@@ -97,11 +99,3 @@ __all__ = [
|
|
|
97
99
|
"web_server",
|
|
98
100
|
"wsgi_app",
|
|
99
101
|
]
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def __getattr__(name):
|
|
103
|
-
if name == "Stub":
|
|
104
|
-
raise AttributeError(
|
|
105
|
-
"Module 'modal' has no attribute 'Stub'. Use `modal.App` instead. This is a simple name change."
|
|
106
|
-
)
|
|
107
|
-
raise AttributeError(f"module 'modal' has no attribute '{name}'")
|
|
@@ -6,7 +6,7 @@ import textwrap
|
|
|
6
6
|
import time
|
|
7
7
|
import typing
|
|
8
8
|
import warnings
|
|
9
|
-
from collections.abc import AsyncGenerator, Sequence, Sized
|
|
9
|
+
from collections.abc import AsyncGenerator, Collection, Sequence, Sized
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from pathlib import PurePosixPath
|
|
12
12
|
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
|
|
@@ -41,7 +41,7 @@ from ._utils.async_utils import (
|
|
|
41
41
|
synchronizer,
|
|
42
42
|
warn_if_generator_is_not_consumed,
|
|
43
43
|
)
|
|
44
|
-
from ._utils.deprecation import deprecation_warning
|
|
44
|
+
from ._utils.deprecation import deprecation_error, deprecation_warning
|
|
45
45
|
from ._utils.function_utils import (
|
|
46
46
|
ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
47
47
|
OUTPUTS_TIMEOUT,
|
|
@@ -71,7 +71,7 @@ from .exception import (
|
|
|
71
71
|
)
|
|
72
72
|
from .gpu import GPU_T, parse_gpu_config
|
|
73
73
|
from .image import _Image
|
|
74
|
-
from .mount import _get_client_mount, _Mount
|
|
74
|
+
from .mount import _get_client_mount, _Mount, get_sys_modules_mounts
|
|
75
75
|
from .network_file_system import _NetworkFileSystem, network_file_system_mount_protos
|
|
76
76
|
from .output import _get_output_manager
|
|
77
77
|
from .parallel_map import (
|
|
@@ -405,6 +405,12 @@ class FunctionStats:
|
|
|
405
405
|
backlog: int
|
|
406
406
|
num_total_runners: int
|
|
407
407
|
|
|
408
|
+
def __getattr__(self, name):
|
|
409
|
+
if name == "num_active_runners":
|
|
410
|
+
msg = "'FunctionStats.num_active_runners' is no longer available."
|
|
411
|
+
deprecation_error((2024, 6, 14), msg)
|
|
412
|
+
raise AttributeError(f"'FunctionStats' object has no attribute '{name}'")
|
|
413
|
+
|
|
408
414
|
|
|
409
415
|
def _parse_retries(
|
|
410
416
|
retries: Optional[Union[int, Retries]],
|
|
@@ -505,6 +511,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
505
511
|
is_generator: bool = False,
|
|
506
512
|
gpu: Union[GPU_T, list[GPU_T]] = None,
|
|
507
513
|
# TODO: maybe break this out into a separate decorator for notebooks.
|
|
514
|
+
mounts: Collection[_Mount] = (),
|
|
508
515
|
network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem] = {},
|
|
509
516
|
volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {},
|
|
510
517
|
webhook_config: Optional[api_pb2.WebhookConfig] = None,
|
|
@@ -560,6 +567,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
560
567
|
assert not webhook_config
|
|
561
568
|
assert not schedule
|
|
562
569
|
|
|
570
|
+
explicit_mounts = mounts
|
|
571
|
+
|
|
563
572
|
include_source_mode = get_include_source_mode(include_source)
|
|
564
573
|
if include_source_mode != IncludeSourceMode.INCLUDE_NOTHING:
|
|
565
574
|
entrypoint_mounts = info.get_entrypoint_mount()
|
|
@@ -568,9 +577,34 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
568
577
|
|
|
569
578
|
all_mounts = [
|
|
570
579
|
_get_client_mount(),
|
|
580
|
+
*explicit_mounts,
|
|
571
581
|
*entrypoint_mounts.values(),
|
|
572
582
|
]
|
|
573
583
|
|
|
584
|
+
if include_source_mode is IncludeSourceMode.INCLUDE_FIRST_PARTY and is_local():
|
|
585
|
+
auto_mounts = get_sys_modules_mounts()
|
|
586
|
+
# don't need to add entrypoint modules to automounts:
|
|
587
|
+
for entrypoint_module in entrypoint_mounts:
|
|
588
|
+
auto_mounts.pop(entrypoint_module, None)
|
|
589
|
+
|
|
590
|
+
warn_missing_modules = set(auto_mounts.keys()) - image._added_python_source_set
|
|
591
|
+
|
|
592
|
+
if warn_missing_modules:
|
|
593
|
+
python_stringified_modules = ", ".join(f'"{mod}"' for mod in sorted(warn_missing_modules))
|
|
594
|
+
deprecation_warning(
|
|
595
|
+
(2025, 2, 3),
|
|
596
|
+
(
|
|
597
|
+
'Modal will stop implicitly adding local Python modules to the Image ("automounting") in a '
|
|
598
|
+
"future update. The following modules need to be explicitly added for future "
|
|
599
|
+
"compatibility:\n"
|
|
600
|
+
)
|
|
601
|
+
+ "\n".join(sorted([f"* {m}" for m in warn_missing_modules]))
|
|
602
|
+
+ "\n\n"
|
|
603
|
+
+ (f"e.g.:\nimage_with_source = my_image.add_local_python_source({python_stringified_modules})\n\n")
|
|
604
|
+
+ "For more information, see https://modal.com/docs/guide/modal-1-0-migration",
|
|
605
|
+
)
|
|
606
|
+
all_mounts += auto_mounts.values()
|
|
607
|
+
|
|
574
608
|
retry_policy = _parse_retries(
|
|
575
609
|
retries, f"Function '{info.get_tag()}'" if info.raw_f else f"Class '{info.get_tag()}'"
|
|
576
610
|
)
|
|
@@ -607,6 +641,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
607
641
|
image=image,
|
|
608
642
|
secrets=secrets,
|
|
609
643
|
gpu=gpu,
|
|
644
|
+
mounts=mounts,
|
|
610
645
|
network_file_systems=network_file_systems,
|
|
611
646
|
volumes=volumes,
|
|
612
647
|
memory=memory,
|
|
@@ -714,7 +749,16 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
714
749
|
|
|
715
750
|
def _deps(only_explicit_mounts=False) -> list[_Object]:
|
|
716
751
|
deps: list[_Object] = list(secrets)
|
|
717
|
-
if
|
|
752
|
+
if only_explicit_mounts:
|
|
753
|
+
# TODO: this is a bit hacky, but all_mounts may differ in the container vs locally
|
|
754
|
+
# We don't want the function dependencies to change, so we have this way to force it to
|
|
755
|
+
# only include its declared dependencies.
|
|
756
|
+
# Only objects that need interaction within a user's container actually need to be
|
|
757
|
+
# included when only_explicit_mounts=True, so omitting auto mounts here
|
|
758
|
+
# wouldn't be a problem as long as Mounts are "passive" and only loaded by the
|
|
759
|
+
# worker runtime
|
|
760
|
+
deps += list(explicit_mounts)
|
|
761
|
+
else:
|
|
718
762
|
deps += list(all_mounts)
|
|
719
763
|
if proxy:
|
|
720
764
|
deps.append(proxy)
|
|
@@ -982,6 +1026,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
982
1026
|
obj._build_args = dict( # See get_build_def
|
|
983
1027
|
secrets=repr(secrets),
|
|
984
1028
|
gpu_config=repr([parse_gpu_config(_gpu) for _gpu in gpus]),
|
|
1029
|
+
mounts=repr(mounts),
|
|
985
1030
|
network_file_systems=repr(network_file_systems),
|
|
986
1031
|
)
|
|
987
1032
|
# these key are excluded if empty to avoid rebuilds on client upgrade
|
|
@@ -1145,8 +1190,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1145
1190
|
|
|
1146
1191
|
@live_method
|
|
1147
1192
|
async def keep_warm(self, warm_pool_size: int) -> None:
|
|
1148
|
-
"""
|
|
1149
|
-
Set the warm pool size for the Function.
|
|
1193
|
+
"""Set the warm pool size for the Function.
|
|
1150
1194
|
|
|
1151
1195
|
DEPRECATED: Please adapt your code to use the more general `update_autoscaler` method instead:
|
|
1152
1196
|
|
|
@@ -1250,8 +1294,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1250
1294
|
client: Optional[_Client] = None,
|
|
1251
1295
|
environment_name: Optional[str] = None,
|
|
1252
1296
|
) -> "_Function":
|
|
1253
|
-
"""
|
|
1254
|
-
Lookup a Function from a deployed App by its name.
|
|
1297
|
+
"""Lookup a Function from a deployed App by its name.
|
|
1255
1298
|
|
|
1256
1299
|
DEPRECATED: This method is deprecated in favor of `modal.Function.from_name`.
|
|
1257
1300
|
|
|
@@ -1375,8 +1418,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1375
1418
|
@property
|
|
1376
1419
|
@live_method
|
|
1377
1420
|
async def web_url(self) -> Optional[str]:
|
|
1378
|
-
"""
|
|
1379
|
-
Deprecated. Use the `Function.get_web_url()` method instead.
|
|
1421
|
+
"""Deprecated. Use the `Function.get_web_url()` method instead.
|
|
1380
1422
|
|
|
1381
1423
|
URL of a Function running as a web endpoint.
|
|
1382
1424
|
"""
|
|
@@ -1493,6 +1535,20 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1493
1535
|
async for res in invocation.run_generator():
|
|
1494
1536
|
yield res
|
|
1495
1537
|
|
|
1538
|
+
@synchronizer.no_io_translation
|
|
1539
|
+
async def _call_generator_nowait(self, args, kwargs):
|
|
1540
|
+
deprecation_warning(
|
|
1541
|
+
(2024, 12, 11),
|
|
1542
|
+
"Calling spawn on a generator function is deprecated and will soon raise an exception.",
|
|
1543
|
+
)
|
|
1544
|
+
return await _Invocation.create(
|
|
1545
|
+
self,
|
|
1546
|
+
args,
|
|
1547
|
+
kwargs,
|
|
1548
|
+
client=self.client,
|
|
1549
|
+
function_call_invocation_type=api_pb2.FUNCTION_CALL_INVOCATION_TYPE_ASYNC,
|
|
1550
|
+
)
|
|
1551
|
+
|
|
1496
1552
|
@synchronizer.no_io_translation
|
|
1497
1553
|
@live_method
|
|
1498
1554
|
async def remote(self, *args: P.args, **kwargs: P.kwargs) -> ReturnType:
|
|
@@ -1605,7 +1661,7 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1605
1661
|
"""
|
|
1606
1662
|
self._check_no_web_url("_experimental_spawn")
|
|
1607
1663
|
if self._is_generator:
|
|
1608
|
-
|
|
1664
|
+
invocation = await self._call_generator_nowait(args, kwargs)
|
|
1609
1665
|
else:
|
|
1610
1666
|
invocation = await self._call_function_nowait(
|
|
1611
1667
|
args, kwargs, function_call_invocation_type=api_pb2.FUNCTION_CALL_INVOCATION_TYPE_ASYNC
|
|
@@ -1637,13 +1693,14 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1637
1693
|
"""
|
|
1638
1694
|
self._check_no_web_url("spawn")
|
|
1639
1695
|
if self._is_generator:
|
|
1640
|
-
|
|
1696
|
+
invocation = await self._call_generator_nowait(args, kwargs)
|
|
1641
1697
|
else:
|
|
1642
1698
|
invocation = await self._call_function_nowait(args, kwargs, api_pb2.FUNCTION_CALL_INVOCATION_TYPE_ASYNC)
|
|
1643
1699
|
|
|
1644
1700
|
fc: _FunctionCall[ReturnType] = _FunctionCall._new_hydrated(
|
|
1645
1701
|
invocation.function_call_id, invocation.client, None
|
|
1646
1702
|
)
|
|
1703
|
+
fc._is_generator = self._is_generator if self._is_generator else False
|
|
1647
1704
|
return fc
|
|
1648
1705
|
|
|
1649
1706
|
def get_raw_f(self) -> Callable[..., Any]:
|
|
@@ -1702,8 +1759,22 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
1702
1759
|
|
|
1703
1760
|
The returned coroutine is not cancellation-safe.
|
|
1704
1761
|
"""
|
|
1762
|
+
|
|
1763
|
+
if self._is_generator:
|
|
1764
|
+
raise Exception("Cannot get the result of a generator function call. Use `get_gen` instead.")
|
|
1765
|
+
|
|
1705
1766
|
return await self._invocation().poll_function(timeout=timeout)
|
|
1706
1767
|
|
|
1768
|
+
async def get_gen(self) -> AsyncGenerator[Any, None]:
|
|
1769
|
+
"""
|
|
1770
|
+
Calls the generator remotely, executing it with the given arguments and returning the execution's result.
|
|
1771
|
+
"""
|
|
1772
|
+
if not self._is_generator:
|
|
1773
|
+
raise Exception("Cannot iterate over a non-generator function call. Use `get` instead.")
|
|
1774
|
+
|
|
1775
|
+
async for res in self._invocation().run_generator():
|
|
1776
|
+
yield res
|
|
1777
|
+
|
|
1707
1778
|
async def get_call_graph(self) -> list[InputInfo]:
|
|
1708
1779
|
"""Returns a structure representing the call graph from a given root
|
|
1709
1780
|
call ID, along with the status of execution for each node.
|
|
@@ -1734,7 +1805,9 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
1734
1805
|
await retry_transient_errors(self._client.stub.FunctionCallCancel, request)
|
|
1735
1806
|
|
|
1736
1807
|
@staticmethod
|
|
1737
|
-
async def from_id(
|
|
1808
|
+
async def from_id(
|
|
1809
|
+
function_call_id: str, client: Optional[_Client] = None, is_generator: bool = False
|
|
1810
|
+
) -> "_FunctionCall[Any]":
|
|
1738
1811
|
"""Instantiate a FunctionCall object from an existing ID.
|
|
1739
1812
|
|
|
1740
1813
|
Examples:
|
|
@@ -1757,6 +1830,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
1757
1830
|
client = await _Client.from_env()
|
|
1758
1831
|
|
|
1759
1832
|
fc: _FunctionCall[Any] = _FunctionCall._new_hydrated(function_call_id, client, None)
|
|
1833
|
+
fc._is_generator = is_generator
|
|
1760
1834
|
return fc
|
|
1761
1835
|
|
|
1762
1836
|
@staticmethod
|
|
@@ -1787,8 +1861,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
1787
1861
|
|
|
1788
1862
|
|
|
1789
1863
|
async def _gather(*function_calls: _FunctionCall[T]) -> typing.Sequence[T]:
|
|
1790
|
-
"""
|
|
1791
|
-
Deprecated: Please use `modal.FunctionCall.gather()` instead."""
|
|
1864
|
+
"""Deprecated: Please use `modal.FunctionCall.gather()` instead."""
|
|
1792
1865
|
deprecation_warning(
|
|
1793
1866
|
(2025, 2, 24),
|
|
1794
1867
|
"`modal.functions.gather()` is deprecated; please use `modal.FunctionCall.gather()` instead.",
|
|
@@ -10,6 +10,7 @@ from typing_extensions import Self
|
|
|
10
10
|
|
|
11
11
|
from ._resolver import Resolver
|
|
12
12
|
from ._utils.async_utils import aclosing
|
|
13
|
+
from ._utils.deprecation import deprecation_warning
|
|
13
14
|
from .client import _Client
|
|
14
15
|
from .config import config, logger
|
|
15
16
|
from .exception import ExecutionError, InvalidError
|
|
@@ -236,6 +237,19 @@ class _Object:
|
|
|
236
237
|
|
|
237
238
|
return self._deps if self._deps is not None else default_deps
|
|
238
239
|
|
|
240
|
+
async def resolve(self, client: Optional[_Client] = None):
|
|
241
|
+
"""mdmd:hidden"""
|
|
242
|
+
obj = self.__class__.__name__.strip("_")
|
|
243
|
+
deprecation_warning(
|
|
244
|
+
(2025, 1, 16),
|
|
245
|
+
f"The `{obj}.resolve` method is deprecated and will be removed in a future release."
|
|
246
|
+
f" Please use `{obj}.hydrate()` or `await {obj}.hydrate.aio()` instead."
|
|
247
|
+
"\n\nNote that it is rarely necessary to explicitly hydrate objects, as most methods"
|
|
248
|
+
" will lazily hydrate when needed.",
|
|
249
|
+
show_source=False, # synchronicity interferes with attributing source correctly
|
|
250
|
+
)
|
|
251
|
+
await self.hydrate(client)
|
|
252
|
+
|
|
239
253
|
async def hydrate(self, client: Optional[_Client] = None) -> Self:
|
|
240
254
|
"""Synchronize the local object with its identity on the Modal server.
|
|
241
255
|
|
|
@@ -18,7 +18,7 @@ from modal_proto import api_pb2
|
|
|
18
18
|
from ._functions import _Function
|
|
19
19
|
from ._utils.async_utils import synchronizer
|
|
20
20
|
from ._utils.deprecation import deprecation_warning
|
|
21
|
-
from ._utils.function_utils import callable_has_non_self_params
|
|
21
|
+
from ._utils.function_utils import callable_has_non_self_non_default_params, callable_has_non_self_params
|
|
22
22
|
from .config import logger
|
|
23
23
|
from .exception import InvalidError
|
|
24
24
|
|
|
@@ -181,11 +181,21 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
|
|
|
181
181
|
|
|
182
182
|
if require_sync and inspect.iscoroutinefunction(self.raw_f):
|
|
183
183
|
self.registered = True # Hacky, avoid false-positive warning
|
|
184
|
-
raise InvalidError(f"
|
|
184
|
+
raise InvalidError(f"`@modal.{decorator_name}` can't be applied to an async function.")
|
|
185
185
|
|
|
186
186
|
if require_nullary and callable_has_non_self_params(self.raw_f):
|
|
187
187
|
self.registered = True # Hacky, avoid false-positive warning
|
|
188
|
-
|
|
188
|
+
if callable_has_non_self_non_default_params(self.raw_f):
|
|
189
|
+
raise InvalidError(f"Functions obj by `@modal.{decorator_name}` can't have parameters.")
|
|
190
|
+
else:
|
|
191
|
+
# TODO(michael): probably fine to just make this an error at this point
|
|
192
|
+
# but best to do it in a separate PR
|
|
193
|
+
deprecation_warning(
|
|
194
|
+
(2024, 9, 4),
|
|
195
|
+
f"The function obj by `@modal.{decorator_name}` has default parameters, "
|
|
196
|
+
"but shouldn't have any parameters - Modal will drop support for "
|
|
197
|
+
"default parameters in a future release.",
|
|
198
|
+
)
|
|
189
199
|
|
|
190
200
|
def _get_raw_f(self) -> Callable[P, ReturnType]:
|
|
191
201
|
assert self.raw_f is not None
|
|
@@ -649,7 +659,7 @@ def _web_server(
|
|
|
649
659
|
def _build(
|
|
650
660
|
_warn_parentheses_missing=None, *, force: bool = False, timeout: int = 86400
|
|
651
661
|
) -> Callable[[Union[_PartialFunction, NullaryMethod]], _PartialFunction]:
|
|
652
|
-
"""
|
|
662
|
+
"""
|
|
653
663
|
Decorator for methods that execute at _build time_ to create a new Image layer.
|
|
654
664
|
|
|
655
665
|
**Deprecated**: This function is deprecated. We recommend using `modal.Volume`
|
|
@@ -324,8 +324,8 @@ def _deserialize_asgi(asgi: api_pb2.Asgi) -> Any:
|
|
|
324
324
|
elif msg_type == "websocket_send":
|
|
325
325
|
return {
|
|
326
326
|
"type": "websocket.send",
|
|
327
|
-
|
|
328
|
-
|
|
327
|
+
"bytes": asgi.websocket_send.bytes if asgi.websocket_send.HasField("bytes") else None,
|
|
328
|
+
"text": asgi.websocket_send.text if asgi.websocket_send.HasField("text") else None,
|
|
329
329
|
}
|
|
330
330
|
elif msg_type == "websocket_disconnect":
|
|
331
331
|
return {
|
|
@@ -23,7 +23,7 @@ from .._serialization import (
|
|
|
23
23
|
signature_to_parameter_specs,
|
|
24
24
|
)
|
|
25
25
|
from .._traceback import append_modal_tb
|
|
26
|
-
from ..config import logger
|
|
26
|
+
from ..config import config, logger
|
|
27
27
|
from ..exception import (
|
|
28
28
|
DeserializationError,
|
|
29
29
|
ExecutionError,
|
|
@@ -627,7 +627,8 @@ class FunctionCreationStatus:
|
|
|
627
627
|
|
|
628
628
|
class IncludeSourceMode(enum.Enum):
|
|
629
629
|
INCLUDE_NOTHING = False # can only be set in source, can't be set in config
|
|
630
|
-
INCLUDE_MAIN_PACKAGE = True #
|
|
630
|
+
INCLUDE_MAIN_PACKAGE = True # also represented by AUTOMOUNT=0 in config
|
|
631
|
+
INCLUDE_FIRST_PARTY = "legacy" # mounts all "local" modules in sys.modules - represented by AUTOMOUNT=1 in config
|
|
631
632
|
|
|
632
633
|
|
|
633
634
|
def get_include_source_mode(function_or_app_specific) -> IncludeSourceMode:
|
|
@@ -649,4 +650,6 @@ def get_include_source_mode(function_or_app_specific) -> IncludeSourceMode:
|
|
|
649
650
|
# explicitly set in app/function
|
|
650
651
|
return IncludeSourceMode(function_or_app_specific)
|
|
651
652
|
|
|
652
|
-
|
|
653
|
+
# note that the automount config boolean isn't a 1-1 mapping with include_source!
|
|
654
|
+
legacy_automount_mode: bool = config.get("automount")
|
|
655
|
+
return IncludeSourceMode.INCLUDE_FIRST_PARTY if legacy_automount_mode else IncludeSourceMode.INCLUDE_MAIN_PACKAGE
|
|
@@ -148,20 +148,15 @@ def create_channel(
|
|
|
148
148
|
|
|
149
149
|
logger.debug(f"Sending request to {event.method_name}")
|
|
150
150
|
|
|
151
|
-
async def
|
|
151
|
+
async def recv_trailing_metadata(trailing_metadata: grpclib.events.RecvTrailingMetadata) -> None:
|
|
152
152
|
# If we receive an auth token from the server, include it in all future requests.
|
|
153
153
|
# TODO(nathan): This isn't perfect because the metadata isn't propagated when the
|
|
154
154
|
# process is forked and a new channel is created. This is OK for now since this
|
|
155
155
|
# token is only used by the experimental input plane
|
|
156
|
-
if token := initial_metadata.metadata.get("x-modal-auth-token"):
|
|
157
|
-
metadata["x-modal-auth-token"] = str(token)
|
|
158
|
-
|
|
159
|
-
async def recv_trailing_metadata(trailing_metadata: grpclib.events.RecvTrailingMetadata) -> None:
|
|
160
156
|
if token := trailing_metadata.metadata.get("x-modal-auth-token"):
|
|
161
157
|
metadata["x-modal-auth-token"] = str(token)
|
|
162
158
|
|
|
163
159
|
grpclib.events.listen(channel, grpclib.events.SendRequest, send_request)
|
|
164
|
-
grpclib.events.listen(channel, grpclib.events.RecvInitialMetadata, recv_initial_metadata)
|
|
165
160
|
grpclib.events.listen(channel, grpclib.events.RecvTrailingMetadata, recv_trailing_metadata)
|
|
166
161
|
|
|
167
162
|
return channel
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
2
|
import inspect
|
|
3
3
|
import typing
|
|
4
|
+
import warnings
|
|
4
5
|
from collections.abc import AsyncGenerator, Coroutine, Sequence
|
|
5
6
|
from pathlib import PurePosixPath
|
|
6
7
|
from textwrap import dedent
|
|
@@ -29,6 +30,7 @@ from ._partial_function import (
|
|
|
29
30
|
)
|
|
30
31
|
from ._utils.async_utils import synchronize_api
|
|
31
32
|
from ._utils.deprecation import (
|
|
33
|
+
deprecation_error,
|
|
32
34
|
deprecation_warning,
|
|
33
35
|
warn_on_renamed_autoscaler_settings,
|
|
34
36
|
)
|
|
@@ -44,6 +46,7 @@ from .exception import ExecutionError, InvalidError
|
|
|
44
46
|
from .functions import Function
|
|
45
47
|
from .gpu import GPU_T
|
|
46
48
|
from .image import _Image
|
|
49
|
+
from .mount import _Mount
|
|
47
50
|
from .network_file_system import _NetworkFileSystem
|
|
48
51
|
from .partial_function import PartialFunction
|
|
49
52
|
from .proxy import _Proxy
|
|
@@ -76,6 +79,11 @@ class _LocalEntrypoint:
|
|
|
76
79
|
def app(self) -> "_App":
|
|
77
80
|
return self._app
|
|
78
81
|
|
|
82
|
+
@property
|
|
83
|
+
def stub(self) -> "_App":
|
|
84
|
+
# Deprecated soon, only for backwards compatibility
|
|
85
|
+
return self._app
|
|
86
|
+
|
|
79
87
|
|
|
80
88
|
LocalEntrypoint = synchronize_api(_LocalEntrypoint)
|
|
81
89
|
|
|
@@ -155,6 +163,7 @@ class _App:
|
|
|
155
163
|
_classes: dict[str, _Cls]
|
|
156
164
|
|
|
157
165
|
_image: Optional[_Image]
|
|
166
|
+
_mounts: Sequence[_Mount]
|
|
158
167
|
_secrets: Sequence[_Secret]
|
|
159
168
|
_volumes: dict[Union[str, PurePosixPath], _Volume]
|
|
160
169
|
_web_endpoints: list[str] # Used by the CLI
|
|
@@ -172,6 +181,7 @@ class _App:
|
|
|
172
181
|
name: Optional[str] = None,
|
|
173
182
|
*,
|
|
174
183
|
image: Optional[_Image] = None, # default image for all functions (default is `modal.Image.debian_slim()`)
|
|
184
|
+
mounts: Sequence[_Mount] = [], # default mounts for all functions
|
|
175
185
|
secrets: Sequence[_Secret] = [], # default secrets for all functions
|
|
176
186
|
volumes: dict[Union[str, PurePosixPath], _Volume] = {}, # default volumes for all functions
|
|
177
187
|
include_source: Optional[bool] = None,
|
|
@@ -192,6 +202,7 @@ class _App:
|
|
|
192
202
|
self._description = name
|
|
193
203
|
self._include_source_default = include_source
|
|
194
204
|
|
|
205
|
+
check_sequence(mounts, _Mount, "`mounts=` has to be a list or tuple of `modal.Mount` objects")
|
|
195
206
|
check_sequence(secrets, _Secret, "`secrets=` has to be a list or tuple of `modal.Secret` objects")
|
|
196
207
|
validate_volumes(volumes)
|
|
197
208
|
|
|
@@ -201,6 +212,7 @@ class _App:
|
|
|
201
212
|
self._functions = {}
|
|
202
213
|
self._classes = {}
|
|
203
214
|
self._image = image
|
|
215
|
+
self._mounts = mounts
|
|
204
216
|
self._secrets = secrets
|
|
205
217
|
self._volumes = volumes
|
|
206
218
|
self._local_entrypoints = {}
|
|
@@ -316,6 +328,7 @@ class _App:
|
|
|
316
328
|
self,
|
|
317
329
|
*,
|
|
318
330
|
client: Optional[_Client] = None,
|
|
331
|
+
show_progress: Optional[bool] = None,
|
|
319
332
|
detach: bool = False,
|
|
320
333
|
interactive: bool = False,
|
|
321
334
|
environment_name: Optional[str] = None,
|
|
@@ -361,6 +374,16 @@ class _App:
|
|
|
361
374
|
"""
|
|
362
375
|
from .runner import _run_app # Defer import of runner.py, which imports a lot from Rich
|
|
363
376
|
|
|
377
|
+
# See Github discussion here: https://github.com/modal-labs/modal-client/pull/2030#issuecomment-2237266186
|
|
378
|
+
|
|
379
|
+
if show_progress is True:
|
|
380
|
+
deprecation_error(
|
|
381
|
+
(2024, 11, 20),
|
|
382
|
+
"`show_progress=True` is no longer supported. Use `with modal.enable_output():` instead.",
|
|
383
|
+
)
|
|
384
|
+
elif show_progress is False:
|
|
385
|
+
deprecation_warning((2024, 11, 20), "`show_progress=False` is deprecated (and has no effect)")
|
|
386
|
+
|
|
364
387
|
async with _run_app(
|
|
365
388
|
self, client=client, detach=detach, interactive=interactive, environment_name=environment_name
|
|
366
389
|
):
|
|
@@ -441,7 +464,9 @@ class _App:
|
|
|
441
464
|
if not self._running_app:
|
|
442
465
|
raise ExecutionError("`_get_watch_mounts` requires a running app.")
|
|
443
466
|
|
|
444
|
-
all_mounts = [
|
|
467
|
+
all_mounts = [
|
|
468
|
+
*self._mounts,
|
|
469
|
+
]
|
|
445
470
|
for function in self.registered_functions.values():
|
|
446
471
|
all_mounts.extend(function._serve_mounts)
|
|
447
472
|
|
|
@@ -521,6 +546,14 @@ class _App:
|
|
|
521
546
|
"""All local CLI entrypoints registered on the app."""
|
|
522
547
|
return self._local_entrypoints
|
|
523
548
|
|
|
549
|
+
@property
|
|
550
|
+
def indexed_objects(self) -> dict[str, _Object]:
|
|
551
|
+
deprecation_warning(
|
|
552
|
+
(2024, 11, 25),
|
|
553
|
+
"`app.indexed_objects` is deprecated! Use `app.registered_functions` or `app.registered_classes` instead.",
|
|
554
|
+
)
|
|
555
|
+
return dict(**self._functions, **self._classes)
|
|
556
|
+
|
|
524
557
|
@property
|
|
525
558
|
def registered_web_endpoints(self) -> list[str]:
|
|
526
559
|
"""Names of web endpoint (ie. webhook) functions registered on the app."""
|
|
@@ -605,6 +638,7 @@ class _App:
|
|
|
605
638
|
GPU_T, list[GPU_T]
|
|
606
639
|
] = None, # GPU request as string ("any", "T4", ...), object (`modal.GPU.A100()`, ...), or a list of either
|
|
607
640
|
serialized: bool = False, # Whether to send the function over using cloudpickle.
|
|
641
|
+
mounts: Sequence[_Mount] = (), # Modal Mounts added to the container
|
|
608
642
|
network_file_systems: dict[
|
|
609
643
|
Union[str, PurePosixPath], _NetworkFileSystem
|
|
610
644
|
] = {}, # Mountpoints for Modal NetworkFileSystems
|
|
@@ -767,6 +801,12 @@ class _App:
|
|
|
767
801
|
rdma = None
|
|
768
802
|
i6pn_enabled = i6pn
|
|
769
803
|
|
|
804
|
+
if info.function_name.endswith(".app"):
|
|
805
|
+
warnings.warn(
|
|
806
|
+
"Beware: the function name is `app`. Modal will soon rename `Stub` to `App`, "
|
|
807
|
+
"so you might run into issues if you have code like `app = modal.App()` in the same scope"
|
|
808
|
+
)
|
|
809
|
+
|
|
770
810
|
if is_generator is None:
|
|
771
811
|
is_generator = inspect.isgeneratorfunction(raw_f) or inspect.isasyncgenfunction(raw_f)
|
|
772
812
|
|
|
@@ -784,6 +824,7 @@ class _App:
|
|
|
784
824
|
schedule=schedule,
|
|
785
825
|
is_generator=is_generator,
|
|
786
826
|
gpu=gpu,
|
|
827
|
+
mounts=[*self._mounts, *mounts],
|
|
787
828
|
network_file_systems=network_file_systems,
|
|
788
829
|
volumes={**self._volumes, **volumes},
|
|
789
830
|
cpu=cpu,
|
|
@@ -834,6 +875,7 @@ class _App:
|
|
|
834
875
|
GPU_T, list[GPU_T]
|
|
835
876
|
] = None, # GPU request as string ("any", "T4", ...), object (`modal.GPU.A100()`, ...), or a list of either
|
|
836
877
|
serialized: bool = False, # Whether to send the function over using cloudpickle.
|
|
878
|
+
mounts: Sequence[_Mount] = (),
|
|
837
879
|
network_file_systems: dict[
|
|
838
880
|
Union[str, PurePosixPath], _NetworkFileSystem
|
|
839
881
|
] = {}, # Mountpoints for Modal NetworkFileSystems
|
|
@@ -955,6 +997,7 @@ class _App:
|
|
|
955
997
|
image=image or self._get_default_image(),
|
|
956
998
|
secrets=[*self._secrets, *secrets],
|
|
957
999
|
gpu=gpu,
|
|
1000
|
+
mounts=[*self._mounts, *mounts],
|
|
958
1001
|
network_file_systems=network_file_systems,
|
|
959
1002
|
volumes={**self._volumes, **volumes},
|
|
960
1003
|
cpu=cpu,
|
|
@@ -994,6 +1037,44 @@ class _App:
|
|
|
994
1037
|
|
|
995
1038
|
return wrapper
|
|
996
1039
|
|
|
1040
|
+
async def spawn_sandbox(
|
|
1041
|
+
self,
|
|
1042
|
+
*entrypoint_args: str,
|
|
1043
|
+
image: Optional[_Image] = None, # The image to run as the container for the sandbox.
|
|
1044
|
+
mounts: Sequence[_Mount] = (), # Mounts to attach to the sandbox.
|
|
1045
|
+
secrets: Sequence[_Secret] = (), # Environment variables to inject into the sandbox.
|
|
1046
|
+
network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem] = {},
|
|
1047
|
+
timeout: Optional[int] = None, # Maximum execution time of the sandbox in seconds.
|
|
1048
|
+
workdir: Optional[str] = None, # Working directory of the sandbox.
|
|
1049
|
+
gpu: GPU_T = None,
|
|
1050
|
+
cloud: Optional[str] = None,
|
|
1051
|
+
region: Optional[Union[str, Sequence[str]]] = None, # Region or regions to run the sandbox on.
|
|
1052
|
+
# Specify, in fractional CPU cores, how many CPU cores to request.
|
|
1053
|
+
# Or, pass (request, limit) to additionally specify a hard limit in fractional CPU cores.
|
|
1054
|
+
# CPU throttling will prevent a container from exceeding its specified limit.
|
|
1055
|
+
cpu: Optional[Union[float, tuple[float, float]]] = None,
|
|
1056
|
+
# Specify, in MiB, a memory request which is the minimum memory required.
|
|
1057
|
+
# Or, pass (request, limit) to additionally specify a hard limit in MiB.
|
|
1058
|
+
memory: Optional[Union[int, tuple[int, int]]] = None,
|
|
1059
|
+
block_network: bool = False, # Whether to block network access
|
|
1060
|
+
volumes: dict[
|
|
1061
|
+
Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]
|
|
1062
|
+
] = {}, # Mount points for Modal Volumes and CloudBucketMounts
|
|
1063
|
+
pty_info: Optional[api_pb2.PTYInfo] = None,
|
|
1064
|
+
_experimental_scheduler_placement: Optional[
|
|
1065
|
+
SchedulerPlacement
|
|
1066
|
+
] = None, # Experimental controls over fine-grained scheduling (alpha).
|
|
1067
|
+
) -> None:
|
|
1068
|
+
"""mdmd:hidden"""
|
|
1069
|
+
arglist = ", ".join(repr(s) for s in entrypoint_args)
|
|
1070
|
+
message = (
|
|
1071
|
+
"`App.spawn_sandbox` is deprecated.\n\n"
|
|
1072
|
+
"Sandboxes can be created using the `Sandbox` object:\n\n"
|
|
1073
|
+
f"```\nsb = Sandbox.create({arglist}, app=app)\n```\n\n"
|
|
1074
|
+
"See https://modal.com/docs/guide/sandbox for more info on working with sandboxes."
|
|
1075
|
+
)
|
|
1076
|
+
deprecation_error((2024, 7, 5), message)
|
|
1077
|
+
|
|
997
1078
|
def include(self, /, other_app: "_App") -> typing_extensions.Self:
|
|
998
1079
|
"""Include another App's objects in this one.
|
|
999
1080
|
|
|
@@ -1073,3 +1154,23 @@ class _App:
|
|
|
1073
1154
|
|
|
1074
1155
|
|
|
1075
1156
|
App = synchronize_api(_App)
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
class _Stub(_App):
|
|
1160
|
+
"""mdmd:hidden
|
|
1161
|
+
This enables using a "Stub" class instead of "App".
|
|
1162
|
+
|
|
1163
|
+
For most of Modal's history, the app class was called "Stub", so this exists for
|
|
1164
|
+
backwards compatibility, in order to facilitate moving from "Stub" to "App".
|
|
1165
|
+
"""
|
|
1166
|
+
|
|
1167
|
+
def __new__(cls, *args, **kwargs):
|
|
1168
|
+
deprecation_warning(
|
|
1169
|
+
(2024, 4, 29),
|
|
1170
|
+
'The use of "Stub" has been deprecated in favor of "App".'
|
|
1171
|
+
" This is a pure name change with no other implications.",
|
|
1172
|
+
)
|
|
1173
|
+
return _App(*args, **kwargs)
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
Stub = synchronize_api(_Stub)
|