modal 1.0.3.dev10__py3-none-any.whl → 1.2.3.dev7__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.

Potentially problematic release.


This version of modal might be problematic. Click here for more details.

Files changed (160) hide show
  1. modal/__init__.py +0 -2
  2. modal/__main__.py +3 -4
  3. modal/_billing.py +80 -0
  4. modal/_clustered_functions.py +7 -3
  5. modal/_clustered_functions.pyi +15 -3
  6. modal/_container_entrypoint.py +51 -69
  7. modal/_functions.py +508 -240
  8. modal/_grpc_client.py +171 -0
  9. modal/_load_context.py +105 -0
  10. modal/_object.py +81 -21
  11. modal/_output.py +58 -45
  12. modal/_partial_function.py +48 -73
  13. modal/_pty.py +7 -3
  14. modal/_resolver.py +26 -46
  15. modal/_runtime/asgi.py +4 -3
  16. modal/_runtime/container_io_manager.py +358 -220
  17. modal/_runtime/container_io_manager.pyi +296 -101
  18. modal/_runtime/execution_context.py +18 -2
  19. modal/_runtime/execution_context.pyi +64 -7
  20. modal/_runtime/gpu_memory_snapshot.py +262 -57
  21. modal/_runtime/user_code_imports.py +28 -58
  22. modal/_serialization.py +90 -6
  23. modal/_traceback.py +42 -1
  24. modal/_tunnel.pyi +380 -12
  25. modal/_utils/async_utils.py +84 -29
  26. modal/_utils/auth_token_manager.py +111 -0
  27. modal/_utils/blob_utils.py +181 -58
  28. modal/_utils/deprecation.py +19 -0
  29. modal/_utils/function_utils.py +91 -47
  30. modal/_utils/grpc_utils.py +89 -66
  31. modal/_utils/mount_utils.py +26 -1
  32. modal/_utils/name_utils.py +17 -3
  33. modal/_utils/task_command_router_client.py +536 -0
  34. modal/_utils/time_utils.py +34 -6
  35. modal/app.py +256 -88
  36. modal/app.pyi +909 -92
  37. modal/billing.py +5 -0
  38. modal/builder/2025.06.txt +18 -0
  39. modal/builder/PREVIEW.txt +18 -0
  40. modal/builder/base-images.json +58 -0
  41. modal/cli/_download.py +19 -3
  42. modal/cli/_traceback.py +3 -2
  43. modal/cli/app.py +4 -4
  44. modal/cli/cluster.py +15 -7
  45. modal/cli/config.py +5 -3
  46. modal/cli/container.py +7 -6
  47. modal/cli/dict.py +22 -16
  48. modal/cli/entry_point.py +12 -5
  49. modal/cli/environment.py +5 -4
  50. modal/cli/import_refs.py +3 -3
  51. modal/cli/launch.py +102 -5
  52. modal/cli/network_file_system.py +11 -12
  53. modal/cli/profile.py +3 -2
  54. modal/cli/programs/launch_instance_ssh.py +94 -0
  55. modal/cli/programs/run_jupyter.py +1 -1
  56. modal/cli/programs/run_marimo.py +95 -0
  57. modal/cli/programs/vscode.py +1 -1
  58. modal/cli/queues.py +57 -26
  59. modal/cli/run.py +91 -23
  60. modal/cli/secret.py +48 -22
  61. modal/cli/token.py +7 -8
  62. modal/cli/utils.py +4 -7
  63. modal/cli/volume.py +31 -25
  64. modal/client.py +15 -85
  65. modal/client.pyi +183 -62
  66. modal/cloud_bucket_mount.py +5 -3
  67. modal/cloud_bucket_mount.pyi +197 -5
  68. modal/cls.py +200 -126
  69. modal/cls.pyi +446 -68
  70. modal/config.py +29 -11
  71. modal/container_process.py +319 -19
  72. modal/container_process.pyi +190 -20
  73. modal/dict.py +290 -71
  74. modal/dict.pyi +835 -83
  75. modal/environments.py +15 -27
  76. modal/environments.pyi +46 -24
  77. modal/exception.py +14 -2
  78. modal/experimental/__init__.py +194 -40
  79. modal/experimental/flash.py +618 -0
  80. modal/experimental/flash.pyi +380 -0
  81. modal/experimental/ipython.py +11 -7
  82. modal/file_io.py +29 -36
  83. modal/file_io.pyi +251 -53
  84. modal/file_pattern_matcher.py +56 -16
  85. modal/functions.pyi +673 -92
  86. modal/gpu.py +1 -1
  87. modal/image.py +528 -176
  88. modal/image.pyi +1572 -145
  89. modal/io_streams.py +458 -128
  90. modal/io_streams.pyi +433 -52
  91. modal/mount.py +216 -151
  92. modal/mount.pyi +225 -78
  93. modal/network_file_system.py +45 -62
  94. modal/network_file_system.pyi +277 -56
  95. modal/object.pyi +93 -17
  96. modal/parallel_map.py +942 -129
  97. modal/parallel_map.pyi +294 -15
  98. modal/partial_function.py +0 -2
  99. modal/partial_function.pyi +234 -19
  100. modal/proxy.py +17 -8
  101. modal/proxy.pyi +36 -3
  102. modal/queue.py +270 -65
  103. modal/queue.pyi +817 -57
  104. modal/runner.py +115 -101
  105. modal/runner.pyi +205 -49
  106. modal/sandbox.py +512 -136
  107. modal/sandbox.pyi +845 -111
  108. modal/schedule.py +1 -1
  109. modal/secret.py +300 -70
  110. modal/secret.pyi +589 -34
  111. modal/serving.py +7 -11
  112. modal/serving.pyi +7 -8
  113. modal/snapshot.py +11 -8
  114. modal/snapshot.pyi +25 -4
  115. modal/token_flow.py +4 -4
  116. modal/token_flow.pyi +28 -8
  117. modal/volume.py +416 -158
  118. modal/volume.pyi +1117 -121
  119. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/METADATA +10 -9
  120. modal-1.2.3.dev7.dist-info/RECORD +195 -0
  121. modal_docs/mdmd/mdmd.py +17 -4
  122. modal_proto/api.proto +534 -79
  123. modal_proto/api_grpc.py +337 -1
  124. modal_proto/api_pb2.py +1522 -968
  125. modal_proto/api_pb2.pyi +1619 -134
  126. modal_proto/api_pb2_grpc.py +699 -4
  127. modal_proto/api_pb2_grpc.pyi +226 -14
  128. modal_proto/modal_api_grpc.py +175 -154
  129. modal_proto/sandbox_router.proto +145 -0
  130. modal_proto/sandbox_router_grpc.py +105 -0
  131. modal_proto/sandbox_router_pb2.py +149 -0
  132. modal_proto/sandbox_router_pb2.pyi +333 -0
  133. modal_proto/sandbox_router_pb2_grpc.py +203 -0
  134. modal_proto/sandbox_router_pb2_grpc.pyi +75 -0
  135. modal_proto/task_command_router.proto +144 -0
  136. modal_proto/task_command_router_grpc.py +105 -0
  137. modal_proto/task_command_router_pb2.py +149 -0
  138. modal_proto/task_command_router_pb2.pyi +333 -0
  139. modal_proto/task_command_router_pb2_grpc.py +203 -0
  140. modal_proto/task_command_router_pb2_grpc.pyi +75 -0
  141. modal_version/__init__.py +1 -1
  142. modal/requirements/PREVIEW.txt +0 -16
  143. modal/requirements/base-images.json +0 -26
  144. modal-1.0.3.dev10.dist-info/RECORD +0 -179
  145. modal_proto/modal_options_grpc.py +0 -3
  146. modal_proto/options.proto +0 -19
  147. modal_proto/options_grpc.py +0 -3
  148. modal_proto/options_pb2.py +0 -35
  149. modal_proto/options_pb2.pyi +0 -20
  150. modal_proto/options_pb2_grpc.py +0 -4
  151. modal_proto/options_pb2_grpc.pyi +0 -7
  152. /modal/{requirements → builder}/2023.12.312.txt +0 -0
  153. /modal/{requirements → builder}/2023.12.txt +0 -0
  154. /modal/{requirements → builder}/2024.04.txt +0 -0
  155. /modal/{requirements → builder}/2024.10.txt +0 -0
  156. /modal/{requirements → builder}/README.md +0 -0
  157. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/WHEEL +0 -0
  158. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/entry_points.txt +0 -0
  159. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/licenses/LICENSE +0 -0
  160. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/top_level.txt +0 -0
modal/volume.pyi CHANGED
@@ -1,6 +1,7 @@
1
1
  import _io
