modal 1.0.3.dev10__py3-none-any.whl → 1.2.3.dev7__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.
Potentially problematic release.
This version of modal might be problematic. Click here for more details.
- modal/__init__.py +0 -2
- modal/__main__.py +3 -4
- modal/_billing.py +80 -0
- modal/_clustered_functions.py +7 -3
- modal/_clustered_functions.pyi +15 -3
- modal/_container_entrypoint.py +51 -69
- modal/_functions.py +508 -240
- modal/_grpc_client.py +171 -0
- modal/_load_context.py +105 -0
- modal/_object.py +81 -21
- modal/_output.py +58 -45
- modal/_partial_function.py +48 -73
- modal/_pty.py +7 -3
- modal/_resolver.py +26 -46
- modal/_runtime/asgi.py +4 -3
- modal/_runtime/container_io_manager.py +358 -220
- modal/_runtime/container_io_manager.pyi +296 -101
- modal/_runtime/execution_context.py +18 -2
- modal/_runtime/execution_context.pyi +64 -7
- modal/_runtime/gpu_memory_snapshot.py +262 -57
- modal/_runtime/user_code_imports.py +28 -58
- modal/_serialization.py +90 -6
- modal/_traceback.py +42 -1
- modal/_tunnel.pyi +380 -12
- modal/_utils/async_utils.py +84 -29
- modal/_utils/auth_token_manager.py +111 -0
- modal/_utils/blob_utils.py +181 -58
- modal/_utils/deprecation.py +19 -0
- modal/_utils/function_utils.py +91 -47
- modal/_utils/grpc_utils.py +89 -66
- modal/_utils/mount_utils.py +26 -1
- modal/_utils/name_utils.py +17 -3
- modal/_utils/task_command_router_client.py +536 -0
- modal/_utils/time_utils.py +34 -6
- modal/app.py +256 -88
- modal/app.pyi +909 -92
- modal/billing.py +5 -0
- modal/builder/2025.06.txt +18 -0
- modal/builder/PREVIEW.txt +18 -0
- modal/builder/base-images.json +58 -0
- modal/cli/_download.py +19 -3
- modal/cli/_traceback.py +3 -2
- modal/cli/app.py +4 -4
- modal/cli/cluster.py +15 -7
- modal/cli/config.py +5 -3
- modal/cli/container.py +7 -6
- modal/cli/dict.py +22 -16
- modal/cli/entry_point.py +12 -5
- modal/cli/environment.py +5 -4
- modal/cli/import_refs.py +3 -3
- modal/cli/launch.py +102 -5
- modal/cli/network_file_system.py +11 -12
- modal/cli/profile.py +3 -2
- modal/cli/programs/launch_instance_ssh.py +94 -0
- modal/cli/programs/run_jupyter.py +1 -1
- modal/cli/programs/run_marimo.py +95 -0
- modal/cli/programs/vscode.py +1 -1
- modal/cli/queues.py +57 -26
- modal/cli/run.py +91 -23
- modal/cli/secret.py +48 -22
- modal/cli/token.py +7 -8
- modal/cli/utils.py +4 -7
- modal/cli/volume.py +31 -25
- modal/client.py +15 -85
- modal/client.pyi +183 -62
- modal/cloud_bucket_mount.py +5 -3
- modal/cloud_bucket_mount.pyi +197 -5
- modal/cls.py +200 -126
- modal/cls.pyi +446 -68
- modal/config.py +29 -11
- modal/container_process.py +319 -19
- modal/container_process.pyi +190 -20
- modal/dict.py +290 -71
- modal/dict.pyi +835 -83
- modal/environments.py +15 -27
- modal/environments.pyi +46 -24
- modal/exception.py +14 -2
- modal/experimental/__init__.py +194 -40
- modal/experimental/flash.py +618 -0
- modal/experimental/flash.pyi +380 -0
- modal/experimental/ipython.py +11 -7
- modal/file_io.py +29 -36
- modal/file_io.pyi +251 -53
- modal/file_pattern_matcher.py +56 -16
- modal/functions.pyi +673 -92
- modal/gpu.py +1 -1
- modal/image.py +528 -176
- modal/image.pyi +1572 -145
- modal/io_streams.py +458 -128
- modal/io_streams.pyi +433 -52
- modal/mount.py +216 -151
- modal/mount.pyi +225 -78
- modal/network_file_system.py +45 -62
- modal/network_file_system.pyi +277 -56
- modal/object.pyi +93 -17
- modal/parallel_map.py +942 -129
- modal/parallel_map.pyi +294 -15
- modal/partial_function.py +0 -2
- modal/partial_function.pyi +234 -19
- modal/proxy.py +17 -8
- modal/proxy.pyi +36 -3
- modal/queue.py +270 -65
- modal/queue.pyi +817 -57
- modal/runner.py +115 -101
- modal/runner.pyi +205 -49
- modal/sandbox.py +512 -136
- modal/sandbox.pyi +845 -111
- modal/schedule.py +1 -1
- modal/secret.py +300 -70
- modal/secret.pyi +589 -34
- modal/serving.py +7 -11
- modal/serving.pyi +7 -8
- modal/snapshot.py +11 -8
- modal/snapshot.pyi +25 -4
- modal/token_flow.py +4 -4
- modal/token_flow.pyi +28 -8
- modal/volume.py +416 -158
- modal/volume.pyi +1117 -121
- {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/METADATA +10 -9
- modal-1.2.3.dev7.dist-info/RECORD +195 -0
- modal_docs/mdmd/mdmd.py +17 -4
- modal_proto/api.proto +534 -79
- modal_proto/api_grpc.py +337 -1
- modal_proto/api_pb2.py +1522 -968
- modal_proto/api_pb2.pyi +1619 -134
- modal_proto/api_pb2_grpc.py +699 -4
- modal_proto/api_pb2_grpc.pyi +226 -14
- modal_proto/modal_api_grpc.py +175 -154
- modal_proto/sandbox_router.proto +145 -0
- modal_proto/sandbox_router_grpc.py +105 -0
- modal_proto/sandbox_router_pb2.py +149 -0
- modal_proto/sandbox_router_pb2.pyi +333 -0
- modal_proto/sandbox_router_pb2_grpc.py +203 -0
- modal_proto/sandbox_router_pb2_grpc.pyi +75 -0
- modal_proto/task_command_router.proto +144 -0
- modal_proto/task_command_router_grpc.py +105 -0
- modal_proto/task_command_router_pb2.py +149 -0
- modal_proto/task_command_router_pb2.pyi +333 -0
- modal_proto/task_command_router_pb2_grpc.py +203 -0
- modal_proto/task_command_router_pb2_grpc.pyi +75 -0
- modal_version/__init__.py +1 -1
- modal/requirements/PREVIEW.txt +0 -16
- modal/requirements/base-images.json +0 -26
- modal-1.0.3.dev10.dist-info/RECORD +0 -179
- modal_proto/modal_options_grpc.py +0 -3
- modal_proto/options.proto +0 -19
- modal_proto/options_grpc.py +0 -3
- modal_proto/options_pb2.py +0 -35
- modal_proto/options_pb2.pyi +0 -20
- modal_proto/options_pb2_grpc.py +0 -4
- modal_proto/options_pb2_grpc.pyi +0 -7
- /modal/{requirements → builder}/2023.12.312.txt +0 -0
- /modal/{requirements → builder}/2023.12.txt +0 -0
- /modal/{requirements → builder}/2024.04.txt +0 -0
- /modal/{requirements → builder}/2024.10.txt +0 -0
- /modal/{requirements → builder}/README.md +0 -0
- {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/WHEEL +0 -0
- {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/entry_points.txt +0 -0
- {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/licenses/LICENSE +0 -0
- {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/top_level.txt +0 -0
|
@@ -9,13 +9,25 @@ import synchronicity.combined_types
|
|
|
9
9
|
import typing
|
|
10
10
|
import typing_extensions
|
|
11
11
|
|
|
12
|
-
class UserException(Exception):
|
|
13
|
-
|
|
12
|
+
class UserException(Exception):
|
|
13
|
+
"""Used to shut down the task gracefully."""
|
|
14
|
+
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
class Sentinel:
|
|
18
|
+
"""Used to get type-stubs to work with this object."""
|
|
19
|
+
|
|
20
|
+
...
|
|
14
21
|
|
|
15
22
|
class IOContext:
|
|
23
|
+
"""Context object for managing input, function calls, and function executions
|
|
24
|
+
in a batched or single input context.
|
|
25
|
+
"""
|
|
26
|
+
|
|
16
27
|
input_ids: list[str]
|
|
17
28
|
retry_counts: list[int]
|
|
18
29
|
function_call_ids: list[str]
|
|
30
|
+
attempt_tokens: list[str]
|
|
19
31
|
function_inputs: list[modal_proto.api_pb2.FunctionInput]
|
|
20
32
|
finalized_function: modal._runtime.user_code_imports.FinalizedFunction
|
|
21
33
|
_cancel_issued: bool
|
|
@@ -26,32 +38,56 @@ class IOContext:
|
|
|
26
38
|
input_ids: list[str],
|
|
27
39
|
retry_counts: list[int],
|
|
28
40
|
function_call_ids: list[str],
|
|
41
|
+
attempt_tokens: list[str],
|
|
29
42
|
finalized_function: modal._runtime.user_code_imports.FinalizedFunction,
|
|
30
43
|
function_inputs: list[modal_proto.api_pb2.FunctionInput],
|
|
31
44
|
is_batched: bool,
|
|
32
45
|
client: modal.client._Client,
|
|
33
|
-
):
|
|
46
|
+
):
|
|
47
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
48
|
+
...
|
|
49
|
+
|
|
34
50
|
@classmethod
|
|
35
51
|
async def create(
|
|
36
52
|
cls,
|
|
37
53
|
client: modal.client._Client,
|
|
38
54
|
finalized_functions: dict[str, modal._runtime.user_code_imports.FinalizedFunction],
|
|
39
|
-
inputs: list[tuple[str, int, str, modal_proto.api_pb2.FunctionInput]],
|
|
55
|
+
inputs: list[tuple[str, int, str, str, modal_proto.api_pb2.FunctionInput]],
|
|
40
56
|
is_batched: bool,
|
|
41
57
|
) -> IOContext: ...
|
|
42
58
|
def set_cancel_callback(self, cb: collections.abc.Callable[[], None]): ...
|
|
43
59
|
def cancel(self): ...
|
|
44
60
|
def _args_and_kwargs(self) -> tuple[tuple[typing.Any, ...], dict[str, list[typing.Any]]]: ...
|
|
45
|
-
def
|
|
46
|
-
def
|
|
61
|
+
def _generator_output_format(self) -> int: ...
|
|
62
|
+
def _prepare_batch_output(self, data: typing.Any) -> list[typing.Any]: ...
|
|
63
|
+
def call_function_sync(self) -> list[typing.Any]: ...
|
|
64
|
+
async def call_function_async(self) -> list[typing.Any]: ...
|
|
65
|
+
def call_generator_sync(self) -> typing.Generator[typing.Any, None, None]: ...
|
|
66
|
+
def call_generator_async(self) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
|
67
|
+
async def output_items_cancellation(self, started_at: float): ...
|
|
68
|
+
def _determine_output_format(self, input_format: int) -> int: ...
|
|
69
|
+
async def output_items_exception(
|
|
70
|
+
self, started_at: float, task_id: str, exc: BaseException
|
|
71
|
+
) -> list[modal_proto.api_pb2.FunctionPutOutputsItem]: ...
|
|
72
|
+
def output_items_generator_done(
|
|
73
|
+
self, started_at: float, items_total: int
|
|
74
|
+
) -> list[modal_proto.api_pb2.FunctionPutOutputsItem]: ...
|
|
75
|
+
async def output_items(
|
|
76
|
+
self, started_at: float, data: list[typing.Any]
|
|
77
|
+
) -> list[modal_proto.api_pb2.FunctionPutOutputsItem]: ...
|
|
47
78
|
|
|
48
79
|
class InputSlots:
|
|
80
|
+
"""A semaphore that allows dynamically adjusting the concurrency."""
|
|
81
|
+
|
|
49
82
|
active: int
|
|
50
83
|
value: int
|
|
51
84
|
waiter: typing.Optional[asyncio.Future]
|
|
52
85
|
closed: bool
|
|
53
86
|
|
|
54
|
-
def __init__(self, value: int) -> None:
|
|
87
|
+
def __init__(self, value: int) -> None:
|
|
88
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
89
|
+
...
|
|
90
|
+
|
|
55
91
|
async def acquire(self) -> None: ...
|
|
56
92
|
def _wake_waiter(self) -> None: ...
|
|
57
93
|
def release(self) -> None: ...
|
|
@@ -59,11 +95,18 @@ class InputSlots:
|
|
|
59
95
|
async def close(self) -> None: ...
|
|
60
96
|
|
|
61
97
|
class _ContainerIOManager:
|
|
98
|
+
"""Synchronizes all RPC calls and network operations for a running container.
|
|
99
|
+
|
|
100
|
+
TODO: maybe we shouldn't synchronize the whole class.
|
|
101
|
+
Then we could potentially move a bunch of the global functions onto it.
|
|
102
|
+
"""
|
|
103
|
+
|
|
62
104
|
task_id: str
|
|
63
105
|
function_id: str
|
|
64
106
|
app_id: str
|
|
65
107
|
function_def: modal_proto.api_pb2.Function
|
|
66
108
|
checkpoint_id: typing.Optional[str]
|
|
109
|
+
input_plane_server_url: typing.Optional[str]
|
|
67
110
|
calls_completed: int
|
|
68
111
|
total_user_time: float
|
|
69
112
|
current_input_id: typing.Optional[str]
|
|
@@ -81,7 +124,6 @@ class _ContainerIOManager:
|
|
|
81
124
|
_is_interactivity_enabled: bool
|
|
82
125
|
_fetching_inputs: bool
|
|
83
126
|
_client: modal.client._Client
|
|
84
|
-
_GENERATOR_STOP_SENTINEL: typing.ClassVar[Sentinel]
|
|
85
127
|
_singleton: typing.ClassVar[typing.Optional[_ContainerIOManager]]
|
|
86
128
|
|
|
87
129
|
def _init(self, container_args: modal_proto.api_pb2.ContainerArguments, client: modal.client._Client): ...
|
|
@@ -90,9 +132,15 @@ class _ContainerIOManager:
|
|
|
90
132
|
@staticmethod
|
|
91
133
|
def __new__(
|
|
92
134
|
cls, container_args: modal_proto.api_pb2.ContainerArguments, client: modal.client._Client
|
|
93
|
-
) -> _ContainerIOManager:
|
|
135
|
+
) -> _ContainerIOManager:
|
|
136
|
+
"""Create and return a new object. See help(type) for accurate signature."""
|
|
137
|
+
...
|
|
138
|
+
|
|
94
139
|
@classmethod
|
|
95
|
-
def _reset_singleton(cls):
|
|
140
|
+
def _reset_singleton(cls):
|
|
141
|
+
"""Only used for tests."""
|
|
142
|
+
...
|
|
143
|
+
|
|
96
144
|
async def hello(self): ...
|
|
97
145
|
async def _run_heartbeat_loop(self): ...
|
|
98
146
|
async def _heartbeat_handle_cancellations(self) -> bool: ...
|
|
@@ -100,46 +148,82 @@ class _ContainerIOManager:
|
|
|
100
148
|
def stop_heartbeat(self): ...
|
|
101
149
|
def dynamic_concurrency_manager(self) -> typing.AsyncContextManager[None]: ...
|
|
102
150
|
async def _dynamic_concurrency_loop(self): ...
|
|
103
|
-
def
|
|
104
|
-
|
|
105
|
-
|
|
151
|
+
def get_data_in(
|
|
152
|
+
self, function_call_id: str, attempt_token: typing.Optional[str]
|
|
153
|
+
) -> collections.abc.AsyncIterator[typing.Any]:
|
|
154
|
+
"""Read from the `data_in` stream of a function call."""
|
|
155
|
+
...
|
|
156
|
+
|
|
106
157
|
async def put_data_out(
|
|
107
|
-
self,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
158
|
+
self,
|
|
159
|
+
function_call_id: str,
|
|
160
|
+
attempt_token: str,
|
|
161
|
+
start_index: int,
|
|
162
|
+
data_format: int,
|
|
163
|
+
serialized_messages: list[typing.Any],
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Put data onto the `data_out` stream of a function call.
|
|
166
|
+
|
|
167
|
+
This is used for generator outputs, which includes web endpoint responses. Note that this
|
|
168
|
+
was introduced as a performance optimization in client version 0.57, so older clients will
|
|
169
|
+
still use the previous Postgres-backed system based on `FunctionPutOutputs()`.
|
|
170
|
+
"""
|
|
171
|
+
...
|
|
172
|
+
|
|
173
|
+
def generator_output_sender(
|
|
174
|
+
self, function_call_id: str, attempt_token: str, data_format: int, message_rx: asyncio.queues.Queue
|
|
175
|
+
) -> typing.AsyncContextManager[None]:
|
|
176
|
+
"""Runs background task that feeds generator outputs into a function call's `data_out` stream."""
|
|
177
|
+
...
|
|
178
|
+
|
|
179
|
+
async def _queue_create(self, size: int) -> asyncio.queues.Queue:
|
|
180
|
+
"""Create a queue, on the synchronicity event loop (needed on Python 3.8 and 3.9)."""
|
|
181
|
+
...
|
|
182
|
+
|
|
183
|
+
async def _queue_put(self, queue: asyncio.queues.Queue, value: typing.Any) -> None:
|
|
184
|
+
"""Put a value onto a queue, using the synchronicity event loop."""
|
|
185
|
+
...
|
|
186
|
+
|
|
114
187
|
def get_average_call_time(self) -> float: ...
|
|
115
188
|
def get_max_inputs_to_fetch(self): ...
|
|
116
189
|
def _generate_inputs(
|
|
117
190
|
self, batch_max_size: int, batch_wait_ms: int
|
|
118
|
-
) -> collections.abc.AsyncIterator[list[tuple[str, int, str, modal_proto.api_pb2.FunctionInput]]]: ...
|
|
191
|
+
) -> collections.abc.AsyncIterator[list[tuple[str, int, str, str, modal_proto.api_pb2.FunctionInput]]]: ...
|
|
119
192
|
def run_inputs_outputs(
|
|
120
193
|
self,
|
|
121
194
|
finalized_functions: dict[str, modal._runtime.user_code_imports.FinalizedFunction],
|
|
122
195
|
batch_max_size: int = 0,
|
|
123
196
|
batch_wait_ms: int = 0,
|
|
124
197
|
) -> collections.abc.AsyncIterator[IOContext]: ...
|
|
125
|
-
async def
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
198
|
+
async def _send_outputs(self, started_at: float, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
|
|
199
|
+
"""Send pre-built output items with retry and chunking."""
|
|
200
|
+
...
|
|
201
|
+
|
|
202
|
+
def handle_user_exception(self) -> typing.AsyncContextManager[None]:
|
|
203
|
+
"""Sets the task as failed in a way where it's not retried.
|
|
204
|
+
|
|
205
|
+
Used for handling exceptions from container lifecycle methods at the moment, which should
|
|
206
|
+
trigger a task failure state.
|
|
207
|
+
"""
|
|
208
|
+
...
|
|
209
|
+
|
|
210
|
+
def handle_input_exception(self, io_context: IOContext, started_at: float) -> typing.AsyncContextManager[None]:
|
|
211
|
+
"""Handle an exception while processing a function input."""
|
|
212
|
+
...
|
|
213
|
+
|
|
136
214
|
def exit_context(self, started_at, input_ids: list[str]): ...
|
|
137
|
-
async def push_outputs(
|
|
138
|
-
self, io_context: IOContext, started_at: float, data: typing.Any, data_format: int
|
|
139
|
-
) -> None: ...
|
|
215
|
+
async def push_outputs(self, io_context: IOContext, started_at: float, output_data: list[typing.Any]) -> None: ...
|
|
140
216
|
async def memory_restore(self) -> None: ...
|
|
141
|
-
async def memory_snapshot(self) -> None:
|
|
142
|
-
|
|
217
|
+
async def memory_snapshot(self) -> None:
|
|
218
|
+
"""Message server indicating that function is ready to be checkpointed."""
|
|
219
|
+
...
|
|
220
|
+
|
|
221
|
+
async def volume_commit(self, volume_ids: list[str]) -> None:
|
|
222
|
+
"""Perform volume commit for given `volume_ids`.
|
|
223
|
+
Only used on container exit to persist uncommitted changes on behalf of user.
|
|
224
|
+
"""
|
|
225
|
+
...
|
|
226
|
+
|
|
143
227
|
async def interact(self, from_breakpoint: bool = False): ...
|
|
144
228
|
@property
|
|
145
229
|
def target_concurrency(self) -> int: ...
|
|
@@ -148,20 +232,41 @@ class _ContainerIOManager:
|
|
|
148
232
|
@property
|
|
149
233
|
def input_concurrency_enabled(self) -> int: ...
|
|
150
234
|
@classmethod
|
|
151
|
-
def get_input_concurrency(cls) -> int:
|
|
235
|
+
def get_input_concurrency(cls) -> int:
|
|
236
|
+
"""Returns the number of usable input slots.
|
|
237
|
+
|
|
238
|
+
If concurrency is reduced, active slots can exceed allotted slots. Returns the larger value
|
|
239
|
+
in this case.
|
|
240
|
+
"""
|
|
241
|
+
...
|
|
242
|
+
|
|
152
243
|
@classmethod
|
|
153
|
-
def set_input_concurrency(cls, concurrency: int):
|
|
244
|
+
def set_input_concurrency(cls, concurrency: int):
|
|
245
|
+
"""Edit the number of input slots.
|
|
246
|
+
|
|
247
|
+
This disables the background loop which automatically adjusts concurrency
|
|
248
|
+
within [target_concurrency, max_concurrency].
|
|
249
|
+
"""
|
|
250
|
+
...
|
|
251
|
+
|
|
154
252
|
@classmethod
|
|
155
253
|
def stop_fetching_inputs(cls): ...
|
|
156
254
|
|
|
157
255
|
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
|
158
256
|
|
|
159
257
|
class ContainerIOManager:
|
|
258
|
+
"""Synchronizes all RPC calls and network operations for a running container.
|
|
259
|
+
|
|
260
|
+
TODO: maybe we shouldn't synchronize the whole class.
|
|
261
|
+
Then we could potentially move a bunch of the global functions onto it.
|
|
262
|
+
"""
|
|
263
|
+
|
|
160
264
|
task_id: str
|
|
161
265
|
function_id: str
|
|
162
266
|
app_id: str
|
|
163
267
|
function_def: modal_proto.api_pb2.Function
|
|
164
268
|
checkpoint_id: typing.Optional[str]
|
|
269
|
+
input_plane_server_url: typing.Optional[str]
|
|
165
270
|
calls_completed: int
|
|
166
271
|
total_user_time: float
|
|
167
272
|
current_input_id: typing.Optional[str]
|
|
@@ -179,15 +284,19 @@ class ContainerIOManager:
|
|
|
179
284
|
_is_interactivity_enabled: bool
|
|
180
285
|
_fetching_inputs: bool
|
|
181
286
|
_client: modal.client.Client
|
|
182
|
-
_GENERATOR_STOP_SENTINEL: typing.ClassVar[Sentinel]
|
|
183
287
|
_singleton: typing.ClassVar[typing.Optional[ContainerIOManager]]
|
|
184
288
|
|
|
185
|
-
def __init__(self, /, *args, **kwargs):
|
|
289
|
+
def __init__(self, /, *args, **kwargs):
|
|
290
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
291
|
+
...
|
|
292
|
+
|
|
186
293
|
def _init(self, container_args: modal_proto.api_pb2.ContainerArguments, client: modal.client.Client): ...
|
|
187
294
|
@property
|
|
188
295
|
def heartbeat_condition(self) -> asyncio.locks.Condition: ...
|
|
189
296
|
@classmethod
|
|
190
|
-
def _reset_singleton(cls):
|
|
297
|
+
def _reset_singleton(cls):
|
|
298
|
+
"""Only used for tests."""
|
|
299
|
+
...
|
|
191
300
|
|
|
192
301
|
class __hello_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
193
302
|
def __call__(self, /): ...
|
|
@@ -229,45 +338,92 @@ class ContainerIOManager:
|
|
|
229
338
|
|
|
230
339
|
_dynamic_concurrency_loop: ___dynamic_concurrency_loop_spec[typing_extensions.Self]
|
|
231
340
|
|
|
232
|
-
def serialize_data_format(self, obj: typing.Any, data_format: int) -> bytes: ...
|
|
233
|
-
|
|
234
|
-
class __format_blob_data_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
235
|
-
def __call__(self, /, data: bytes) -> dict[str, typing.Any]: ...
|
|
236
|
-
async def aio(self, /, data: bytes) -> dict[str, typing.Any]: ...
|
|
237
|
-
|
|
238
|
-
format_blob_data: __format_blob_data_spec[typing_extensions.Self]
|
|
239
|
-
|
|
240
341
|
class __get_data_in_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
241
|
-
def __call__(
|
|
242
|
-
|
|
342
|
+
def __call__(
|
|
343
|
+
self, /, function_call_id: str, attempt_token: typing.Optional[str]
|
|
344
|
+
) -> typing.Iterator[typing.Any]:
|
|
345
|
+
"""Read from the `data_in` stream of a function call."""
|
|
346
|
+
...
|
|
347
|
+
|
|
348
|
+
def aio(
|
|
349
|
+
self, /, function_call_id: str, attempt_token: typing.Optional[str]
|
|
350
|
+
) -> collections.abc.AsyncIterator[typing.Any]:
|
|
351
|
+
"""Read from the `data_in` stream of a function call."""
|
|
352
|
+
...
|
|
243
353
|
|
|
244
354
|
get_data_in: __get_data_in_spec[typing_extensions.Self]
|
|
245
355
|
|
|
246
356
|
class __put_data_out_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
247
357
|
def __call__(
|
|
248
|
-
self,
|
|
249
|
-
|
|
358
|
+
self,
|
|
359
|
+
/,
|
|
360
|
+
function_call_id: str,
|
|
361
|
+
attempt_token: str,
|
|
362
|
+
start_index: int,
|
|
363
|
+
data_format: int,
|
|
364
|
+
serialized_messages: list[typing.Any],
|
|
365
|
+
) -> None:
|
|
366
|
+
"""Put data onto the `data_out` stream of a function call.
|
|
367
|
+
|
|
368
|
+
This is used for generator outputs, which includes web endpoint responses. Note that this
|
|
369
|
+
was introduced as a performance optimization in client version 0.57, so older clients will
|
|
370
|
+
still use the previous Postgres-backed system based on `FunctionPutOutputs()`.
|
|
371
|
+
"""
|
|
372
|
+
...
|
|
373
|
+
|
|
250
374
|
async def aio(
|
|
251
|
-
self,
|
|
252
|
-
|
|
375
|
+
self,
|
|
376
|
+
/,
|
|
377
|
+
function_call_id: str,
|
|
378
|
+
attempt_token: str,
|
|
379
|
+
start_index: int,
|
|
380
|
+
data_format: int,
|
|
381
|
+
serialized_messages: list[typing.Any],
|
|
382
|
+
) -> None:
|
|
383
|
+
"""Put data onto the `data_out` stream of a function call.
|
|
384
|
+
|
|
385
|
+
This is used for generator outputs, which includes web endpoint responses. Note that this
|
|
386
|
+
was introduced as a performance optimization in client version 0.57, so older clients will
|
|
387
|
+
still use the previous Postgres-backed system based on `FunctionPutOutputs()`.
|
|
388
|
+
"""
|
|
389
|
+
...
|
|
253
390
|
|
|
254
391
|
put_data_out: __put_data_out_spec[typing_extensions.Self]
|
|
255
392
|
|
|
256
|
-
class
|
|
257
|
-
def __call__(
|
|
258
|
-
|
|
393
|
+
class __generator_output_sender_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
394
|
+
def __call__(
|
|
395
|
+
self, /, function_call_id: str, attempt_token: str, data_format: int, message_rx: asyncio.queues.Queue
|
|
396
|
+
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[None]:
|
|
397
|
+
"""Runs background task that feeds generator outputs into a function call's `data_out` stream."""
|
|
398
|
+
...
|
|
259
399
|
|
|
260
|
-
|
|
400
|
+
def aio(
|
|
401
|
+
self, /, function_call_id: str, attempt_token: str, data_format: int, message_rx: asyncio.queues.Queue
|
|
402
|
+
) -> typing.AsyncContextManager[None]:
|
|
403
|
+
"""Runs background task that feeds generator outputs into a function call's `data_out` stream."""
|
|
404
|
+
...
|
|
405
|
+
|
|
406
|
+
generator_output_sender: __generator_output_sender_spec[typing_extensions.Self]
|
|
261
407
|
|
|
262
408
|
class ___queue_create_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
263
|
-
def __call__(self, /, size: int) -> asyncio.queues.Queue:
|
|
264
|
-
|
|
409
|
+
def __call__(self, /, size: int) -> asyncio.queues.Queue:
|
|
410
|
+
"""Create a queue, on the synchronicity event loop (needed on Python 3.8 and 3.9)."""
|
|
411
|
+
...
|
|
412
|
+
|
|
413
|
+
async def aio(self, /, size: int) -> asyncio.queues.Queue:
|
|
414
|
+
"""Create a queue, on the synchronicity event loop (needed on Python 3.8 and 3.9)."""
|
|
415
|
+
...
|
|
265
416
|
|
|
266
417
|
_queue_create: ___queue_create_spec[typing_extensions.Self]
|
|
267
418
|
|
|
268
419
|
class ___queue_put_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
269
|
-
def __call__(self, /, queue: asyncio.queues.Queue, value: typing.Any) -> None:
|
|
270
|
-
|
|
420
|
+
def __call__(self, /, queue: asyncio.queues.Queue, value: typing.Any) -> None:
|
|
421
|
+
"""Put a value onto a queue, using the synchronicity event loop."""
|
|
422
|
+
...
|
|
423
|
+
|
|
424
|
+
async def aio(self, /, queue: asyncio.queues.Queue, value: typing.Any) -> None:
|
|
425
|
+
"""Put a value onto a queue, using the synchronicity event loop."""
|
|
426
|
+
...
|
|
271
427
|
|
|
272
428
|
_queue_put: ___queue_put_spec[typing_extensions.Self]
|
|
273
429
|
|
|
@@ -277,10 +433,10 @@ class ContainerIOManager:
|
|
|
277
433
|
class ___generate_inputs_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
278
434
|
def __call__(
|
|
279
435
|
self, /, batch_max_size: int, batch_wait_ms: int
|
|
280
|
-
) -> typing.Iterator[list[tuple[str, int, str, modal_proto.api_pb2.FunctionInput]]]: ...
|
|
436
|
+
) -> typing.Iterator[list[tuple[str, int, str, str, modal_proto.api_pb2.FunctionInput]]]: ...
|
|
281
437
|
def aio(
|
|
282
438
|
self, /, batch_max_size: int, batch_wait_ms: int
|
|
283
|
-
) -> collections.abc.AsyncIterator[list[tuple[str, int, str, modal_proto.api_pb2.FunctionInput]]]: ...
|
|
439
|
+
) -> collections.abc.AsyncIterator[list[tuple[str, int, str, str, modal_proto.api_pb2.FunctionInput]]]: ...
|
|
284
440
|
|
|
285
441
|
_generate_inputs: ___generate_inputs_spec[typing_extensions.Self]
|
|
286
442
|
|
|
@@ -302,50 +458,54 @@ class ContainerIOManager:
|
|
|
302
458
|
|
|
303
459
|
run_inputs_outputs: __run_inputs_outputs_spec[typing_extensions.Self]
|
|
304
460
|
|
|
305
|
-
class
|
|
306
|
-
def __call__(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
io_context: IOContext,
|
|
310
|
-
started_at: float,
|
|
311
|
-
data_format: int,
|
|
312
|
-
results: list[modal_proto.api_pb2.GenericResult],
|
|
313
|
-
) -> None: ...
|
|
314
|
-
async def aio(
|
|
315
|
-
self,
|
|
316
|
-
/,
|
|
317
|
-
io_context: IOContext,
|
|
318
|
-
started_at: float,
|
|
319
|
-
data_format: int,
|
|
320
|
-
results: list[modal_proto.api_pb2.GenericResult],
|
|
321
|
-
) -> None: ...
|
|
461
|
+
class ___send_outputs_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
462
|
+
def __call__(self, /, started_at: float, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
|
|
463
|
+
"""Send pre-built output items with retry and chunking."""
|
|
464
|
+
...
|
|
322
465
|
|
|
323
|
-
|
|
466
|
+
async def aio(self, /, started_at: float, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
|
|
467
|
+
"""Send pre-built output items with retry and chunking."""
|
|
468
|
+
...
|
|
324
469
|
|
|
325
|
-
|
|
326
|
-
def serialize_traceback(self, exc: BaseException) -> tuple[typing.Optional[bytes], typing.Optional[bytes]]: ...
|
|
470
|
+
_send_outputs: ___send_outputs_spec[typing_extensions.Self]
|
|
327
471
|
|
|
328
472
|
class __handle_user_exception_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
329
|
-
def __call__(self, /) -> synchronicity.combined_types.AsyncAndBlockingContextManager[None]:
|
|
330
|
-
|
|
473
|
+
def __call__(self, /) -> synchronicity.combined_types.AsyncAndBlockingContextManager[None]:
|
|
474
|
+
"""Sets the task as failed in a way where it's not retried.
|
|
475
|
+
|
|
476
|
+
Used for handling exceptions from container lifecycle methods at the moment, which should
|
|
477
|
+
trigger a task failure state.
|
|
478
|
+
"""
|
|
479
|
+
...
|
|
480
|
+
|
|
481
|
+
def aio(self, /) -> typing.AsyncContextManager[None]:
|
|
482
|
+
"""Sets the task as failed in a way where it's not retried.
|
|
483
|
+
|
|
484
|
+
Used for handling exceptions from container lifecycle methods at the moment, which should
|
|
485
|
+
trigger a task failure state.
|
|
486
|
+
"""
|
|
487
|
+
...
|
|
331
488
|
|
|
332
489
|
handle_user_exception: __handle_user_exception_spec[typing_extensions.Self]
|
|
333
490
|
|
|
334
491
|
class __handle_input_exception_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
335
492
|
def __call__(
|
|
336
493
|
self, /, io_context: IOContext, started_at: float
|
|
337
|
-
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[None]:
|
|
338
|
-
|
|
494
|
+
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[None]:
|
|
495
|
+
"""Handle an exception while processing a function input."""
|
|
496
|
+
...
|
|
497
|
+
|
|
498
|
+
def aio(self, /, io_context: IOContext, started_at: float) -> typing.AsyncContextManager[None]:
|
|
499
|
+
"""Handle an exception while processing a function input."""
|
|
500
|
+
...
|
|
339
501
|
|
|
340
502
|
handle_input_exception: __handle_input_exception_spec[typing_extensions.Self]
|
|
341
503
|
|
|
342
504
|
def exit_context(self, started_at, input_ids: list[str]): ...
|
|
343
505
|
|
|
344
506
|
class __push_outputs_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
345
|
-
def __call__(self, /, io_context: IOContext, started_at: float,
|
|
346
|
-
async def aio(
|
|
347
|
-
self, /, io_context: IOContext, started_at: float, data: typing.Any, data_format: int
|
|
348
|
-
) -> None: ...
|
|
507
|
+
def __call__(self, /, io_context: IOContext, started_at: float, output_data: list[typing.Any]) -> None: ...
|
|
508
|
+
async def aio(self, /, io_context: IOContext, started_at: float, output_data: list[typing.Any]) -> None: ...
|
|
349
509
|
|
|
350
510
|
push_outputs: __push_outputs_spec[typing_extensions.Self]
|
|
351
511
|
|
|
@@ -356,14 +516,28 @@ class ContainerIOManager:
|
|
|
356
516
|
memory_restore: __memory_restore_spec[typing_extensions.Self]
|
|
357
517
|
|
|
358
518
|
class __memory_snapshot_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
359
|
-
def __call__(self, /) -> None:
|
|
360
|
-
|
|
519
|
+
def __call__(self, /) -> None:
|
|
520
|
+
"""Message server indicating that function is ready to be checkpointed."""
|
|
521
|
+
...
|
|
522
|
+
|
|
523
|
+
async def aio(self, /) -> None:
|
|
524
|
+
"""Message server indicating that function is ready to be checkpointed."""
|
|
525
|
+
...
|
|
361
526
|
|
|
362
527
|
memory_snapshot: __memory_snapshot_spec[typing_extensions.Self]
|
|
363
528
|
|
|
364
529
|
class __volume_commit_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
365
|
-
def __call__(self, /, volume_ids: list[str]) -> None:
|
|
366
|
-
|
|
530
|
+
def __call__(self, /, volume_ids: list[str]) -> None:
|
|
531
|
+
"""Perform volume commit for given `volume_ids`.
|
|
532
|
+
Only used on container exit to persist uncommitted changes on behalf of user.
|
|
533
|
+
"""
|
|
534
|
+
...
|
|
535
|
+
|
|
536
|
+
async def aio(self, /, volume_ids: list[str]) -> None:
|
|
537
|
+
"""Perform volume commit for given `volume_ids`.
|
|
538
|
+
Only used on container exit to persist uncommitted changes on behalf of user.
|
|
539
|
+
"""
|
|
540
|
+
...
|
|
367
541
|
|
|
368
542
|
volume_commit: __volume_commit_spec[typing_extensions.Self]
|
|
369
543
|
|
|
@@ -380,13 +554,34 @@ class ContainerIOManager:
|
|
|
380
554
|
@property
|
|
381
555
|
def input_concurrency_enabled(self) -> int: ...
|
|
382
556
|
@classmethod
|
|
383
|
-
def get_input_concurrency(cls) -> int:
|
|
557
|
+
def get_input_concurrency(cls) -> int:
|
|
558
|
+
"""Returns the number of usable input slots.
|
|
559
|
+
|
|
560
|
+
If concurrency is reduced, active slots can exceed allotted slots. Returns the larger value
|
|
561
|
+
in this case.
|
|
562
|
+
"""
|
|
563
|
+
...
|
|
564
|
+
|
|
384
565
|
@classmethod
|
|
385
|
-
def set_input_concurrency(cls, concurrency: int):
|
|
566
|
+
def set_input_concurrency(cls, concurrency: int):
|
|
567
|
+
"""Edit the number of input slots.
|
|
568
|
+
|
|
569
|
+
This disables the background loop which automatically adjusts concurrency
|
|
570
|
+
within [target_concurrency, max_concurrency].
|
|
571
|
+
"""
|
|
572
|
+
...
|
|
573
|
+
|
|
386
574
|
@classmethod
|
|
387
575
|
def stop_fetching_inputs(cls): ...
|
|
388
576
|
|
|
389
|
-
def check_fastapi_pydantic_compatibility(exc: ImportError) -> None:
|
|
577
|
+
def check_fastapi_pydantic_compatibility(exc: ImportError) -> None:
|
|
578
|
+
"""Add a helpful note to an exception that is likely caused by a pydantic<>fastapi version incompatibility.
|
|
579
|
+
|
|
580
|
+
We need this becasue the legacy set of container requirements (image_builder_version=2023.12) contains a
|
|
581
|
+
version of fastapi that is not forwards-compatible with pydantic 2.0+, and users commonly run into issues
|
|
582
|
+
building an image that specifies a more recent version only for pydantic.
|
|
583
|
+
"""
|
|
584
|
+
...
|
|
390
585
|
|
|
391
586
|
MAX_OUTPUT_BATCH_SIZE: int
|
|
392
587
|
|
|
@@ -72,22 +72,38 @@ def current_function_call_id() -> Optional[str]:
|
|
|
72
72
|
return None
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
def
|
|
76
|
-
|
|
75
|
+
def current_attempt_token() -> Optional[str]:
|
|
76
|
+
# This ContextVar isn't useful to expose to users.
|
|
77
|
+
try:
|
|
78
|
+
return _current_attempt_token.get()
|
|
79
|
+
except LookupError:
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _set_current_context_ids(
|
|
84
|
+
input_ids: list[str], function_call_ids: list[str], attempt_tokens: list[str]
|
|
85
|
+
) -> Callable[[], None]:
|
|
86
|
+
assert len(input_ids) == len(function_call_ids) == len(attempt_tokens) and input_ids
|
|
87
|
+
|
|
77
88
|
input_id = input_ids[0]
|
|
78
89
|
function_call_id = function_call_ids[0]
|
|
90
|
+
attempt_token = attempt_tokens[0]
|
|
91
|
+
|
|
79
92
|
input_token = _current_input_id.set(input_id)
|
|
80
93
|
function_call_token = _current_function_call_id.set(function_call_id)
|
|
94
|
+
attempt_token_token = _current_attempt_token.set(attempt_token)
|
|
81
95
|
|
|
82
96
|
def _reset_current_context_ids():
|
|
83
97
|
_current_input_id.reset(input_token)
|
|
84
98
|
_current_function_call_id.reset(function_call_token)
|
|
99
|
+
_current_attempt_token.reset(attempt_token_token)
|
|
85
100
|
|
|
86
101
|
return _reset_current_context_ids
|
|
87
102
|
|
|
88
103
|
|
|
89
104
|
_current_input_id: ContextVar = ContextVar("_current_input_id")
|
|
90
105
|
_current_function_call_id: ContextVar = ContextVar("_current_function_call_id")
|
|
106
|
+
_current_attempt_token: ContextVar = ContextVar("_current_attempt_token")
|
|
91
107
|
|
|
92
108
|
_is_currently_importing = False # we set this to True while a container is importing user code
|
|
93
109
|
|