modal 0.62.115__py3-none-any.whl → 0.72.13__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.
Files changed (220) hide show
  1. modal/__init__.py +13 -9
  2. modal/__main__.py +41 -3
  3. modal/_clustered_functions.py +80 -0
  4. modal/_clustered_functions.pyi +22 -0
  5. modal/_container_entrypoint.py +402 -398
  6. modal/_ipython.py +3 -13
  7. modal/_location.py +17 -10
  8. modal/_output.py +243 -99
  9. modal/_pty.py +2 -2
  10. modal/_resolver.py +55 -60
  11. modal/_resources.py +26 -7
  12. modal/_runtime/__init__.py +1 -0
  13. modal/_runtime/asgi.py +519 -0
  14. modal/_runtime/container_io_manager.py +1025 -0
  15. modal/{execution_context.py → _runtime/execution_context.py} +11 -2
  16. modal/_runtime/telemetry.py +169 -0
  17. modal/_runtime/user_code_imports.py +356 -0
  18. modal/_serialization.py +123 -6
  19. modal/_traceback.py +47 -187
  20. modal/_tunnel.py +50 -14
  21. modal/_tunnel.pyi +19 -36
  22. modal/_utils/app_utils.py +3 -17
  23. modal/_utils/async_utils.py +386 -104
  24. modal/_utils/blob_utils.py +157 -186
  25. modal/_utils/bytes_io_segment_payload.py +97 -0
  26. modal/_utils/deprecation.py +89 -0
  27. modal/_utils/docker_utils.py +98 -0
  28. modal/_utils/function_utils.py +299 -98
  29. modal/_utils/grpc_testing.py +47 -34
  30. modal/_utils/grpc_utils.py +54 -21
  31. modal/_utils/hash_utils.py +51 -10
  32. modal/_utils/http_utils.py +39 -9
  33. modal/_utils/logger.py +2 -1
  34. modal/_utils/mount_utils.py +34 -16
  35. modal/_utils/name_utils.py +58 -0
  36. modal/_utils/package_utils.py +14 -1
  37. modal/_utils/pattern_utils.py +205 -0
  38. modal/_utils/rand_pb_testing.py +3 -3
  39. modal/_utils/shell_utils.py +15 -49
  40. modal/_vendor/a2wsgi_wsgi.py +62 -72
  41. modal/_vendor/cloudpickle.py +1 -1
  42. modal/_watcher.py +12 -10
  43. modal/app.py +561 -323
  44. modal/app.pyi +474 -262
  45. modal/call_graph.py +7 -6
  46. modal/cli/_download.py +22 -6
  47. modal/cli/_traceback.py +200 -0
  48. modal/cli/app.py +203 -42
  49. modal/cli/config.py +12 -5
  50. modal/cli/container.py +61 -13
  51. modal/cli/dict.py +128 -0
  52. modal/cli/entry_point.py +26 -13
  53. modal/cli/environment.py +40 -9
  54. modal/cli/import_refs.py +21 -48
  55. modal/cli/launch.py +28 -14
  56. modal/cli/network_file_system.py +57 -21
  57. modal/cli/profile.py +1 -1
  58. modal/cli/programs/run_jupyter.py +34 -9
  59. modal/cli/programs/vscode.py +58 -8
  60. modal/cli/queues.py +131 -0
  61. modal/cli/run.py +199 -96
  62. modal/cli/secret.py +5 -4
  63. modal/cli/token.py +7 -2
  64. modal/cli/utils.py +74 -8
  65. modal/cli/volume.py +97 -56
  66. modal/client.py +248 -144
  67. modal/client.pyi +156 -124
  68. modal/cloud_bucket_mount.py +43 -30
  69. modal/cloud_bucket_mount.pyi +32 -25
  70. modal/cls.py +528 -141
  71. modal/cls.pyi +189 -145
  72. modal/config.py +32 -15
  73. modal/container_process.py +177 -0
  74. modal/container_process.pyi +82 -0
  75. modal/dict.py +50 -54
  76. modal/dict.pyi +120 -164
  77. modal/environments.py +106 -5
  78. modal/environments.pyi +77 -25
  79. modal/exception.py +30 -43
  80. modal/experimental.py +62 -2
  81. modal/file_io.py +537 -0
  82. modal/file_io.pyi +235 -0
  83. modal/file_pattern_matcher.py +196 -0
  84. modal/functions.py +846 -428
  85. modal/functions.pyi +446 -387
  86. modal/gpu.py +57 -44
  87. modal/image.py +943 -417
  88. modal/image.pyi +584 -245
  89. modal/io_streams.py +434 -0
  90. modal/io_streams.pyi +122 -0
  91. modal/mount.py +223 -90
  92. modal/mount.pyi +241 -243
  93. modal/network_file_system.py +85 -86
  94. modal/network_file_system.pyi +151 -110
  95. modal/object.py +66 -36
  96. modal/object.pyi +166 -143
  97. modal/output.py +63 -0
  98. modal/parallel_map.py +73 -47
  99. modal/parallel_map.pyi +51 -63
  100. modal/partial_function.py +272 -107
  101. modal/partial_function.pyi +219 -120
  102. modal/proxy.py +15 -12
  103. modal/proxy.pyi +3 -8
  104. modal/queue.py +96 -72
  105. modal/queue.pyi +210 -135
  106. modal/requirements/2024.04.txt +2 -1
  107. modal/requirements/2024.10.txt +16 -0
  108. modal/requirements/README.md +21 -0
  109. modal/requirements/base-images.json +22 -0
  110. modal/retries.py +45 -4
  111. modal/runner.py +325 -203
  112. modal/runner.pyi +124 -110
  113. modal/running_app.py +27 -4
  114. modal/sandbox.py +509 -231
  115. modal/sandbox.pyi +396 -169
  116. modal/schedule.py +2 -2
  117. modal/scheduler_placement.py +20 -3
  118. modal/secret.py +41 -25
  119. modal/secret.pyi +62 -42
  120. modal/serving.py +39 -49
  121. modal/serving.pyi +37 -43
  122. modal/stream_type.py +15 -0
  123. modal/token_flow.py +5 -3
  124. modal/token_flow.pyi +37 -32
  125. modal/volume.py +123 -137
  126. modal/volume.pyi +228 -221
  127. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/METADATA +5 -5
  128. modal-0.72.13.dist-info/RECORD +174 -0
  129. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/top_level.txt +0 -1
  130. modal_docs/gen_reference_docs.py +3 -1
  131. modal_docs/mdmd/mdmd.py +0 -1
  132. modal_docs/mdmd/signatures.py +1 -2
  133. modal_global_objects/images/base_images.py +28 -0
  134. modal_global_objects/mounts/python_standalone.py +2 -2
  135. modal_proto/__init__.py +1 -1
  136. modal_proto/api.proto +1231 -531
  137. modal_proto/api_grpc.py +750 -430
  138. modal_proto/api_pb2.py +2102 -1176
  139. modal_proto/api_pb2.pyi +8859 -0
  140. modal_proto/api_pb2_grpc.py +1329 -675
  141. modal_proto/api_pb2_grpc.pyi +1416 -0
  142. modal_proto/modal_api_grpc.py +149 -0
  143. modal_proto/modal_options_grpc.py +3 -0
  144. modal_proto/options_pb2.pyi +20 -0
  145. modal_proto/options_pb2_grpc.pyi +7 -0
  146. modal_proto/py.typed +0 -0
  147. modal_version/__init__.py +1 -1
  148. modal_version/_version_generated.py +2 -2
  149. modal/_asgi.py +0 -370
  150. modal/_container_exec.py +0 -128
  151. modal/_container_io_manager.py +0 -646
  152. modal/_container_io_manager.pyi +0 -412
  153. modal/_sandbox_shell.py +0 -49
  154. modal/app_utils.py +0 -20
  155. modal/app_utils.pyi +0 -17
  156. modal/execution_context.pyi +0 -37
  157. modal/shared_volume.py +0 -23
  158. modal/shared_volume.pyi +0 -24
  159. modal-0.62.115.dist-info/RECORD +0 -207
  160. modal_global_objects/images/conda.py +0 -15
  161. modal_global_objects/images/debian_slim.py +0 -15
  162. modal_global_objects/images/micromamba.py +0 -15
  163. test/__init__.py +0 -1
  164. test/aio_test.py +0 -12
  165. test/async_utils_test.py +0 -279
  166. test/blob_test.py +0 -67
  167. test/cli_imports_test.py +0 -149
  168. test/cli_test.py +0 -674
  169. test/client_test.py +0 -203
  170. test/cloud_bucket_mount_test.py +0 -22
  171. test/cls_test.py +0 -636
  172. test/config_test.py +0 -149
  173. test/conftest.py +0 -1485
  174. test/container_app_test.py +0 -50
  175. test/container_test.py +0 -1405
  176. test/cpu_test.py +0 -23
  177. test/decorator_test.py +0 -85
  178. test/deprecation_test.py +0 -34
  179. test/dict_test.py +0 -51
  180. test/e2e_test.py +0 -68
  181. test/error_test.py +0 -7
  182. test/function_serialization_test.py +0 -32
  183. test/function_test.py +0 -791
  184. test/function_utils_test.py +0 -101
  185. test/gpu_test.py +0 -159
  186. test/grpc_utils_test.py +0 -82
  187. test/helpers.py +0 -47
  188. test/image_test.py +0 -814
  189. test/live_reload_test.py +0 -80
  190. test/lookup_test.py +0 -70
  191. test/mdmd_test.py +0 -329
  192. test/mount_test.py +0 -162
  193. test/mounted_files_test.py +0 -327
  194. test/network_file_system_test.py +0 -188
  195. test/notebook_test.py +0 -66
  196. test/object_test.py +0 -41
  197. test/package_utils_test.py +0 -25
  198. test/queue_test.py +0 -115
  199. test/resolver_test.py +0 -59
  200. test/retries_test.py +0 -67
  201. test/runner_test.py +0 -85
  202. test/sandbox_test.py +0 -191
  203. test/schedule_test.py +0 -15
  204. test/scheduler_placement_test.py +0 -57
  205. test/secret_test.py +0 -89
  206. test/serialization_test.py +0 -50
  207. test/stub_composition_test.py +0 -10
  208. test/stub_test.py +0 -361
  209. test/test_asgi_wrapper.py +0 -234
  210. test/token_flow_test.py +0 -18
  211. test/traceback_test.py +0 -135
  212. test/tunnel_test.py +0 -29
  213. test/utils_test.py +0 -88
  214. test/version_test.py +0 -14
  215. test/volume_test.py +0 -397
  216. test/watcher_test.py +0 -58
  217. test/webhook_test.py +0 -145
  218. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/LICENSE +0 -0
  219. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/WHEEL +0 -0
  220. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/entry_points.txt +0 -0