2
2
  import asyncio.locks
3
3
  import collections.abc
4
+ import datetime
4
5
  import enum
5
6
  import google.protobuf.message
6
7
  import modal._object
@@ -9,6 +10,7 @@ import modal.client
9
10
  import modal.object
10
11
  import modal_proto.api_pb2
11
12
  import pathlib
13
+ import synchronicity
12
14
  import synchronicity.combined_types
13
15
  import typing
14
16
  import typing_extensions
@@ -24,6 +26,8 @@ class FileEntryType(enum.IntEnum):
24
26
  SOCKET = 5
25
27
 
26
28
  class FileEntry:
29
+ """A file or directory entry listed from a Modal volume."""
30
+
27
31
  path: str
28
32
  type: FileEntryType
29
33
  mtime: int
@@ -31,31 +35,481 @@ class FileEntry:
31
35
 
32
36
  @classmethod
33
37
  def _from_proto(cls, proto: modal_proto.api_pb2.FileEntry) -> FileEntry: ...
34
- def __init__(self, path: str, type: FileEntryType, mtime: int, size: int) -> None: ...
35
- def __repr__(self): ...
36
- def __eq__(self, other): ...
37
- def __setattr__(self, name, value): ...
38
- def __delattr__(self, name): ...
39
- def __hash__(self): ...
38
+ def __init__(self, path: str, type: FileEntryType, mtime: int, size: int) -> None:
39
+ """Initialize self. See help(type(self)) for accurate signature."""
40
+ ...
41
+
42
+ def __repr__(self):
43
+ """Return repr(self)."""
44
+ ...
45
+
46
+ def __eq__(self, other):
47
+ """Return self==value."""
48
+ ...
49
+
50
+ def __setattr__(self, name, value):
51
+ """Implement setattr(self, name, value)."""
52
+ ...
53
+
54
+ def __delattr__(self, name):
55
+ """Implement delattr(self, name)."""
56
+ ...
57
+
58
+ def __hash__(self):
59
+ """Return hash(self)."""
60
+ ...
61
+
62
+ class VolumeInfo:
63
+ """Information about the Volume object."""
64
+
65
+ name: typing.Optional[str]
66
+ created_at: datetime.datetime
67
+ created_by: typing.Optional[str]
68
+
69
+ def __init__(
70
+ self, name: typing.Optional[str], created_at: datetime.datetime, created_by: typing.Optional[str]
71
+ ) -> None:
72
+ """Initialize self. See help(type(self)) for accurate signature."""
73
+ ...
74
+
75
+ def __repr__(self):
76
+ """Return repr(self)."""
77
+ ...
78
+
79
+ def __eq__(self, other):
80
+ """Return self==value."""
81
+ ...
82
+
83
+ class _VolumeManager:
84
+ """Namespace with methods for managing named Volume objects."""
85
+ @staticmethod
86
+ async def create(
87
+ name: str,
88
+ *,
89
+ version: typing.Optional[int] = None,
90
+ allow_existing: bool = False,
91
+ environment_name: typing.Optional[str] = None,
92
+ client: typing.Optional[modal.client._Client] = None,
93
+ ) -> None:
94
+ """Create a new Volume object.
95
+
96
+ **Examples:**
97
+
98
+ ```python notest
99
+ modal.Volume.objects.create("my-volume")
100
+ ```
101
+
102
+ Volumes will be created in the active environment, or another one can be specified:
103
+
104
+ ```python notest
105
+ modal.Volume.objects.create("my-volume", environment_name="dev")
106
+ ```
107
+
108
+ By default, an error will be raised if the Volume already exists, but passing
109
+ `allow_existing=True` will make the creation attempt a no-op in this case.
110
+
111
+ ```python notest
112
+ modal.Volume.objects.create("my-volume", allow_existing=True)
113
+ ```
114
+
115
+ Note that this method does not return a local instance of the Volume. You can use
116
+ `modal.Volume.from_name` to perform a lookup after creation.
117
+
118
+ Added in v1.1.2.
119
+ """
120
+ ...
121
+
122
+ @staticmethod
123
+ async def list(
124
+ *,
125
+ max_objects: typing.Optional[int] = None,
126
+ created_before: typing.Union[datetime.datetime, str, None] = None,
127
+ environment_name: str = "",
128
+ client: typing.Optional[modal.client._Client] = None,
129
+ ) -> list[_Volume]:
130
+ """Return a list of hydrated Volume objects.
131
+
132
+ **Examples:**
133
+
134
+ ```python
135
+ volumes = modal.Volume.objects.list()
136
+ print([v.name for v in volumes])
137
+ ```
138
+
139
+ Volumes will be retreived from the active environment, or another one can be specified:
140
+
141
+ ```python notest
142
+ dev_volumes = modal.Volume.objects.list(environment_name="dev")
143
+ ```
144
+
145
+ By default, all named Volumes are returned, newest to oldest. It's also possible to limit the
146
+ number of results and to filter by creation date:
147
+
148
+ ```python
149
+ volumes = modal.Volume.objects.list(max_objects=10, created_before="2025-01-01")
150
+ ```
151
+
152
+ Added in v1.1.2.
153
+ """
154
+ ...
155
+
156
+ @staticmethod
157
+ async def delete(
158
+ name: str,
159
+ *,
160
+ allow_missing: bool = False,
161
+ environment_name: typing.Optional[str] = None,
162
+ client: typing.Optional[modal.client._Client] = None,
163
+ ):
164
+ """Delete a named Volume.
165
+
166
+ Warning: This deletes an *entire Volume*, not just a specific file.
167
+ Deletion is irreversible and will affect any Apps currently using the Volume.
168
+
169
+ **Examples:**
170
+
171
+ ```python notest
172
+ await modal.Volume.objects.delete("my-volume")
173
+ ```
174
+
175
+ Volumes will be deleted from the active environment, or another one can be specified:
176
+
177
+ ```python notest
178
+ await modal.Volume.objects.delete("my-volume", environment_name="dev")
179
+ ```
180
+
181
+ Added in v1.1.2.
182
+ """
183
+ ...
184
+
185
+ class VolumeManager:
186
+ """Namespace with methods for managing named Volume objects."""
187
+ def __init__(self, /, *args, **kwargs):
188
+ """Initialize self. See help(type(self)) for accurate signature."""
189
+ ...
190
+
191
+ class __create_spec(typing_extensions.Protocol):
192
+ def __call__(
193
+ self,
194
+ /,
195
+ name: str,
196
+ *,
197
+ version: typing.Optional[int] = None,
198
+ allow_existing: bool = False,
199
+ environment_name: typing.Optional[str] = None,
200
+ client: typing.Optional[modal.client.Client] = None,
201
+ ) -> None:
202
+ """Create a new Volume object.
203
+
204
+ **Examples:**
205
+
206
+ ```python notest
207
+ modal.Volume.objects.create("my-volume")
208
+ ```
209
+
210
+ Volumes will be created in the active environment, or another one can be specified:
211
+
212
+ ```python notest
213
+ modal.Volume.objects.create("my-volume", environment_name="dev")
214
+ ```
215
+
216
+ By default, an error will be raised if the Volume already exists, but passing
217
+ `allow_existing=True` will make the creation attempt a no-op in this case.
218
+
219
+ ```python notest
220
+ modal.Volume.objects.create("my-volume", allow_existing=True)
221
+ ```
222
+
223
+ Note that this method does not return a local instance of the Volume. You can use
224
+ `modal.Volume.from_name` to perform a lookup after creation.
225
+
226
+ Added in v1.1.2.
227
+ """
228
+ ...
229
+
230
+ async def aio(
231
+ self,
232
+ /,
233
+ name: str,
234
+ *,
235
+ version: typing.Optional[int] = None,
236
+ allow_existing: bool = False,
237
+ environment_name: typing.Optional[str] = None,
238
+ client: typing.Optional[modal.client.Client] = None,
239
+ ) -> None:
240
+ """Create a new Volume object.
241
+
242
+ **Examples:**
243
+
244
+ ```python notest
245
+ modal.Volume.objects.create("my-volume")
246
+ ```
247
+
248
+ Volumes will be created in the active environment, or another one can be specified:
249
+
250
+ ```python notest
251
+ modal.Volume.objects.create("my-volume", environment_name="dev")
252
+ ```
253
+
254
+ By default, an error will be raised if the Volume already exists, but passing
255
+ `allow_existing=True` will make the creation attempt a no-op in this case.
256
+
257
+ ```python notest
258
+ modal.Volume.objects.create("my-volume", allow_existing=True)
259
+ ```
260
+
261
+ Note that this method does not return a local instance of the Volume. You can use
262
+ `modal.Volume.from_name` to perform a lookup after creation.
263
+
264
+ Added in v1.1.2.
265
+ """
266
+ ...
267
+
268
+ create: __create_spec
269
+
270
+ class __list_spec(typing_extensions.Protocol):
271
+ def __call__(
272
+ self,
273
+ /,
274
+ *,
275
+ max_objects: typing.Optional[int] = None,
276
+ created_before: typing.Union[datetime.datetime, str, None] = None,
277
+ environment_name: str = "",
278
+ client: typing.Optional[modal.client.Client] = None,
279
+ ) -> list[Volume]:
280
+ """Return a list of hydrated Volume objects.
281
+
282
+ **Examples:**
283
+
284
+ ```python
285
+ volumes = modal.Volume.objects.list()
286
+ print([v.name for v in volumes])
287
+ ```
288
+
289
+ Volumes will be retreived from the active environment, or another one can be specified:
290
+
291
+ ```python notest
292
+ dev_volumes = modal.Volume.objects.list(environment_name="dev")
293
+ ```
294
+
295
+ By default, all named Volumes are returned, newest to oldest. It's also possible to limit the
296
+ number of results and to filter by creation date:
297
+
298
+ ```python
299
+ volumes = modal.Volume.objects.list(max_objects=10, created_before="2025-01-01")
300
+ ```
301
+
302
+ Added in v1.1.2.
303
+ """
304
+ ...
305
+
306
+ async def aio(
307
+ self,
308
+ /,
309
+ *,
310
+ max_objects: typing.Optional[int] = None,
311
+ created_before: typing.Union[datetime.datetime, str, None] = None,
312
+ environment_name: str = "",
313
+ client: typing.Optional[modal.client.Client] = None,
314
+ ) -> list[Volume]:
315
+ """Return a list of hydrated Volume objects.
316
+
317
+ **Examples:**
318
+
319
+ ```python
320
+ volumes = modal.Volume.objects.list()
321
+ print([v.name for v in volumes])
322
+ ```
323
+
324
+ Volumes will be retreived from the active environment, or another one can be specified:
325
+
326
+ ```python notest
327
+ dev_volumes = modal.Volume.objects.list(environment_name="dev")
328
+ ```
329
+
330
+ By default, all named Volumes are returned, newest to oldest. It's also possible to limit the
331
+ number of results and to filter by creation date:
332
+
333
+ ```python
334
+ volumes = modal.Volume.objects.list(max_objects=10, created_before="2025-01-01")
335
+ ```
336
+
337
+ Added in v1.1.2.
338
+ """
339
+ ...
340
+
341
+ list: __list_spec
342
+
343
+ class __delete_spec(typing_extensions.Protocol):
344
+ def __call__(
345
+ self,
346
+ /,
347
+ name: str,
348
+ *,
349
+ allow_missing: bool = False,
350
+ environment_name: typing.Optional[str] = None,
351
+ client: typing.Optional[modal.client.Client] = None,
352
+ ):
353
+ """Delete a named Volume.
354
+
355
+ Warning: This deletes an *entire Volume*, not just a specific file.
356
+ Deletion is irreversible and will affect any Apps currently using the Volume.
357
+
358
+ **Examples:**
359
+
360
+ ```python notest
361
+ await modal.Volume.objects.delete("my-volume")
362
+ ```
363
+
364
+ Volumes will be deleted from the active environment, or another one can be specified:
365
+
366
+ ```python notest
367
+ await modal.Volume.objects.delete("my-volume", environment_name="dev")
368
+ ```
369
+
370
+ Added in v1.1.2.
371
+ """
372
+ ...
373
+
374
+ async def aio(
375
+ self,
376
+ /,
377
+ name: str,
378
+ *,
379
+ allow_missing: bool = False,
380
+ environment_name: typing.Optional[str] = None,
381
+ client: typing.Optional[modal.client.Client] = None,
382
+ ):
383
+ """Delete a named Volume.
384
+
385
+ Warning: This deletes an *entire Volume*, not just a specific file.
386
+ Deletion is irreversible and will affect any Apps currently using the Volume.
387
+
388
+ **Examples:**
389
+
390
+ ```python notest
391
+ await modal.Volume.objects.delete("my-volume")
392
+ ```
393
+
394
+ Volumes will be deleted from the active environment, or another one can be specified:
395
+
396
+ ```python notest
397
+ await modal.Volume.objects.delete("my-volume", environment_name="dev")
398
+ ```
399
+
400
+ Added in v1.1.2.
401
+ """
402
+ ...
403
+
404
+ delete: __delete_spec
40
405
 
