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/client.pyi
CHANGED
@@ -1,164 +1,196 @@
|
|
1
|
+
import asyncio.events
|
1
2
|
import asyncio.locks
|
2
|
-
import
|
3
|
+
import collections.abc
|
4
|
+
import google.protobuf.message
|
5
|
+
import grpclib.client
|
6
|
+
import modal._utils.async_utils
|
3
7
|
import modal_proto.api_grpc
|
8
|
+
import modal_proto.modal_api_grpc
|
4
9
|
import synchronicity.combined_types
|
5
10
|
import typing
|
6
11
|
import typing_extensions
|
7
12
|
|
8
|
-
def _get_metadata(client_type: int, credentials: typing.
|
9
|
-
...
|
13
|
+
def _get_metadata(client_type: int, credentials: typing.Optional[tuple[str, str]], version: str) -> dict[str, str]: ...
|
10
14
|
|
15
|
+
ReturnType = typing.TypeVar("ReturnType")
|
11
16
|
|
12
|
-
|
13
|
-
...
|
14
|
-
|
15
|
-
|
16
|
-
async def _grpc_exc_string(exc: grpclib.exceptions.GRPCError, method_name: str, server_url: str, timeout: float) -> str:
|
17
|
-
...
|
17
|
+
RequestType = typing.TypeVar("RequestType", bound="google.protobuf.message.Message")
|
18
18
|
|
19
|
+
ResponseType = typing.TypeVar("ResponseType", bound="google.protobuf.message.Message")
|
19
20
|
|
20
21
|
class _Client:
|
21
|
-
_client_from_env: typing.ClassVar[typing.
|
22
|
-
_client_from_env_lock: typing.ClassVar[typing.
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
22
|
+
_client_from_env: typing.ClassVar[typing.Optional[_Client]]
|
23
|
+
_client_from_env_lock: typing.ClassVar[typing.Optional[asyncio.locks.Lock]]
|
24
|
+
_cancellation_context: modal._utils.async_utils.TaskContext
|
25
|
+
_cancellation_context_event_loop: asyncio.events.AbstractEventLoop
|
26
|
+
_stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
|
27
|
+
|
28
|
+
def __init__(
|
29
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.72.13"
|
30
|
+
): ...
|
31
|
+
def is_closed(self) -> bool: ...
|
31
32
|
@property
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
async def
|
36
|
-
|
37
|
-
|
38
|
-
async def _close(self):
|
39
|
-
...
|
40
|
-
|
41
|
-
def set_pre_stop(self, pre_stop: typing.Callable[[], typing.Awaitable[None]]):
|
42
|
-
...
|
43
|
-
|
44
|
-
async def _init(self):
|
45
|
-
...
|
46
|
-
|
47
|
-
async def __aenter__(self):
|
48
|
-
...
|
49
|
-
|
50
|
-
async def __aexit__(self, exc_type, exc, tb):
|
51
|
-
...
|
52
|
-
|
33
|
+
def stub(self) -> modal_proto.modal_api_grpc.ModalClientModal: ...
|
34
|
+
async def _open(self): ...
|
35
|
+
async def _close(self, prep_for_restore: bool = False): ...
|
36
|
+
async def hello(self): ...
|
37
|
+
async def __aenter__(self): ...
|
38
|
+
async def __aexit__(self, exc_type, exc, tb): ...
|
53
39
|
@classmethod
|
54
|
-
def anonymous(cls, server_url: str) -> typing.AsyncContextManager[_Client]:
|
55
|
-
...
|
56
|
-
|
40
|
+
def anonymous(cls, server_url: str) -> typing.AsyncContextManager[_Client]: ...
|
57
41
|
@classmethod
|
58
|
-
async def from_env(cls, _override_config=None) -> _Client:
|
59
|
-
...
|
60
|
-
|
42
|
+
async def from_env(cls, _override_config=None) -> _Client: ...
|
61
43
|
@classmethod
|
62
|
-
async def from_credentials(cls, token_id: str, token_secret: str) -> _Client:
|
63
|
-
...
|
64
|
-
|
44
|
+
async def from_credentials(cls, token_id: str, token_secret: str) -> _Client: ...
|
65
45
|
@classmethod
|
66
|
-
async def verify(cls, server_url: str, credentials:
|
67
|
-
...
|
68
|
-
|
46
|
+
async def verify(cls, server_url: str, credentials: tuple[str, str]) -> None: ...
|
69
47
|
@classmethod
|
70
|
-
def set_env_client(cls, client: typing.
|
71
|
-
|
72
|
-
|
48
|
+
def set_env_client(cls, client: typing.Optional[_Client]): ...
|
49
|
+
async def _call_safely(self, coro, readable_method: str): ...
|
50
|
+
async def _reset_on_pid_change(self): ...
|
51
|
+
async def _get_grpclib_method(self, method_name: str) -> typing.Any: ...
|
52
|
+
async def _call_unary(
|
53
|
+
self,
|
54
|
+
method_name: str,
|
55
|
+
request: typing.Any,
|
56
|
+
*,
|
57
|
+
timeout: typing.Optional[float] = None,
|
58
|
+
metadata: typing.Union[
|
59
|
+
collections.abc.Mapping[str, typing.Union[str, bytes]],
|
60
|
+
collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
|
61
|
+
None,
|
62
|
+
] = None,
|
63
|
+
) -> typing.Any: ...
|
64
|
+
def _call_stream(
|
65
|
+
self,
|
66
|
+
method_name: str,
|
67
|
+
request: typing.Any,
|
68
|
+
*,
|
69
|
+
metadata: typing.Union[
|
70
|
+
collections.abc.Mapping[str, typing.Union[str, bytes]],
|
71
|
+
collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
|
72
|
+
None,
|
73
|
+
],
|
74
|
+
) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
73
75
|
|
74
76
|
class Client:
|
75
|
-
_client_from_env: typing.ClassVar[typing.
|
76
|
-
_client_from_env_lock: typing.ClassVar[typing.
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
77
|
+
_client_from_env: typing.ClassVar[typing.Optional[Client]]
|
78
|
+
_client_from_env_lock: typing.ClassVar[typing.Optional[asyncio.locks.Lock]]
|
79
|
+
_cancellation_context: modal._utils.async_utils.TaskContext
|
80
|
+
_cancellation_context_event_loop: asyncio.events.AbstractEventLoop
|
81
|
+
_stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
|
82
|
+
|
83
|
+
def __init__(
|
84
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.72.13"
|
85
|
+
): ...
|
86
|
+
def is_closed(self) -> bool: ...
|
81
87
|
@property
|
82
|
-
def stub(self) ->
|
83
|
-
...
|
84
|
-
|
85
|
-
@property
|
86
|
-
def authenticated(self) -> bool:
|
87
|
-
...
|
88
|
+
def stub(self) -> modal_proto.modal_api_grpc.ModalClientModal: ...
|
88
89
|
|
89
90
|
class ___open_spec(typing_extensions.Protocol):
|
90
|
-
def __call__(self):
|
91
|
-
|
92
|
-
|
93
|
-
async def aio(self, *args, **kwargs):
|
94
|
-
...
|
91
|
+
def __call__(self): ...
|
92
|
+
async def aio(self): ...
|
95
93
|
|
96
94
|
_open: ___open_spec
|
97
95
|
|
98
96
|
class ___close_spec(typing_extensions.Protocol):
|
99
|
-
def __call__(self):
|
100
|
-
|
101
|
-
|
102
|
-
async def aio(self, *args, **kwargs):
|
103
|
-
...
|
97
|
+
def __call__(self, prep_for_restore: bool = False): ...
|
98
|
+
async def aio(self, prep_for_restore: bool = False): ...
|
104
99
|
|
105
100
|
_close: ___close_spec
|
106
101
|
|
107
|
-
class
|
108
|
-
def __call__(self
|
109
|
-
|
110
|
-
|
111
|
-
def aio(self, pre_stop: typing.Callable[[], typing.Awaitable[None]]):
|
112
|
-
...
|
113
|
-
|
114
|
-
set_pre_stop: __set_pre_stop_spec
|
115
|
-
|
116
|
-
class ___init_spec(typing_extensions.Protocol):
|
117
|
-
def __call__(self):
|
118
|
-
...
|
119
|
-
|
120
|
-
async def aio(self, *args, **kwargs):
|
121
|
-
...
|
122
|
-
|
123
|
-
_init: ___init_spec
|
124
|
-
|
125
|
-
def __enter__(self):
|
126
|
-
...
|
127
|
-
|
128
|
-
async def __aenter__(self, *args, **kwargs):
|
129
|
-
...
|
102
|
+
class __hello_spec(typing_extensions.Protocol):
|
103
|
+
def __call__(self): ...
|
104
|
+
async def aio(self): ...
|
130
105
|
|
131
|
-
|
132
|
-
...
|
133
|
-
|
134
|
-
async def __aexit__(self, *args, **kwargs):
|
135
|
-
...
|
106
|
+
hello: __hello_spec
|
136
107
|
|
108
|
+
def __enter__(self): ...
|
109
|
+
async def __aenter__(self): ...
|
110
|
+
def __exit__(self, exc_type, exc, tb): ...
|
111
|
+
async def __aexit__(self, exc_type, exc, tb): ...
|
137
112
|
@classmethod
|
138
|
-
def anonymous(cls, server_url: str) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Client]:
|
139
|
-
...
|
140
|
-
|
113
|
+
def anonymous(cls, server_url: str) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Client]: ...
|
141
114
|
@classmethod
|
142
|
-
def from_env(cls, _override_config=None) -> Client:
|
143
|
-
...
|
144
|
-
|
115
|
+
def from_env(cls, _override_config=None) -> Client: ...
|
145
116
|
@classmethod
|
146
|
-
def from_credentials(cls, token_id: str, token_secret: str) -> Client:
|
147
|
-
...
|
148
|
-
|
117
|
+
def from_credentials(cls, token_id: str, token_secret: str) -> Client: ...
|
149
118
|
@classmethod
|
150
|
-
def verify(cls, server_url: str, credentials:
|
151
|
-
...
|
152
|
-
|
119
|
+
def verify(cls, server_url: str, credentials: tuple[str, str]) -> None: ...
|
153
120
|
@classmethod
|
154
|
-
def set_env_client(cls, client: typing.
|
155
|
-
|
156
|
-
|
121
|
+
def set_env_client(cls, client: typing.Optional[Client]): ...
|
122
|
+
|
123
|
+
class ___call_safely_spec(typing_extensions.Protocol):
|
124
|
+
def __call__(self, coro, readable_method: str): ...
|
125
|
+
async def aio(self, coro, readable_method: str): ...
|
126
|
+
|
127
|
+
_call_safely: ___call_safely_spec
|
128
|
+
|
129
|
+
class ___reset_on_pid_change_spec(typing_extensions.Protocol):
|
130
|
+
def __call__(self): ...
|
131
|
+
async def aio(self): ...
|
132
|
+
|
133
|
+
_reset_on_pid_change: ___reset_on_pid_change_spec
|
134
|
+
|
135
|
+
class ___get_grpclib_method_spec(typing_extensions.Protocol):
|
136
|
+
def __call__(self, method_name: str) -> typing.Any: ...
|
137
|
+
async def aio(self, method_name: str) -> typing.Any: ...
|
138
|
+
|
139
|
+
_get_grpclib_method: ___get_grpclib_method_spec
|
140
|
+
|
141
|
+
async def _call_unary(
|
142
|
+
self,
|
143
|
+
method_name: str,
|
144
|
+
request: typing.Any,
|
145
|
+
*,
|
146
|
+
timeout: typing.Optional[float] = None,
|
147
|
+
metadata: typing.Union[
|
148
|
+
collections.abc.Mapping[str, typing.Union[str, bytes]],
|
149
|
+
collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
|
150
|
+
None,
|
151
|
+
] = None,
|
152
|
+
) -> typing.Any: ...
|
153
|
+
def _call_stream(
|
154
|
+
self,
|
155
|
+
method_name: str,
|
156
|
+
request: typing.Any,
|
157
|
+
*,
|
158
|
+
metadata: typing.Union[
|
159
|
+
collections.abc.Mapping[str, typing.Union[str, bytes]],
|
160
|
+
collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
|
161
|
+
None,
|
162
|
+
],
|
163
|
+
) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
164
|
+
|
165
|
+
class UnaryUnaryWrapper(typing.Generic[RequestType, ResponseType]):
|
166
|
+
wrapped_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType]
|
167
|
+
client: _Client
|
168
|
+
|
169
|
+
def __init__(self, wrapped_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType], client: _Client): ...
|
170
|
+
@property
|
171
|
+
def name(self) -> str: ...
|
172
|
+
async def __call__(
|
173
|
+
self,
|
174
|
+
req: RequestType,
|
175
|
+
*,
|
176
|
+
timeout: typing.Optional[float] = None,
|
177
|
+
metadata: typing.Union[
|
178
|
+
collections.abc.Mapping[str, typing.Union[str, bytes]],
|
179
|
+
collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
|
180
|
+
None,
|
181
|
+
] = None,
|
182
|
+
) -> ResponseType: ...
|
183
|
+
|
184
|
+
class UnaryStreamWrapper(typing.Generic[RequestType, ResponseType]):
|
185
|
+
wrapped_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType]
|
186
|
+
|
187
|
+
def __init__(
|
188
|
+
self, wrapped_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType], client: _Client
|
189
|
+
): ...
|
190
|
+
@property
|
191
|
+
def name(self) -> str: ...
|
192
|
+
def unary_stream(self, request, metadata: typing.Optional[typing.Any] = None): ...
|
157
193
|
|
158
194
|
HEARTBEAT_INTERVAL: float
|
159
195
|
|
160
196
|
HEARTBEAT_TIMEOUT: float
|
161
|
-
|
162
|
-
CLIENT_CREATE_ATTEMPT_TIMEOUT: float
|
163
|
-
|
164
|
-
CLIENT_CREATE_TOTAL_TIMEOUT: float
|
modal/cloud_bucket_mount.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# Copyright Modal Labs 2022
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import
|
3
|
+
from typing import Optional
|
4
4
|
from urllib.parse import urlparse
|
5
5
|
|
6
6
|
from modal_proto import api_pb2
|
7
7
|
|
8
8
|
from ._utils.async_utils import synchronize_api
|
9
|
+
from .config import logger
|
9
10
|
from .secret import _Secret
|
10
11
|
|
11
12
|
|
@@ -15,18 +16,21 @@ class _CloudBucketMount:
|
|
15
16
|
|
16
17
|
S3 buckets are mounted using [AWS S3 Mountpoint](https://github.com/awslabs/mountpoint-s3).
|
17
18
|
S3 mounts are optimized for reading large files sequentially. It does not support every file operation; consult
|
18
|
-
[the AWS S3 Mountpoint documentation](https://github.com/awslabs/mountpoint-s3/blob/main/doc/SEMANTICS.md)
|
19
|
+
[the AWS S3 Mountpoint documentation](https://github.com/awslabs/mountpoint-s3/blob/main/doc/SEMANTICS.md)
|
20
|
+
for more information.
|
19
21
|
|
20
22
|
**AWS S3 Usage**
|
21
23
|
|
22
24
|
```python
|
23
25
|
import subprocess
|
24
26
|
|
25
|
-
app = modal.App()
|
26
|
-
secret = modal.Secret.
|
27
|
-
"
|
28
|
-
"
|
29
|
-
|
27
|
+
app = modal.App()
|
28
|
+
secret = modal.Secret.from_name(
|
29
|
+
"aws-secret",
|
30
|
+
required_keys=["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]
|
31
|
+
# Note: providing AWS_REGION can help when automatic detection of the bucket region fails.
|
32
|
+
)
|
33
|
+
|
30
34
|
@app.function(
|
31
35
|
volumes={
|
32
36
|
"/my-mount": modal.CloudBucketMount(
|
@@ -42,17 +46,18 @@ class _CloudBucketMount:
|
|
42
46
|
|
43
47
|
**Cloudflare R2 Usage**
|
44
48
|
|
45
|
-
Cloudflare R2 is [S3-compatible](https://developers.cloudflare.com/r2/api/s3/api/) so its setup looks
|
46
|
-
But additionally the `bucket_endpoint_url` argument must be passed.
|
49
|
+
Cloudflare R2 is [S3-compatible](https://developers.cloudflare.com/r2/api/s3/api/) so its setup looks
|
50
|
+
very similar to S3. But additionally the `bucket_endpoint_url` argument must be passed.
|
47
51
|
|
48
52
|
```python
|
49
53
|
import subprocess
|
50
54
|
|
51
|
-
app = modal.App()
|
52
|
-
secret = modal.Secret.
|
53
|
-
"
|
54
|
-
"
|
55
|
-
|
55
|
+
app = modal.App()
|
56
|
+
secret = modal.Secret.from_name(
|
57
|
+
"r2-secret",
|
58
|
+
required_keys=["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]
|
59
|
+
)
|
60
|
+
|
56
61
|
@app.function(
|
57
62
|
volumes={
|
58
63
|
"/my-mount": modal.CloudBucketMount(
|
@@ -69,25 +74,25 @@ class _CloudBucketMount:
|
|
69
74
|
|
70
75
|
**Google GCS Usage**
|
71
76
|
|
72
|
-
Google Cloud Storage (GCS) is
|
73
|
-
|
74
|
-
|
77
|
+
Google Cloud Storage (GCS) is [S3-compatible](https://cloud.google.com/storage/docs/interoperability).
|
78
|
+
GCS Buckets also require a secret with Google-specific key names (see below) populated with
|
79
|
+
a [HMAC key](https://cloud.google.com/storage/docs/authentication/managing-hmackeys#create).
|
75
80
|
|
76
81
|
```python
|
77
82
|
import subprocess
|
78
83
|
|
79
|
-
app = modal.App()
|
80
|
-
gcp_hmac_secret = modal.Secret.
|
81
|
-
"
|
82
|
-
"
|
83
|
-
|
84
|
+
app = modal.App()
|
85
|
+
gcp_hmac_secret = modal.Secret.from_name(
|
86
|
+
"gcp-secret",
|
87
|
+
required_keys=["GOOGLE_ACCESS_KEY_ID", "GOOGLE_ACCESS_KEY_SECRET"]
|
88
|
+
)
|
89
|
+
|
84
90
|
@app.function(
|
85
91
|
volumes={
|
86
92
|
"/my-mount": modal.CloudBucketMount(
|
87
93
|
bucket_name="my-gcs-bucket",
|
88
94
|
bucket_endpoint_url="https://storage.googleapis.com",
|
89
95
|
secret=gcp_hmac_secret,
|
90
|
-
read_only=True, # writing to bucket currently unsupported
|
91
96
|
)
|
92
97
|
}
|
93
98
|
)
|
@@ -100,6 +105,8 @@ class _CloudBucketMount:
|
|
100
105
|
# Endpoint URL is used to support Cloudflare R2 and Google Cloud Platform GCS.
|
101
106
|
bucket_endpoint_url: Optional[str] = None
|
102
107
|
|
108
|
+
key_prefix: Optional[str] = None
|
109
|
+
|
103
110
|
# Credentials used to access a cloud bucket.
|
104
111
|
# If the bucket is private, the secret **must** contain AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
|
105
112
|
# If the bucket is publicly accessible, the secret is unnecessary and can be omitted.
|
@@ -109,9 +116,9 @@ class _CloudBucketMount:
|
|
109
116
|
requester_pays: bool = False
|
110
117
|
|
111
118
|
|
112
|
-
def cloud_bucket_mounts_to_proto(mounts:
|
119
|
+
def cloud_bucket_mounts_to_proto(mounts: list[tuple[str, _CloudBucketMount]]) -> list[api_pb2.CloudBucketMount]:
|
113
120
|
"""Helper function to convert `CloudBucketMount` to a list of protobufs that can be passed to the server."""
|
114
|
-
cloud_bucket_mounts:
|
121
|
+
cloud_bucket_mounts: list[api_pb2.CloudBucketMount] = []
|
115
122
|
|
116
123
|
for path, mount in mounts:
|
117
124
|
# crude mapping from mount arguments to type.
|
@@ -121,12 +128,12 @@ def cloud_bucket_mounts_to_proto(mounts: List[Tuple[str, _CloudBucketMount]]) ->
|
|
121
128
|
bucket_type = api_pb2.CloudBucketMount.BucketType.R2
|
122
129
|
elif parse_result.hostname.endswith("storage.googleapis.com"):
|
123
130
|
bucket_type = api_pb2.CloudBucketMount.BucketType.GCP
|
124
|
-
if not mount.read_only:
|
125
|
-
raise ValueError(
|
126
|
-
f"CloudBucketMount of '{mount.bucket_name}' is invalid. Writing to GCP buckets with modal.CloudBucketMount in currently unsupported."
|
127
|
-
)
|
128
131
|
else:
|
129
|
-
|
132
|
+
logger.warning(
|
133
|
+
"CloudBucketMount received unrecognized bucket endpoint URL. "
|
134
|
+
"Assuming AWS S3 configuration as fallback."
|
135
|
+
)
|
136
|
+
bucket_type = api_pb2.CloudBucketMount.BucketType.S3
|
130
137
|
else:
|
131
138
|
# just assume S3; this is backwards and forwards compatible.
|
132
139
|
bucket_type = api_pb2.CloudBucketMount.BucketType.S3
|
@@ -134,6 +141,11 @@ def cloud_bucket_mounts_to_proto(mounts: List[Tuple[str, _CloudBucketMount]]) ->
|
|
134
141
|
if mount.requester_pays and not mount.secret:
|
135
142
|
raise ValueError("Credentials required in order to use Requester Pays.")
|
136
143
|
|
144
|
+
if mount.key_prefix and not mount.key_prefix.endswith("/"):
|
145
|
+
raise ValueError("key_prefix will be prefixed to all object paths, so it must end in a '/'")
|
146
|
+
else:
|
147
|
+
key_prefix = mount.key_prefix
|
148
|
+
|
137
149
|
cloud_bucket_mount = api_pb2.CloudBucketMount(
|
138
150
|
bucket_name=mount.bucket_name,
|
139
151
|
bucket_endpoint_url=mount.bucket_endpoint_url,
|
@@ -142,6 +154,7 @@ def cloud_bucket_mounts_to_proto(mounts: List[Tuple[str, _CloudBucketMount]]) ->
|
|
142
154
|
read_only=mount.read_only,
|
143
155
|
bucket_type=bucket_type,
|
144
156
|
requester_pays=mount.requester_pays,
|
157
|
+
key_prefix=key_prefix,
|
145
158
|
)
|
146
159
|
cloud_bucket_mounts.append(cloud_bucket_mount)
|
147
160
|
|
modal/cloud_bucket_mount.pyi
CHANGED
@@ -4,37 +4,44 @@ import typing
|
|
4
4
|
|
5
5
|
class _CloudBucketMount:
|
6
6
|
bucket_name: str
|
7
|
-
bucket_endpoint_url: typing.
|
8
|
-
|
7
|
+
bucket_endpoint_url: typing.Optional[str]
|
8
|
+
key_prefix: typing.Optional[str]
|
9
|
+
secret: typing.Optional[modal.secret._Secret]
|
9
10
|
read_only: bool
|
10
11
|
requester_pays: bool
|
11
12
|
|
12
|
-
def __init__(
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
def
|
23
|
-
|
24
|
-
|
13
|
+
def __init__(
|
14
|
+
self,
|
15
|
+
bucket_name: str,
|
16
|
+
bucket_endpoint_url: typing.Optional[str] = None,
|
17
|
+
key_prefix: typing.Optional[str] = None,
|
18
|
+
secret: typing.Optional[modal.secret._Secret] = None,
|
19
|
+
read_only: bool = False,
|
20
|
+
requester_pays: bool = False,
|
21
|
+
) -> None: ...
|
22
|
+
def __repr__(self): ...
|
23
|
+
def __eq__(self, other): ...
|
24
|
+
|
25
|
+
def cloud_bucket_mounts_to_proto(
|
26
|
+
mounts: list[tuple[str, _CloudBucketMount]],
|
27
|
+
) -> list[modal_proto.api_pb2.CloudBucketMount]: ...
|
25
28
|
|
26
29
|
class CloudBucketMount:
|
27
30
|
bucket_name: str
|
28
|
-
bucket_endpoint_url: typing.
|
29
|
-
|
31
|
+
bucket_endpoint_url: typing.Optional[str]
|
32
|
+
key_prefix: typing.Optional[str]
|
33
|
+
secret: typing.Optional[modal.secret.Secret]
|
30
34
|
read_only: bool
|
31
35
|
requester_pays: bool
|
32
36
|
|
33
|
-
def __init__(
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
bucket_name: str,
|
40
|
+
bucket_endpoint_url: typing.Optional[str] = None,
|
41
|
+
key_prefix: typing.Optional[str] = None,
|
42
|
+
secret: typing.Optional[modal.secret.Secret] = None,
|
43
|
+
read_only: bool = False,
|
44
|
+
requester_pays: bool = False,
|
45
|
+
) -> None: ...
|
46
|
+
def __repr__(self): ...
|
47
|
+
def __eq__(self, other): ...
|