modal 0.67.14__py3-none-any.whl → 0.67.19__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.
@@ -6,7 +6,7 @@ import inspect
6
6
  import itertools
7
7
  import time
8
8
  import typing
9
- from collections.abc import AsyncGenerator, Awaitable, Iterable, Iterator
9
+ from collections.abc import AsyncGenerator, AsyncIterable, Awaitable, Iterable, Iterator
10
10
  from contextlib import asynccontextmanager
11
11
  from dataclasses import dataclass
12
12
  from typing import (
@@ -484,14 +484,17 @@ class aclosing(typing.Generic[T]): # noqa
484
484
  await self.agen.aclose()
485
485
 
486
486
 
487
- async def sync_or_async_iter(iter: Union[Iterable[T], AsyncGenerator[T, None]]) -> AsyncGenerator[T, None]:
487
+ async def sync_or_async_iter(iter: Union[Iterable[T], AsyncIterable[T]]) -> AsyncGenerator[T, None]:
488
488
  if hasattr(iter, "__aiter__"):
489
489
  agen = typing.cast(AsyncGenerator[T, None], iter)
490
490
  try:
491
491
  async for item in agen:
492
492
  yield item
493
493
  finally:
494
- await agen.aclose()
494
+ if hasattr(agen, "aclose"):
495
+ # All AsyncGenerator's have an aclose method
496
+ # but some AsyncIterable's don't necessarily
497
+ await agen.aclose()
495
498
  else:
496
499
  assert hasattr(iter, "__iter__"), "sync_or_async_iter requires an Iterable or AsyncGenerator"
497
500
  # This intentionally could block the event loop for the duration of calling __iter__ and __next__,
@@ -5,6 +5,7 @@ import hashlib
5
5
  import io
6
6
  import os
7
7
  import platform
8
+ import time
8
9
  from collections.abc import AsyncIterator
9
10
  from contextlib import AbstractContextManager, contextmanager
10
11
  from pathlib import Path, PurePosixPath
@@ -289,11 +290,18 @@ async def _blob_upload(
289
290
 
290
291
 
291
292
  async def blob_upload(payload: bytes, stub: ModalClientModal) -> str:
293
+ size_mib = len(payload) / 1024 / 1024
294
+ logger.debug(f"Uploading large blob of size {size_mib:.2f} MiB")
295
+ t0 = time.time()
292
296
  if isinstance(payload, str):
293
297
  logger.warning("Blob uploading string, not bytes - auto-encoding as utf8")
294
298
  payload = payload.encode("utf8")
295
299
  upload_hashes = get_upload_hashes(payload)
296
- return await _blob_upload(upload_hashes, payload, stub)
300
+ blob_id = await _blob_upload(upload_hashes, payload, stub)
301
+ dur_s = max(time.time() - t0, 0.001) # avoid division by zero
302
+ throughput_mib_s = (size_mib) / dur_s
303
+ logger.debug(f"Uploaded large blob of size {size_mib:.2f} MiB ({throughput_mib_s:.2f} MiB/s)." f" {blob_id}")
304
+ return blob_id
297
305
 
298
306
 
299
307
  async def blob_upload_file(
@@ -318,11 +326,17 @@ async def _download_from_url(download_url: str) -> bytes:
318
326
 
319
327
 
320
328
  async def blob_download(blob_id: str, stub: ModalClientModal) -> bytes:
321
- # convenience function reading all of the downloaded file into memory
329
+ """Convenience function for reading all of the downloaded file into memory."""
330
+ logger.debug(f"Downloading large blob {blob_id}")
331
+ t0 = time.time()
322
332
  req = api_pb2.BlobGetRequest(blob_id=blob_id)
323
333
  resp = await retry_transient_errors(stub.BlobGet, req)
324
-
325
- return await _download_from_url(resp.download_url)
334
+ data = await _download_from_url(resp.download_url)
335
+ size_mib = len(data) / 1024 / 1024
336
+ dur_s = max(time.time() - t0, 0.001) # avoid division by zero
337
+ throughput_mib_s = size_mib / dur_s
338
+ logger.debug(f"Downloaded large blob {blob_id} of size {size_mib:.2f} MiB ({throughput_mib_s:.2f} MiB/s)")
339
+ return data
326
340
 
327
341
 
328
342
  async def blob_iter(blob_id: str, stub: ModalClientModal) -> AsyncIterator[bytes]:
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.67.14"
29
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.67.19"
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.67.14"
84
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.67.19"
85
85
  ): ...
86
86
  def is_closed(self) -> bool: ...
87
87
  @property
modal/sandbox.py CHANGED
@@ -28,6 +28,7 @@ from .io_streams import StreamReader, StreamWriter, _StreamReader, _StreamWriter
28
28
  from .mount import _Mount
29
29
  from .network_file_system import _NetworkFileSystem, network_file_system_mount_protos
30
30
  from .object import _get_environment_name, _Object
31
+ from .proxy import _Proxy
31
32
  from .scheduler_placement import SchedulerPlacement
32
33
  from .secret import _Secret
33
34
  from .stream_type import StreamType
@@ -73,6 +74,7 @@ class _Sandbox(_Object, type_prefix="sb"):
73
74
  pty_info: Optional[api_pb2.PTYInfo] = None,
74
75
  encrypted_ports: Sequence[int] = [],
75
76
  unencrypted_ports: Sequence[int] = [],
77
+ proxy: Optional[_Proxy] = None,
76
78
  _experimental_scheduler_placement: Optional[SchedulerPlacement] = None,
77
79
  ) -> "_Sandbox":
78
80
  """mdmd:hidden"""
@@ -166,6 +168,7 @@ class _Sandbox(_Object, type_prefix="sb"):
166
168
  worker_id=config.get("worker_id"),
167
169
  open_ports=api_pb2.PortSpecs(ports=open_ports),
168
170
  network_access=network_access,
171
+ proxy_id=(proxy.object_id if proxy else None),
169
172
  )