41
406
  class _Volume(modal._object._Object):
407
+ """A writeable volume that can be used to share files between one or more Modal functions.
408
+
409
+ The contents of a volume is exposed as a filesystem. You can use it to share data between different functions, or
410
+ to persist durable state across several instances of the same function.
411
+
412
+ Unlike a networked filesystem, you need to explicitly reload the volume to see changes made since it was mounted.
413
+ Similarly, you need to explicitly commit any changes you make to the volume for the changes to become visible
414
+ outside the current container.
415
+
416
+ Concurrent modification is supported, but concurrent modifications of the same files should be avoided! Last write
417
+ wins in case of concurrent modification of the same file - any data the last writer didn't have when committing
418
+ changes will be lost!
419
+
420
+ As a result, volumes are typically not a good fit for use cases where you need to make concurrent modifications to
421
+ the same file (nor is distributed file locking supported).
422
+
423
+ Volumes can only be reloaded if there are no open files for the volume - attempting to reload with open files
424
+ will result in an error.
425
+
426
+ **Usage**
427
+
428
+ ```python
429
+ import modal
430
+
431
+ app = modal.App()
432
+ volume = modal.Volume.from_name("my-persisted-volume", create_if_missing=True)
433
+
434
+ @app.function(volumes={"/root/foo": volume})
435
+ def f():
436
+ with open("/root/foo/bar.txt", "w") as f:
437
+ f.write("hello")
438
+ volume.commit() # Persist changes
439
+
440
+ @app.function(volumes={"/root/foo": volume})
441
+ def g():
442
+ volume.reload() # Fetch latest changes
443
+ with open("/root/foo/bar.txt", "r") as f:
444
+ print(f.read())
445
+ ```
446
+ """
447
+
42
448
  _lock: typing.Optional[asyncio.locks.Lock]
43
449
  _metadata: typing.Optional[modal_proto.api_pb2.VolumeMetadata]
450
+ _read_only: bool
44
451
 
452
+ @synchronicity.classproperty
453
+ def objects(cls) -> _VolumeManager: ...
454
+ @property
455
+ def name(self) -> typing.Optional[str]: ...
456
+ def read_only(self) -> _Volume:
457
+ """Configure Volume to mount as read-only.
458
+
459
+ **Example**
460
+
461
+ ```python
462
+ import modal
463
+
464
+ volume = modal.Volume.from_name("my-volume", create_if_missing=True)
465
+
466
+ @app.function(volumes={"/mnt/items": volume.read_only()})
467
+ def f():
468
+ with open("/mnt/items/my-file.txt") as f:
469
+ return f.read()
470
+ ```
471
+
472
+ The Volume is mounted as a read-only volume in a function. Any file system write operation into the
473
+ mounted volume will result in an error.
474
+
475
+ Added in v1.0.5.
476
+ """
477
+ ...
478
+
479
+ def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
480
+ def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
45
481
  async def _get_lock(self): ...
482
+ @property
483
+ def _is_v1(self) -> bool: ...
46
484
  @staticmethod
47
485
  def from_name(
48
486
  name: str,
49
487
  *,
50
- namespace=1,
488
+ namespace=None,
51
489
  environment_name: typing.Optional[str] = None,
52
490
  create_if_missing: bool = False,
53
491
  version: typing.Optional[int] = None,
54
- ) -> _Volume: ...
55
- def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
56
- def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
57
- @property
58
- def _is_v1(self) -> bool: ...
492
+ client: typing.Optional[modal.client._Client] = None,
493
+ ) -> _Volume:
494
+ """Reference a Volume by name, creating if necessary.
495
+
496
+ This is a lazy method that defers hydrating the local
497
+ object with metadata from Modal servers until the first
498
+ time is is actually used.
499
+
500
+ ```python
501
+ vol = modal.Volume.from_name("my-volume", create_if_missing=True)
502
+
503
+ app = modal.App()
504
+
505
+ # Volume refers to the same object, even across instances of `app`.
506
+ @app.function(volumes={"/data": vol})
507
+ def f():
508
+ pass
509
+ ```
510
+ """
511
+ ...
512
+
59
513
  @classmethod