@@ -1,8 +1,10 @@
1
1
  # Copyright Modal Labs 2023
2
+ import functools
2
3
  import os
3
4
  import time
5
+ from collections.abc import AsyncIterator
4
6
  from pathlib import Path, PurePosixPath
5
- from typing import AsyncIterator, BinaryIO, List, Optional, Tuple, Type, Union
7
+ from typing import Any, BinaryIO, Callable, Optional, Union
6
8
 
7
9
  from grpclib import GRPCError, Status
8
10
  from synchronicity.async_wrap import asynccontextmanager
@@ -11,12 +13,14 @@ import modal
11
13
  from modal_proto import api_pb2
12
14
 
13
15
  from ._resolver import Resolver
14
- from ._utils.async_utils import ConcurrencyPool, TaskContext, synchronize_api
16
+ from ._utils.async_utils import TaskContext, aclosing, async_map, sync_or_async_iter, synchronize_api
15
17
  from ._utils.blob_utils import LARGE_FILE_LIMIT, blob_iter, blob_upload_file
16
- from ._utils.grpc_utils import retry_transient_errors, unary_stream
18
+ from ._utils.deprecation import renamed_parameter
19
+ from ._utils.grpc_utils import retry_transient_errors
17
20
  from ._utils.hash_utils import get_sha256_hex