170
173
 
171
174
  # Note - `resolver.app_id` will be `None` for app-less sandboxes
@@ -208,6 +211,8 @@ class _Sandbox(_Object, type_prefix="sb"):
208
211
  encrypted_ports: Sequence[int] = [],
209
212
  # List of ports to tunnel into the sandbox without encryption.
210
213
  unencrypted_ports: Sequence[int] = [],
214
+ # Reference to a Modal Proxy to use in front of this Sandbox.
215
+ proxy: Optional[_Proxy] = None,
211
216
  _experimental_scheduler_placement: Optional[
212
217
  SchedulerPlacement
213
218
  ] = None, # Experimental controls over fine-grained scheduling (alpha).
@@ -243,6 +248,7 @@ class _Sandbox(_Object, type_prefix="sb"):
243
248
  pty_info=pty_info,
244
249
  encrypted_ports=encrypted_ports,
245
250
  unencrypted_ports=unencrypted_ports,
251
+ proxy=proxy,
246
252
  _experimental_scheduler_placement=_experimental_scheduler_placement,
247
253
  )
248
254
 
modal/sandbox.pyi CHANGED
@@ -11,6 +11,7 @@ import modal.io_streams
11
11
  import modal.mount
12
12
  import modal.network_file_system
13
13
  import modal.object
14
+ import modal.proxy
14
15
  import modal.scheduler_placement
15
16
  import modal.secret
16
17
  import modal.stream_type
@@ -51,6 +52,7 @@ class _Sandbox(modal.object._Object):
51
52
  pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
52
53
  encrypted_ports: collections.abc.Sequence[int] = [],
53
54
  unencrypted_ports: collections.abc.Sequence[int] = [],
55
+ proxy: typing.Optional[modal.proxy._Proxy] = None,
54
56
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
55
57
  ) -> _Sandbox: ...