60
514
  def ephemeral(
61
515
  cls: type[_Volume],
@@ -63,52 +517,189 @@ class _Volume(modal._object._Object):
63
517
  environment_name: typing.Optional[str] = None,
64
518
  version: typing.Optional[int] = None,
65
519
  _heartbeat_sleep: float = 300,
66
- ) -> typing.AsyncContextManager[_Volume]: ...
520
+ ) -> typing.AsyncContextManager[_Volume]:
521
+ """Creates a new ephemeral volume within a context manager:
522
+
523
+ Usage:
524
+ ```python
525
+ import modal
526
+ with modal.Volume.ephemeral() as vol:
527
+ assert vol.listdir("/") == []
528
+ ```
529
+
530
+ ```python notest
531
+ async with modal.Volume.ephemeral() as vol:
532
+ assert await vol.listdir("/") == []
533
+ ```
534
+ """
535
+ ...
536
+
67
537
  @staticmethod
68
- async def lookup(
69
- name: str,
70
- namespace=1,
538
+ async def create_deployed(
539
+ deployment_name: str,
540
+ namespace=None,
71
541
  client: typing.Optional[modal.client._Client] = None,
72
542
  environment_name: typing.Optional[str] = None,
73
- create_if_missing: bool = False,
74
543
  version: typing.Optional[int] = None,
75
- ) -> _Volume: ...
544
+ ) -> str:
545
+ """mdmd:hidden"""
546
+ ...
547
+
76
548
  @staticmethod
77
- async def create_deployed(
549
+ async def _create_deployed(
78
550
  deployment_name: str,
79
- namespace=1,
551
+ namespace=None,
80
552
  client: typing.Optional[modal.client._Client] = None,
81
553
  environment_name: typing.Optional[str] = None,
82
554
  version: typing.Optional[int] = None,
83
- ) -> str: ...
555
+ ) -> str:
556
+ """mdmd:hidden"""
557
+ ...
558
+
559
+ async def info(self) -> VolumeInfo:
560
+ """Return information about the Volume object."""
561
+ ...
562
+
84
563
  async def _do_reload(self, lock=True): ...
85
- async def commit(self): ...
86
- async def reload(self): ...
87
- def iterdir(self, path: str, *, recursive: bool = True) -> collections.abc.AsyncIterator[FileEntry]: ...
88
- async def listdir(self, path: str, *, recursive: bool = False) -> list[FileEntry]: ...
89
- def read_file(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
90
- def _read_file1(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
91
- def _read_file2(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
564
+ async def commit(self):
565
+ """Commit changes to a mounted volume.
566
+
567
+ If successful, the changes made are now persisted in durable storage and available to other containers accessing
568
+ the volume.
569
+ """
570
+ ...
571
+
572
+ async def reload(self):
573
+ """Make latest committed state of volume available in the running container.
574
+
575
+ Any uncommitted changes to the volume, such as new or modified files, may implicitly be committed when
576
+ reloading.
577
+
578
+ Reloading will fail if there are open files for the volume.
579
+ """
580
+ ...
581
+
582
+ def iterdir(self, path: str, *, recursive: bool = True) -> collections.abc.AsyncIterator[FileEntry]:
583
+ """Iterate over all files in a directory in the volume.
584
+
585
+ Passing a directory path lists all files in the directory. For a file path, return only that
586
+ file's description. If `recursive` is set to True, list all files and folders under the path
587
+ recursively.
588
+ """
589
+ ...
590
+
591
+ async def listdir(self, path: str, *, recursive: bool = False) -> list[FileEntry]:
592
+ """List all files under a path prefix in the modal.Volume.
593
+
594
+ Passing a directory path lists all files in the directory. For a file path, return only that
595
+ file's description. If `recursive` is set to True, list all files and folders under the path
596
+ recursively.
597
+ """
598
+ ...
599
+
600
+ def read_file(self, path: str) -> collections.abc.AsyncIterator[bytes]:
601
+ """Read a file from the modal.Volume.
602
+
603
+ Note - this function is primarily intended to be used outside of a Modal App.
604
+ For more information on downloading files from a Modal Volume, see
605
+ [the guide](https://modal.com/docs/guide/volumes).
606
+
607
+ **Example:**
608
+
609
+ ```python notest
610
+ vol = modal.Volume.from_name("my-modal-volume")
611
+ data = b""
612
+ for chunk in vol.read_file("1mb.csv"):
613
+ data += chunk
614
+ print(len(data)) # == 1024 * 1024
615
+ ```
616
+ """
617
+ ...
618
+
92
619
  async def read_file_into_fileobj(
93
620
  self,
94
621
  path: str,
95
622
  fileobj: typing.IO[bytes],
96
623
  progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
624
+ ) -> int:
625
+ """mdmd:hidden
626
+ Read volume file into file-like IO object.
627
+ """
628
+ ...
629
+
630
+ async def _read_file_into_fileobj(
631
+ self,
632
+ path: str,
633
+ fileobj: typing.IO[bytes],
634
+ concurrency: typing.Optional[int] = None,
635
+ download_semaphore: typing.Optional[asyncio.locks.Semaphore] = None,
636
+ progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
97
637
  ) -> int: ...
98
- async def _read_file_into_fileobj1(
99
- self, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
100
- ) -> int: ...
101
- async def _read_file_into_fileobj2(
102
- self, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
103
- ) -> int: ...
104
- async def remove_file(self, path: str, recursive: bool = False) -> None: ...
105
- async def copy_files(self, src_paths: collections.abc.Sequence[str], dst_path: str) -> None: ...
106
- async def batch_upload(self, force: bool = False) -> _AbstractVolumeUploadContextManager: ...
638
+ async def remove_file(self, path: str, recursive: bool = False) -> None:
639
+ """Remove a file or directory from a volume."""
640
+ ...
641
+
642
+ async def copy_files(
643
+ self, src_paths: collections.abc.Sequence[str], dst_path: str, recursive: bool = False
644
+ ) -> None:
645
+ """Copy files within the volume from src_paths to dst_path.
646
+ The semantics of the copy operation follow those of the UNIX cp command.
647
+
648
+ The `src_paths` parameter is a list. If you want to copy a single file, you should pass a list with a
649
+ single element.
650
+
651
+ `src_paths` and `dst_path` should refer to the desired location *inside* the volume. You do not need to prepend
652
+ the volume mount path.
653
+
654
+ **Usage**
655
+
656
+ ```python notest
657
+ vol = modal.Volume.from_name("my-modal-volume")
658
+
659
+ vol.copy_files(["bar/example.txt"], "bar2") # Copy files to another directory
660
+ vol.copy_files(["bar/example.txt"], "bar/example2.txt") # Rename a file by copying
661
+ ```
662
+
663
+ Note that if the volume is already mounted on the Modal function, you should use normal filesystem operations
664
+ like `os.rename()` and then `commit()` the volume. The `copy_files()` method is useful when you don't have
665
+ the volume mounted as a filesystem, e.g. when running a script on your local computer.
666
+ """
667
+ ...
668
+
669
+ async def batch_upload(self, force: bool = False) -> _AbstractVolumeUploadContextManager:
670
+ """Initiate a batched upload to a volume.
671
+
672
+ To allow overwriting existing files, set `force` to `True` (you cannot overwrite existing directories with
673
+ uploaded files regardless).
674
+
675
+ **Example:**
676
+
677
+ ```python notest
678
+ vol = modal.Volume.from_name("my-modal-volume")
679
+
680
+ with vol.batch_upload() as batch:
681
+ batch.put_file("local-path.txt", "/remote-path.txt")
682
+ batch.put_directory("/local/directory/", "/remote/directory")
683
+ batch.put_file(io.BytesIO(b"some data"), "/foobar")
684
+ ```
685
+ """
686
+ ...
687
+
107
688
  async def _instance_delete(self): ...
108
689
  @staticmethod
109
690
  async def delete(
110
691
  name: str, client: typing.Optional[modal.client._Client] = None, environment_name: typing.Optional[str] = None
111
- ): ...
692
+ ):
693
+ """mdmd:hidden
694
+ Delete a named Volume.
695
+
696
+ Warning: This deletes an *entire Volume*, not just a specific file.
697
+ Deletion is irreversible and will affect any Apps currently using the Volume.
698
+
699
+ DEPRECATED: This method is deprecated; we recommend using `modal.Volume.objects.delete` instead.
700
+ """
701
+ ...
702
+
112
703
  @staticmethod
113
704
  async def rename(
114
705
  old_name: str,
@@ -121,10 +712,84 @@ class _Volume(modal._object._Object):
121
712
  SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
122
713
 
123
714
  class Volume(modal.object.Object):
715
+ """A writeable volume that can be used to share files between one or more Modal functions.
716
+
717
+ The contents of a volume is exposed as a filesystem. You can use it to share data between different functions, or
718
+ to persist durable state across several instances of the same function.
719
+
720
+ Unlike a networked filesystem, you need to explicitly reload the volume to see changes made since it was mounted.
721
+ Similarly, you need to explicitly commit any changes you make to the volume for the changes to become visible
722
+ outside the current container.
723
+
724
+ Concurrent modification is supported, but concurrent modifications of the same files should be avoided! Last write
725
+ wins in case of concurrent modification of the same file - any data the last writer didn't have when committing
726
+ changes will be lost!
727
+
728
+ As a result, volumes are typically not a good fit for use cases where you need to make concurrent modifications to
729
+ the same file (nor is distributed file locking supported).
730
+
731
+ Volumes can only be reloaded if there are no open files for the volume - attempting to reload with open files
732
+ will result in an error.
733
+
734
+ **Usage**
735
+
736
+ ```python
737
+ import modal
738
+
739
+ app = modal.App()
740
+ volume = modal.Volume.from_name("my-persisted-volume", create_if_missing=True)
741
+
742
+ @app.function(volumes={"/root/foo": volume})
743
+ def f():
744
+ with open("/root/foo/bar.txt", "w") as f:
745
+ f.write("hello")
746
+ volume.commit() # Persist changes
747
+
748
+ @app.function(volumes={"/root/foo": volume})
749
+ def g():
750
+ volume.reload() # Fetch latest changes
751
+ with open("/root/foo/bar.txt", "r") as f:
752
+ print(f.read())
753
+ ```
754
+ """
755
+
124
756
  _lock: typing.Optional[asyncio.locks.Lock]
125
757
  _metadata: typing.Optional[modal_proto.api_pb2.VolumeMetadata]
758
+ _read_only: bool
759
+
760
+ def __init__(self, *args, **kwargs):
761
+ """mdmd:hidden"""
762
+ ...
763
+
764
+ @synchronicity.classproperty
765
+ def objects(cls) -> VolumeManager: ...
766
+ @property
767
+ def name(self) -> typing.Optional[str]: ...
768
+ def read_only(self) -> Volume:
769
+ """Configure Volume to mount as read-only.
770
+
771
+ **Example**
126
772
 
127
- def __init__(self, *args, **kwargs): ...
773
+ ```python
774
+ import modal
775
+
776
+ volume = modal.Volume.from_name("my-volume", create_if_missing=True)
777
+
778
+ @app.function(volumes={"/mnt/items": volume.read_only()})
779
+ def f():
780
+ with open("/mnt/items/my-file.txt") as f:
781
+ return f.read()
782
+ ```
783
+
784
+ The Volume is mounted as a read-only volume in a function. Any file system write operation into the
785
+ mounted volume will result in an error.
786
+
787
+ Added in v1.0.5.
788
+ """
789
+ ...
790
+
791
+ def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
792
+ def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
128
793
 
129
794
  class ___get_lock_spec(typing_extensions.Protocol[SUPERSELF]):
130
795
  def __call__(self, /): ...
@@ -132,19 +797,37 @@ class Volume(modal.object.Object):
132
797
 
133
798
  _get_lock: ___get_lock_spec[typing_extensions.Self]
134
799
 
800
+ @property
801
+ def _is_v1(self) -> bool: ...
135
802
  @staticmethod
136
803
  def from_name(
137
804
  name: str,
138
805
  *,
139
- namespace=1,
806
+ namespace=None,
140
807
  environment_name: typing.Optional[str] = None,
141
808
  create_if_missing: bool = False,
142
809
  version: typing.Optional[int] = None,
143
- ) -> Volume: ...
144
- def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
145
- def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
146
- @property
147
- def _is_v1(self) -> bool: ...
810
+ client: typing.Optional[modal.client.Client] = None,
811
+ ) -> Volume:
812
+ """Reference a Volume by name, creating if necessary.
813
+
814
+ This is a lazy method that defers hydrating the local
815
+ object with metadata from Modal servers until the first
816
+ time is is actually used.
817
+
818
+ ```python
819
+ vol = modal.Volume.from_name("my-volume", create_if_missing=True)
820
+
821
+ app = modal.App()
822
+
823
+ # Volume refers to the same object, even across instances of `app`.
824
+ @app.function(volumes={"/data": vol})
825
+ def f():
826
+ pass
827
+ ```
828
+ """
829
+ ...
830
+
148
831
  @classmethod
149
832
  def ephemeral(
150
833
  cls: type[Volume],
@@ -152,53 +835,87 @@ class Volume(modal.object.Object):
152
835
  environment_name: typing.Optional[str] = None,
153
836
  version: typing.Optional[int] = None,
154
837
  _heartbeat_sleep: float = 300,
155
- ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Volume]: ...
838
+ ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Volume]:
839
+ """Creates a new ephemeral volume within a context manager:
840
+
841
+ Usage:
842
+ ```python
843
+ import modal
844
+ with modal.Volume.ephemeral() as vol:
845
+ assert vol.listdir("/") == []
846
+ ```
847
+
848
+ ```python notest
849
+ async with modal.Volume.ephemeral() as vol:
850
+ assert await vol.listdir("/") == []
851
+ ```
852
+ """
853
+ ...
156
854
 
157
- class __lookup_spec(typing_extensions.Protocol):
855
+ class __create_deployed_spec(typing_extensions.Protocol):
158
856
  def __call__(
159
857
  self,
160
858
  /,
161
- name: str,
162
- namespace=1,
859
+ deployment_name: str,
860
+ namespace=None,
163
861
  client: typing.Optional[modal.client.Client] = None,
164
862
  environment_name: typing.Optional[str] = None,
165
- create_if_missing: bool = False,
166
863
  version: typing.Optional[int] = None,
167
- ) -> Volume: ...
864
+ ) -> str:
865
+ """mdmd:hidden"""
866
+ ...
867
+
168
868
  async def aio(
169
869
  self,
170
870
  /,
171
- name: str,
172
- namespace=1,
871
+ deployment_name: str,
872
+ namespace=None,
173
873
  client: typing.Optional[modal.client.Client] = None,
174
874
  environment_name: typing.Optional[str] = None,
175
- create_if_missing: bool = False,
176
875
  version: typing.Optional[int] = None,
177
- ) -> Volume: ...
876
+ ) -> str:
877
+ """mdmd:hidden"""
878
+ ...
178
879
 
