modAL 1.2.5.dev0__py3-none-any.whl → 1.2.5.dev2__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/_container_entrypoint.py +29 -206
- modal/_runtime/user_code_event_loop.py +80 -0
- modal/_runtime/user_code_imports.py +209 -9
- modal/client.pyi +2 -2
- modal/experimental/__init__.py +13 -0
- {modal-1.2.5.dev0.dist-info → modal-1.2.5.dev2.dist-info}/METADATA +1 -1
- {modal-1.2.5.dev0.dist-info → modal-1.2.5.dev2.dist-info}/RECORD +12 -11
- modal_version/__init__.py +1 -1
- {modal-1.2.5.dev0.dist-info → modal-1.2.5.dev2.dist-info}/WHEEL +0 -0
- {modal-1.2.5.dev0.dist-info → modal-1.2.5.dev2.dist-info}/entry_points.txt +0 -0
- {modal-1.2.5.dev0.dist-info → modal-1.2.5.dev2.dist-info}/licenses/LICENSE +0 -0
- {modal-1.2.5.dev0.dist-info → modal-1.2.5.dev2.dist-info}/top_level.txt +0 -0
modal/_container_entrypoint.py
CHANGED
|
@@ -15,27 +15,19 @@ if telemetry_socket:
|
|
|
15
15
|
instrument_imports(telemetry_socket)
|
|
16
16
|
|
|
17
17
|
import asyncio
|
|
18
|
-
import inspect
|
|
19
18
|
import queue
|
|
20
19
|
import signal
|
|
21
|
-
import sys
|
|
22
20
|
import threading
|
|
23
21
|
import time
|
|
24
|
-
|
|
25
|
-
from typing import TYPE_CHECKING, Any,
|
|
22
|
+
import types
|
|
23
|
+
from typing import TYPE_CHECKING, Any, Optional, cast
|
|
26
24
|
|
|
27
25
|
from google.protobuf.message import Message
|
|
28
26
|
|
|
29
27
|
from modal._clustered_functions import initialize_clustered_function
|
|
30
|
-
from modal.
|
|
31
|
-
_find_callables_for_obj,
|
|
32
|
-
_PartialFunctionFlags,
|
|
33
|
-
)
|
|
28
|
+
from modal._runtime.user_code_event_loop import UserCodeEventLoop
|
|
34
29
|
from modal._serialization import deserialize, deserialize_params
|
|
35
30
|
from modal._utils.async_utils import TaskContext, aclosing, synchronizer
|
|
36
|
-
from modal._utils.function_utils import (
|
|
37
|
-
callable_has_non_self_params,
|
|
38
|
-
)
|
|
39
31
|
from modal.app import App, _App
|
|
40
32
|
from modal.client import Client, _Client
|
|
41
33
|
from modal.config import logger
|
|
@@ -53,6 +45,7 @@ from ._runtime.container_io_manager import (
|
|
|
53
45
|
if TYPE_CHECKING:
|
|
54
46
|
import modal._object
|
|
55
47
|
import modal._runtime.container_io_manager
|
|
48
|
+
import modal._runtime.user_code_imports
|
|
56
49
|
|
|
57
50
|
|
|
58
51
|
class DaemonizedThreadPool:
|
|
@@ -101,81 +94,6 @@ class DaemonizedThreadPool:
|
|
|
101
94
|
self.inputs.put((func, args))
|
|
102
95
|
|
|
103
96
|
|
|
104
|
-
class UserCodeEventLoop:
|
|
105
|
-
"""Run an async event loop as a context manager and handle signals.
|
|
106
|
-
|
|
107
|
-
This will run all *user supplied* async code, i.e. async functions, as well as async enter/exit managers
|
|
108
|
-
|
|
109
|
-
The following signals are handled while a coroutine is running on the event loop until
|
|
110
|
-
completion (and then handlers are deregistered):
|
|
111
|
-
|
|
112
|
-
- `SIGUSR1`: converted to an async task cancellation. Note that this only affects the event
|
|
113
|
-
loop, and the signal handler defined here doesn't run for sync functions.
|
|
114
|
-
- `SIGINT`: Unless the global signal handler has been set to SIGIGN, the loop's signal handler
|
|
115
|
-
is set to cancel the current task and raise KeyboardInterrupt to the caller.
|
|
116
|
-
"""
|
|
117
|
-
|
|
118
|
-
def __enter__(self):
|
|
119
|
-
self.loop = asyncio.new_event_loop()
|
|
120
|
-
self.tasks = set()
|
|
121
|
-
return self
|
|
122
|
-
|
|
123
|
-
def __exit__(self, exc_type, exc_value, traceback):
|
|
124
|
-
self.loop.run_until_complete(self.loop.shutdown_asyncgens())
|
|
125
|
-
if sys.version_info[:2] >= (3, 9):
|
|
126
|
-
self.loop.run_until_complete(self.loop.shutdown_default_executor()) # Introduced in Python 3.9
|
|
127
|
-
|
|
128
|
-
for task in self.tasks:
|
|
129
|
-
task.cancel()
|
|
130
|
-
|
|
131
|
-
self.loop.close()
|
|
132
|
-
|
|
133
|
-
def create_task(self, coro):
|
|
134
|
-
task = self.loop.create_task(coro)
|
|
135
|
-
self.tasks.add(task)
|
|
136
|
-
task.add_done_callback(self.tasks.discard)
|
|
137
|
-
return task
|
|
138
|
-
|
|
139
|
-
def run(self, coro):
|
|
140
|
-
task = asyncio.ensure_future(coro, loop=self.loop)
|
|
141
|
-
self._sigints = 0
|
|
142
|
-
|
|
143
|
-
def _sigint_handler():
|
|
144
|
-
# cancel the task in order to have run_until_complete return soon and
|
|
145
|
-
# prevent a bunch of unwanted tracebacks when shutting down the
|
|
146
|
-
# event loop.
|
|
147
|
-
|
|
148
|
-
# this basically replicates the sigint handler installed by asyncio.run()
|
|
149
|
-
self._sigints += 1
|
|
150
|
-
if self._sigints == 1:
|
|
151
|
-
# first sigint is graceful
|
|
152
|
-
task.cancel()
|
|
153
|
-
return
|
|
154
|
-
|
|
155
|
-
# this should normally not happen, but the second sigint would "hard kill" the event loop!
|
|
156
|
-
raise KeyboardInterrupt()
|
|
157
|
-
|
|
158
|
-
ignore_sigint = signal.getsignal(signal.SIGINT) == signal.SIG_IGN
|
|
159
|
-
if not ignore_sigint:
|
|
160
|
-
self.loop.add_signal_handler(signal.SIGINT, _sigint_handler)
|
|
161
|
-
|
|
162
|
-
# Before Python 3.9 there is no argument to Task.cancel
|
|
163
|
-
if sys.version_info[:2] >= (3, 9):
|
|
164
|
-
self.loop.add_signal_handler(signal.SIGUSR1, task.cancel, "Input was cancelled by user")
|
|
165
|
-
else:
|
|
166
|
-
self.loop.add_signal_handler(signal.SIGUSR1, task.cancel)
|
|
167
|
-
|
|
168
|
-
try:
|
|
169
|
-
return self.loop.run_until_complete(task)
|
|
170
|
-
except asyncio.CancelledError:
|
|
171
|
-
if self._sigints > 0:
|
|
172
|
-
raise KeyboardInterrupt()
|
|
173
|
-
finally:
|
|
174
|
-
self.loop.remove_signal_handler(signal.SIGUSR1)
|
|
175
|
-
if not ignore_sigint:
|
|
176
|
-
self.loop.remove_signal_handler(signal.SIGINT)
|
|
177
|
-
|
|
178
|
-
|
|
179
97
|
def call_function(
|
|
180
98
|
user_code_event_loop: UserCodeEventLoop,
|
|
181
99
|
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
|
@@ -331,23 +249,25 @@ def call_function(
|
|
|
331
249
|
signal.signal(signal.SIGUSR1, usr1_handler) # reset signal handler
|
|
332
250
|
|
|
333
251
|
|
|
334
|
-
def
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
252
|
+
def get_serialized_user_class_and_function(
|
|
253
|
+
function_def: api_pb2.Function, client: _Client
|
|
254
|
+
) -> tuple[Optional[type], Optional[types.FunctionType]]:
|
|
255
|
+
if function_def.definition_type == api_pb2.Function.DEFINITION_TYPE_SERIALIZED:
|
|
256
|
+
assert function_def.function_serialized or function_def.class_serialized
|
|
257
|
+
|
|
258
|
+
if function_def.function_serialized:
|
|
259
|
+
ser_fun = deserialize(function_def.function_serialized, client)
|
|
260
|
+
else:
|
|
261
|
+
ser_fun = None
|
|
262
|
+
|
|
263
|
+
if function_def.class_serialized:
|
|
264
|
+
ser_usr_cls = deserialize(function_def.class_serialized, client)
|
|
265
|
+
else:
|
|
266
|
+
ser_usr_cls = None
|
|
267
|
+
else:
|
|
268
|
+
ser_usr_cls, ser_fun = None, None
|
|
269
|
+
|
|
270
|
+
return ser_usr_cls, ser_fun
|
|
351
271
|
|
|
352
272
|
|
|
353
273
|
def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
@@ -357,34 +277,20 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
|
357
277
|
active_app: _App
|
|
358
278
|
service: Service
|
|
359
279
|
function_def = container_args.function_def
|
|
360
|
-
is_auto_snapshot: bool = function_def.is_auto_snapshot
|
|
361
280
|
# The worker sets this flag to "1" for snapshot and restore tasks. Otherwise, this flag is unset,
|
|
362
281
|
# in which case snapshots should be disabled.
|
|
363
282
|
is_snapshotting_function = (
|
|
364
283
|
function_def.is_checkpointing_function and os.environ.get("MODAL_ENABLE_SNAP_RESTORE") == "1"
|
|
365
284
|
)
|
|
366
285
|
|
|
367
|
-
_client: _Client = synchronizer._translate_in(client) # TODO(erikbern): ugly
|
|
286
|
+
_client: _Client = cast(_Client, synchronizer._translate_in(client)) # TODO(erikbern): ugly
|
|
368
287
|
|
|
369
288
|
# Call ContainerHello - currently a noop but might be used later for things
|
|
370
289
|
container_io_manager.hello()
|
|
371
290
|
|
|
372
291
|
with container_io_manager.heartbeats(is_snapshotting_function), UserCodeEventLoop() as event_loop:
|
|
373
292
|
# If this is a serialized function, fetch the definition from the server
|
|
374
|
-
|
|
375
|
-
assert function_def.function_serialized or function_def.class_serialized
|
|
376
|
-
|
|
377
|
-
if function_def.function_serialized:
|
|
378
|
-
ser_fun = deserialize(function_def.function_serialized, _client)
|
|
379
|
-
else:
|
|
380
|
-
ser_fun = None
|
|
381
|
-
|
|
382
|
-
if function_def.class_serialized:
|
|
383
|
-
ser_usr_cls = deserialize(function_def.class_serialized, _client)
|
|
384
|
-
else:
|
|
385
|
-
ser_usr_cls = None
|
|
386
|
-
else:
|
|
387
|
-
ser_usr_cls, ser_fun = None, None
|
|
293
|
+
ser_usr_cls, ser_fun = get_serialized_user_class_and_function(function_def, _client)
|
|
388
294
|
|
|
389
295
|
# Initialize the function, importing user code.
|
|
390
296
|
with container_io_manager.handle_user_exception():
|
|
@@ -436,7 +342,7 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
|
436
342
|
|
|
437
343
|
# Initialize objects on the app.
|
|
438
344
|
# This is basically only functions and classes - anything else is deprecated and will be unsupported soon
|
|
439
|
-
app: App = synchronizer._translate_out(active_app)
|
|
345
|
+
app: App = cast(App, synchronizer._translate_out(active_app))
|
|
440
346
|
app._init_container(client, container_app)
|
|
441
347
|
|
|
442
348
|
# Hydrate all function dependencies.
|
|
@@ -456,7 +362,7 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
|
456
362
|
"that evaluates differently in the local and remote environments."
|
|
457
363
|
)
|
|
458
364
|
for object_id, obj in zip(dep_object_ids, service.service_deps):
|
|
459
|
-
metadata: Message = container_app.object_handle_metadata[object_id]
|
|
365
|
+
metadata: Optional[Message] = container_app.object_handle_metadata[object_id]
|
|
460
366
|
obj._hydrate(object_id, _client, metadata)
|
|
461
367
|
|
|
462
368
|
# Initialize clustered functions.
|
|
@@ -467,91 +373,8 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
|
467
373
|
function_def._experimental_group_size,
|
|
468
374
|
)
|
|
469
375
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
pre_snapshot_methods = _find_callables_for_obj(
|
|
473
|
-
service.user_cls_instance, _PartialFunctionFlags.ENTER_PRE_SNAPSHOT
|
|
474
|
-
)
|
|
475
|
-
call_lifecycle_functions(event_loop, container_io_manager, list(pre_snapshot_methods.values()))
|
|
476
|
-
|
|
477
|
-
# If this container is being used to create a checkpoint, checkpoint the container after
|
|
478
|
-
# global imports and initialization. Checkpointed containers run from this point onwards.
|
|
479
|
-
if is_snapshotting_function:
|
|
480
|
-
container_io_manager.memory_snapshot()
|
|
481
|
-
|
|
482
|
-
# Install hooks for interactive functions.
|
|
483
|
-
def breakpoint_wrapper():
|
|
484
|
-
# note: it would be nice to not have breakpoint_wrapper() included in the backtrace
|
|
485
|
-
container_io_manager.interact(from_breakpoint=True)
|
|
486
|
-
import pdb
|
|
487
|
-
|
|
488
|
-
frame = inspect.currentframe().f_back
|
|
489
|
-
|
|
490
|
-
pdb.Pdb().set_trace(frame)
|
|
491
|
-
|
|
492
|
-
sys.breakpointhook = breakpoint_wrapper
|
|
493
|
-
|
|
494
|
-
# Identify the "enter" methods to run after resuming from a snapshot.
|
|
495
|
-
if service.user_cls_instance is not None and not is_auto_snapshot:
|
|
496
|
-
post_snapshot_methods = _find_callables_for_obj(
|
|
497
|
-
service.user_cls_instance, _PartialFunctionFlags.ENTER_POST_SNAPSHOT
|
|
498
|
-
)
|
|
499
|
-
call_lifecycle_functions(event_loop, container_io_manager, list(post_snapshot_methods.values()))
|
|
500
|
-
|
|
501
|
-
with container_io_manager.handle_user_exception():
|
|
502
|
-
finalized_functions = service.get_finalized_functions(function_def, container_io_manager)
|
|
503
|
-
# Execute the function.
|
|
504
|
-
lifespan_background_tasks = []
|
|
505
|
-
try:
|
|
506
|
-
for finalized_function in finalized_functions.values():
|
|
507
|
-
if finalized_function.lifespan_manager:
|
|
508
|
-
lifespan_background_tasks.append(
|
|
509
|
-
event_loop.create_task(finalized_function.lifespan_manager.background_task())
|
|
510
|
-
)
|
|
511
|
-
with container_io_manager.handle_user_exception():
|
|
512
|
-
event_loop.run(finalized_function.lifespan_manager.lifespan_startup())
|
|
513
|
-
call_function(
|
|
514
|
-
event_loop,
|
|
515
|
-
container_io_manager,
|
|
516
|
-
finalized_functions,
|
|
517
|
-
batch_max_size,
|
|
518
|
-
batch_wait_ms,
|
|
519
|
-
)
|
|
520
|
-
finally:
|
|
521
|
-
# Run exit handlers. From this point onward, ignore all SIGINT signals that come from
|
|
522
|
-
# graceful shutdowns originating on the worker, as well as stray SIGUSR1 signals that
|
|
523
|
-
# may have been sent to cancel inputs.
|
|
524
|
-
int_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
525
|
-
usr1_handler = signal.signal(signal.SIGUSR1, signal.SIG_IGN)
|
|
526
|
-
|
|
527
|
-
try:
|
|
528
|
-
try:
|
|
529
|
-
# run lifespan shutdown for asgi apps
|
|
530
|
-
for finalized_function in finalized_functions.values():
|
|
531
|
-
if finalized_function.lifespan_manager:
|
|
532
|
-
with container_io_manager.handle_user_exception():
|
|
533
|
-
event_loop.run(finalized_function.lifespan_manager.lifespan_shutdown())
|
|
534
|
-
finally:
|
|
535
|
-
# no need to keep the lifespan asgi call around - we send it no more messages
|
|
536
|
-
for lifespan_background_task in lifespan_background_tasks:
|
|
537
|
-
lifespan_background_task.cancel() # prevent dangling tasks
|
|
538
|
-
|
|
539
|
-
# Identify "exit" methods and run them.
|
|
540
|
-
# want to make sure this is called even if the lifespan manager fails
|
|
541
|
-
if service.user_cls_instance is not None and not is_auto_snapshot:
|
|
542
|
-
exit_methods = _find_callables_for_obj(service.user_cls_instance, _PartialFunctionFlags.EXIT)
|
|
543
|
-
call_lifecycle_functions(event_loop, container_io_manager, list(exit_methods.values()))
|
|
544
|
-
|
|
545
|
-
# Finally, commit on exit to catch uncommitted volume changes and surface background
|
|
546
|
-
# commit errors.
|
|
547
|
-
container_io_manager.volume_commit(
|
|
548
|
-
[v.volume_id for v in function_def.volume_mounts if v.allow_background_commits]
|
|
549
|
-
)
|
|
550
|
-
finally:
|
|
551
|
-
# Restore the original signal handler, needed for container_test hygiene since the
|
|
552
|
-
# test runs `main()` multiple times in the same process.
|
|
553
|
-
signal.signal(signal.SIGINT, int_handler)
|
|
554
|
-
signal.signal(signal.SIGUSR1, usr1_handler)
|
|
376
|
+
with service.execution_context(event_loop, container_io_manager) as finalized_functions:
|
|
377
|
+
call_function(event_loop, container_io_manager, finalized_functions, batch_max_size, batch_wait_ms)
|
|
555
378
|
|
|
556
379
|
|
|
557
380
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Copyright Modal Labs 2022
|
|
2
|
+
# ruff: noqa: E402
|
|
3
|
+
import asyncio
|
|
4
|
+
import signal
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UserCodeEventLoop:
|
|
9
|
+
"""Run an async event loop as a context manager and handle signals.
|
|
10
|
+
|
|
11
|
+
This will run all *user supplied* async code, i.e. async functions, as well as async enter/exit managers
|
|
12
|
+
|
|
13
|
+
The following signals are handled while a coroutine is running on the event loop until
|
|
14
|
+
completion (and then handlers are deregistered):
|
|
15
|
+
|
|
16
|
+
- `SIGUSR1`: converted to an async task cancellation. Note that this only affects the event
|
|
17
|
+
loop, and the signal handler defined here doesn't run for sync functions.
|
|
18
|
+
- `SIGINT`: Unless the global signal handler has been set to SIGIGN, the loop's signal handler
|
|
19
|
+
is set to cancel the current task and raise KeyboardInterrupt to the caller.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __enter__(self):
|
|
23
|
+
self.loop = asyncio.new_event_loop()
|
|
24
|
+
self.tasks = set()
|
|
25
|
+
return self
|
|
26
|
+
|
|
27
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
28
|
+
self.loop.run_until_complete(self.loop.shutdown_asyncgens())
|
|
29
|
+
if sys.version_info[:2] >= (3, 9):
|
|
30
|
+
self.loop.run_until_complete(self.loop.shutdown_default_executor()) # Introduced in Python 3.9
|
|
31
|
+
|
|
32
|
+
for task in self.tasks:
|
|
33
|
+
task.cancel()
|
|
34
|
+
|
|
35
|
+
self.loop.close()
|
|
36
|
+
|
|
37
|
+
def create_task(self, coro):
|
|
38
|
+
task = self.loop.create_task(coro)
|
|
39
|
+
self.tasks.add(task)
|
|
40
|
+
task.add_done_callback(self.tasks.discard)
|
|
41
|
+
return task
|
|
42
|
+
|
|
43
|
+
def run(self, coro):
|
|
44
|
+
task = asyncio.ensure_future(coro, loop=self.loop)
|
|
45
|
+
self._sigints = 0
|
|
46
|
+
|
|
47
|
+
def _sigint_handler():
|
|
48
|
+
# cancel the task in order to have run_until_complete return soon and
|
|
49
|
+
# prevent a bunch of unwanted tracebacks when shutting down the
|
|
50
|
+
# event loop.
|
|
51
|
+
|
|
52
|
+
# this basically replicates the sigint handler installed by asyncio.run()
|
|
53
|
+
self._sigints += 1
|
|
54
|
+
if self._sigints == 1:
|
|
55
|
+
# first sigint is graceful
|
|
56
|
+
task.cancel()
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
# this should normally not happen, but the second sigint would "hard kill" the event loop!
|
|
60
|
+
raise KeyboardInterrupt()
|
|
61
|
+
|
|
62
|
+
ignore_sigint = signal.getsignal(signal.SIGINT) == signal.SIG_IGN
|
|
63
|
+
if not ignore_sigint:
|
|
64
|
+
self.loop.add_signal_handler(signal.SIGINT, _sigint_handler)
|
|
65
|
+
|
|
66
|
+
# Before Python 3.9 there is no argument to Task.cancel
|
|
67
|
+
if sys.version_info[:2] >= (3, 9):
|
|
68
|
+
self.loop.add_signal_handler(signal.SIGUSR1, task.cancel, "Input was cancelled by user")
|
|
69
|
+
else:
|
|
70
|
+
self.loop.add_signal_handler(signal.SIGUSR1, task.cancel)
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
return self.loop.run_until_complete(task)
|
|
74
|
+
except asyncio.CancelledError:
|
|
75
|
+
if self._sigints > 0:
|
|
76
|
+
raise KeyboardInterrupt()
|
|
77
|
+
finally:
|
|
78
|
+
self.loop.remove_signal_handler(signal.SIGUSR1)
|
|
79
|
+
if not ignore_sigint:
|
|
80
|
+
self.loop.remove_signal_handler(signal.SIGINT)
|
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
# Copyright Modal Labs 2024
|
|
2
2
|
import importlib
|
|
3
|
+
import inspect
|
|
4
|
+
import os
|
|
5
|
+
import signal
|
|
6
|
+
import sys
|
|
3
7
|
import typing
|
|
4
8
|
from abc import ABCMeta, abstractmethod
|
|
9
|
+
from contextlib import contextmanager
|
|
5
10
|
from dataclasses import dataclass
|
|
6
|
-
from typing import Any, Callable, Optional, Sequence
|
|
11
|
+
from typing import Any, Callable, Generator, Optional, Sequence
|
|
7
12
|
|
|
8
13
|
import modal._object
|
|
9
14
|
import modal._runtime.container_io_manager
|
|
10
15
|
import modal.cls
|
|
11
16
|
from modal import Function
|
|
12
17
|
from modal._functions import _Function
|
|
18
|
+
from modal._partial_function import (
|
|
19
|
+
_find_callables_for_obj,
|
|
20
|
+
_PartialFunctionFlags,
|
|
21
|
+
)
|
|
22
|
+
from modal._runtime.user_code_event_loop import UserCodeEventLoop
|
|
13
23
|
from modal._utils.async_utils import synchronizer
|
|
14
|
-
from modal._utils.function_utils import
|
|
24
|
+
from modal._utils.function_utils import (
|
|
25
|
+
LocalFunctionError,
|
|
26
|
+
callable_has_non_self_params,
|
|
27
|
+
is_async as get_is_async,
|
|
28
|
+
is_global_object,
|
|
29
|
+
)
|
|
15
30
|
from modal.app import _App
|
|
16
31
|
from modal.config import logger
|
|
17
32
|
from modal.exception import ExecutionError, InvalidError
|
|
@@ -33,6 +48,74 @@ class FinalizedFunction:
|
|
|
33
48
|
lifespan_manager: Optional["LifespanManager"] = None
|
|
34
49
|
|
|
35
50
|
|
|
51
|
+
def call_lifecycle_functions(
|
|
52
|
+
event_loop: UserCodeEventLoop,
|
|
53
|
+
container_io_manager: Any,
|
|
54
|
+
funcs: Sequence[Callable[..., Any]],
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Call function(s), can be sync or async, but any return values are ignored."""
|
|
57
|
+
with container_io_manager.handle_user_exception():
|
|
58
|
+
for func in funcs:
|
|
59
|
+
# We are deprecating parametrized exit methods but want to gracefully handle old code.
|
|
60
|
+
args = (None, None, None) if callable_has_non_self_params(func) else ()
|
|
61
|
+
res = func(*args)
|
|
62
|
+
if inspect.iscoroutine(res):
|
|
63
|
+
event_loop.run(res)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@contextmanager
|
|
67
|
+
def _run_service_lifecycle(
|
|
68
|
+
event_loop: UserCodeEventLoop,
|
|
69
|
+
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
|
70
|
+
function_def: api_pb2.Function,
|
|
71
|
+
finalized_functions: dict[str, FinalizedFunction],
|
|
72
|
+
exit_callback: Optional[Callable[[], None]],
|
|
73
|
+
) -> Generator[None, None, None]:
|
|
74
|
+
lifespan_background_tasks = []
|
|
75
|
+
try:
|
|
76
|
+
for finalized_function in finalized_functions.values():
|
|
77
|
+
if finalized_function.lifespan_manager:
|
|
78
|
+
lifespan_background_tasks.append(
|
|
79
|
+
event_loop.create_task(finalized_function.lifespan_manager.background_task())
|
|
80
|
+
)
|
|
81
|
+
with container_io_manager.handle_user_exception():
|
|
82
|
+
event_loop.run(finalized_function.lifespan_manager.lifespan_startup())
|
|
83
|
+
yield
|
|
84
|
+
finally:
|
|
85
|
+
# Run exit handlers. From this point onward, ignore all SIGINT signals that come from
|
|
86
|
+
# graceful shutdowns originating on the worker, as well as stray SIGUSR1 signals that
|
|
87
|
+
# may have been sent to cancel inputs.
|
|
88
|
+
int_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
89
|
+
usr1_handler = signal.signal(signal.SIGUSR1, signal.SIG_IGN)
|
|
90
|
+
try:
|
|
91
|
+
try:
|
|
92
|
+
# run lifespan shutdown for asgi apps
|
|
93
|
+
for finalized_function in finalized_functions.values():
|
|
94
|
+
if finalized_function.lifespan_manager:
|
|
95
|
+
with container_io_manager.handle_user_exception():
|
|
96
|
+
event_loop.run(finalized_function.lifespan_manager.lifespan_shutdown())
|
|
97
|
+
finally:
|
|
98
|
+
# no need to keep the lifespan asgi call around - we send it no more messages
|
|
99
|
+
for task in lifespan_background_tasks:
|
|
100
|
+
task.cancel()
|
|
101
|
+
|
|
102
|
+
# Identify "exit" methods and run them.
|
|
103
|
+
# want to make sure this is called even if the lifespan manager fails
|
|
104
|
+
if exit_callback:
|
|
105
|
+
exit_callback()
|
|
106
|
+
|
|
107
|
+
# Finally, commit on exit to catch uncommitted volume changes and surface background
|
|
108
|
+
# commit errors.
|
|
109
|
+
container_io_manager.volume_commit(
|
|
110
|
+
[v.volume_id for v in function_def.volume_mounts if v.allow_background_commits]
|
|
111
|
+
)
|
|
112
|
+
finally:
|
|
113
|
+
# Restore the original signal handler, needed for container_test hygiene since the
|
|
114
|
+
# test runs `main()` multiple times in the same process.
|
|
115
|
+
signal.signal(signal.SIGINT, int_handler)
|
|
116
|
+
signal.signal(signal.SIGUSR1, usr1_handler)
|
|
117
|
+
|
|
118
|
+
|
|
36
119
|
class Service(metaclass=ABCMeta):
|
|
37
120
|
"""Common interface for singular functions and class-based "services"
|
|
38
121
|
|
|
@@ -50,6 +133,24 @@ class Service(metaclass=ABCMeta):
|
|
|
50
133
|
self, fun_def: api_pb2.Function, container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager"
|
|
51
134
|
) -> dict[str, "FinalizedFunction"]: ...
|
|
52
135
|
|
|
136
|
+
@contextmanager
|
|
137
|
+
@abstractmethod
|
|
138
|
+
def execution_context(
|
|
139
|
+
self,
|
|
140
|
+
event_loop: UserCodeEventLoop,
|
|
141
|
+
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
|
142
|
+
) -> Generator[dict[str, "FinalizedFunction"], None, None]:
|
|
143
|
+
"""
|
|
144
|
+
Manages the lifecycle of the user code:
|
|
145
|
+
1. Runs pre-snapshot 'enter' methods
|
|
146
|
+
2. Calls maybe_snapshot(container_io_manager, function_def)
|
|
147
|
+
3. Creates breakpoint wrapper
|
|
148
|
+
4. Runs post-snapshot 'enter' methods
|
|
149
|
+
5. Initializes finalized functions (and ASGI/WSGI lifespan)
|
|
150
|
+
6. Yield finalized_functions for execution
|
|
151
|
+
7. Handles cleanup (lifespan shutdown, 'exit' methods)
|
|
152
|
+
"""
|
|
153
|
+
|
|
53
154
|
|
|
54
155
|
def construct_webhook_callable(
|
|
55
156
|
user_defined_callable: Callable,
|
|
@@ -91,11 +192,36 @@ def construct_webhook_callable(
|
|
|
91
192
|
raise InvalidError(f"Unrecognized web endpoint type {webhook_config.type}")
|
|
92
193
|
|
|
93
194
|
|
|
195
|
+
def maybe_snapshot(
|
|
196
|
+
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager", function_def: api_pb2.Function
|
|
197
|
+
):
|
|
198
|
+
if function_def.is_checkpointing_function and os.environ.get("MODAL_ENABLE_SNAP_RESTORE") == "1":
|
|
199
|
+
container_io_manager.memory_snapshot()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def create_breakpoint_wrapper(container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager"):
|
|
203
|
+
# Install hooks for interactive functions.
|
|
204
|
+
def breakpoint_wrapper():
|
|
205
|
+
# note: it would be nice to not have breakpoint_wrapper() included in the backtrace
|
|
206
|
+
container_io_manager.interact(from_breakpoint=True)
|
|
207
|
+
import pdb
|
|
208
|
+
|
|
209
|
+
current_frame = inspect.currentframe()
|
|
210
|
+
if current_frame is not None:
|
|
211
|
+
frame = current_frame.f_back
|
|
212
|
+
pdb.Pdb().set_trace(frame)
|
|
213
|
+
else:
|
|
214
|
+
raise RuntimeError("No current frame found")
|
|
215
|
+
|
|
216
|
+
sys.breakpointhook = breakpoint_wrapper
|
|
217
|
+
|
|
218
|
+
|
|
94
219
|
@dataclass
|
|
95
220
|
class ImportedFunction(Service):
|
|
96
221
|
app: modal.app._App
|
|
97
222
|
service_deps: Optional[Sequence["modal._object._Object"]]
|
|
98
223
|
user_cls_instance = None
|
|
224
|
+
function_def: api_pb2.Function
|
|
99
225
|
|
|
100
226
|
_user_defined_callable: Callable[..., Any]
|
|
101
227
|
|
|
@@ -138,6 +264,29 @@ class ImportedFunction(Service):
|
|
|
138
264
|
)
|
|
139
265
|
}
|
|
140
266
|
|
|
267
|
+
@contextmanager
|
|
268
|
+
def execution_context(
|
|
269
|
+
self,
|
|
270
|
+
event_loop: UserCodeEventLoop,
|
|
271
|
+
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
|
272
|
+
) -> Generator[dict[str, "FinalizedFunction"], None, None]:
|
|
273
|
+
# If this container is being used to create a checkpoint, checkpoint the container after
|
|
274
|
+
# global imports and initialization. Checkpointed containers run from this point onwards.
|
|
275
|
+
maybe_snapshot(container_io_manager, self.function_def)
|
|
276
|
+
create_breakpoint_wrapper(container_io_manager)
|
|
277
|
+
|
|
278
|
+
with container_io_manager.handle_user_exception():
|
|
279
|
+
finalized_functions = self.get_finalized_functions(self.function_def, container_io_manager)
|
|
280
|
+
|
|
281
|
+
with _run_service_lifecycle(
|
|
282
|
+
event_loop,
|
|
283
|
+
container_io_manager,
|
|
284
|
+
self.function_def,
|
|
285
|
+
finalized_functions,
|
|
286
|
+
None,
|
|
287
|
+
):
|
|
288
|
+
yield finalized_functions
|
|
289
|
+
|
|
141
290
|
|
|
142
291
|
@dataclass
|
|
143
292
|
class ImportedClass(Service):
|
|
@@ -146,6 +295,7 @@ class ImportedClass(Service):
|
|
|
146
295
|
service_deps: Optional[Sequence["modal._object._Object"]]
|
|
147
296
|
|
|
148
297
|
_partial_functions: dict[str, "modal._partial_function._PartialFunction"]
|
|
298
|
+
function_def: api_pb2.Function
|
|
149
299
|
|
|
150
300
|
def get_finalized_functions(
|
|
151
301
|
self, fun_def: api_pb2.Function, container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager"
|
|
@@ -189,6 +339,54 @@ class ImportedClass(Service):
|
|
|
189
339
|
finalized_functions[method_name] = finalized_function
|
|
190
340
|
return finalized_functions
|
|
191
341
|
|
|
342
|
+
@contextmanager
|
|
343
|
+
def execution_context(
|
|
344
|
+
self,
|
|
345
|
+
event_loop: UserCodeEventLoop,
|
|
346
|
+
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
|
347
|
+
) -> Generator[dict[str, "FinalizedFunction"], None, None]:
|
|
348
|
+
# 1. Pre-snapshot Enter
|
|
349
|
+
# Identify all "enter" methods that need to run before we snapshot.
|
|
350
|
+
if not self.function_def.is_auto_snapshot:
|
|
351
|
+
pre_snapshot_methods = _find_callables_for_obj(
|
|
352
|
+
self.user_cls_instance, _PartialFunctionFlags.ENTER_PRE_SNAPSHOT
|
|
353
|
+
)
|
|
354
|
+
call_lifecycle_functions(event_loop, container_io_manager, list(pre_snapshot_methods.values()))
|
|
355
|
+
|
|
356
|
+
# 2. Snapshot
|
|
357
|
+
# If this container is being used to create a checkpoint, checkpoint the container after
|
|
358
|
+
# global imports and initialization. Checkpointed containers run from this point onwards.
|
|
359
|
+
maybe_snapshot(container_io_manager, self.function_def)
|
|
360
|
+
|
|
361
|
+
# 3. Breakpoint wrapper
|
|
362
|
+
create_breakpoint_wrapper(container_io_manager)
|
|
363
|
+
|
|
364
|
+
# 4. Post-snapshot Enter
|
|
365
|
+
# Identify the "enter" methods to run after resuming from a snapshot.
|
|
366
|
+
if not self.function_def.is_auto_snapshot:
|
|
367
|
+
post_snapshot_methods = _find_callables_for_obj(
|
|
368
|
+
self.user_cls_instance, _PartialFunctionFlags.ENTER_POST_SNAPSHOT
|
|
369
|
+
)
|
|
370
|
+
call_lifecycle_functions(event_loop, container_io_manager, list(post_snapshot_methods.values()))
|
|
371
|
+
|
|
372
|
+
# 5. Get Functions & Start Lifespan
|
|
373
|
+
with container_io_manager.handle_user_exception():
|
|
374
|
+
finalized_functions = self.get_finalized_functions(self.function_def, container_io_manager)
|
|
375
|
+
|
|
376
|
+
def exit_callback():
|
|
377
|
+
if not self.function_def.is_auto_snapshot:
|
|
378
|
+
exit_methods = _find_callables_for_obj(self.user_cls_instance, _PartialFunctionFlags.EXIT)
|
|
379
|
+
call_lifecycle_functions(event_loop, container_io_manager, list(exit_methods.values()))
|
|
380
|
+
|
|
381
|
+
with _run_service_lifecycle(
|
|
382
|
+
event_loop,
|
|
383
|
+
container_io_manager,
|
|
384
|
+
self.function_def,
|
|
385
|
+
finalized_functions,
|
|
386
|
+
exit_callback,
|
|
387
|
+
):
|
|
388
|
+
yield finalized_functions
|
|
389
|
+
|
|
192
390
|
|
|
193
391
|
def get_user_class_instance(_cls: modal.cls._Cls, args: tuple[Any, ...], kwargs: dict[str, Any]) -> typing.Any:
|
|
194
392
|
"""Returns instance of the underlying class to be used as the `self`
|
|
@@ -266,9 +464,10 @@ def import_single_function_service(
|
|
|
266
464
|
active_app = get_active_app_fallback(function_def)
|
|
267
465
|
|
|
268
466
|
return ImportedFunction(
|
|
269
|
-
active_app,
|
|
270
|
-
service_deps,
|
|
271
|
-
|
|
467
|
+
app=active_app,
|
|
468
|
+
service_deps=service_deps,
|
|
469
|
+
function_def=function_def,
|
|
470
|
+
_user_defined_callable=user_defined_callable,
|
|
272
471
|
)
|
|
273
472
|
|
|
274
473
|
|
|
@@ -338,11 +537,12 @@ def import_class_service(
|
|
|
338
537
|
user_cls_instance = get_user_class_instance(_cls, cls_args, cls_kwargs)
|
|
339
538
|
|
|
340
539
|
return ImportedClass(
|
|
341
|
-
user_cls_instance,
|
|
342
|
-
active_app,
|
|
343
|
-
service_deps,
|
|
540
|
+
user_cls_instance=user_cls_instance,
|
|
541
|
+
app=active_app,
|
|
542
|
+
service_deps=service_deps,
|
|
344
543
|
# TODO (elias/deven): instead of using method_partials here we should use a set of api_pb2.MethodDefinition
|
|
345
|
-
method_partials,
|
|
544
|
+
_partial_functions=method_partials,
|
|
545
|
+
function_def=function_def,
|
|
346
546
|
)
|
|
347
547
|
|
|
348
548
|
|
modal/client.pyi
CHANGED
|
@@ -32,7 +32,7 @@ class _Client:
|
|
|
32
32
|
server_url: str,
|
|
33
33
|
client_type: int,
|
|
34
34
|
credentials: typing.Optional[tuple[str, str]],
|
|
35
|
-
version: str = "1.2.5.
|
|
35
|
+
version: str = "1.2.5.dev2",
|
|
36
36
|
):
|
|
37
37
|
"""mdmd:hidden
|
|
38
38
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -163,7 +163,7 @@ class Client:
|
|
|
163
163
|
server_url: str,
|
|
164
164
|
client_type: int,
|
|
165
165
|
credentials: typing.Optional[tuple[str, str]],
|
|
166
|
-
version: str = "1.2.5.
|
|
166
|
+
version: str = "1.2.5.dev2",
|
|
167
167
|
):
|
|
168
168
|
"""mdmd:hidden
|
|
169
169
|
The Modal client object is not intended to be instantiated directly by users.
|
modal/experimental/__init__.py
CHANGED
|
@@ -85,6 +85,19 @@ async def list_deployed_apps(environment_name: str = "", client: Optional[_Clien
|
|
|
85
85
|
return app_infos
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
@synchronizer.create_blocking
|
|
89
|
+
async def stop_app(name: str, *, environment_name: Optional[str] = None, client: Optional[_Client] = None) -> None:
|
|
90
|
+
"""Stop a deployed App.
|
|
91
|
+
|
|
92
|
+
This interface is experimental and may change in the future,
|
|
93
|
+
although the functionality will continue to be supported.
|
|
94
|
+
"""
|
|
95
|
+
client_ = client or await _Client.from_env()
|
|
96
|
+
app = await _App.lookup(name, environment_name=environment_name, client=client_)
|
|
97
|
+
req = api_pb2.AppStopRequest(app_id=app.app_id, source=api_pb2.APP_STOP_SOURCE_PYTHON_CLIENT)
|
|
98
|
+
await client_.stub.AppStop(req)
|
|
99
|
+
|
|
100
|
+
|
|
88
101
|
@synchronizer.create_blocking
|
|
89
102
|
async def get_app_objects(
|
|
90
103
|
app_name: str, *, environment_name: Optional[str] = None, client: Optional[_Client] = None
|
|
@@ -3,7 +3,7 @@ modal/__main__.py,sha256=45H-GtwzaDfN-1nP4_HYvzN3s7AG_HXR4-ynrsjO_OI,2803
|
|
|
3
3
|
modal/_billing.py,sha256=C1jUN9f_1WqozSZAt9EOk1nImXUdiLrgFeeAu3R23cI,3012
|
|
4
4
|
modal/_clustered_functions.py,sha256=7amiOxErtlciWeBJBL2KvaxerUomJpTAJ3qElflFHQA,2884
|
|
5
5
|
modal/_clustered_functions.pyi,sha256=JmYwAGOLEnD5AF-gYF9O5tu-SgGjeoJz-X1j48b1Ijg,1157
|
|
6
|
-
modal/_container_entrypoint.py,sha256=
|
|
6
|
+
modal/_container_entrypoint.py,sha256=aRuWG1xl3kDy28CfQrqQ-LU8liX2W_bwm2avVOE63Tw,19812
|
|
7
7
|
modal/_functions.py,sha256=fnmNMS4nGi-YvbWPn6nGt4RYqs11JmSa0b_B1UNSwPU,91499
|
|
8
8
|
modal/_grpc_client.py,sha256=KZcggVNvFL5jRQkFumshMppeFHMbaZI9Dzf13run6As,6083
|
|
9
9
|
modal/_ipython.py,sha256=TW1fkVOmZL3YYqdS2YlM1hqpf654Yf8ZyybHdBnlhSw,301
|
|
@@ -26,7 +26,7 @@ modal/app.pyi,sha256=nI_Tibj-rI5I7dJFJnczbHvcmVzCvvCih0tMzoQlr_Y,50835
|
|
|
26
26
|
modal/billing.py,sha256=zmQ3bcCJlwa4KD1IA_QgdWpm1pn13c-7qfy79iEauYI,195
|
|
27
27
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
|
28
28
|
modal/client.py,sha256=k1CqQ_0t4-vHylkqYZgYCnClg-FT0QrTPAKivL01A_k,14354
|
|
29
|
-
modal/client.pyi,sha256=
|
|
29
|
+
modal/client.pyi,sha256=DDiMjmFSIARj1BaJNqxZtDRDkt1GM2jSvnvh06g7hxo,13146
|
|
30
30
|
modal/cloud_bucket_mount.py,sha256=cvVj5Rt4nDHoQbTneoJPTaAcnl_Ab_mrsjnnzd0v9OI,6013
|
|
31
31
|
modal/cloud_bucket_mount.pyi,sha256=vXusGMOUk9HjCs4lhxteuCK4xLQclPfugGWKo7LECEA,7539
|
|
32
32
|
modal/cls.py,sha256=4T__mtYlQUnkatif1Wnr8Ld9DN_ySydHj2UwhRg89ok,41984
|
|
@@ -92,7 +92,8 @@ modal/_runtime/execution_context.py,sha256=AYrNQRHHXEqX2MwMf8zxelKZnYf25RE_B-NRL
|
|
|
92
92
|
modal/_runtime/execution_context.pyi,sha256=FVzakehz72ndL-ufe8-EC7TM4IHO_MEBcAdgWuU4W9k,2426
|
|
93
93
|
modal/_runtime/gpu_memory_snapshot.py,sha256=8T85kyfGJ11AnhFRCZTeWj9zin9e9f5XbrRqU8_WjAk,11955
|
|
94
94
|
modal/_runtime/telemetry.py,sha256=T1RoAGyjBDr1swiM6pPsGRSITm7LI5FDK18oNXxY08U,5163
|
|
95
|
-
modal/_runtime/
|
|
95
|
+
modal/_runtime/user_code_event_loop.py,sha256=HwOuDyCZLOakr_DG7p2MrcscjSQNheIhBBuLgB5I3Cc,3014
|
|
96
|
+
modal/_runtime/user_code_imports.py,sha256=9mxg2FMZGR2UQfLcxEPNIiH51SBJTl_1JCCRrGMM0rI,25086
|
|
96
97
|
modal/_utils/__init__.py,sha256=waLjl5c6IPDhSsdWAm9Bji4e2PVxamYABKAze6CHVXY,28
|
|
97
98
|
modal/_utils/app_utils.py,sha256=88BT4TPLWfYAQwKTHcyzNQRHg8n9B-QE2UyJs96iV-0,108
|
|
98
99
|
modal/_utils/async_utils.py,sha256=PnwHfaHcqjLwMnlGC2kn8nqubxpXcSKcy4jHx6477dg,30625
|
|
@@ -155,11 +156,11 @@ modal/cli/programs/launch_instance_ssh.py,sha256=GrwK_Vy8-7B4x5a6AqFaF7lqNVgu75J
|
|
|
155
156
|
modal/cli/programs/run_jupyter.py,sha256=IJw8nds8Cjl9j4dxBqMGxhz-bIyVX0kle7jbt8HFOhM,2688
|
|
156
157
|
modal/cli/programs/run_marimo.py,sha256=QlCGPkwQ0XLajt2LtujR_BGsRV1AZ3OCUgZxkkM1lug,2893
|
|
157
158
|
modal/cli/programs/vscode.py,sha256=E1aJPU7b8RWWj-JX71DifsCoLYsCAnre7lATiBJjUms,3386
|
|
158
|
-
modal/experimental/__init__.py,sha256=
|
|
159
|
+
modal/experimental/__init__.py,sha256=WlBNjzahOTuhWdfHt0OIArU3pdrErsFrprVhZ7UqU0k,14520
|
|
159
160
|
modal/experimental/flash.py,sha256=0afbNEg8SLBdUbvFpT3R9FE1mfHAV_Iual8x2hqfPMY,26785
|
|
160
161
|
modal/experimental/flash.pyi,sha256=BU7v9gFLWX1OanZ3HpRoVGp4qFfHo93lM9UkzcNFZUY,14832
|
|
161
162
|
modal/experimental/ipython.py,sha256=TrCfmol9LGsRZMeDoeMPx3Hv3BFqQhYnmD_iH0pqdhk,2904
|
|
162
|
-
modal-1.2.5.
|
|
163
|
+
modal-1.2.5.dev2.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
|
163
164
|
modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
|
|
164
165
|
modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
|
|
165
166
|
modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
|
|
@@ -187,10 +188,10 @@ modal_proto/task_command_router_pb2.py,sha256=_pD2ZpU0bNzhwBdzmLoLyLtAtftI_Agxwn
|
|
|
187
188
|
modal_proto/task_command_router_pb2.pyi,sha256=EyDgXPLr7alqjXYERV8w_MPuO404x0uCppmSkrfE9IE,14589
|
|
188
189
|
modal_proto/task_command_router_pb2_grpc.py,sha256=uEQ0HdrCp8v-9bB5yIic9muA8spCShLHY6Bz9cCgOUE,10114
|
|
189
190
|
modal_proto/task_command_router_pb2_grpc.pyi,sha256=s3Yxsrawdj4nr8vqQqsAxyX6ilWaGbdECy425KKbLIA,3301
|
|
190
|
-
modal_version/__init__.py,sha256=
|
|
191
|
+
modal_version/__init__.py,sha256=y2ih8aafsJLowH_EmA8i4Ghzhd9x2mf_p9cG3NYWFfk,120
|
|
191
192
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
|
192
|
-
modal-1.2.5.
|
|
193
|
-
modal-1.2.5.
|
|
194
|
-
modal-1.2.5.
|
|
195
|
-
modal-1.2.5.
|
|
196
|
-
modal-1.2.5.
|
|
193
|
+
modal-1.2.5.dev2.dist-info/METADATA,sha256=3yOTToFAFT9kAmIjL-NrDmej6f3kE8okAIrwF8r2W3Y,2489
|
|
194
|
+
modal-1.2.5.dev2.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
|
195
|
+
modal-1.2.5.dev2.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
|
196
|
+
modal-1.2.5.dev2.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
|
|
197
|
+
modal-1.2.5.dev2.dist-info/RECORD,,
|
modal_version/__init__.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|