56
58
  @staticmethod
@@ -78,6 +80,7 @@ class _Sandbox(modal.object._Object):
78
80
  pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
79
81
  encrypted_ports: collections.abc.Sequence[int] = [],
80
82
  unencrypted_ports: collections.abc.Sequence[int] = [],
83
+ proxy: typing.Optional[modal.proxy._Proxy] = None,
81
84
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
82
85
  client: typing.Optional[modal.client._Client] = None,
83
86
  ) -> _Sandbox: ...
@@ -165,6 +168,7 @@ class Sandbox(modal.object.Object):
165
168
  pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
166
169
  encrypted_ports: collections.abc.Sequence[int] = [],
167
170
  unencrypted_ports: collections.abc.Sequence[int] = [],
171
+ proxy: typing.Optional[modal.proxy.Proxy] = None,
168
172
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
169
173
  ) -> Sandbox: ...
170
174
 
@@ -196,6 +200,7 @@ class Sandbox(modal.object.Object):
196
200
  pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
197
201
  encrypted_ports: collections.abc.Sequence[int] = [],
198
202
  unencrypted_ports: collections.abc.Sequence[int] = [],
203
+ proxy: typing.Optional[modal.proxy.Proxy] = None,
199
204
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
200
205
  client: typing.Optional[modal.client.Client] = None,
201
206
  ) -> Sandbox: ...
@@ -226,6 +231,7 @@ class Sandbox(modal.object.Object):
226
231
  pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
227
232
  encrypted_ports: collections.abc.Sequence[int] = [],
228
233
  unencrypted_ports: collections.abc.Sequence[int] = [],
234
+ proxy: typing.Optional[modal.proxy.Proxy] = None,
229
235
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
230
236
  client: typing.Optional[modal.client.Client] = None,
231
237
  ) -> Sandbox: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.67.14
3
+ Version: 0.67.19
4
4
  Summary: Python client library for Modal
5
5
  Author: Modal Labs
6
6
  Author-email: support@modal.com
@@ -19,7 +19,7 @@ modal/app.py,sha256=EJ7FUN6rWnSwLJoYJh8nmKg_t-8hdN8_rt0OrkP7JvQ,46084
19
19
  modal/app.pyi,sha256=BE5SlR5tRECuc6-e2lUuOknDdov3zxgZ4N0AsLb5ZVQ,25270
20
20
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
21
21
  modal/client.py,sha256=VMg_aIuo_LOEe2ttxBHEND3PLhTp5lo-onH4wELhIyY,16375
22
- modal/client.pyi,sha256=GfMKvzgJgycbuVP2WPB33Dk5M7kg0kIdsvTiwmLS1mI,7354
22
+ modal/client.pyi,sha256=thOQnTpBj0M-83tyx4cAzEtK1hyiaunf2CNsegg9DjM,7354
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=F2jk5zFCAA8h-GfM0dbdBG3Mu5wiG9k9Z9JLYRYuT2Q,24758
@@ -60,8 +60,8 @@ modal/retries.py,sha256=HKR2Q9aNPWkMjQ5nwobqYTuZaSuw0a8lI2zrtY5IW98,5230
60
60
  modal/runner.py,sha256=7obU-Gq1ocpBGCuR6pvn1T-D6ggg1T48qFo2TNUGWkU,24089
61
61
  modal/runner.pyi,sha256=RAtCvx_lXWjyFjIaZ3t9-X1c7rqpgAQlhl4Hww53OY8,5038
62
62
  modal/running_app.py,sha256=CshNvGDJtagOdKW54uYjY8HY73j2TpnsL9jkPFZAsfA,560