179
- lookup: __lookup_spec
880
+ create_deployed: __create_deployed_spec
180
881
 
181
- class __create_deployed_spec(typing_extensions.Protocol):
882
+ class ___create_deployed_spec(typing_extensions.Protocol):
182
883
  def __call__(
183
884
  self,
184
885
  /,
185
886
  deployment_name: str,
186
- namespace=1,
887
+ namespace=None,
187
888
  client: typing.Optional[modal.client.Client] = None,
188
889
  environment_name: typing.Optional[str] = None,
189
890
  version: typing.Optional[int] = None,
190
- ) -> str: ...
891
+ ) -> str:
892
+ """mdmd:hidden"""
893
+ ...
894
+
191
895
  async def aio(
192
896
  self,
193
897
  /,
194
898
  deployment_name: str,
195
- namespace=1,
899
+ namespace=None,
196
900
  client: typing.Optional[modal.client.Client] = None,
197
901
  environment_name: typing.Optional[str] = None,
198
902
  version: typing.Optional[int] = None,
199
- ) -> str: ...
903
+ ) -> str:
904
+ """mdmd:hidden"""
905
+ ...
200
906
 
201
- create_deployed: __create_deployed_spec
907
+ _create_deployed: ___create_deployed_spec
908
+
909
+ class __info_spec(typing_extensions.Protocol[SUPERSELF]):
910
+ def __call__(self, /) -> VolumeInfo:
911
+ """Return information about the Volume object."""
912
+ ...
913
+
914
+ async def aio(self, /) -> VolumeInfo:
915
+ """Return information about the Volume object."""
916
+ ...
917
+
918
+ info: __info_spec[typing_extensions.Self]
202
919
 
203
920
  class ___do_reload_spec(typing_extensions.Protocol[SUPERSELF]):
204
921
  def __call__(self, /, lock=True): ...
@@ -207,46 +924,129 @@ class Volume(modal.object.Object):
207
924
  _do_reload: ___do_reload_spec[typing_extensions.Self]
208
925
 
209
926
  class __commit_spec(typing_extensions.Protocol[SUPERSELF]):
