modal 0.71.7__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.
@@ -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.tasks.append(self.loop.create_task(coro))
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
- event_loop.create_task(finalized_function.lifespan_manager.background_task())
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
- startup: asyncio.Future
26
- shutdown: asyncio.Future
27
- queue: asyncio.Queue
28
- has_run_init: bool = False
29
- lifespan_supported: bool = False
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.has_run_init:
41
- self.queue = asyncio.Queue()
42
- self.startup = asyncio.Future()
43
- self.shutdown = asyncio.Future()
44
- self.has_run_init = True
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.lifespan_supported = True
51
- return await self.queue.get()
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.startup.set_result(None)
55
+ self._startup.set_result(None)
56
56
  elif message["type"] == "lifespan.startup.failed":
57
- self.startup.set_exception(ExecutionError("ASGI lifespan startup failed"))
57
+ self._startup.set_exception(ExecutionError("ASGI lifespan startup failed"))
58
58
  elif message["type"] == "lifespan.shutdown.complete":
59
- self.shutdown.set_result(None)
59
+ self._shutdown.set_result(None)
60
60
  elif message["type"] == "lifespan.shutdown.failed":
61
- self.shutdown.set_exception(ExecutionError("ASGI lifespan shutdown failed"))
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.lifespan_supported:
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.startup.set_result(None)
71
- self.shutdown.set_result(None)
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.startup.done():
76
- self.startup.set_exception(ExecutionError("ASGI lifespan task exited startup"))
77
- if not self.shutdown.done():
78
- self.shutdown.set_exception(ExecutionError("ASGI lifespan task exited shutdown"))
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.startup.set_result(None)
82
- self.shutdown.set_result(None)
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.queue.put_nowait({"type": "lifespan.startup"})
87
- await self.startup
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.queue.put_nowait({"type": "lifespan.shutdown"})
92
- await self.shutdown
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.7"
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.7"
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/functions.pyi CHANGED
@@ -462,11 +462,11 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
462
462
 
463
463
  _call_generator_nowait: ___call_generator_nowait_spec
464
464
 
465
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
465
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
466
466
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
467
467
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
468
468
 
469
- remote: __remote_spec[ReturnType, P]
469
+ remote: __remote_spec[P, ReturnType]
470
470
 
471
471
  class __remote_gen_spec(typing_extensions.Protocol):
472
472
  def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
@@ -479,17 +479,17 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
479
479
  def _get_obj(self) -> typing.Optional[modal.cls.Obj]: ...
480
480
  def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
481
481
 
482
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
482
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
483
483
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
484
484
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
485
485
 
486
- _experimental_spawn: ___experimental_spawn_spec[ReturnType, P]
486
+ _experimental_spawn: ___experimental_spawn_spec[P, ReturnType]
487
487
 
488
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
488
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
489
489
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
490
490
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
491
491
 
492
- spawn: __spawn_spec[ReturnType, P]
492
+ spawn: __spawn_spec[P, ReturnType]
493
493
 
494
494
  def get_raw_f(self) -> typing.Callable[..., typing.Any]: ...
495
495
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.71.7
3
+ Version: 0.71.8
4
4
  Summary: Python client library for Modal
5
5
  Author: Modal Labs
6
6
  Author-email: support@modal.com
@@ -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=-zUa567FgOKmF0TtFWQ6DgehUD2CMfABDBQ8oLSpjyc,29171
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=BemCZNYj-Ol_UF2iZJVU15N3VIKhxO2CPtgxitRJWw4,7278
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
@@ -37,7 +37,7 @@ modal/file_io.py,sha256=ZR8VBCDsDt5uB9TNN9XbEh7sniJzM_5YL47m8WP0m5c,19617
37
37
  modal/file_io.pyi,sha256=79Fg75BjmMEeCX0Lx-Z8C4XSNPCotwNdK6ZLIDFm2f4,9770
38
38
  modal/file_pattern_matcher.py,sha256=LaI7Paxg0xR9D-D7Tgc60xR0w1KZee22LjGbFie1Vms,5571
39
39
  modal/functions.py,sha256=3uJPbrEAWhpFfLfUnoRjGmvEUC-_wVh-8yNJBx8eVeM,68249
40
- modal/functions.pyi,sha256=3ESJ61f8oEDycDmrpnuNB2vjFKuLBG_aqyliXPTdY7M,25407
40
+ modal/functions.pyi,sha256=LiSDgH-X7jcZ56pAoLMwo3x9Dzdp_3Sd7W5MVAJPoCg,25407
41
41
  modal/gpu.py,sha256=MTxj6ql8EpgfBg8YmZ5a1cLznyuZFssX1qXbEX4LKVM,7503
42
42
  modal/image.py,sha256=oKqqLhc3Ap2XMG5MKVlERKkMTwJPkNMNcSzxoZh4zuw,85259
43
43
  modal/image.pyi,sha256=Pa1_LVr3FyNsnu_MhBO08fBgCeLazTEe25phYdu0bzE,25365
@@ -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=Mjs859pSgOmtZL-YmEsSKN557v1A2Ax_5-ERgPfj55E,21920
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=vCubBvI9-d34zmRrXH9kK-02zZQnqA3cVMw5ONOUPds,148
170
- modal-0.71.7.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
171
- modal-0.71.7.dist-info/METADATA,sha256=a3pklm2HmEel6rdqBnRdP8yR4-pVDyljfp9i_JULRvU,2328
172
- modal-0.71.7.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
173
- modal-0.71.7.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
174
- modal-0.71.7.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
175
- modal-0.71.7.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 7 # git: 947d8d6
4
+ build_number = 8 # git: e068215
File without changes