63
- modal/sandbox.py,sha256=5oDrV9XL8JkGl_gcbu5D20vbaxFRpWv_bCdw-Fo-qNE,24886
64
- modal/sandbox.pyi,sha256=FKdkLGmJrdRT0pHlhV_brxo1pvxr6uWZeL-zomLHSas,17366
63
+ modal/sandbox.py,sha256=25DvTWSgClANvk67HM3FHukRVLig_Fw_aQC1BLMCRhs,25150
64
+ modal/sandbox.pyi,sha256=JRh6Q-WdY6GgVSOGm0L_pgo5bfsi2UacsZezpT0-cDU,17685
65
65
  modal/schedule.py,sha256=0ZFpKs1bOxeo5n3HZjoL7OE2ktsb-_oGtq-WJEPO4tY,2615
66
66
  modal/scheduler_placement.py,sha256=BAREdOY5HzHpzSBqt6jDVR6YC_jYfHMVqOzkyqQfngU,1235
67
67
  modal/secret.py,sha256=Y1WgybQIkfkxdzH9CQ1h-Wd1DJJpzipigMhyyvSxTww,10007
@@ -81,8 +81,8 @@ modal/_runtime/telemetry.py,sha256=T1RoAGyjBDr1swiM6pPsGRSITm7LI5FDK18oNXxY08U,5
81
81
  modal/_runtime/user_code_imports.py,sha256=q_3JOYqCPDcVFZWCHEjyEqj8yzdFsQ49HzeqYmFDLbk,14521
82
82
  modal/_utils/__init__.py,sha256=waLjl5c6IPDhSsdWAm9Bji4e2PVxamYABKAze6CHVXY,28
83
83
  modal/_utils/app_utils.py,sha256=88BT4TPLWfYAQwKTHcyzNQRHg8n9B-QE2UyJs96iV-0,108
84
- modal/_utils/async_utils.py,sha256=CYXogDVqqUtSe-DVP2A3F-6KztjPZaW6ez2lrYBCW_Y,24917
85
- modal/_utils/blob_utils.py,sha256=XJpFr6SfNoODZEjyGm7WH9R3pKsK4yqpzZOFR3YX6fc,15856
84
+ modal/_utils/async_utils.py,sha256=9ubwMkwiDB4gzOYG2jL9j7Fs-5dxHjcifZe3r7JRg-k,25091
85
+ modal/_utils/blob_utils.py,sha256=0k_qUpO5GHnz538wjRhyRw4NdJ5O322N7QSilIu32jw,16601
86
86
  modal/_utils/function_utils.py,sha256=SkT5emqGJ8NNASk0BlBmgDfDBUYAkUM851K74qCHL98,24641
87
87
  modal/_utils/grpc_testing.py,sha256=iqM9n5M0cWUUIIWNaEDer_pIfPnzXdZBO4L8FVbNepQ,8309
88
88
  modal/_utils/grpc_utils.py,sha256=PPB5ay-vXencXNIWPVw5modr3EH7gfq2QPcO5YJ1lMU,7737
@@ -142,13 +142,13 @@ modal_global_objects/mounts/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0
142
142
  modal_global_objects/mounts/modal_client_package.py,sha256=W0E_yShsRojPzWm6LtIQqNVolapdnrZkm2hVEQuZK_4,767
143
143
  modal_global_objects/mounts/python_standalone.py,sha256=_vTEX3PECUsatzhDs8lyJmDK0LbFetT1sJB6MIDfFAo,1870
144
144
  modal_proto/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
