modal 0.71.6__py3-none-any.whl → 0.71.8__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 +13 -3
- modal/_runtime/asgi.py +33 -29
- modal/client.pyi +2 -2
- modal/image.py +18 -0
- modal/image.pyi +9 -0
- {modal-0.71.6.dist-info → modal-0.71.8.dist-info}/METADATA +1 -1
- {modal-0.71.6.dist-info → modal-0.71.8.dist-info}/RECORD +12 -12
- modal_version/_version_generated.py +1 -1
- {modal-0.71.6.dist-info → modal-0.71.8.dist-info}/LICENSE +0 -0
- {modal-0.71.6.dist-info → modal-0.71.8.dist-info}/WHEEL +0 -0
- {modal-0.71.6.dist-info → modal-0.71.8.dist-info}/entry_points.txt +0 -0
- {modal-0.71.6.dist-info → modal-0.71.8.dist-info}/top_level.txt +0 -0
modal/_container_entrypoint.py
CHANGED
@@ -116,7 +116,7 @@ class UserCodeEventLoop:
|
|
116
116
|
|
117
117
|
def __enter__(self):
|
118
118
|
self.loop = asyncio.new_event_loop()
|
119
|
-
self.tasks =
|
119
|
+
self.tasks = set()
|
120
120
|
return self
|
121
121
|
|
122
122
|
def __exit__(self, exc_type, exc_value, traceback):
|
@@ -130,7 +130,10 @@ class UserCodeEventLoop:
|
|
130
130
|
self.loop.close()
|
131
131
|
|
132
132
|
def create_task(self, coro):
|
133
|
-
self.
|
133
|
+
task = self.loop.create_task(coro)
|
134
|
+
self.tasks.add(task)
|
135
|
+
task.add_done_callback(self.tasks.discard)
|
136
|
+
return task
|
134
137
|
|
135
138
|
def run(self, coro):
|
136
139
|
task = asyncio.ensure_future(coro, loop=self.loop)
|
@@ -531,10 +534,13 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
531
534
|
with container_io_manager.handle_user_exception():
|
532
535
|
finalized_functions = service.get_finalized_functions(function_def, container_io_manager)
|
533
536
|
# Execute the function.
|
537
|
+
lifespan_background_tasks = []
|
534
538
|
try:
|
535
539
|
for finalized_function in finalized_functions.values():
|
536
540
|
if finalized_function.lifespan_manager:
|
537
|
-
|
541
|
+
lifespan_background_tasks.append(
|
542
|
+
event_loop.create_task(finalized_function.lifespan_manager.background_task())
|
543
|
+
)
|
538
544
|
with container_io_manager.handle_user_exception():
|
539
545
|
event_loop.run(finalized_function.lifespan_manager.lifespan_startup())
|
540
546
|
call_function(
|
@@ -559,6 +565,10 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
559
565
|
with container_io_manager.handle_user_exception():
|
560
566
|
event_loop.run(finalized_function.lifespan_manager.lifespan_shutdown())
|
561
567
|
finally:
|
568
|
+
# no need to keep the lifespan asgi call around - we send it no more messages
|
569
|
+
for lifespan_background_task in lifespan_background_tasks:
|
570
|
+
lifespan_background_task.cancel() # prevent dangling tasks
|
571
|
+
|
562
572
|
# Identify "exit" methods and run them.
|
563
573
|
# want to make sure this is called even if the lifespan manager fails
|
564
574
|
if service.user_cls_instance is not None and not is_auto_snapshot:
|
modal/_runtime/asgi.py
CHANGED
@@ -22,11 +22,11 @@ FIRST_MESSAGE_TIMEOUT_SECONDS = 5.0
|
|
22
22
|
|
23
23
|
|
24
24
|
class LifespanManager:
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
_startup: asyncio.Future
|
26
|
+
_shutdown: asyncio.Future
|
27
|
+
_queue: asyncio.Queue
|
28
|
+
_has_run_init: bool = False
|
29
|
+
_lifespan_supported: bool = False
|
30
30
|
|
31
31
|
def __init__(self, asgi_app, state):
|
32
32
|
self.asgi_app = asgi_app
|
@@ -37,59 +37,63 @@ class LifespanManager:
|
|
37
37
|
# no async code since it has to run inside
|
38
38
|
# the event loop to tie the
|
39
39
|
# objects to the correct loop in python 3.9
|
40
|
-
if not self.
|
41
|
-
self.
|
42
|
-
self.
|
43
|
-
self.
|
44
|
-
self.
|
40
|
+
if not self._has_run_init:
|
41
|
+
self._queue = asyncio.Queue()
|
42
|
+
self._startup = asyncio.Future()
|
43
|
+
self._shutdown = asyncio.Future()
|
44
|
+
self._has_run_init = True
|
45
45
|
|
46
46
|
async def background_task(self):
|
47
47
|
await self.ensure_init()
|
48
48
|
|
49
49
|
async def receive():
|
50
|
-
self.
|
51
|
-
return await self.
|
50
|
+
self._lifespan_supported = True
|
51
|
+
return await self._queue.get()
|
52
52
|
|
53
53
|
async def send(message):
|
54
54
|
if message["type"] == "lifespan.startup.complete":
|
55
|
-
self.
|
55
|
+
self._startup.set_result(None)
|
56
56
|
elif message["type"] == "lifespan.startup.failed":
|
57
|
-
self.
|
57
|
+
self._startup.set_exception(ExecutionError("ASGI lifespan startup failed"))
|
58
58
|
elif message["type"] == "lifespan.shutdown.complete":
|
59
|
-
self.
|
59
|
+
self._shutdown.set_result(None)
|
60
60
|
elif message["type"] == "lifespan.shutdown.failed":
|
61
|
-
self.
|
61
|
+
self._shutdown.set_exception(ExecutionError("ASGI lifespan shutdown failed"))
|
62
62
|
else:
|
63
63
|
raise ExecutionError(f"Unexpected message type: {message['type']}")
|
64
64
|
|
65
65
|
try:
|
66
66
|
await self.asgi_app({"type": "lifespan", "state": self.state}, receive, send)
|
67
67
|
except Exception as e:
|
68
|
-
if not self.
|
68
|
+
if not self._lifespan_supported:
|
69
69
|
logger.info(f"ASGI lifespan task exited before receiving any messages with exception:\n{e}")
|
70
|
-
self.
|
71
|
-
|
70
|
+
if not self._startup.done():
|
71
|
+
self._startup.set_result(None)
|
72
|
+
if not self._shutdown.done():
|
73
|
+
self._shutdown.set_result(None)
|
72
74
|
return
|
73
75
|
|
74
76
|
logger.error(f"Error in ASGI lifespan task: {e}")
|
75
|
-
if not self.
|
76
|
-
self.
|
77
|
-
if not self.
|
78
|
-
self.
|
77
|
+
if not self._startup.done():
|
78
|
+
self._startup.set_exception(ExecutionError("ASGI lifespan task exited startup"))
|
79
|
+
if not self._shutdown.done():
|
80
|
+
self._shutdown.set_exception(ExecutionError("ASGI lifespan task exited shutdown"))
|
79
81
|
else:
|
80
82
|
logger.info("ASGI Lifespan protocol is probably not supported by this library")
|
81
|
-
self.
|
82
|
-
|
83
|
+
if not self._startup.done():
|
84
|
+
self._startup.set_result(None)
|
85
|
+
if not self._shutdown.done():
|
86
|
+
self._shutdown.set_result(None)
|
83
87
|
|
84
88
|
async def lifespan_startup(self):
|
85
89
|
await self.ensure_init()
|
86
|
-
self.
|
87
|
-
await self.
|
90
|
+
self._queue.put_nowait({"type": "lifespan.startup"})
|
91
|
+
await self._startup
|
88
92
|
|
89
93
|
async def lifespan_shutdown(self):
|
90
94
|
await self.ensure_init()
|
91
|
-
self.
|
92
|
-
await self.
|
95
|
+
self._queue.put_nowait({"type": "lifespan.shutdown"})
|
96
|
+
await self._shutdown
|
93
97
|
|
94
98
|
|
95
99
|
def asgi_app_wrapper(asgi_app, container_io_manager) -> tuple[Callable[..., AsyncGenerator], LifespanManager]:
|
modal/client.pyi
CHANGED
@@ -26,7 +26,7 @@ class _Client:
|
|
26
26
|
_stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
|
27
27
|
|
28
28
|
def __init__(
|
29
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.71.
|
29
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.71.8"
|
30
30
|
): ...
|
31
31
|
def is_closed(self) -> bool: ...
|
32
32
|
@property
|
@@ -81,7 +81,7 @@ class Client:
|
|
81
81
|
_stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
|
82
82
|
|
83
83
|
def __init__(
|
84
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.71.
|
84
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.71.8"
|
85
85
|
): ...
|
86
86
|
def is_closed(self) -> bool: ...
|
87
87
|
@property
|
modal/image.py
CHANGED
@@ -832,6 +832,24 @@ class _Image(_Object, type_prefix="im"):
|
|
832
832
|
),
|
833
833
|
)
|
834
834
|
|
835
|
+
@staticmethod
|
836
|
+
async def from_id(image_id: str, client: Optional[_Client] = None) -> "_Image":
|
837
|
+
"""Construct an Image from an id and look up the Image result.
|
838
|
+
|
839
|
+
The ID of an Image object can be accessed using `.object_id`.
|
840
|
+
"""
|
841
|
+
if client is None:
|
842
|
+
client = await _Client.from_env()
|
843
|
+
|
844
|
+
async def _load(self: _Image, resolver: Resolver, existing_object_id: Optional[str]):
|
845
|
+
resp = await retry_transient_errors(client.stub.ImageFromId, api_pb2.ImageFromIdRequest(image_id=image_id))
|
846
|
+
self._hydrate(resp.image_id, resolver.client, resp.metadata)
|
847
|
+
|
848
|
+
rep = "Image()"
|
849
|
+
obj = _Image._from_loader(_load, rep)
|
850
|
+
|
851
|
+
return obj
|
852
|
+
|
835
853
|
def pip_install(
|
836
854
|
self,
|
837
855
|
*packages: Union[str, list[str]], # A list of Python packages, eg. ["numpy", "matplotlib>=3.5.0"]
|
modal/image.pyi
CHANGED
@@ -126,6 +126,8 @@ class _Image(modal.object._Object):
|
|
126
126
|
remote_path: typing.Union[str, pathlib.Path] = ".",
|
127
127
|
ignore: typing.Union[collections.abc.Sequence[str], typing.Callable[[pathlib.Path], bool]] = [],
|
128
128
|
) -> _Image: ...
|
129
|
+
@staticmethod
|
130
|
+
async def from_id(image_id: str, client: typing.Optional[modal.client._Client] = None) -> _Image: ...
|
129
131
|
def pip_install(
|
130
132
|
self,
|
131
133
|
*packages: typing.Union[str, list[str]],
|
@@ -387,6 +389,13 @@ class Image(modal.object.Object):
|
|
387
389
|
remote_path: typing.Union[str, pathlib.Path] = ".",
|
388
390
|
ignore: typing.Union[collections.abc.Sequence[str], typing.Callable[[pathlib.Path], bool]] = [],
|
389
391
|
) -> Image: ...
|
392
|
+
|
393
|
+
class __from_id_spec(typing_extensions.Protocol):
|
394
|
+
def __call__(self, image_id: str, client: typing.Optional[modal.client.Client] = None) -> Image: ...
|
395
|
+
async def aio(self, image_id: str, client: typing.Optional[modal.client.Client] = None) -> Image: ...
|
396
|
+
|
397
|
+
from_id: __from_id_spec
|
398
|
+
|
390
399
|
def pip_install(
|
391
400
|
self,
|
392
401
|
*packages: typing.Union[str, list[str]],
|
@@ -2,7 +2,7 @@ modal/__init__.py,sha256=3NJLLHb0TRc2tc68kf8NHzORx38GbtbZvPEWDWrQ6N4,2234
|
|
2
2
|
modal/__main__.py,sha256=scYhGFqh8OJcVDo-VOxIT6CCwxOgzgflYWMnIZiMRqE,2871
|
3
3
|
modal/_clustered_functions.py,sha256=kTf-9YBXY88NutC1akI-gCbvf01RhMPCw-zoOI_YIUE,2700
|
4
4
|
modal/_clustered_functions.pyi,sha256=vllkegc99A0jrUOWa8mdlSbdp6uz36TsHhGxysAOpaQ,771
|
5
|
-
modal/_container_entrypoint.py,sha256
|
5
|
+
modal/_container_entrypoint.py,sha256=U1ZU-Sa0LC646YPh0AWSGVBHmzfTxYUiGu2viKzW-Zs,29643
|
6
6
|
modal/_ipython.py,sha256=TW1fkVOmZL3YYqdS2YlM1hqpf654Yf8ZyybHdBnlhSw,301
|
7
7
|
modal/_location.py,sha256=S3lSxIU3h9HkWpkJ3Pwo0pqjIOSB1fjeSgUsY3x7eec,1202
|
8
8
|
modal/_output.py,sha256=0fWX_KQwhER--U81ys16CL-pA5A-LN20C0EZjElKGJQ,25410
|
@@ -19,7 +19,7 @@ modal/app.py,sha256=vEE0cK5QPF6_cdW5AJvcuWxz5KmeprHwBEtlDkVRHgE,45582
|
|
19
19
|
modal/app.pyi,sha256=Gx7gxjfQ70sxhbwfpx1VjvzEON-ZEMTJ_Vy8qt0oQvo,25302
|
20
20
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
21
21
|
modal/client.py,sha256=JAnd4-GCN093BwkvOFAK5a6iy5ycxofjpUncMxlrIMw,15253
|
22
|
-
modal/client.pyi,sha256=
|
22
|
+
modal/client.pyi,sha256=TrQn13Pm1t7N2XRd5-jvxe5zzSL69I5EcZndQUyFmcM,7278
|
23
23
|
modal/cloud_bucket_mount.py,sha256=G7T7jWLD0QkmrfKR75mSTwdUZ2xNfj7pkVqb4ipmxmI,5735
|
24
24
|
modal/cloud_bucket_mount.pyi,sha256=CEi7vrH3kDUF4LAy4qP6tfImy2UJuFRcRbsgRNM1wo8,1403
|
25
25
|
modal/cls.py,sha256=3hjb0JcoPjxKZNeK22f5rR43bZRBjoRI7_EMZXY7YrE,31172
|
@@ -39,8 +39,8 @@ modal/file_pattern_matcher.py,sha256=LaI7Paxg0xR9D-D7Tgc60xR0w1KZee22LjGbFie1Vms
|
|
39
39
|
modal/functions.py,sha256=3uJPbrEAWhpFfLfUnoRjGmvEUC-_wVh-8yNJBx8eVeM,68249
|
40
40
|
modal/functions.pyi,sha256=LiSDgH-X7jcZ56pAoLMwo3x9Dzdp_3Sd7W5MVAJPoCg,25407
|
41
41
|
modal/gpu.py,sha256=MTxj6ql8EpgfBg8YmZ5a1cLznyuZFssX1qXbEX4LKVM,7503
|
42
|
-
modal/image.py,sha256=
|
43
|
-
modal/image.pyi,sha256=
|
42
|
+
modal/image.py,sha256=oKqqLhc3Ap2XMG5MKVlERKkMTwJPkNMNcSzxoZh4zuw,85259
|
43
|
+
modal/image.pyi,sha256=Pa1_LVr3FyNsnu_MhBO08fBgCeLazTEe25phYdu0bzE,25365
|
44
44
|
modal/io_streams.py,sha256=Xxc5grJiO94nBA48FFWH3S3g6SPR0xFVgZ_DZ1oFmvI,14428
|
45
45
|
modal/io_streams.pyi,sha256=bCCVSxkMcosYd8O3PQDDwJw7TQ8JEcnYonLJ5t27TQs,4804
|
46
46
|
modal/io_streams_helper.py,sha256=B5Ui56ph7LkRpZX0tAF80Q-gOMsvPPLx5bpIPX0kgDc,1772
|
@@ -78,7 +78,7 @@ modal/token_flow.pyi,sha256=gOYtYujrWt_JFZeiI8EmfahXPx5GCR5Na-VaPQcWgEY,1937
|
|
78
78
|
modal/volume.py,sha256=T-pLxCYqmqRO6OolpAXlPxomMu0RWjti2e4kUpaj2cQ,29229
|
79
79
|
modal/volume.pyi,sha256=eekb2dnAAwFK_NO9ciAOOTthl8NP1iAmMFrCGgjDA2k,11100
|
80
80
|
modal/_runtime/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
81
|
-
modal/_runtime/asgi.py,sha256=
|
81
|
+
modal/_runtime/asgi.py,sha256=c4hmaMW1pLo-cm7ouriJjieuFm4ZF6D2LMy0638sfOs,22139
|
82
82
|
modal/_runtime/container_io_manager.py,sha256=HgDLjE78yy1P7WZTmsEVDf89YnFFWG63Ddes8uYLVDY,43764
|
83
83
|
modal/_runtime/execution_context.py,sha256=E6ofm6j1POXGPxS841X3V7JU6NheVb8OkQc7JpLq4Kg,2712
|
84
84
|
modal/_runtime/telemetry.py,sha256=T1RoAGyjBDr1swiM6pPsGRSITm7LI5FDK18oNXxY08U,5163
|
@@ -166,10 +166,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
|
|
166
166
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
167
167
|
modal_version/__init__.py,sha256=BEBWj9tcbFUwzEjUrqly601rauw5cYsHdcmJHs3iu0s,470
|
168
168
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
169
|
-
modal_version/_version_generated.py,sha256=
|
170
|
-
modal-0.71.
|
171
|
-
modal-0.71.
|
172
|
-
modal-0.71.
|
173
|
-
modal-0.71.
|
174
|
-
modal-0.71.
|
175
|
-
modal-0.71.
|
169
|
+
modal_version/_version_generated.py,sha256=Wsequ_fpOFVKI_31Vx8o7dmn9xo9m9A_dDR6wZzyDIU,148
|
170
|
+
modal-0.71.8.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
171
|
+
modal-0.71.8.dist-info/METADATA,sha256=KRAySrnrs0NscfurQ57XIYvXEEJ9FKumg47sf3ufFi8,2328
|
172
|
+
modal-0.71.8.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
173
|
+
modal-0.71.8.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
174
|
+
modal-0.71.8.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
|
175
|
+
modal-0.71.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|