21
+ from ._utils.name_utils import check_object_name
18
22
  from .client import _Client
19
- from .exception import deprecation_warning
23
+ from .exception import InvalidError
20
24
  from .object import (
21
25
  EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
22
26
  _get_environment_name,
@@ -32,9 +36,9 @@ NETWORK_FILE_SYSTEM_PUT_FILE_CLIENT_TIMEOUT = (
32
36
 
33
37
 
34
38
  def network_file_system_mount_protos(
35
- validated_network_file_systems: List[Tuple[str, "_NetworkFileSystem"]],
39
+ validated_network_file_systems: list[tuple[str, "_NetworkFileSystem"]],
36
40
  allow_cross_region_volumes: bool,
37
- ) -> List[api_pb2.SharedVolumeMount]:
41
+ ) -> list[api_pb2.SharedVolumeMount]:
38
42
  network_file_system_mounts = []
39
43
  # Relies on dicts being ordered (true as of Python 3.6).
40
44
  for path, volume in validated_network_file_systems:
@@ -60,7 +64,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
60
64
  import modal
61
65
 
62
66
  nfs = modal.NetworkFileSystem.from_name("my-nfs", create_if_missing=True)
63
- app = modal.App() # Note: "app" was called "stub" up until April 2024
67
+ app = modal.App()
64
68
 
65
69
  @app.function(network_file_systems={"/root/foo": nfs})
66
70
  def f():
@@ -73,7 +77,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
73
77
 
74
78
  Also see the CLI methods for accessing network file systems:
75
79
 
76
- ```bash
80
+ ```
77
81
  modal nfs --help
78
82
  ```
79
83
 
@@ -87,68 +91,52 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
87
91
  """
88
92
 
89
93
  @staticmethod
90
- def new(cloud: Optional[str] = None) -> "_NetworkFileSystem":
91
- """`NetworkFileSystem.new` is deprecated.
92
-
93
- Please use `NetworkFileSystem.from_name` (for persisted) or `NetworkFileSystem.ephemeral`
94
- (for ephemeral) network filesystems.
95
- """
96
- deprecation_warning((2024, 3, 20), NetworkFileSystem.new.__doc__)
97
-
98
- async def _load(self: _NetworkFileSystem, resolver: Resolver, existing_object_id: Optional[str]):
99
- status_row = resolver.add_status_row()
100
- if existing_object_id:
101
- # Volume already exists; do nothing.
102
- self._hydrate(existing_object_id, resolver.client, None)
103
- return
104
-
105
- if cloud:
106
- deprecation_warning((2024, 1, 17), "Argument `cloud` is deprecated (has no effect).")
107
-
108
- status_row.message("Creating network file system...")
109
- req = api_pb2.SharedVolumeCreateRequest(app_id=resolver.app_id)
110
- resp = await retry_transient_errors(resolver.client.stub.SharedVolumeCreate, req)
111
- status_row.finish("Created network file system.")
112
- self._hydrate(resp.shared_volume_id, resolver.client, None)
113
-
114
- return _NetworkFileSystem._from_loader(_load, "NetworkFileSystem()")
115
-
116
- @staticmethod
94
+ @renamed_parameter((2024, 12, 18), "label", "name")
117
95
  def from_name(
118
- label: str,
96
+ name: str,
119
97
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
120
98
  environment_name: Optional[str] = None,
121
99
  create_if_missing: bool = False,
122
100
  ) -> "_NetworkFileSystem":
123
- """Create a reference to a persisted network filesystem, optionally creating it lazily.
101
+ """Reference a NetworkFileSystem by its name, creating if necessary.
124
102
 
125
- **Examples**
103
+ In contrast to `modal.NetworkFileSystem.lookup`, this is a lazy method
104
+ that defers hydrating the local object with metadata from Modal servers
105
+ until the first time it is actually used.
126
106
 
127
107
  ```python notest
128
- volume = NetworkFileSystem.from_name("my-volume", create_if_missing=True)
108
+ nfs = NetworkFileSystem.from_name("my-nfs", create_if_missing=True)
129
109
 
130
- @app.function(network_file_systems={"/vol": volume})
110
+ @app.function(network_file_systems={"/data": nfs})
131
111
  def f():
132
112
  pass
133
113
  ```
134
114
  """
115
+ check_object_name(name, "NetworkFileSystem")
135
116
 
136
117
  async def _load(self: _NetworkFileSystem, resolver: Resolver, existing_object_id: Optional[str]):
137
118
  req = api_pb2.SharedVolumeGetOrCreateRequest(
138
- deployment_name=label,
119
+ deployment_name=name,
139
120
  namespace=namespace,
140
121
  environment_name=_get_environment_name(environment_name, resolver),
141
122
  object_creation_type=(api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING if create_if_missing else None),
142
123
  )
143
- response = await resolver.client.stub.SharedVolumeGetOrCreate(req)
144
- self._hydrate(response.shared_volume_id, resolver.client, None)
124
+ try:
125
+ response = await resolver.client.stub.SharedVolumeGetOrCreate(req)
126
+ self._hydrate(response.shared_volume_id, resolver.client, None)
127
+ except GRPCError as exc:
128
+ if exc.status == Status.NOT_FOUND and exc.message == "App has wrong entity vo":
129
+ raise InvalidError(
130
+ f"Attempted to mount: `{name}` as a NetworkFileSystem " + "which already exists as a Volume"
131
+ )
132
+ raise
145
133
 
146
134
  return _NetworkFileSystem._from_loader(_load, "NetworkFileSystem()", hydrate_lazily=True)
147
135
 
148
136
  @classmethod
149
137
  @asynccontextmanager
150
138
  async def ephemeral(
151
- cls: Type["_NetworkFileSystem"],
139
+ cls: type["_NetworkFileSystem"],
152
140
  client: Optional[_Client] = None,
153
141
  environment_name: Optional[str] = None,
154
142
  _heartbeat_sleep: float = EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
@@ -157,11 +145,13 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
157
145
 
158
146
  Usage:
159
147
  ```python
160
- with NetworkFileSystem.ephemeral() as nfs:
161
- assert nfs.listdir() == []
148
+ with modal.NetworkFileSystem.ephemeral() as nfs:
149
+ assert nfs.listdir("/") == []
150
+ ```
162
151
 
163
- async with NetworkFileSystem.ephemeral() as nfs:
164
- assert await nfs.listdir() == []
152
+ ```python notest
153
+ async with modal.NetworkFileSystem.ephemeral() as nfs:
154
+ assert await nfs.listdir("/") == []
165
155
  ```
166
156
  """
167
157
  if client is None:
@@ -177,44 +167,26 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
177
167
  yield cls._new_hydrated(response.shared_volume_id, client, None, is_another_app=True)
178
168
 
179
169
  @staticmethod
180
- def persisted(
181
- label: str,
182
- namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
183
- environment_name: Optional[str] = None,
184
- cloud: Optional[str] = None,
185
- ) -> "_NetworkFileSystem":
186
- """Deprecated! Use `NetworkFileSystem.from_name(name, create_if_missing=True)`."""
187
- deprecation_warning((2024, 3, 1), _NetworkFileSystem.persisted.__doc__)
188
- return _NetworkFileSystem.from_name(label, namespace, environment_name, create_if_missing=True)
189
-
190
- def persist(
191
- self,
192
- label: str,
193
- namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
194
- environment_name: Optional[str] = None,
195
- cloud: Optional[str] = None,
196
- ):
197
- """`NetworkFileSystem().persist("my-volume")` is deprecated. Use `NetworkFileSystem.from_name("my-volume", create_if_missing=True)` instead."""
198
- deprecation_warning((2024, 2, 29), _NetworkFileSystem.persist.__doc__)
199
- return self.persisted(label, namespace, environment_name, cloud)
200
-
201
- @staticmethod
170
+ @renamed_parameter((2024, 12, 18), "label", "name")
202
171
  async def lookup(
203
- label: str,
172
+ name: str,
204
173
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
205
174
  client: Optional[_Client] = None,
206
175
  environment_name: Optional[str] = None,
207
176
  create_if_missing: bool = False,
208
177
  ) -> "_NetworkFileSystem":
209
- """Lookup a network file system with a given name
178
+ """Lookup a named NetworkFileSystem.
210
179
 
211
- ```python
212
- n = modal.NetworkFileSystem.lookup("my-nfs")
213
- print(n.listdir("/"))
180
+ In contrast to `modal.NetworkFileSystem.from_name`, this is an eager method
181
+ that will hydrate the local object with metadata from Modal servers.
182
+
183
+ ```python notest
184
+ nfs = modal.NetworkFileSystem.lookup("my-nfs")
185
+ print(nfs.listdir("/"))
214
186
  ```
215
187
  """
216
188
  obj = _NetworkFileSystem.from_name(
217
- label, namespace=namespace, environment_name=environment_name, create_if_missing=create_if_missing
189
+ name, namespace=namespace, environment_name=environment_name, create_if_missing=create_if_missing
218
190
  )
219
191
  if client is None:
220
192
  client = await _Client.from_env()
@@ -230,6 +202,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
230
202
  environment_name: Optional[str] = None,
231
203
  ) -> str:
232
204
  """mdmd:hidden"""
205
+ check_object_name(deployment_name, "NetworkFileSystem")
233
206
  if client is None:
234
207
  client = await _Client.from_env()
235
208
  request = api_pb2.SharedVolumeGetOrCreateRequest(
@@ -241,8 +214,15 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
241
214
  resp = await retry_transient_errors(client.stub.SharedVolumeGetOrCreate, request)
242
215
  return resp.shared_volume_id
243
216
 
217
+ @staticmethod
218
+ @renamed_parameter((2024, 12, 18), "label", "name")
219
+ async def delete(name: str, client: Optional[_Client] = None, environment_name: Optional[str] = None):
220
+ obj = await _NetworkFileSystem.lookup(name, client=client, environment_name=environment_name)
221
+ req = api_pb2.SharedVolumeDeleteRequest(shared_volume_id=obj.object_id)
222
+ await retry_transient_errors(obj._client.stub.SharedVolumeDelete, req)
223
+
244
224
  @live_method
245
- async def write_file(self, remote_path: str, fp: BinaryIO) -> int:
225
+ async def write_file(self, remote_path: str, fp: BinaryIO, progress_cb: Optional[Callable[..., Any]] = None) -> int:
246
226
  """Write from a file object to a path on the network file system, atomically.
247
227
 
248
228
  Will create any needed parent directories automatically.
@@ -250,12 +230,20 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
250
230
  If remote_path ends with `/` it's assumed to be a directory and the
251
231
  file will be uploaded with its current name to that directory.
252
232
  """
233
+ progress_cb = progress_cb or (lambda *_, **__: None)
234
+
253
235
  sha_hash = get_sha256_hex(fp)
254
236
  fp.seek(0, os.SEEK_END)
255
237
  data_size = fp.tell()
256
238
  fp.seek(0)
257
239
  if data_size > LARGE_FILE_LIMIT:
258
- blob_id = await blob_upload_file(fp, self._client.stub)
240
+ progress_task_id = progress_cb(name=remote_path, size=data_size)
241
+ blob_id = await blob_upload_file(
242
+ fp,
243
+ self._client.stub,
244
+ progress_report_cb=functools.partial(progress_cb, progress_task_id),
245
+ sha256_hex=sha_hash,
246
+ )
259
247
  req = api_pb2.SharedVolumePutFileRequest(
260
248
  shared_volume_id=self.object_id,
261
249
  path=remote_path,
@@ -299,16 +287,20 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
299
287
 
300
288
  * Passing a directory path lists all files in the directory (names are relative to the directory)
301
289
  * Passing a file path returns a list containing only that file's listing description
302
- * Passing a glob path (including at least one * or ** sequence) returns all files matching that glob path (using absolute paths)
290
+ * Passing a glob path (including at least one * or ** sequence) returns all files matching
291
+ that glob path (using absolute paths)
303
292
  """
304
293
  req = api_pb2.SharedVolumeListFilesRequest(shared_volume_id=self.object_id, path=path)
305
- async for batch in unary_stream(self._client.stub.SharedVolumeListFilesStream, req):
294
+ async for batch in self._client.stub.SharedVolumeListFilesStream.unary_stream(req):
306
295
  for entry in batch.entries:
307
296
  yield FileEntry._from_proto(entry)
308
297
 
309
298
  @live_method
310
299
  async def add_local_file(
311
- self, local_path: Union[Path, str], remote_path: Optional[Union[str, PurePosixPath, None]] = None
300
+ self,
301
+ local_path: Union[Path, str],
302
+ remote_path: Optional[Union[str, PurePosixPath, None]] = None,
303
+ progress_cb: Optional[Callable[..., Any]] = None,
312
304
  ):
313
305
  local_path = Path(local_path)
314
306
  if remote_path is None:
@@ -317,13 +309,14 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
317
309
  remote_path = PurePosixPath(remote_path).as_posix()
318
310
 
319
311
  with local_path.open("rb") as local_file:
320
- return await self.write_file(remote_path, local_file)
312
+ return await self.write_file(remote_path, local_file, progress_cb=progress_cb)
321
313
 
322
314
  @live_method
323
315
  async def add_local_dir(
324
316
  self,
325
317
  local_path: Union[Path, str],
326
318
  remote_path: Optional[Union[str, PurePosixPath, None]] = None,
319
+ progress_cb: Optional[Callable[..., Any]] = None,
327
320
  ):
328
321
  _local_path = Path(local_path)
329
322
  if remote_path is None:
@@ -338,17 +331,23 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
338
331
  if subpath.is_dir():
339
332
  continue
340
333
  relpath_str = subpath.relative_to(_local_path).as_posix()
341
- yield self.add_local_file(subpath, PurePosixPath(remote_path, relpath_str))
334
+ yield subpath, PurePosixPath(remote_path, relpath_str)
335
+
336
+ async def _add_local_file(paths: tuple[Path, PurePosixPath]) -> int:
337
+ return await self.add_local_file(paths[0], paths[1], progress_cb)
342
338
 
343
- await ConcurrencyPool(20).run_coros(gen_transfers(), return_exceptions=True)
339
+ async with aclosing(async_map(sync_or_async_iter(gen_transfers()), _add_local_file, concurrency=20)) as stream:
340
+ async for _ in stream: # consume/execute the map
341
+ pass
344
342
 
345
343
  @live_method
346
- async def listdir(self, path: str) -> List[FileEntry]:
344
+ async def listdir(self, path: str) -> list[FileEntry]:
347
345
  """List all files in a directory in the network file system.
348
346
 
349
347
  * Passing a directory path lists all files in the directory (names are relative to the directory)
350
348
  * Passing a file path returns a list containing only that file's listing description
351
- * Passing a glob path (including at least one * or ** sequence) returns all files matching that glob path (using absolute paths)
349
+ * Passing a glob path (including at least one * or ** sequence) returns all files matching
350
+ that glob path (using absolute paths)
352
351
  """
353
352
  return [entry async for entry in self.iterdir(path)]
354
353
 
@@ -1,3 +1,4 @@
1
+ import collections.abc
1
2
  import modal.client
2
3
  import modal.object
3
4
  import modal.volume
@@ -7,160 +8,200 @@ import synchronicity.combined_types
7
8
  import typing
8
9
  import typing_extensions
9
10
 
10
- def network_file_system_mount_protos(validated_network_file_systems: typing.List[typing.Tuple[str, _NetworkFileSystem]], allow_cross_region_volumes: bool) -> typing.List[modal_proto.api_pb2.SharedVolumeMount]:
11
- ...
12
-
11
+ def network_file_system_mount_protos(
12
+ validated_network_file_systems: list[tuple[str, _NetworkFileSystem]], allow_cross_region_volumes: bool
13
+ ) -> list[modal_proto.api_pb2.SharedVolumeMount]: ...
13
14
 
14
15
  class _NetworkFileSystem(modal.object._Object):
15
16
  @staticmethod
16
- def new(cloud: typing.Union[str, None] = None) -> _NetworkFileSystem:
17
- ...
18
-
19
- @staticmethod
20
- def from_name(label: str, namespace=1, environment_name: typing.Union[str, None] = None, create_if_missing: bool = False) -> _NetworkFileSystem:
21
- ...
22
-
17
+ def from_name(
18
+ name: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
19
+ ) -> _NetworkFileSystem: ...
23
20
  @classmethod
24
- def ephemeral(cls: typing.Type[_NetworkFileSystem], client: typing.Union[modal.client._Client, None] = None, environment_name: typing.Union[str, None] = None, _heartbeat_sleep: float = 300) -> typing.AsyncContextManager[_NetworkFileSystem]:
25
- ...
26
-
21
+ def ephemeral(
22
+ cls: type[_NetworkFileSystem],
23
+ client: typing.Optional[modal.client._Client] = None,
24
+ environment_name: typing.Optional[str] = None,
25
+ _heartbeat_sleep: float = 300,
26
+ ) -> typing.AsyncContextManager[_NetworkFileSystem]: ...
27
27
  @staticmethod
28
- def persisted(label: str, namespace=1, environment_name: typing.Union[str, None] = None, cloud: typing.Union[str, None] = None) -> _NetworkFileSystem:
29
- ...
30
-
31
- def persist(self, label: str, namespace=1, environment_name: typing.Union[str, None] = None, cloud: typing.Union[str, None] = None):
32
- ...
33
-
28
+ async def lookup(
29
+ name: str,
30
+ namespace=1,
31
+ client: typing.Optional[modal.client._Client] = None,
32
+ environment_name: typing.Optional[str] = None,
33
+ create_if_missing: bool = False,
34
+ ) -> _NetworkFileSystem: ...
34
35
  @staticmethod
35
- async def lookup(label: str, namespace=1, client: typing.Union[modal.client._Client, None] = None, environment_name: typing.Union[str, None] = None, create_if_missing: bool = False) -> _NetworkFileSystem:
36
- ...
37
-
36
+ async def create_deployed(
37
+ deployment_name: str,
38
+ namespace=1,
39
+ client: typing.Optional[modal.client._Client] = None,
40
+ environment_name: typing.Optional[str] = None,
41
+ ) -> str: ...
38
42
  @staticmethod
39
- async def create_deployed(deployment_name: str, namespace=1, client: typing.Union[modal.client._Client, None] = None, environment_name: typing.Union[str, None] = None) -> str:
40
- ...
41
-
42
- async def write_file(self, remote_path: str, fp: typing.BinaryIO) -> int:
43
- ...
44
-
45
- def read_file(self, path: str) -> typing.AsyncIterator[bytes]:
46
- ...
47
-
48
- def iterdir(self, path: str) -> typing.AsyncIterator[modal.volume.FileEntry]:
49
- ...
50
-
51
- async def add_local_file(self, local_path: typing.Union[pathlib.Path, str], remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None):
52
- ...
53
-
54
- async def add_local_dir(self, local_path: typing.Union[pathlib.Path, str], remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None):
55
- ...
56
-
57
- async def listdir(self, path: str) -> typing.List[modal.volume.FileEntry]:
58
- ...
59
-
60
- async def remove_file(self, path: str, recursive=False):
61
- ...
62
-
43
+ async def delete(
44
+ name: str, client: typing.Optional[modal.client._Client] = None, environment_name: typing.Optional[str] = None
45
+ ): ...
46
+ async def write_file(
47
+ self,
48
+ remote_path: str,
49
+ fp: typing.BinaryIO,
50
+ progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
51
+ ) -> int: ...
52
+ def read_file(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
53
+ def iterdir(self, path: str) -> collections.abc.AsyncIterator[modal.volume.FileEntry]: ...
54
+ async def add_local_file(
55
+ self,
56
+ local_path: typing.Union[pathlib.Path, str],
57
+ remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
58
+ progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
59
+ ): ...
60
+ async def add_local_dir(
61
+ self,
62
+ local_path: typing.Union[pathlib.Path, str],
63
+ remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
64
+ progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
65
+ ): ...
66
+ async def listdir(self, path: str) -> list[modal.volume.FileEntry]: ...
67
+ async def remove_file(self, path: str, recursive=False): ...
63
68
 
64
69
  class NetworkFileSystem(modal.object.Object):
65
- def __init__(self, *args, **kwargs):
66
- ...
67
-
70
+ def __init__(self, *args, **kwargs): ...
68
71
  @staticmethod
69
- def new(cloud: typing.Union[str, None] = None) -> NetworkFileSystem:
70
- ...
71
-
72
- @staticmethod
73
- def from_name(label: str, namespace=1, environment_name: typing.Union[str, None] = None, create_if_missing: bool = False) -> NetworkFileSystem:
74
- ...
75
-
72
+ def from_name(
73
+ name: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
74
+ ) -> NetworkFileSystem: ...
76
75
  @classmethod
77
- def ephemeral(cls: typing.Type[NetworkFileSystem], client: typing.Union[modal.client.Client, None] = None, environment_name: typing.Union[str, None] = None, _heartbeat_sleep: float = 300) -> synchronicity.combined_types.AsyncAndBlockingContextManager[NetworkFileSystem]:
78
- ...
79
-
80
- @staticmethod
81
- def persisted(label: str, namespace=1, environment_name: typing.Union[str, None] = None, cloud: typing.Union[str, None] = None) -> NetworkFileSystem:
82
- ...
83
-
84
- def persist(self, label: str, namespace=1, environment_name: typing.Union[str, None] = None, cloud: typing.Union[str, None] = None):
85
- ...
76
+ def ephemeral(
77
+ cls: type[NetworkFileSystem],
78
+ client: typing.Optional[modal.client.Client] = None,
79
+ environment_name: typing.Optional[str] = None,
80
+ _heartbeat_sleep: float = 300,
81
+ ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[NetworkFileSystem]: ...
86
82
 
87
83
  class __lookup_spec(typing_extensions.Protocol):
88
- def __call__(self, label: str, namespace=1, client: typing.Union[modal.client.Client, None] = None, environment_name: typing.Union[str, None] = None, create_if_missing: bool = False) -> NetworkFileSystem:
89
- ...
90
-
91
- async def aio(self, *args, **kwargs) -> NetworkFileSystem:
92
- ...
84
+ def __call__(
85
+ self,
86
+ name: str,
87
+ namespace=1,
88
+ client: typing.Optional[modal.client.Client] = None,
89
+ environment_name: typing.Optional[str] = None,
90
+ create_if_missing: bool = False,
91
+ ) -> NetworkFileSystem: ...
92
+ async def aio(
93
+ self,
94
+ name: str,
95
+ namespace=1,
96
+ client: typing.Optional[modal.client.Client] = None,
97
+ environment_name: typing.Optional[str] = None,
98
+ create_if_missing: bool = False,
99
+ ) -> NetworkFileSystem: ...
93
100
 
94
101
  lookup: __lookup_spec
95
102
 
96
103
  class __create_deployed_spec(typing_extensions.Protocol):
97
- def __call__(self, deployment_name: str, namespace=1, client: typing.Union[modal.client.Client, None] = None, environment_name: typing.Union[str, None] = None) -> str:
98
- ...
99
-
100
- async def aio(self, *args, **kwargs) -> str:
101
- ...
104
+ def __call__(
105
+ self,
106
+ deployment_name: str,
107
+ namespace=1,
108
+ client: typing.Optional[modal.client.Client] = None,
109
+ environment_name: typing.Optional[str] = None,
110
+ ) -> str: ...
111
+ async def aio(
112
+ self,
113
+ deployment_name: str,
114
+ namespace=1,
115
+ client: typing.Optional[modal.client.Client] = None,
116
+ environment_name: typing.Optional[str] = None,
117
+ ) -> str: ...
102
118
 
103
119
  create_deployed: __create_deployed_spec
104
120
 
105
- class __write_file_spec(typing_extensions.Protocol):
106
- def __call__(self, remote_path: str, fp: typing.BinaryIO) -> int:
107
- ...
121
+ class __delete_spec(typing_extensions.Protocol):
122
+ def __call__(
123
+ self,
124
+ name: str,
125
+ client: typing.Optional[modal.client.Client] = None,
126
+ environment_name: typing.Optional[str] = None,
127
+ ): ...
128
+ async def aio(
129
+ self,
130
+ name: str,
131
+ client: typing.Optional[modal.client.Client] = None,
132
+ environment_name: typing.Optional[str] = None,
133
+ ): ...
134
+
135
+ delete: __delete_spec
108
136
 
109
- async def aio(self, *args, **kwargs) -> int:
110
- ...
137
+ class __write_file_spec(typing_extensions.Protocol):
138
+ def __call__(
139
+ self,
140
+ remote_path: str,
141
+ fp: typing.BinaryIO,
142
+ progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
143
+ ) -> int: ...
144
+ async def aio(
145
+ self,
146
+ remote_path: str,
147
+ fp: typing.BinaryIO,
148
+ progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
149
+ ) -> int: ...
111
150
 
112
151
  write_file: __write_file_spec
113
152
 
114
153
  class __read_file_spec(typing_extensions.Protocol):
115
- def __call__(self, path: str) -> typing.Iterator[bytes]:
116
- ...
117
-
118
- def aio(self, path: str) -> typing.AsyncIterator[bytes]:
119
- ...
154
+ def __call__(self, path: str) -> typing.Iterator[bytes]: ...
155
+ def aio(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
120
156
 
121
157
  read_file: __read_file_spec
122
158
 
123
159
  class __iterdir_spec(typing_extensions.Protocol):
124
- def __call__(self, path: str) -> typing.Iterator[modal.volume.FileEntry]:
125
- ...
126
-
127
- def aio(self, path: str) -> typing.AsyncIterator[modal.volume.FileEntry]:
128
- ...
160
+ def __call__(self, path: str) -> typing.Iterator[modal.volume.FileEntry]: ...
161
+ def aio(self, path: str) -> collections.abc.AsyncIterator[modal.volume.FileEntry]: ...
129
162
 
130
163
  iterdir: __iterdir_spec
131
164
 
132
165
  class __add_local_file_spec(typing_extensions.Protocol):
133
- def __call__(self, local_path: typing.Union[pathlib.Path, str], remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None):
134
- ...
135
-
136
- async def aio(self, *args, **kwargs):
137
- ...
166
+ def __call__(
167
+ self,
168
+ local_path: typing.Union[pathlib.Path, str],
169
+ remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
170
+ progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
171
+ ): ...
172
+ async def aio(
173
+ self,
174
+ local_path: typing.Union[pathlib.Path, str],
175
+ remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
176
+ progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
177
+ ): ...
138
178
 
139
179
  add_local_file: __add_local_file_spec
140
180
 
141
181
  class __add_local_dir_spec(typing_extensions.Protocol):
142
- def __call__(self, local_path: typing.Union[pathlib.Path, str], remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None):
143
- ...
144
-
145
- async def aio(self, *args, **kwargs):
146
- ...
182
+ def __call__(
183
+ self,
184
+ local_path: typing.Union[pathlib.Path, str],
185
+ remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
186
+ progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
187
+ ): ...
188
+ async def aio(
189
+ self,
190
+ local_path: typing.Union[pathlib.Path, str],
191
+ remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
192
+ progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
193
+ ): ...
147
194
 
148
195
  add_local_dir: __add_local_dir_spec
149
196
 
150
197
  class __listdir_spec(typing_extensions.Protocol):
151
- def __call__(self, path: str) -> typing.List[modal.volume.FileEntry]:
152
- ...
153
-
154
- async def aio(self, *args, **kwargs) -> typing.List[modal.volume.FileEntry]:
155
- ...
198
+ def __call__(self, path: str) -> list[modal.volume.FileEntry]: ...
199
+ async def aio(self, path: str) -> list[modal.volume.FileEntry]: ...
156
200
 
157
201
  listdir: __listdir_spec
158
202
 
159
203
  class __remove_file_spec(typing_extensions.Protocol):
160
- def __call__(self, path: str, recursive=False):
161
- ...
162
-
163
- async def aio(self, *args, **kwargs):
164
- ...
204
+ def __call__(self, path: str, recursive=False): ...
205
+ async def aio(self, path: str, recursive=False): ...
165
206
 
166
207
  remove_file: __remove_file_spec