145
- modal_proto/api.proto,sha256=1zr0aMwKHYgPfkvAjQ5hCRiWbJcuVXxwiuOlLpDU46Y,77644
146
- modal_proto/api_grpc.py,sha256=S7h8xe-msb3-Q8oSd7DUoB46z-dcRhsXGb6LjFCLNFI,99013
147
- modal_proto/api_pb2.py,sha256=PLRmPloiKDiiziRzbZrzF3Cr-Cn5uCv9USwc7nC_eeg,282809
148
- modal_proto/api_pb2.pyi,sha256=LAs_gmu_GYKzfa_8dxXiXsNejnWyWjX5hQ-6MuJmww0,378389
149
- modal_proto/api_pb2_grpc.py,sha256=g7EfCSir3xStPPjJOU2U668zz6cGdN6u7SxvTTwU9aU,214126
150
- modal_proto/api_pb2_grpc.pyi,sha256=9GhLZVRm69Qhyj_jmGqEGv1rD37Tzj6E6hGzKV08u48,49961
151
- modal_proto/modal_api_grpc.py,sha256=en48QTR5fwA7x0twtlsqLKRjjDEAKVoh6EeSznQfQ3U,13236
145
+ modal_proto/api.proto,sha256=I36DzPZ4fs045HqQCEdgEiiJr1Dcd6T6fFA1RbQW7aE,78014
146
+ modal_proto/api_grpc.py,sha256=cQOfwiGd2Nyj9esTgtu39EK1QKGZJXharISgWG7_lQ8,99814
147
+ modal_proto/api_pb2.py,sha256=rk-8yxgiqsevrf8ZOQl7Elr5xKKz0nYdI46Yd6S0iJI,284335
148
+ modal_proto/api_pb2.pyi,sha256=FCMnndhn2lqdFo1kr49PjfOQzxacPg2UHM9hGpatdS8,380051
149
+ modal_proto/api_pb2_grpc.py,sha256=VTrD72cWvA2SscVVpR7w0swwGJ2O6pY-uASamt9Qm7M,215824
150
+ modal_proto/api_pb2_grpc.pyi,sha256=SbWtkTeCztsSOz8WaM07UZ5fxfKn7nQG4Dxbp5PQUz8,50356
151
+ modal_proto/modal_api_grpc.py,sha256=AUfZ2n1xOaCv-NzMp5w91fgsQi5zs79IDLH7RGOg1-o,13340
152
152
  modal_proto/modal_options_grpc.py,sha256=qJ1cuwA54oRqrdTyPTbvfhFZYd9HhJKK5UCwt523r3Y,120
153
153
  modal_proto/options.proto,sha256=a-siq4swVbZPfaFRXAipRZzGP2bq8OsdUvjlyzAeodQ,488
154
154
  modal_proto/options_grpc.py,sha256=M18X3d-8F_cNYSVM3I25dUTO5rZ0rd-vCCfynfh13Nc,125
@@ -159,10 +159,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
159
159
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
160
160
  modal_version/__init__.py,sha256=3IY-AWLH55r35_mQXIaut0jrJvoPuf1NZJBQQfSbPuo,470
161
161
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
162
- modal_version/_version_generated.py,sha256=C_bhmRQ5vhAyOuxu_gJXQnJ-QqoRX-Hrts9qMSeIr38,149
163
- modal-0.67.14.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
164
- modal-0.67.14.dist-info/METADATA,sha256=aumIF34KL_3jyBsm0akwFP5lgbjpt1thUxa9oahCh2Y,2329
165
- modal-0.67.14.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
166
- modal-0.67.14.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
167
- modal-0.67.14.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
168
- modal-0.67.14.dist-info/RECORD,,
162
+ modal_version/_version_generated.py,sha256=zYKLeeniwCulLrYJ4NLXigHdUUd3VY_DsZP7BG5Ck7A,149
163
+ modal-0.67.19.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
164
+ modal-0.67.19.dist-info/METADATA,sha256=AsTc53FDpIG8sHxoyYC0LDTwQGt3ff6rMtMUuVlV3cw,2329
165
+ modal-0.67.19.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
166
+ modal-0.67.19.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
167
+ modal-0.67.19.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
168
+ modal-0.67.19.dist-info/RECORD,,
modal_proto/api.proto CHANGED
@@ -2146,6 +2146,18 @@ message SandboxListResponse {
2146
2146
  repeated SandboxInfo sandboxes = 1;
2147
2147
  }
2148
2148
 
