modal 0.70.2__py3-none-any.whl → 0.71.5__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/io_streams.py CHANGED
@@ -14,7 +14,8 @@ from typing import (
14
14
  from grpclib import Status
15
15
  from grpclib.exceptions import GRPCError, StreamTerminatedError
16
16
 
17
- from modal.exception import ClientClosed, InvalidError
17
+ from modal.exception import InvalidError
18
+ from modal.io_streams_helper import consume_stream_with_retries
18
19
  from modal_proto import api_pb2
19
20
 
20
21
  from ._utils.async_utils import synchronize_api
@@ -176,34 +177,21 @@ class _StreamReader(Generic[T]):
176
177
  if self._stream_type == StreamType.DEVNULL:
177
178
  return
178
179
 
179
- completed = False
180
- retries_remaining = 10
181
- while not completed:
182
- try:
183
- iterator = _container_process_logs_iterator(self._object_id, self._file_descriptor, self._client)
180
+ def item_handler(item: Optional[bytes]):
181
+ if self._stream_type == StreamType.STDOUT and item is not None:
182
+ print(item.decode("utf-8"), end="")
183
+ elif self._stream_type == StreamType.PIPE:
184
+ self._container_process_buffer.append(item)
184
185
 
185
- async for message in iterator:
186
- if self._stream_type == StreamType.STDOUT and message:
187
- print(message.decode("utf-8"), end="")
188
- elif self._stream_type == StreamType.PIPE:
189
- self._container_process_buffer.append(message)
190
- if message is None:
191
- completed = True
192
- break
186
+ def completion_check(item: Optional[bytes]):
187
+ return item is None
193
188
 
194
- except (GRPCError, StreamTerminatedError, ClientClosed) as exc:
195
- if retries_remaining > 0:
196
- retries_remaining -= 1
197
- if isinstance(exc, GRPCError):
198
- if exc.status in RETRYABLE_GRPC_STATUS_CODES:
199
- await asyncio.sleep(1.0)
200
- continue
201
- elif isinstance(exc, StreamTerminatedError):
202
- continue
203
- elif isinstance(exc, ClientClosed):
204
- # If the client was closed, the user has triggered a cleanup.
205
- break
206
- raise exc
189
+ iterator = _container_process_logs_iterator(self._object_id, self._file_descriptor, self._client)
190
+ await consume_stream_with_retries(
191
+ iterator,
192
+ item_handler,
193
+ completion_check,
194
+ )
207
195
 
208
196
  async def _stream_container_process(self) -> AsyncGenerator[tuple[Optional[bytes], str], None]:
209
197
  """Streams the container process buffer to the reader."""
@@ -0,0 +1,53 @@
1
+ # Copyright Modal Labs 2024
2
+ import asyncio
3
+ from typing import AsyncIterator, Callable, TypeVar
4
+
5
+ from grpclib.exceptions import GRPCError, StreamTerminatedError
6
+
7
+ from modal.exception import ClientClosed
8
+
9
+ from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES
10
+
11
+ T = TypeVar("T")
12
+
13
+
14
+ async def consume_stream_with_retries(
15
+ stream: AsyncIterator[T],
16
+ item_handler: Callable[[T], None],
17
+ completion_check: Callable[[T], bool],
18
+ max_retries: int = 10,
19
+ retry_delay: float = 1.0,
20
+ ) -> None:
21
+ """mdmd:hidden
22
+ Helper function to consume a stream with retry logic for transient errors.
23
+
24
+ Args:
25
+ stream_generator: Function that returns an AsyncIterator to consume
26
+ item_handler: Callback function to handle each item from the stream
27
+ completion_check: Callback function to check if the stream is complete
28
+ max_retries: Maximum number of retry attempts
29
+ retry_delay: Delay in seconds between retries
30
+ """
31
+ completed = False
32
+ retries_remaining = max_retries
33
+
34
+ while not completed:
35
+ try:
36
+ async for item in stream:
37
+ item_handler(item)
38
+ if completion_check(item):
39
+ completed = True
40
+ break
41
+
42
+ except (GRPCError, StreamTerminatedError, ClientClosed) as exc:
43
+ if retries_remaining > 0:
44
+ retries_remaining -= 1
45
+ if isinstance(exc, GRPCError):
46
+ if exc.status in RETRYABLE_GRPC_STATUS_CODES:
47
+ await asyncio.sleep(retry_delay)
48
+ continue
49
+ elif isinstance(exc, StreamTerminatedError):
50
+ continue
51
+ elif isinstance(exc, ClientClosed):
52
+ break
53
+ raise
modal/runner.py CHANGED
@@ -6,7 +6,7 @@ import time
6
6
  import typing
7
7
  from collections.abc import AsyncGenerator
8
8
  from multiprocessing.synchronize import Event
9
- from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar
9
+ from typing import TYPE_CHECKING, Any, Optional, TypeVar
10
10
 
11
11
  from grpclib import GRPCError, Status
12
12
  from synchronicity.async_wrap import asynccontextmanager
@@ -64,7 +64,6 @@ async def _init_local_app_existing(client: _Client, existing_app_id: str, enviro
64
64
  return running_app_from_layout(
65
65
  existing_app_id,
66
66
  obj_resp.app_layout,
67
- client,
68
67
  app_page_url=app_page_url,
69
68
  )
70
69
 
@@ -89,10 +88,8 @@ async def _init_local_app_new(
89
88
  logger.debug(f"Created new app with id {app_resp.app_id}")
90
89
  return RunningApp(
91
90
  app_resp.app_id,
92
- client=client,
93
91
  app_page_url=app_resp.app_page_url,
94
92
  app_logs_url=app_resp.app_logs_url,
95
- environment_name=environment_name,
96
93
  interactive=interactive,
97
94
  )
98
95
 
@@ -124,10 +121,12 @@ async def _init_local_app_from_name(
124
121
  async def _create_all_objects(
125
122
  client: _Client,
126
123
  running_app: RunningApp,
127
- indexed_objects: dict[str, _Object],
124
+ functions: dict[str, _Function],
125
+ classes: dict[str, _Cls],
128
126
  environment_name: str,
129
127
  ) -> None:
130
128
  """Create objects that have been defined but not created on the server."""
129
+ indexed_objects: dict[str, _Object] = {**functions, **classes}
131
130
  resolver = Resolver(
132
131
  client,
133
132
  environment_name=environment_name,
@@ -135,8 +134,9 @@ async def _create_all_objects(
135
134
  )
136
135
  with resolver.display():
137
136
  # Get current objects, and reset all objects
138
- tag_to_object_id = running_app.tag_to_object_id
139
- running_app.tag_to_object_id = {}
137
+ tag_to_object_id = {**running_app.function_ids, **running_app.class_ids}
138
+ running_app.function_ids = {}
139
+ running_app.class_ids = {}
140
140
 
141
141
  # Assign all objects
142
142
  for tag, obj in indexed_objects.items():
@@ -163,7 +163,12 @@ async def _create_all_objects(
163
163
  async def _load(tag, obj):
164
164
  existing_object_id = tag_to_object_id.get(tag)
165
165
  await resolver.load(obj, existing_object_id)
166
- running_app.tag_to_object_id[tag] = obj.object_id
166
+ if _Function._is_id_type(obj.object_id):
167
+ running_app.function_ids[tag] = obj.object_id
168
+ elif _Cls._is_id_type(obj.object_id):
169
+ running_app.class_ids[tag] = obj.object_id
170
+ else:
171
+ raise RuntimeError(f"Unexpected object {obj.object_id}")
167
172
 
168
173
  await TaskContext.gather(*(_load(tag, obj) for tag, obj in indexed_objects.items()))
169
174
 
@@ -172,30 +177,22 @@ async def _publish_app(
172
177
  client: _Client,
173
178
  running_app: RunningApp,
174
179
  app_state: int, # api_pb2.AppState.value
175
- indexed_objects: dict[str, _Object],
180
+ functions: dict[str, _Function],
181
+ classes: dict[str, _Cls],
176
182
  name: str = "", # Only relevant for deployments
177
183
  tag: str = "", # Only relevant for deployments
178
184
  ) -> tuple[str, list[api_pb2.Warning]]:
179
185
  """Wrapper for AppPublish RPC."""
180
186
 
181
- # Could simplify this function some changing the internal representation to use
182
- # function_ids / class_ids rather than the current tag_to_object_id (i.e. "indexed_objects")
183
- def filter_values(full_dict: dict[str, V], condition: Callable[[V], bool]) -> dict[str, V]:
184
- return {k: v for k, v in full_dict.items() if condition(v)}
185
-
186
- function_ids = filter_values(running_app.tag_to_object_id, _Function._is_id_type)
187
- class_ids = filter_values(running_app.tag_to_object_id, _Cls._is_id_type)
188
-
189
- function_objs = filter_values(indexed_objects, lambda v: v.object_id in function_ids.values())
190
- definition_ids = {obj.object_id: obj._get_metadata().definition_id for obj in function_objs.values()} # type: ignore
187
+ definition_ids = {obj.object_id: obj._get_metadata().definition_id for obj in functions.values()} # type: ignore
191
188
 
192
189
  request = api_pb2.AppPublishRequest(
193
190
  app_id=running_app.app_id,
194
191
  name=name,
195
192
  deployment_tag=tag,
196
193
  app_state=app_state, # type: ignore : should be a api_pb2.AppState.value
197
- function_ids=function_ids,
198
- class_ids=class_ids,
194
+ function_ids=running_app.function_ids,
195
+ class_ids=running_app.class_ids,
199
196
  definition_ids=definition_ids,
200
197
  )
201
198
  try:
@@ -329,13 +326,11 @@ async def _run_app(
329
326
  )
330
327
 
331
328
  try:
332
- indexed_objects = dict(**app._functions, **app._classes) # TODO(erikbern): remove
333
-
334
329
  # Create all members
335
- await _create_all_objects(client, running_app, indexed_objects, environment_name)
330
+ await _create_all_objects(client, running_app, app._functions, app._classes, environment_name)
336
331
 
337
332
  # Publish the app
338
- await _publish_app(client, running_app, app_state, indexed_objects)
333
+ await _publish_app(client, running_app, app_state, app._functions, app._classes)
339
334
  except asyncio.CancelledError as e:
340
335
  # this typically happens on sigint/ctrl-C during setup (the KeyboardInterrupt happens in the main thread)
341
336
  if output_mgr := _get_output_manager():
@@ -428,18 +423,17 @@ async def _serve_update(
428
423
  try:
429
424
  running_app: RunningApp = await _init_local_app_existing(client, existing_app_id, environment_name)
430
425
 
431
- indexed_objects = dict(**app._functions, **app._classes) # TODO(erikbern): remove
432
-
433
426
  # Create objects
434
427
  await _create_all_objects(
435
428
  client,
436
429
  running_app,
437
- indexed_objects,
430
+ app._functions,
431
+ app._classes,
438
432
  environment_name,
439
433
  )
440
434
 
441
435
  # Publish the updated app
442
- await _publish_app(client, running_app, api_pb2.APP_STATE_UNSPECIFIED, indexed_objects)
436
+ await _publish_app(client, running_app, api_pb2.APP_STATE_UNSPECIFIED, app._functions, app._classes)
443
437
 
444
438
  # Communicate to the parent process
445
439
  is_ready.set()
@@ -527,19 +521,18 @@ async def _deploy_app(
527
521
 
528
522
  tc.infinite_loop(heartbeat, sleep=HEARTBEAT_INTERVAL)
529
523
 
530
- indexed_objects = dict(**app._functions, **app._classes) # TODO(erikbern): remove
531
-
532
524
  try:
533
525
  # Create all members
534
526
  await _create_all_objects(
535
527
  client,
536
528
  running_app,
537
- indexed_objects,
529
+ app._functions,
530
+ app._classes,
538
531
  environment_name=environment_name,
539
532
  )
540
533
 
541
534
  app_url, warnings = await _publish_app(
542
- client, running_app, api_pb2.APP_STATE_DEPLOYED, indexed_objects, name, tag
535
+ client, running_app, api_pb2.APP_STATE_DEPLOYED, app._functions, app._classes, name, tag
543
536
  )
544
537
  except Exception as e:
545
538
  # Note that AppClientDisconnect only stops the app if it's still initializing, and is a no-op otherwise.
modal/runner.pyi CHANGED
@@ -1,5 +1,6 @@
1
1
  import modal.client
2
- import modal.object
2
+ import modal.cls
3
+ import modal.functions
3
4
  import modal.running_app
4
5
  import modal_proto.api_pb2
5
6
  import multiprocessing.synchronize
@@ -28,14 +29,16 @@ async def _init_local_app_from_name(
28
29
  async def _create_all_objects(
29
30
  client: modal.client._Client,
30
31
  running_app: modal.running_app.RunningApp,
31
- indexed_objects: dict[str, modal.object._Object],
32
+ functions: dict[str, modal.functions._Function],
33
+ classes: dict[str, modal.cls._Cls],
32
34
  environment_name: str,
33
35
  ) -> None: ...
34
36
  async def _publish_app(
35
37
  client: modal.client._Client,
36
38
  running_app: modal.running_app.RunningApp,
37
39
  app_state: int,
38
- indexed_objects: dict[str, modal.object._Object],
40
+ functions: dict[str, modal.functions._Function],
41
+ classes: dict[str, modal.cls._Cls],
39
42
  name: str = "",
40
43
  tag: str = "",
41
44
  ) -> tuple[str, list[modal_proto.api_pb2.Warning]]: ...
modal/running_app.py CHANGED
@@ -7,17 +7,14 @@ from google.protobuf.message import Message
7
7
  from modal._utils.grpc_utils import get_proto_oneof
8
8
  from modal_proto import api_pb2
9
9
 
10
- from .client import _Client
11
-
12
10
 
13
11
  @dataclass
14
12
  class RunningApp:
15
13
  app_id: str
16
- client: _Client
17
- environment_name: Optional[str] = None
18
14
  app_page_url: Optional[str] = None
19
15
  app_logs_url: Optional[str] = None
20
- tag_to_object_id: dict[str, str] = field(default_factory=dict)
16
+ function_ids: dict[str, str] = field(default_factory=dict)
17
+ class_ids: dict[str, str] = field(default_factory=dict)
21
18
  object_handle_metadata: dict[str, Optional[Message]] = field(default_factory=dict)
22
19
  interactive: bool = False
23
20
 
@@ -25,11 +22,8 @@ class RunningApp:
25
22
  def running_app_from_layout(
26
23
  app_id: str,
27
24
  app_layout: api_pb2.AppLayout,
28
- client: _Client,
29
- environment_name: Optional[str] = None,
30
25
  app_page_url: Optional[str] = None,
31
26
  ) -> RunningApp:
32
- tag_to_object_id = dict(**app_layout.function_ids, **app_layout.class_ids)
33
27
  object_handle_metadata = {}
34
28
  for obj in app_layout.objects:
35
29
  handle_metadata: Optional[Message] = get_proto_oneof(obj, "handle_metadata_oneof")
@@ -37,9 +31,8 @@ def running_app_from_layout(
37
31
 
38
32
  return RunningApp(
39
33
  app_id,
40
- client,
41
- environment_name=environment_name,
42
- tag_to_object_id=tag_to_object_id,
34
+ function_ids=dict(app_layout.function_ids),
35
+ class_ids=dict(app_layout.class_ids),
43
36
  object_handle_metadata=object_handle_metadata,
44
37
  app_page_url=app_page_url,
45
38
  )
modal/sandbox.py CHANGED
@@ -2,7 +2,7 @@
2
2
  import asyncio
3
3
  import os
4
4
  from collections.abc import AsyncGenerator, Sequence
5
- from typing import TYPE_CHECKING, Literal, Optional, Union, overload
5
+ from typing import TYPE_CHECKING, AsyncIterator, Literal, Optional, Union, overload
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  import _typeshed
@@ -26,7 +26,7 @@ from .client import _Client
26
26
  from .config import config
27
27
  from .container_process import _ContainerProcess
28
28
  from .exception import ExecutionError, InvalidError, SandboxTerminatedError, SandboxTimeoutError
29
- from .file_io import _FileIO
29
+ from .file_io import FileWatchEvent, FileWatchEventType, _FileIO
30
30
  from .gpu import GPU_T
31
31
  from .image import _Image
32
32
  from .io_streams import StreamReader, StreamWriter, _StreamReader, _StreamWriter
@@ -276,9 +276,9 @@ class _Sandbox(_Object, type_prefix="sb"):
276
276
 
277
277
  app_id = app.app_id
278
278
  app_client = app._client
279
- elif _App._container_app is not None:
280
- app_id = _App._container_app.app_id
281
- app_client = _App._container_app.client
279
+ elif (container_app := _App._get_container_app()) is not None:
280
+ app_id = container_app.app_id
281
+ app_client = container_app._client
282
282
  else:
283
283
  arglist = ", ".join(repr(s) for s in entrypoint_args)
284
284
  deprecation_error(
@@ -320,7 +320,9 @@ class _Sandbox(_Object, type_prefix="sb"):
320
320
  resp = await retry_transient_errors(client.stub.SandboxWait, req)
321
321
 
322
322
  obj = _Sandbox._new_hydrated(sandbox_id, client, None)
323
- obj._result = resp.result
323
+
324
+ if resp.result.status:
325
+ obj._result = resp.result
324
326
 
325
327
  return obj
326
328
 
@@ -579,6 +581,17 @@ class _Sandbox(_Object, type_prefix="sb"):
579
581
  task_id = await self._get_task_id()
580
582
  return await _FileIO.rm(path, self._client, task_id, recursive)
581
583
 
584
+ async def watch(
585
+ self,
586
+ path: str,
587
+ filter: Optional[list[FileWatchEventType]] = None,
588
+ recursive: Optional[bool] = None,
589
+ timeout: Optional[int] = None,
590
+ ) -> AsyncIterator[FileWatchEvent]:
591
+ task_id = await self._get_task_id()
592
+ async for event in _FileIO.watch(path, self._client, task_id, filter, recursive, timeout):
593
+ yield event
594
+
582
595
  @property
583
596
  def stdout(self) -> _StreamReader[str]:
584
597
  """
modal/sandbox.pyi CHANGED
@@ -131,6 +131,13 @@ class _Sandbox(modal.object._Object):
131
131
  async def ls(self, path: str) -> list[str]: ...
132
132
  async def mkdir(self, path: str, parents: bool = False) -> None: ...
133
133
  async def rm(self, path: str, recursive: bool = False) -> None: ...
134
+ def watch(
135
+ self,
136
+ path: str,
137
+ filter: typing.Optional[list[modal.file_io.FileWatchEventType]] = None,
138
+ recursive: typing.Optional[bool] = None,
139
+ timeout: typing.Optional[int] = None,
140
+ ) -> typing.AsyncIterator[modal.file_io.FileWatchEvent]: ...
134
141
  @property
135
142
  def stdout(self) -> modal.io_streams._StreamReader[str]: ...
136
143
  @property
@@ -388,6 +395,24 @@ class Sandbox(modal.object.Object):
388
395
 
389
396
  rm: __rm_spec
390
397
 
398
+ class __watch_spec(typing_extensions.Protocol):
399
+ def __call__(
400
+ self,
401
+ path: str,
402
+ filter: typing.Optional[list[modal.file_io.FileWatchEventType]] = None,
403
+ recursive: typing.Optional[bool] = None,
404
+ timeout: typing.Optional[int] = None,
405
+ ) -> typing.Iterator[modal.file_io.FileWatchEvent]: ...
406
+ def aio(
407
+ self,
408
+ path: str,
409
+ filter: typing.Optional[list[modal.file_io.FileWatchEventType]] = None,
410
+ recursive: typing.Optional[bool] = None,
411
+ timeout: typing.Optional[int] = None,
412
+ ) -> typing.AsyncIterator[modal.file_io.FileWatchEvent]: ...
413
+
414
+ watch: __watch_spec
415
+
391
416
  @property
392
417
  def stdout(self) -> modal.io_streams.StreamReader[str]: ...
393
418
  @property
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.70.2
3
+ Version: 0.71.5
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=7EAMe-85a-uWDaRAjRy9m4nnCAmjRJnh9x7FgW_QjLU,28935
5
+ modal/_container_entrypoint.py,sha256=-zUa567FgOKmF0TtFWQ6DgehUD2CMfABDBQ8oLSpjyc,29171
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
@@ -15,11 +15,11 @@ modal/_traceback.py,sha256=IZQzB3fVlUfMHOSyKUgw0H6qv4yHnpyq-XVCNZKfUdA,5023
15
15
  modal/_tunnel.py,sha256=o-jJhS4vQ6-XswDhHcJWGMZZmD03SC0e9i8fEu1JTjo,6310
16
16
  modal/_tunnel.pyi,sha256=JmmDYAy9F1FpgJ_hWx0xkom2nTOFQjn4mTPYlU3PFo4,1245
17
17
  modal/_watcher.py,sha256=K6LYnlmSGQB4tWWI9JADv-tvSvQ1j522FwT71B51CX8,3584
18
- modal/app.py,sha256=JWefPs4yB70BKQwSZejB_4_muhxn63cC9UmnNvpQ9XY,45526
19
- modal/app.pyi,sha256=FYPCEJNhof4YF6HIuNP_2yG6s2PgZnKW9tO1hFE6sfA,25194
18
+ modal/app.py,sha256=vEE0cK5QPF6_cdW5AJvcuWxz5KmeprHwBEtlDkVRHgE,45582
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=WXHQoJWWNrFs3BR6wW3LRHc5XaH4_KTOFt0eo7HGJGs,7278
22
+ modal/client.pyi,sha256=pjZG1Z_8vLGkYtlrkEBAkl9tV9wqACVl0a3qWxHPacQ,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
@@ -31,18 +31,19 @@ modal/dict.py,sha256=ei9jsA5iTj4UFGPJxTAed6vjd49W47ezDtj0koUmVts,12497
31
31
  modal/dict.pyi,sha256=VmbzxltA2vFlIHZCxpNGtd-ieXwcUwdw3iyy3WCweqU,7115
32
32
  modal/environments.py,sha256=wbv9ttFCbzATGfwcmvYiG608PfHovx0AQmawsg-jmic,6660
33
33
  modal/environments.pyi,sha256=rF7oaaELoSNuoD6qImGnIbuGPtgWwR5SlcExyYJ61hQ,3515
34
- modal/exception.py,sha256=GEV6xMnVnkle0gsFZVLB4B7cUMyw8HzVDvAvPr34ZV4,5185
35
- modal/experimental.py,sha256=jFuNbwrNHos47viMB9q-cHJSvf2RDxDdoEcss9plaZE,2302
36
- modal/file_io.py,sha256=QXACorWL9NAfvRcnBcP34e5H7u9xf7g5zt2BHj9quC8,16713
37
- modal/file_io.pyi,sha256=GMhCCRyMftXYI3HqI9EdGPOx70CbCNi-VC5Sfy5TYnc,7631
34
+ modal/exception.py,sha256=4JyO-SACaLNDe2QC48EjsK8GMkZ8AgEurZ8j1YdRu8E,5263
35
+ modal/experimental.py,sha256=npfKbyMpI41uZZs9HW_QiB3E4ykWfDXZbACXXbw6qeA,2385
36
+ modal/file_io.py,sha256=ZR8VBCDsDt5uB9TNN9XbEh7sniJzM_5YL47m8WP0m5c,19617
37
+ modal/file_io.pyi,sha256=79Fg75BjmMEeCX0Lx-Z8C4XSNPCotwNdK6ZLIDFm2f4,9770
38
38
  modal/file_pattern_matcher.py,sha256=LaI7Paxg0xR9D-D7Tgc60xR0w1KZee22LjGbFie1Vms,5571
39
- modal/functions.py,sha256=aXXXr3rk7BCeh5OWMvxGksGm8FQoYCyrBDGV74FPoPE,67827
40
- modal/functions.pyi,sha256=oMmcExtQxHwPej06jQ3uBe1tUlSR3VbAx7u3Vm-Ohhg,25317
39
+ modal/functions.py,sha256=3uJPbrEAWhpFfLfUnoRjGmvEUC-_wVh-8yNJBx8eVeM,68249
40
+ modal/functions.pyi,sha256=LiSDgH-X7jcZ56pAoLMwo3x9Dzdp_3Sd7W5MVAJPoCg,25407
41
41
  modal/gpu.py,sha256=MTxj6ql8EpgfBg8YmZ5a1cLznyuZFssX1qXbEX4LKVM,7503
42
42
  modal/image.py,sha256=Krvcsclomp9YsqSNwFj2FoAyg10OvU47RDnsNCwjGbQ,84550
43
43
  modal/image.pyi,sha256=1fgGvsL5Rb0Sa-_2OCgIyJ_QgHcL0_9MWD_oY7cyFFM,24937
44
- modal/io_streams.py,sha256=QkQiizKRzd5bnbKQsap31LJgBYlAnj4-XkV_50xPYX0,15079
44
+ modal/io_streams.py,sha256=Xxc5grJiO94nBA48FFWH3S3g6SPR0xFVgZ_DZ1oFmvI,14428
45
45
  modal/io_streams.pyi,sha256=bCCVSxkMcosYd8O3PQDDwJw7TQ8JEcnYonLJ5t27TQs,4804
46
+ modal/io_streams_helper.py,sha256=B5Ui56ph7LkRpZX0tAF80Q-gOMsvPPLx5bpIPX0kgDc,1772
46
47
  modal/mount.py,sha256=wOr-2vmKImsE3lHBII8hL2gYy5ng46R58QwId4JultQ,29313
47
48
  modal/mount.pyi,sha256=FiNV1wIKFvd0ZMZ0tm1mz6ZSA5Hjsge-kFSA5tPWfcI,10503
48
49
  modal/network_file_system.py,sha256=INj1TfN_Fsmabmlte7anvey1epodjbMmjBW_TIJSST4,14406
@@ -60,11 +61,11 @@ modal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
61
  modal/queue.py,sha256=zMUQtdAyqZzBg-2iAo3c3G54HLP7TEWfVhiQXLjewb4,18556
61
62
  modal/queue.pyi,sha256=gGV97pWelSSYqMV9Bl4ys3mSP7q82fS71oqSWeAwyDE,9818
62
63
  modal/retries.py,sha256=HKR2Q9aNPWkMjQ5nwobqYTuZaSuw0a8lI2zrtY5IW98,5230
63
- modal/runner.py,sha256=1nPBsIfef2sOr2ebQ348EmDemvYFDhp1-_Gr3BKsjdM,24542
64
- modal/runner.pyi,sha256=BvMS1ZVzWSn8B8q0KnIZOJKPkN5L-i5b-USbV6SWWHQ,5177
65
- modal/running_app.py,sha256=FSr0XoL4mPLPCBMj2TozWuEvcvApdY_t68nUowwf8x4,1372
66
- modal/sandbox.py,sha256=c-Qli3QJPN7bBQzsTk4iS51zurNlq--InZ2eRR-B6No,28106
67
- modal/sandbox.pyi,sha256=k8_vHjN3oigxSCF13Cm2HfcSHuliGuSb8ryd3CGqwoA,19815
64
+ modal/runner.py,sha256=mhqgRdjD5cUDpBQIokiX7OCfVblpGV6aWmZ-WvWJgGg,24114
65
+ modal/runner.pyi,sha256=YmP4EOCNjjkwSIPi2Gl6hF_ji_ytkxz9dw3iB9KXaOI,5275
66
+ modal/running_app.py,sha256=v61mapYNV1-O-Uaho5EfJlryMLvIT9We0amUOSvSGx8,1188
67
+ modal/sandbox.py,sha256=H63K3MTcgdb9KkKc79sMKe6UzFMGBNI7HB2TUwBbE_U,28609
68
+ modal/sandbox.pyi,sha256=lceWDeXqzdeRc1iIuM5YmpoZlBJcVBpQO1Jc3AT1AQI,20809
68
69
  modal/schedule.py,sha256=0ZFpKs1bOxeo5n3HZjoL7OE2ktsb-_oGtq-WJEPO4tY,2615
69
70
  modal/scheduler_placement.py,sha256=BAREdOY5HzHpzSBqt6jDVR6YC_jYfHMVqOzkyqQfngU,1235
70
71
  modal/secret.py,sha256=lebVTZi4fC9PXQpLVmsvgQLzy-2Kzxv1WBD0Jr2wsxQ,10117
@@ -78,7 +79,7 @@ modal/volume.py,sha256=T-pLxCYqmqRO6OolpAXlPxomMu0RWjti2e4kUpaj2cQ,29229
78
79
  modal/volume.pyi,sha256=eekb2dnAAwFK_NO9ciAOOTthl8NP1iAmMFrCGgjDA2k,11100
79
80
  modal/_runtime/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
80
81
  modal/_runtime/asgi.py,sha256=Mjs859pSgOmtZL-YmEsSKN557v1A2Ax_5-ERgPfj55E,21920
81
- modal/_runtime/container_io_manager.py,sha256=8k84QF8dNPY344tm0jz6DySCnIwIlUH66KKKh95FjWY,43878
82
+ modal/_runtime/container_io_manager.py,sha256=HgDLjE78yy1P7WZTmsEVDf89YnFFWG63Ddes8uYLVDY,43764
82
83
  modal/_runtime/execution_context.py,sha256=E6ofm6j1POXGPxS841X3V7JU6NheVb8OkQc7JpLq4Kg,2712
83
84
  modal/_runtime/telemetry.py,sha256=T1RoAGyjBDr1swiM6pPsGRSITm7LI5FDK18oNXxY08U,5163
84
85
  modal/_runtime/user_code_imports.py,sha256=n4CQOojzSdf0jwSKSy6MEnVX3IWl3t3Dq54-x9VS2Ds,14663
@@ -89,7 +90,7 @@ modal/_utils/blob_utils.py,sha256=N66LtZI8PpCkZ7maA7GLW5CAmYUoNJdG-GjaAUR4_NQ,14
89
90
  modal/_utils/bytes_io_segment_payload.py,sha256=uunxVJS4PE1LojF_UpURMzVK9GuvmYWRqQo_bxEj5TU,3385
90
91
  modal/_utils/deprecation.py,sha256=dycySRBxyZf3ITzEqPNM6MxXTk9-0VVLA8oCPQ5j_Os,3426
91
92
  modal/_utils/docker_utils.py,sha256=FLz1q0YicL6i_Iq-4inkgDVFfEINVG6YPT2s_P6ly0o,2264
92
- modal/_utils/function_utils.py,sha256=4LYFbNY5aHc96QitwP4Ty-dBl45SD1HjfZrvBFUF-ko,25343
93
+ modal/_utils/function_utils.py,sha256=q68HhFH16MwhHRnGD8jvIgqDjduRQVp3a_qMWXPyrgU,25518
93
94
  modal/_utils/grpc_testing.py,sha256=H1zHqthv19eGPJz2HKXDyWXWGSqO4BRsxah3L5Xaa8A,8619
94
95
  modal/_utils/grpc_utils.py,sha256=PPB5ay-vXencXNIWPVw5modr3EH7gfq2QPcO5YJ1lMU,7737
95
96
  modal/_utils/hash_utils.py,sha256=zg3J6OGxTFGSFri1qQ12giDz90lWk8bzaxCTUCRtiX4,3034
@@ -119,7 +120,7 @@ modal/cli/launch.py,sha256=44oOlGB0KYDBMfuIlkhW2uzjkWbHhDRR64UOEnGwsJ4,2989
119
120
  modal/cli/network_file_system.py,sha256=o6VLTgN4xn5XUiNPBfxYec-5uWCgYrDmfFFLM1ZW_eE,8180
120
121
  modal/cli/profile.py,sha256=rLXfjJObfPNjaZvNfHGIKqs7y9bGYyGe-K7V0w-Ni0M,3110
121
122
  modal/cli/queues.py,sha256=MIh2OsliNE2QeL1erubfsRsNuG4fxqcqWA2vgIfQ4Mg,4494
122
- modal/cli/run.py,sha256=9SvPmBzB8ZZRaqUJc9LmL2tfT5OMiR4Ow0dLANVwuB8,17870
123
+ modal/cli/run.py,sha256=QMs3BVSkq8jve76BqgstGt4C6jilbDD9gpCCRPuHUy0,17879
123
124
  modal/cli/secret.py,sha256=uQpwYrMY98iMCWeZOQTcktOYhPTZ8IHnyealDc2CZqo,4206
124
125
  modal/cli/token.py,sha256=mxSgOWakXG6N71hQb1ko61XAR9ZGkTMZD-Txn7gmTac,1924
125
126
  modal/cli/utils.py,sha256=hZmjyzcPjDnQSkLvycZD2LhGdcsfdZshs_rOU78EpvI,3717
@@ -148,13 +149,13 @@ modal_global_objects/mounts/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0
148
149
  modal_global_objects/mounts/modal_client_package.py,sha256=W0E_yShsRojPzWm6LtIQqNVolapdnrZkm2hVEQuZK_4,767
149
150
  modal_global_objects/mounts/python_standalone.py,sha256=SL_riIxpd8mP4i4CLDCWiFFNj0Ltknm9c_UIGfX5d60,1836
150
151
  modal_proto/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
151
- modal_proto/api.proto,sha256=LdD5XC5IhLP4rL_7nLeO0FL3emTs7jpEZGON2JIJDs4,79741
152
- modal_proto/api_grpc.py,sha256=AiUCWTHQQFC9RFB_XuavB_OnVMr7GJMRLEwcf4FSTio,102088
153
- modal_proto/api_pb2.py,sha256=rvBtjxpHVIXZmecc0OZS5PgZm4n1DcI4q1EHE675iW0,293583
154
- modal_proto/api_pb2.pyi,sha256=LNvol6MFzFuG1J_wKA4AKPiSKSLkYfNW1kF7c5XXABk,391781
155
- modal_proto/api_pb2_grpc.py,sha256=dFxVTgosyp_o8NCI1JIySlR0qTzG4ILm9mq8MNo4jYc,220795
156
- modal_proto/api_pb2_grpc.pyi,sha256=yJgwEl-1YU42m7MU_Sm5SK3rB9xdkisPk3nZB-mlqkg,51463
157
- modal_proto/modal_api_grpc.py,sha256=MyNzvY_WqB7QTMOQjoH6lsqCY5-6_s1HP-knsOSjANs,13640
152
+ modal_proto/api.proto,sha256=yxqa3djtNDAD6_AolYTUR_BM5w2apPN6UIXcvjRAFvY,80137
153
+ modal_proto/api_grpc.py,sha256=MLsxlZXikcv36aSs0iVZywIOANMWstVZMWJGQTDjdM0,102823
154
+ modal_proto/api_pb2.py,sha256=Mt0QjSEeSNi9vbpK8LoEAfrCF2obuDyJdFWAob2zYFw,294928
155
+ modal_proto/api_pb2.pyi,sha256=uSsIePIeIK06rkW3rPHSOnpKoagr5bhDWuXjk73W_l8,393499
156
+ modal_proto/api_pb2_grpc.py,sha256=4d5SwJPLldCqqHz_BGqmJLlX-BMG592WFKUQ6IBg5rg,222415
157
+ modal_proto/api_pb2_grpc.pyi,sha256=C4c3jndI6TKEgVnx3vAmT86In4T9JKmB1CImiye_aQk,51822
158
+ modal_proto/modal_api_grpc.py,sha256=eeH1vHXgwO768tM7DXqvj-P37u15SI00gZtm8_EK15I,13732
158
159
  modal_proto/modal_options_grpc.py,sha256=qJ1cuwA54oRqrdTyPTbvfhFZYd9HhJKK5UCwt523r3Y,120
159
160
  modal_proto/options.proto,sha256=a-siq4swVbZPfaFRXAipRZzGP2bq8OsdUvjlyzAeodQ,488
160
161
  modal_proto/options_grpc.py,sha256=M18X3d-8F_cNYSVM3I25dUTO5rZ0rd-vCCfynfh13Nc,125
@@ -163,12 +164,12 @@ modal_proto/options_pb2.pyi,sha256=l7DBrbLO7q3Ir-XDkWsajm0d0TQqqrfuX54i4BMpdQg,1
163
164
  modal_proto/options_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
164
165
  modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0yJSI,247
165
166
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
166
- modal_version/__init__.py,sha256=N9Kh4DrM2649_INTJG4Lp3NKdux7cxGuiDtXpq_hkFY,470
167
+ modal_version/__init__.py,sha256=BEBWj9tcbFUwzEjUrqly601rauw5cYsHdcmJHs3iu0s,470
167
168
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
168
- modal_version/_version_generated.py,sha256=-cO-pRiLwIwgch5Ik_s4Jq9WJA6bUJuOEZ1MivmPWJ8,148
169
- modal-0.70.2.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
170
- modal-0.70.2.dist-info/METADATA,sha256=FuikYBU1iOwsYxHyv1pnYclfrOMwXJgY7JkfqndJnnQ,2328
171
- modal-0.70.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
172
- modal-0.70.2.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
173
- modal-0.70.2.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
174
- modal-0.70.2.dist-info/RECORD,,
169
+ modal_version/_version_generated.py,sha256=Ryi8NC5BNPKr4cGNqMzFAc_4pmvMQHAGNycNZBu1EBI,148
170
+ modal-0.71.5.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
171
+ modal-0.71.5.dist-info/METADATA,sha256=-r6kJm_xHIGyfR0rcCVEpqeh7hJcMF9WlwUEaGzcm8k,2328
172
+ modal-0.71.5.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
173
+ modal-0.71.5.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
174
+ modal-0.71.5.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
175
+ modal-0.71.5.dist-info/RECORD,,
modal_proto/api.proto CHANGED
@@ -1479,6 +1479,8 @@ message FunctionGetOutputsRequest {
1479
1479
  string last_entry_id = 6;
1480
1480
  bool clear_on_success = 7; // expires *any* remaining outputs soon after this call, not just the returned ones
1481
1481
  double requested_at = 8; // Used for waypoints.
1482
+ // The jwts the client expects the server to be processing. This is optional and used for sync inputs only.
1483
+ repeated string input_jwts = 9;
1482
1484
  }
1483
1485
 
1484
1486
  message FunctionGetOutputsResponse {
@@ -1678,6 +1680,7 @@ message GenericResult { // Used for both tasks and function outputs
1678
1680
  // Used when the user's function fails to initialize (ex. S3 mount failed due to invalid credentials).
1679
1681
  // Terminates the function and all remaining inputs.
1680
1682
  GENERIC_STATUS_INIT_FAILURE = 5;
1683
+ GENERIC_STATUS_INTERNAL_FAILURE = 6;
1681
1684
  }
1682
1685
 
1683
1686
  GenericStatus status = 1; // Status of the task or function output.
@@ -1727,6 +1730,15 @@ message ImageContextFile {
1727
1730
  bytes data = 2;
1728
1731
  }
1729
1732
 
1733
+ message ImageFromIdRequest {
1734
+ string image_id = 1;
1735
+ }
1736
+
1737
+ message ImageFromIdResponse {
1738
+ string image_id = 1;
1739
+ ImageMetadata metadata = 2;
1740
+ }
1741
+
1730
1742
  message ImageGetOrCreateRequest {
1731
1743
  Image image = 2;
1732
1744
  string app_id = 4 [ (modal.options.audit_target_attr) = true ];
@@ -2794,6 +2806,7 @@ service ModalClient {
2794
2806
  rpc FunctionUpdateSchedulingParams(FunctionUpdateSchedulingParamsRequest) returns (FunctionUpdateSchedulingParamsResponse);
2795
2807
 
2796
2808
  // Images
2809
+ rpc ImageFromId(ImageFromIdRequest) returns (ImageFromIdResponse);
2797
2810
  rpc ImageGetOrCreate(ImageGetOrCreateRequest) returns (ImageGetOrCreateResponse);
2798
2811
  rpc ImageJoinStreaming(ImageJoinStreamingRequest) returns (stream ImageJoinStreamingResponse);
2799
2812