210
- def __call__(self, /): ...
211
- async def aio(self, /): ...
927
+ def __call__(self, /):
928
+ """Commit changes to a mounted volume.
929
+
930
+ If successful, the changes made are now persisted in durable storage and available to other containers accessing
931
+ the volume.
932
+ """
933
+ ...
934
+
935
+ async def aio(self, /):
936
+ """Commit changes to a mounted volume.
937
+
938
+ If successful, the changes made are now persisted in durable storage and available to other containers accessing
939
+ the volume.
940
+ """
941
+ ...
212
942
 
213
943
  commit: __commit_spec[typing_extensions.Self]
214
944
 
215
945
  class __reload_spec(typing_extensions.Protocol[SUPERSELF]):
216
- def __call__(self, /): ...
217
- async def aio(self, /): ...
946
+ def __call__(self, /):
947
+ """Make latest committed state of volume available in the running container.
948
+
949
+ Any uncommitted changes to the volume, such as new or modified files, may implicitly be committed when
950
+ reloading.
951
+
952
+ Reloading will fail if there are open files for the volume.
953
+ """
954
+ ...
955
+
956
+ async def aio(self, /):
957
+ """Make latest committed state of volume available in the running container.
958
+
959
+ Any uncommitted changes to the volume, such as new or modified files, may implicitly be committed when
960
+ reloading.
961
+
962
+ Reloading will fail if there are open files for the volume.
963
+ """
964
+ ...
218
965
 
219
966
  reload: __reload_spec[typing_extensions.Self]
220
967
 
221
968
  class __iterdir_spec(typing_extensions.Protocol[SUPERSELF]):
222
- def __call__(self, /, path: str, *, recursive: bool = True) -> typing.Iterator[FileEntry]: ...
223
- def aio(self, /, path: str, *, recursive: bool = True) -> collections.abc.AsyncIterator[FileEntry]: ...
969
+ def __call__(self, /, path: str, *, recursive: bool = True) -> typing.Iterator[FileEntry]:
970
+ """Iterate over all files in a directory in the volume.
971
+
972
+ Passing a directory path lists all files in the directory. For a file path, return only that
973
+ file's description. If `recursive` is set to True, list all files and folders under the path
974
+ recursively.
975
+ """
976
+ ...
977
+
978
+ def aio(self, /, path: str, *, recursive: bool = True) -> collections.abc.AsyncIterator[FileEntry]:
979
+ """Iterate over all files in a directory in the volume.
980
+
981
+ Passing a directory path lists all files in the directory. For a file path, return only that
982
+ file's description. If `recursive` is set to True, list all files and folders under the path
983
+ recursively.
984
+ """
985
+ ...
224
986
 
225
987
  iterdir: __iterdir_spec[typing_extensions.Self]
226
988
 
227
989
  class __listdir_spec(typing_extensions.Protocol[SUPERSELF]):
228
- def __call__(self, /, path: str, *, recursive: bool = False) -> list[FileEntry]: ...
229
- async def aio(self, /, path: str, *, recursive: bool = False) -> list[FileEntry]: ...
990
+ def __call__(self, /, path: str, *, recursive: bool = False) -> list[FileEntry]:
991
+ """List all files under a path prefix in the modal.Volume.
230
992
 
231
- listdir: __listdir_spec[typing_extensions.Self]
993
+ Passing a directory path lists all files in the directory. For a file path, return only that
994
+ file's description. If `recursive` is set to True, list all files and folders under the path
995
+ recursively.
996
+ """
997
+ ...
232
998
 
233
- class __read_file_spec(typing_extensions.Protocol[SUPERSELF]):
234
- def __call__(self, /, path: str) -> typing.Iterator[bytes]: ...
235
- def aio(self, /, path: str) -> collections.abc.AsyncIterator[bytes]: ...
236
-
237
- read_file: __read_file_spec[typing_extensions.Self]
999
+ async def aio(self, /, path: str, *, recursive: bool = False) -> list[FileEntry]:
1000
+ """List all files under a path prefix in the modal.Volume.
238
1001
 
239
- class ___read_file1_spec(typing_extensions.Protocol[SUPERSELF]):
240
- def __call__(self, /, path: str) -> typing.Iterator[bytes]: ...
241
- def aio(self, /, path: str) -> collections.abc.AsyncIterator[bytes]: ...
1002
+ Passing a directory path lists all files in the directory. For a file path, return only that
1003
+ file's description. If `recursive` is set to True, list all files and folders under the path
1004
+ recursively.
1005
+ """
1006
+ ...
242
1007
 
243
- _read_file1: ___read_file1_spec[typing_extensions.Self]
1008
+ listdir: __listdir_spec[typing_extensions.Self]
244
1009
 
245
- class ___read_file2_spec(typing_extensions.Protocol[SUPERSELF]):
246
- def __call__(self, /, path: str) -> typing.Iterator[bytes]: ...
247
- def aio(self, /, path: str) -> collections.abc.AsyncIterator[bytes]: ...
1010
+ class __read_file_spec(typing_extensions.Protocol[SUPERSELF]):
1011
+ def __call__(self, /, path: str) -> typing.Iterator[bytes]:
1012
+ """Read a file from the modal.Volume.
1013
+
1014
+ Note - this function is primarily intended to be used outside of a Modal App.
1015
+ For more information on downloading files from a Modal Volume, see
1016
+ [the guide](https://modal.com/docs/guide/volumes).
1017
+
1018
+ **Example:**
1019
+
1020
+ ```python notest
1021
+ vol = modal.Volume.from_name("my-modal-volume")
1022
+ data = b""
1023
+ for chunk in vol.read_file("1mb.csv"):
1024
+ data += chunk
1025
+ print(len(data)) # == 1024 * 1024
1026
+ ```
1027
+ """
1028
+ ...
1029
+
1030
+ def aio(self, /, path: str) -> collections.abc.AsyncIterator[bytes]:
1031
+ """Read a file from the modal.Volume.
1032
+
1033
+ Note - this function is primarily intended to be used outside of a Modal App.
1034
+ For more information on downloading files from a Modal Volume, see
1035
+ [the guide](https://modal.com/docs/guide/volumes).
1036
+
1037
+ **Example:**
1038
+
1039
+ ```python notest
1040
+ vol = modal.Volume.from_name("my-modal-volume")
1041
+ data = b""
1042
+ for chunk in vol.read_file("1mb.csv"):
1043
+ data += chunk
1044
+ print(len(data)) # == 1024 * 1024
1045
+ ```
1046
+ """
1047
+ ...
248
1048
 
249
- _read_file2: ___read_file2_spec[typing_extensions.Self]
1049
+ read_file: __read_file_spec[typing_extensions.Self]
250
1050
 
251
1051
  class __read_file_into_fileobj_spec(typing_extensions.Protocol[SUPERSELF]):
252
1052
  def __call__(
@@ -255,52 +1055,152 @@ class Volume(modal.object.Object):
255
1055
  path: str,
256
1056
  fileobj: typing.IO[bytes],
257
1057
  progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
258
- ) -> int: ...
1058
+ ) -> int:
1059
+ """mdmd:hidden
1060
+ Read volume file into file-like IO object.
1061
+ """
1062
+ ...
1063
+
259
1064
  async def aio(
260
1065
  self,
261
1066
  /,
262
1067
  path: str,
263
1068
  fileobj: typing.IO[bytes],
264
1069
  progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
265
- ) -> int: ...
1070
+ ) -> int:
1071
+ """mdmd:hidden
1072
+ Read volume file into file-like IO object.
1073
+ """
1074
+ ...
266
1075
 
267
1076
  read_file_into_fileobj: __read_file_into_fileobj_spec[typing_extensions.Self]
268
1077
 
269
- class ___read_file_into_fileobj1_spec(typing_extensions.Protocol[SUPERSELF]):
270
- def __call__(
271
- self, /, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
272
- ) -> int: ...
273
- async def aio(
274
- self, /, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
275
- ) -> int: ...
276
-
277
- _read_file_into_fileobj1: ___read_file_into_fileobj1_spec[typing_extensions.Self]
278
-
279
- class ___read_file_into_fileobj2_spec(typing_extensions.Protocol[SUPERSELF]):
1078
+ class ___read_file_into_fileobj_spec(typing_extensions.Protocol[SUPERSELF]):
280
1079
  def __call__(
281
- self, /, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
1080
+ self,
1081
+ /,
1082
+ path: str,
1083
+ fileobj: typing.IO[bytes],
1084
+ concurrency: typing.Optional[int] = None,
1085
+ download_semaphore: typing.Optional[asyncio.locks.Semaphore] = None,
1086
+ progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
282
1087
  ) -> int: ...