2149
+ message SandboxSnapshotFsRequest {
2150
+ string sandbox_id = 1;
2151
+ float timeout = 2;
2152
+ }
2153
+
2154
+ message SandboxSnapshotFsResponse {
2155
+ string image_id = 1;
2156
+ GenericResult result = 2;
2157
+ // Metadata may be empty since we may skip it for performance reasons.
2158
+ ImageMetadata image_metadata = 3;
2159
+ }
2160
+
2149
2161
  message SandboxStdinWriteRequest {
2150
2162
  string sandbox_id = 1;
2151
2163
  bytes input = 2;
@@ -2745,6 +2757,7 @@ service ModalClient {
2745
2757
  rpc SandboxGetTaskId(SandboxGetTaskIdRequest) returns (SandboxGetTaskIdResponse); // needed for modal container exec
2746
2758
  rpc SandboxGetTunnels(SandboxGetTunnelsRequest) returns (SandboxGetTunnelsResponse);
2747
2759
  rpc SandboxList(SandboxListRequest) returns (SandboxListResponse);
2760
+ rpc SandboxSnapshotFs(SandboxSnapshotFsRequest) returns (SandboxSnapshotFsResponse);
2748
2761
  rpc SandboxStdinWrite(SandboxStdinWriteRequest) returns (SandboxStdinWriteResponse);
2749
2762
  rpc SandboxTagsSet(SandboxTagsSetRequest) returns (google.protobuf.Empty);
2750
2763
  rpc SandboxTerminate(SandboxTerminateRequest) returns (SandboxTerminateResponse);
modal_proto/api_grpc.py CHANGED
@@ -389,6 +389,10 @@ class ModalClientBase(abc.ABC):
389
389
  async def SandboxList(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.SandboxListRequest, modal_proto.api_pb2.SandboxListResponse]') -> None:
390
390
  pass
391
391
 
392
+ @abc.abstractmethod
393
+ async def SandboxSnapshotFs(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.SandboxSnapshotFsRequest, modal_proto.api_pb2.SandboxSnapshotFsResponse]') -> None:
394
+ pass
395
+
392
396
  @abc.abstractmethod
393
397
  async def SandboxStdinWrite(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.SandboxStdinWriteRequest, modal_proto.api_pb2.SandboxStdinWriteResponse]') -> None:
394
398
  pass
@@ -1093,6 +1097,12 @@ class ModalClientBase(abc.ABC):
1093
1097
  modal_proto.api_pb2.SandboxListRequest,
1094
1098
  modal_proto.api_pb2.SandboxListResponse,
1095
1099
  ),
1100
+ '/modal.client.ModalClient/SandboxSnapshotFs': grpclib.const.Handler(
1101
+ self.SandboxSnapshotFs,
1102
+ grpclib.const.Cardinality.UNARY_UNARY,
1103
+ modal_proto.api_pb2.SandboxSnapshotFsRequest,
1104
+ modal_proto.api_pb2.SandboxSnapshotFsResponse,
1105
+ ),
1096
1106
  '/modal.client.ModalClient/SandboxStdinWrite': grpclib.const.Handler(
1097
1107
  self.SandboxStdinWrite,
1098
1108
  grpclib.const.Cardinality.UNARY_UNARY,
@@ -1873,6 +1883,12 @@ class ModalClientStub:
1873
1883
  modal_proto.api_pb2.SandboxListRequest,
1874
1884
  modal_proto.api_pb2.SandboxListResponse,
1875
1885
  )
1886
+ self.SandboxSnapshotFs = grpclib.client.UnaryUnaryMethod(
1887
+ channel,
1888
+ '/modal.client.ModalClient/SandboxSnapshotFs',
1889
+ modal_proto.api_pb2.SandboxSnapshotFsRequest,
1890
+ modal_proto.api_pb2.SandboxSnapshotFsResponse,
1891
+ )
1876
1892
  self.SandboxStdinWrite = grpclib.client.UnaryUnaryMethod(
1877
1893
  channel,
1878
1894
  '/modal.client.ModalClient/SandboxStdinWrite',