283
1088
  async def aio(
284
- self, /, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
1089
+ self,
1090
+ /,
1091
+ path: str,
1092
+ fileobj: typing.IO[bytes],
1093
+ concurrency: typing.Optional[int] = None,
1094
+ download_semaphore: typing.Optional[asyncio.locks.Semaphore] = None,
1095
+ progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
285
1096
  ) -> int: ...
286
1097
 
287
- _read_file_into_fileobj2: ___read_file_into_fileobj2_spec[typing_extensions.Self]
1098
+ _read_file_into_fileobj: ___read_file_into_fileobj_spec[typing_extensions.Self]
288
1099
 
289
1100
  class __remove_file_spec(typing_extensions.Protocol[SUPERSELF]):
290
- def __call__(self, /, path: str, recursive: bool = False) -> None: ...
291
- async def aio(self, /, path: str, recursive: bool = False) -> None: ...
1101
+ def __call__(self, /, path: str, recursive: bool = False) -> None:
1102
+ """Remove a file or directory from a volume."""
1103
+ ...
1104
+
1105
+ async def aio(self, /, path: str, recursive: bool = False) -> None:
1106
+ """Remove a file or directory from a volume."""
1107
+ ...
292
1108
 
293
1109
  remove_file: __remove_file_spec[typing_extensions.Self]
294
1110
 
295
1111
  class __copy_files_spec(typing_extensions.Protocol[SUPERSELF]):
296
- def __call__(self, /, src_paths: collections.abc.Sequence[str], dst_path: str) -> None: ...
297
- async def aio(self, /, src_paths: collections.abc.Sequence[str], dst_path: str) -> None: ...
1112
+ def __call__(self, /, src_paths: collections.abc.Sequence[str], dst_path: str, recursive: bool = False) -> None:
1113
+ """Copy files within the volume from src_paths to dst_path.
1114
+ The semantics of the copy operation follow those of the UNIX cp command.
1115
+
1116
+ The `src_paths` parameter is a list. If you want to copy a single file, you should pass a list with a
1117
+ single element.
1118
+
1119
+ `src_paths` and `dst_path` should refer to the desired location *inside* the volume. You do not need to prepend
1120
+ the volume mount path.
1121
+
1122
+ **Usage**
1123
+
1124
+ ```python notest
1125
+ vol = modal.Volume.from_name("my-modal-volume")
1126
+
1127
+ vol.copy_files(["bar/example.txt"], "bar2") # Copy files to another directory
1128
+ vol.copy_files(["bar/example.txt"], "bar/example2.txt") # Rename a file by copying
1129
+ ```
1130
+
1131
+ Note that if the volume is already mounted on the Modal function, you should use normal filesystem operations
1132
+ like `os.rename()` and then `commit()` the volume. The `copy_files()` method is useful when you don't have
1133
+ the volume mounted as a filesystem, e.g. when running a script on your local computer.
1134
+ """
1135
+ ...
1136
+
1137
+ async def aio(
1138
+ self, /, src_paths: collections.abc.Sequence[str], dst_path: str, recursive: bool = False
1139
+ ) -> None:
1140
+ """Copy files within the volume from src_paths to dst_path.
1141
+ The semantics of the copy operation follow those of the UNIX cp command.
1142
+
1143
+ The `src_paths` parameter is a list. If you want to copy a single file, you should pass a list with a
1144
+ single element.
1145
+
1146
+ `src_paths` and `dst_path` should refer to the desired location *inside* the volume. You do not need to prepend
1147
+ the volume mount path.
1148
+
1149
+ **Usage**
1150
+
1151
+ ```python notest
1152
+ vol = modal.Volume.from_name("my-modal-volume")
1153
+
1154
+ vol.copy_files(["bar/example.txt"], "bar2") # Copy files to another directory
1155
+ vol.copy_files(["bar/example.txt"], "bar/example2.txt") # Rename a file by copying
1156
+ ```
1157
+
1158
+ Note that if the volume is already mounted on the Modal function, you should use normal filesystem operations
1159
+ like `os.rename()` and then `commit()` the volume. The `copy_files()` method is useful when you don't have
1160
+ the volume mounted as a filesystem, e.g. when running a script on your local computer.
1161
+ """
1162
+ ...
298
1163
 
299
1164
  copy_files: __copy_files_spec[typing_extensions.Self]
300
1165
 
301
1166
  class __batch_upload_spec(typing_extensions.Protocol[SUPERSELF]):
302
- def __call__(self, /, force: bool = False) -> AbstractVolumeUploadContextManager: ...
303
- async def aio(self, /, force: bool = False) -> AbstractVolumeUploadContextManager: ...
1167
+ def __call__(self, /, force: bool = False) -> AbstractVolumeUploadContextManager:
1168
+ """Initiate a batched upload to a volume.
1169
+
1170
+ To allow overwriting existing files, set `force` to `True` (you cannot overwrite existing directories with
1171
+ uploaded files regardless).
1172
+
1173
+ **Example:**
1174
+
1175
+ ```python notest
1176
+ vol = modal.Volume.from_name("my-modal-volume")
1177
+
1178
+ with vol.batch_upload() as batch:
1179
+ batch.put_file("local-path.txt", "/remote-path.txt")
1180
+ batch.put_directory("/local/directory/", "/remote/directory")
1181
+ batch.put_file(io.BytesIO(b"some data"), "/foobar")
1182
+ ```
1183
+ """
1184
+ ...
1185
+
1186
+ async def aio(self, /, force: bool = False) -> AbstractVolumeUploadContextManager:
1187
+ """Initiate a batched upload to a volume.
1188
+
1189
+ To allow overwriting existing files, set `force` to `True` (you cannot overwrite existing directories with
1190
+ uploaded files regardless).
1191
+
1192
+ **Example:**
1193
+
1194
+ ```python notest
1195
+ vol = modal.Volume.from_name("my-modal-volume")
1196
+
1197
+ with vol.batch_upload() as batch:
1198
+ batch.put_file("local-path.txt", "/remote-path.txt")
1199
+ batch.put_directory("/local/directory/", "/remote/directory")
1200
+ batch.put_file(io.BytesIO(b"some data"), "/foobar")
1201
+ ```
1202
+ """
1203
+ ...
304
1204
 
305
1205
  batch_upload: __batch_upload_spec[typing_extensions.Self]
306
1206
 
@@ -317,14 +1217,33 @@ class Volume(modal.object.Object):
317
1217
  name: str,
318
1218
  client: typing.Optional[modal.client.Client] = None,
319
1219
  environment_name: typing.Optional[str] = None,
320
- ): ...
1220
+ ):
1221
+ """mdmd:hidden
1222
+ Delete a named Volume.
1223
+
1224
+ Warning: This deletes an *entire Volume*, not just a specific file.
1225
+ Deletion is irreversible and will affect any Apps currently using the Volume.
1226
+
1227
+ DEPRECATED: This method is deprecated; we recommend using `modal.Volume.objects.delete` instead.
1228
+ """
1229
+ ...
1230
+
321
1231
  async def aio(
322
1232
  self,
323
1233
  /,
324
1234
  name: str,
325
1235
  client: typing.Optional[modal.client.Client] = None,
326
1236
  environment_name: typing.Optional[str] = None,
327
- ): ...
1237
+ ):
1238
+ """mdmd:hidden
1239
+ Delete a named Volume.
1240
+
1241
+ Warning: This deletes an *entire Volume*, not just a specific file.
1242
+ Deletion is irreversible and will affect any Apps currently using the Volume.
1243
+
1244
+ DEPRECATED: This method is deprecated; we recommend using `modal.Volume.objects.delete` instead.
1245
+ """
1246
+ ...
328
1247
 
329
1248
  delete: __delete_spec
330
1249
 
@@ -375,7 +1294,10 @@ class _AbstractVolumeUploadContextManager:
375
1294
  ) -> _AbstractVolumeUploadContextManager: ...
376
1295
 
377
1296
  class AbstractVolumeUploadContextManager:
378
- def __init__(self, /, *args, **kwargs): ...
1297
+ def __init__(self, /, *args, **kwargs):
1298
+ """Initialize self. See help(type(self)) for accurate signature."""
1299
+ ...
1300
+
379
1301
  def __enter__(self): ...
380
1302
  async def __aenter__(self): ...
381
1303
  def __exit__(self, exc_type, exc_val, exc_tb): ...
@@ -402,6 +1324,8 @@ class AbstractVolumeUploadContextManager:
402
1324
  ) -> AbstractVolumeUploadContextManager: ...
403
1325
 
404
1326
  class _VolumeUploadContextManager(_AbstractVolumeUploadContextManager):
1327
+ """Context manager for batch-uploading files to a Volume."""
1328
+
405
1329
  _volume_id: str
406
1330
  _client: modal.client._Client
407
1331
  _force: bool
@@ -416,7 +1340,10 @@ class _VolumeUploadContextManager(_AbstractVolumeUploadContextManager):
416
1340
  client: modal.client._Client,
417
1341
  progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
418
1342
  force: bool = False,
419
- ): ...
1343
+ ):
1344
+ """mdmd:hidden"""
1345
+ ...
1346
+
420
1347
  async def __aenter__(self): ...
421
1348
  async def __aexit__(self, exc_type, exc_val, exc_tb): ...
422
1349
  def put_file(
@@ -424,18 +1351,34 @@ class _VolumeUploadContextManager(_AbstractVolumeUploadContextManager):
424
1351
  local_file: typing.Union[pathlib.Path, str, typing.BinaryIO, _io.BytesIO],
425
1352
  remote_path: typing.Union[pathlib.PurePosixPath, str],
426
1353
  mode: typing.Optional[int] = None,
427
- ): ...
1354
+ ):
1355
+ """Upload a file from a local file or file-like object.
1356
+
1357
+ Will create any needed parent directories automatically.
1358
+
1359
+ If `local_file` is a file-like object it must remain readable for the lifetime of the batch.
1360
+ """
1361
+ ...
1362
+
428
1363
  def put_directory(
429
1364
  self,
430
1365
  local_path: typing.Union[pathlib.Path, str],
431
1366
  remote_path: typing.Union[pathlib.PurePosixPath, str],
432
1367
  recursive: bool = True,
433
- ): ...
1368
+ ):
1369
+ """Upload all files in a local directory.
1370
+
1371
+ Will create any needed parent directories automatically.
1372
+ """
1373
+ ...
1374
+
434
1375
  async def _upload_file(
435
1376
  self, file_spec: modal._utils.blob_utils.FileUploadSpec
436
1377
  ) -> modal_proto.api_pb2.MountFile: ...
437
1378
 
438
1379
  class VolumeUploadContextManager(AbstractVolumeUploadContextManager):
1380
+ """Context manager for batch-uploading files to a Volume."""
1381
+
439
1382
  _volume_id: str
440
1383
  _client: modal.client.Client
441
1384
  _force: bool
@@ -450,7 +1393,10 @@ class VolumeUploadContextManager(AbstractVolumeUploadContextManager):
450
1393
  client: modal.client.Client,
451
1394
  progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
452
1395
  force: bool = False,
453
- ): ...
1396
+ ):
1397
+ """mdmd:hidden"""
1398
+ ...
1399
+
454
1400
  def __enter__(self): ...
455
1401
  async def __aenter__(self): ...
456
1402
  def __exit__(self, exc_type, exc_val, exc_tb): ...
@@ -460,13 +1406,26 @@ class VolumeUploadContextManager(AbstractVolumeUploadContextManager):
460
1406
  local_file: typing.Union[pathlib.Path, str, typing.BinaryIO, _io.BytesIO],
461
1407
  remote_path: typing.Union[pathlib.PurePosixPath, str],
462
1408
  mode: typing.Optional[int] = None,
463
- ): ...
1409
+ ):
1410
+ """Upload a file from a local file or file-like object.
1411
+
1412
+ Will create any needed parent directories automatically.
1413
+
1414
+ If `local_file` is a file-like object it must remain readable for the lifetime of the batch.
1415
+ """
1416
+ ...
1417
+
464
1418
  def put_directory(
465
1419
  self,
466
1420
  local_path: typing.Union[pathlib.Path, str],
467
1421
  remote_path: typing.Union[pathlib.PurePosixPath, str],
468
1422
  recursive: bool = True,
469
- ): ...
1423
+ ):
1424
+ """Upload all files in a local directory.
1425
+
1426
+ Will create any needed parent directories automatically.
1427
+ """
1428
+ ...
470
1429
 
471
1430
  class ___upload_file_spec(typing_extensions.Protocol[SUPERSELF]):
472
1431
  def __call__(self, /, file_spec: modal._utils.blob_utils.FileUploadSpec) -> modal_proto.api_pb2.MountFile: ...
@@ -475,6 +1434,8 @@ class VolumeUploadContextManager(AbstractVolumeUploadContextManager):
475
1434
  _upload_file: ___upload_file_spec[typing_extensions.Self]
476
1435
 
477
1436
  class _VolumeUploadContextManager2(_AbstractVolumeUploadContextManager):
1437
+ """Context manager for batch-uploading files to a Volume version 2."""
1438
+
478
1439
  _volume_id: str
479
1440
  _client: modal.client._Client
480
1441
  _progress_cb: collections.abc.Callable[..., typing.Any]
@@ -497,7 +1458,10 @@ class _VolumeUploadContextManager2(_AbstractVolumeUploadContextManager):
497
1458
  force: bool = False,
498
1459
  hash_concurrency: int = 4,
499
1460
  put_concurrency: int = 128,
500
- ): ...
1461
+ ):
1462
+ """mdmd:hidden"""
1463
+ ...
1464
+
501
1465
  async def __aenter__(self): ...
502
1466
  async def __aexit__(self, exc_type, exc_val, exc_tb): ...
503
1467
  def put_file(
@@ -505,16 +1469,32 @@ class _VolumeUploadContextManager2(_AbstractVolumeUploadContextManager):
505
1469
  local_file: typing.Union[pathlib.Path, str, typing.BinaryIO, _io.BytesIO],
506
1470
  remote_path: typing.Union[pathlib.PurePosixPath, str],
507
1471
  mode: typing.Optional[int] = None,
508
- ): ...
1472
+ ):
1473
+ """Upload a file from a local file or file-like object.
1474
+
1475
+ Will create any needed parent directories automatically.
1476
+
1477
+ If `local_file` is a file-like object it must remain readable for the lifetime of the batch.
1478
+ """
1479
+ ...
1480
+
509
1481
  def put_directory(
510
1482
  self,
511
1483
  local_path: typing.Union[pathlib.Path, str],
512
1484
  remote_path: typing.Union[pathlib.PurePosixPath, str],
513
1485
  recursive: bool = True,
514
- ): ...
1486
+ ):
1487
+ """Upload all files in a local directory.
1488
+
1489
+ Will create any needed parent directories automatically.
1490
+ """
1491
+ ...
1492
+
515
1493
  async def _put_file_specs(self, file_specs: list[modal._utils.blob_utils.FileUploadSpec2]): ...
516
1494
 
517
1495
  class VolumeUploadContextManager2(AbstractVolumeUploadContextManager):
1496
+ """Context manager for batch-uploading files to a Volume version 2."""
1497
+
518
1498
  _volume_id: str
519
1499
  _client: modal.client.Client
520
1500
  _progress_cb: collections.abc.Callable[..., typing.Any]
@@ -537,7 +1517,10 @@ class VolumeUploadContextManager2(AbstractVolumeUploadContextManager):
537
1517
  force: bool = False,
538
1518
  hash_concurrency: int = 4,
539
1519
  put_concurrency: int = 128,
540
- ): ...
1520
+ ):
1521
+ """mdmd:hidden"""
1522
+ ...
1523
+
541
1524
  def __enter__(self): ...
542
1525
  async def __aenter__(self): ...
543
1526
  def __exit__(self, exc_type, exc_val, exc_tb): ...
@@ -547,13 +1530,26 @@ class VolumeUploadContextManager2(AbstractVolumeUploadContextManager):
547
1530
  local_file: typing.Union[pathlib.Path, str, typing.BinaryIO, _io.BytesIO],
548
1531
  remote_path: typing.Union[pathlib.PurePosixPath, str],
549
1532
  mode: typing.Optional[int] = None,
550
- ): ...
1533
+ ):
1534
+ """Upload a file from a local file or file-like object.
1535
+
1536
+ Will create any needed parent directories automatically.
1537
+
1538
+ If `local_file` is a file-like object it must remain readable for the lifetime of the batch.
1539
+ """
1540
+ ...
1541
+
551
1542
  def put_directory(
552
1543
  self,
553
1544
  local_path: typing.Union[pathlib.Path, str],
554
1545
  remote_path: typing.Union[pathlib.PurePosixPath, str],
555
1546
  recursive: bool = True,
556
- ): ...
1547
+ ):
1548
+ """Upload all files in a local directory.
1549
+
1550
+ Will create any needed parent directories automatically.
1551
+ """
1552
+ ...
557
1553
 
558
1554
  class ___put_file_specs_spec(typing_extensions.Protocol[SUPERSELF]):
559
1555
  def __call__(self, /, file_specs: list[modal._utils.blob_utils.FileUploadSpec2]): ...