modal 1.0.5.dev2__py3-none-any.whl → 1.0.5.dev4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modal/_clustered_functions.pyi +13 -3
- modal/_functions.py +5 -4
- modal/_partial_function.py +1 -1
- modal/_runtime/container_io_manager.pyi +222 -40
- modal/_runtime/execution_context.pyi +60 -6
- modal/_tunnel.pyi +380 -12
- modal/app.py +4 -4
- modal/app.pyi +658 -48
- modal/cli/run.py +2 -1
- modal/client.pyi +224 -28
- modal/cloud_bucket_mount.pyi +192 -4
- modal/cls.py +3 -3
- modal/cls.pyi +442 -35
- modal/container_process.pyi +103 -14
- modal/dict.py +1 -1
- modal/dict.pyi +453 -51
- modal/environments.pyi +41 -9
- modal/exception.py +2 -2
- modal/file_io.pyi +236 -45
- modal/functions.pyi +545 -56
- modal/gpu.py +1 -1
- modal/image.py +1 -1
- modal/image.pyi +1256 -74
- modal/io_streams.pyi +342 -39
- modal/mount.pyi +261 -31
- modal/network_file_system.pyi +307 -26
- modal/object.pyi +48 -9
- modal/parallel_map.pyi +144 -14
- modal/partial_function.pyi +255 -14
- modal/proxy.py +1 -1
- modal/proxy.pyi +28 -3
- modal/queue.py +1 -1
- modal/queue.pyi +447 -30
- modal/runner.pyi +160 -22
- modal/sandbox.py +7 -7
- modal/sandbox.pyi +310 -50
- modal/schedule.py +1 -1
- modal/secret.py +2 -2
- modal/secret.pyi +164 -15
- modal/snapshot.pyi +25 -4
- modal/token_flow.pyi +28 -8
- modal/volume.py +1 -1
- modal/volume.pyi +649 -59
- {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/METADATA +1 -1
- {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/RECORD +50 -50
- modal_version/__init__.py +1 -1
- {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/WHEEL +0 -0
- {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/entry_points.txt +0 -0
- {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/licenses/LICENSE +0 -0
- {modal-1.0.5.dev2.dist-info → modal-1.0.5.dev4.dist-info}/top_level.txt +0 -0
modal/volume.pyi
CHANGED
@@ -24,6 +24,8 @@ class FileEntryType(enum.IntEnum):
|
|
24
24
|
SOCKET = 5
|
25
25
|
|
26
26
|
class FileEntry:
|
27
|
+
"""A file or directory entry listed from a Modal volume."""
|
28
|
+
|
27
29
|
path: str
|
28
30
|
type: FileEntryType
|
29
31
|
mtime: int
|
@@ -31,14 +33,72 @@ class FileEntry:
|
|
31
33
|
|
32
34
|
@classmethod
|
33
35
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
def
|
39
|
-
|
36
|
+
def __init__(self, path: str, type: FileEntryType, mtime: int, size: int) -> None:
|
37
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
38
|
+
...
|
39
|
+
|
40
|
+
def __repr__(self):
|
41
|
+
"""Return repr(self)."""
|
42
|
+
...
|
43
|
+
|
44
|
+
def __eq__(self, other):
|
45
|
+
"""Return self==value."""
|
46
|
+
...
|
47
|
+
|
48
|
+
def __setattr__(self, name, value):
|
49
|
+
"""Implement setattr(self, name, value)."""
|
50
|
+
...
|
51
|
+
|
52
|
+
def __delattr__(self, name):
|
53
|
+
"""Implement delattr(self, name)."""
|
54
|
+
...
|
55
|
+
|
56
|
+
def __hash__(self):
|
57
|
+
"""Return hash(self)."""
|
58
|
+
...
|
40
59
|
|
41
60
|
class _Volume(modal._object._Object):
|
61
|
+
"""A writeable volume that can be used to share files between one or more Modal functions.
|
62
|
+
|
63
|
+
The contents of a volume is exposed as a filesystem. You can use it to share data between different functions, or
|
64
|
+
to persist durable state across several instances of the same function.
|
65
|
+
|
66
|
+
Unlike a networked filesystem, you need to explicitly reload the volume to see changes made since it was mounted.
|
67
|
+
Similarly, you need to explicitly commit any changes you make to the volume for the changes to become visible
|
68
|
+
outside the current container.
|
69
|
+
|
70
|
+
Concurrent modification is supported, but concurrent modifications of the same files should be avoided! Last write
|
71
|
+
wins in case of concurrent modification of the same file - any data the last writer didn't have when committing
|
72
|
+
changes will be lost!
|
73
|
+
|
74
|
+
As a result, volumes are typically not a good fit for use cases where you need to make concurrent modifications to
|
75
|
+
the same file (nor is distributed file locking supported).
|
76
|
+
|
77
|
+
Volumes can only be reloaded if there are no open files for the volume - attempting to reload with open files
|
78
|
+
will result in an error.
|
79
|
+
|
80
|
+
**Usage**
|
81
|
+
|
82
|
+
```python
|
83
|
+
import modal
|
84
|
+
|
85
|
+
app = modal.App()
|
86
|
+
volume = modal.Volume.from_name("my-persisted-volume", create_if_missing=True)
|
87
|
+
|
88
|
+
@app.function(volumes={"/root/foo": volume})
|
89
|
+
def f():
|
90
|
+
with open("/root/foo/bar.txt", "w") as f:
|
91
|
+
f.write("hello")
|
92
|
+
volume.commit() # Persist changes
|
93
|
+
|
94
|
+
@app.function(volumes={"/root/foo": volume})
|
95
|
+
def g():
|
96
|
+
volume.reload() # Fetch latest changes
|
97
|
+
with open("/root/foo/bar.txt", "r") as f:
|
98
|
+
print(f.read())
|
99
|
+
```
|
100
|
+
"""
|
101
|
+
|
42
102
|
_lock: typing.Optional[asyncio.locks.Lock]
|
43
103
|
_metadata: typing.Optional[modal_proto.api_pb2.VolumeMetadata]
|
44
104
|
|
@@ -51,7 +111,26 @@ class _Volume(modal._object._Object):
|
|
51
111
|
environment_name: typing.Optional[str] = None,
|
52
112
|
create_if_missing: bool = False,
|
53
113
|
version: typing.Optional[int] = None,
|
54
|
-
) -> _Volume:
|
114
|
+
) -> _Volume:
|
115
|
+
"""Reference a Volume by name, creating if necessary.
|
116
|
+
|
117
|
+
In contrast to `modal.Volume.lookup`, this is a lazy method
|
118
|
+
that defers hydrating the local object with metadata from
|
119
|
+
Modal servers until the first time is is actually used.
|
120
|
+
|
121
|
+
```python
|
122
|
+
vol = modal.Volume.from_name("my-volume", create_if_missing=True)
|
123
|
+
|
124
|
+
app = modal.App()
|
125
|
+
|
126
|
+
# Volume refers to the same object, even across instances of `app`.
|
127
|
+
@app.function(volumes={"/data": vol})
|
128
|
+
def f():
|
129
|
+
pass
|
130
|
+
```
|
131
|
+
"""
|
132
|
+
...
|
133
|
+
|
55
134
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
56
135
|
def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
|
57
136
|
@property
|
@@ -63,7 +142,23 @@ class _Volume(modal._object._Object):
|
|
63
142
|
environment_name: typing.Optional[str] = None,
|
64
143
|
version: typing.Optional[int] = None,
|
65
144
|
_heartbeat_sleep: float = 300,
|
66
|
-
) -> typing.AsyncContextManager[_Volume]:
|
145
|
+
) -> typing.AsyncContextManager[_Volume]:
|
146
|
+
"""Creates a new ephemeral volume within a context manager:
|
147
|
+
|
148
|
+
Usage:
|
149
|
+
```python
|
150
|
+
import modal
|
151
|
+
with modal.Volume.ephemeral() as vol:
|
152
|
+
assert vol.listdir("/") == []
|
153
|
+
```
|
154
|
+
|
155
|
+
```python notest
|
156
|
+
async with modal.Volume.ephemeral() as vol:
|
157
|
+
assert await vol.listdir("/") == []
|
158
|
+
```
|
159
|
+
"""
|
160
|
+
...
|
161
|
+
|
67
162
|
@staticmethod
|
68
163
|
async def lookup(
|
69
164
|
name: str,
|
@@ -72,7 +167,22 @@ class _Volume(modal._object._Object):
|
|
72
167
|
environment_name: typing.Optional[str] = None,
|
73
168
|
create_if_missing: bool = False,
|
74
169
|
version: typing.Optional[int] = None,
|
75
|
-
) -> _Volume:
|
170
|
+
) -> _Volume:
|
171
|
+
"""mdmd:hidden
|
172
|
+
Lookup a named Volume.
|
173
|
+
|
174
|
+
DEPRECATED: This method is deprecated in favor of `modal.Volume.from_name`.
|
175
|
+
|
176
|
+
In contrast to `modal.Volume.from_name`, this is an eager method
|
177
|
+
that will hydrate the local object with metadata from Modal servers.
|
178
|
+
|
179
|
+
```python notest
|
180
|
+
vol = modal.Volume.from_name("my-volume")
|
181
|
+
print(vol.listdir("/"))
|
182
|
+
```
|
183
|
+
"""
|
184
|
+
...
|
185
|
+
|
76
186
|
@staticmethod
|
77
187
|
async def create_deployed(
|
78
188
|
deployment_name: str,
|
@@ -80,24 +190,127 @@ class _Volume(modal._object._Object):
|
|
80
190
|
client: typing.Optional[modal.client._Client] = None,
|
81
191
|
environment_name: typing.Optional[str] = None,
|
82
192
|
version: typing.Optional[int] = None,
|
83
|
-
) -> str:
|
193
|
+
) -> str:
|
194
|
+
"""mdmd:hidden"""
|
195
|
+
...
|
196
|
+
|
84
197
|
async def _do_reload(self, lock=True): ...
|
85
|
-
async def commit(self):
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
198
|
+
async def commit(self):
|
199
|
+
"""Commit changes to the volume.
|
200
|
+
|
201
|
+
If successful, the changes made are now persisted in durable storage and available to other containers accessing
|
202
|
+
the volume.
|
203
|
+
"""
|
204
|
+
...
|
205
|
+
|
206
|
+
async def reload(self):
|
207
|
+
"""Make latest committed state of volume available in the running container.
|
208
|
+
|
209
|
+
Any uncommitted changes to the volume, such as new or modified files, may implicitly be committed when
|
210
|
+
reloading.
|
211
|
+
|
212
|
+
Reloading will fail if there are open files for the volume.
|
213
|
+
"""
|
214
|
+
...
|
215
|
+
|
216
|
+
def iterdir(self, path: str, *, recursive: bool = True) -> collections.abc.AsyncIterator[FileEntry]:
|
217
|
+
"""Iterate over all files in a directory in the volume.
|
218
|
+
|
219
|
+
Passing a directory path lists all files in the directory. For a file path, return only that
|
220
|
+
file's description. If `recursive` is set to True, list all files and folders under the path
|
221
|
+
recursively.
|
222
|
+
"""
|
223
|
+
...
|
224
|
+
|
225
|
+
async def listdir(self, path: str, *, recursive: bool = False) -> list[FileEntry]:
|
226
|
+
"""List all files under a path prefix in the modal.Volume.
|
227
|
+
|
228
|
+
Passing a directory path lists all files in the directory. For a file path, return only that
|
229
|
+
file's description. If `recursive` is set to True, list all files and folders under the path
|
230
|
+
recursively.
|
231
|
+
"""
|
232
|
+
...
|
233
|
+
|
234
|
+
def read_file(self, path: str) -> collections.abc.AsyncIterator[bytes]:
|
235
|
+
"""Read a file from the modal.Volume.
|
236
|
+
|
237
|
+
Note - this function is primarily intended to be used outside of a Modal App.
|
238
|
+
For more information on downloading files from a Modal Volume, see
|
239
|
+
[the guide](https://modal.com/docs/guide/volumes).
|
240
|
+
|
241
|
+
**Example:**
|
242
|
+
|
243
|
+
```python notest
|
244
|
+
vol = modal.Volume.from_name("my-modal-volume")
|
245
|
+
data = b""
|
246
|
+
for chunk in vol.read_file("1mb.csv"):
|
247
|
+
data += chunk
|
248
|
+
print(len(data)) # == 1024 * 1024
|
249
|
+
```
|
250
|
+
"""
|
251
|
+
...
|
252
|
+
|
90
253
|
async def read_file_into_fileobj(
|
91
254
|
self,
|
92
255
|
path: str,
|
93
256
|
fileobj: typing.IO[bytes],
|
94
257
|
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
95
|
-
) -> int:
|
96
|
-
|
258
|
+
) -> int:
|
259
|
+
"""mdmd:hidden
|
260
|
+
Read volume file into file-like IO object.
|
261
|
+
"""
|
262
|
+
...
|
263
|
+
|
264
|
+
async def remove_file(self, path: str, recursive: bool = False) -> None:
|
265
|
+
"""Remove a file or directory from a volume."""
|
266
|
+
...
|
267
|
+
|
97
268
|
async def copy_files(
|
98
269
|
self, src_paths: collections.abc.Sequence[str], dst_path: str, recursive: bool = False
|
99
|
-
) -> None:
|
100
|
-
|
270
|
+
) -> None:
|
271
|
+
"""Copy files within the volume from src_paths to dst_path.
|
272
|
+
The semantics of the copy operation follow those of the UNIX cp command.
|
273
|
+
|
274
|
+
The `src_paths` parameter is a list. If you want to copy a single file, you should pass a list with a
|
275
|
+
single element.
|
276
|
+
|
277
|
+
`src_paths` and `dst_path` should refer to the desired location *inside* the volume. You do not need to prepend
|
278
|
+
the volume mount path.
|
279
|
+
|
280
|
+
**Usage**
|
281
|
+
|
282
|
+
```python notest
|
283
|
+
vol = modal.Volume.from_name("my-modal-volume")
|
284
|
+
|
285
|
+
vol.copy_files(["bar/example.txt"], "bar2") # Copy files to another directory
|
286
|
+
vol.copy_files(["bar/example.txt"], "bar/example2.txt") # Rename a file by copying
|
287
|
+
```
|
288
|
+
|
289
|
+
Note that if the volume is already mounted on the Modal function, you should use normal filesystem operations
|
290
|
+
like `os.rename()` and then `commit()` the volume. The `copy_files()` method is useful when you don't have
|
291
|
+
the volume mounted as a filesystem, e.g. when running a script on your local computer.
|
292
|
+
"""
|
293
|
+
...
|
294
|
+
|
295
|
+
async def batch_upload(self, force: bool = False) -> _AbstractVolumeUploadContextManager:
|
296
|
+
"""Initiate a batched upload to a volume.
|
297
|
+
|
298
|
+
To allow overwriting existing files, set `force` to `True` (you cannot overwrite existing directories with
|
299
|
+
uploaded files regardless).
|
300
|
+
|
301
|
+
**Example:**
|
302
|
+
|
303
|
+
```python notest
|
304
|
+
vol = modal.Volume.from_name("my-modal-volume")
|
305
|
+
|
306
|
+
with vol.batch_upload() as batch:
|
307
|
+
batch.put_file("local-path.txt", "/remote-path.txt")
|
308
|
+
batch.put_directory("/local/directory/", "/remote/directory")
|
309
|
+
batch.put_file(io.BytesIO(b"some data"), "/foobar")
|
310
|
+
```
|
311
|
+
"""
|
312
|
+
...
|
313
|
+
|
101
314
|
async def _instance_delete(self): ...
|
102
315
|
@staticmethod
|
103
316
|
async def delete(
|
@@ -115,10 +328,53 @@ class _Volume(modal._object._Object):
|
|
115
328
|
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
116
329
|
|
117
330
|
class Volume(modal.object.Object):
|
331
|
+
"""A writeable volume that can be used to share files between one or more Modal functions.
|
332
|
+
|
333
|
+
The contents of a volume is exposed as a filesystem. You can use it to share data between different functions, or
|
334
|
+
to persist durable state across several instances of the same function.
|
335
|
+
|
336
|
+
Unlike a networked filesystem, you need to explicitly reload the volume to see changes made since it was mounted.
|
337
|
+
Similarly, you need to explicitly commit any changes you make to the volume for the changes to become visible
|
338
|
+
outside the current container.
|
339
|
+
|
340
|
+
Concurrent modification is supported, but concurrent modifications of the same files should be avoided! Last write
|
341
|
+
wins in case of concurrent modification of the same file - any data the last writer didn't have when committing
|
342
|
+
changes will be lost!
|
343
|
+
|
344
|
+
As a result, volumes are typically not a good fit for use cases where you need to make concurrent modifications to
|
345
|
+
the same file (nor is distributed file locking supported).
|
346
|
+
|
347
|
+
Volumes can only be reloaded if there are no open files for the volume - attempting to reload with open files
|
348
|
+
will result in an error.
|
349
|
+
|
350
|
+
**Usage**
|
351
|
+
|
352
|
+
```python
|
353
|
+
import modal
|
354
|
+
|
355
|
+
app = modal.App()
|
356
|
+
volume = modal.Volume.from_name("my-persisted-volume", create_if_missing=True)
|
357
|
+
|
358
|
+
@app.function(volumes={"/root/foo": volume})
|
359
|
+
def f():
|
360
|
+
with open("/root/foo/bar.txt", "w") as f:
|
361
|
+
f.write("hello")
|
362
|
+
volume.commit() # Persist changes
|
363
|
+
|
364
|
+
@app.function(volumes={"/root/foo": volume})
|
365
|
+
def g():
|
366
|
+
volume.reload() # Fetch latest changes
|
367
|
+
with open("/root/foo/bar.txt", "r") as f:
|
368
|
+
print(f.read())
|
369
|
+
```
|
370
|
+
"""
|
371
|
+
|
118
372
|
_lock: typing.Optional[asyncio.locks.Lock]
|
119
373
|
_metadata: typing.Optional[modal_proto.api_pb2.VolumeMetadata]
|
120
374
|
|
121
|
-
def __init__(self, *args, **kwargs):
|
375
|
+
def __init__(self, *args, **kwargs):
|
376
|
+
"""mdmd:hidden"""
|
377
|
+
...
|
122
378
|
|
123
379
|
class ___get_lock_spec(typing_extensions.Protocol[SUPERSELF]):
|
124
380
|
def __call__(self, /): ...
|
@@ -134,7 +390,26 @@ class Volume(modal.object.Object):
|
|
134
390
|
environment_name: typing.Optional[str] = None,
|
135
391
|
create_if_missing: bool = False,
|
136
392
|
version: typing.Optional[int] = None,
|
137
|
-
) -> Volume:
|
393
|
+
) -> Volume:
|
394
|
+
"""Reference a Volume by name, creating if necessary.
|
395
|
+
|
396
|
+
In contrast to `modal.Volume.lookup`, this is a lazy method
|
397
|
+
that defers hydrating the local object with metadata from
|
398
|
+
Modal servers until the first time is is actually used.
|
399
|
+
|
400
|
+
```python
|
401
|
+
vol = modal.Volume.from_name("my-volume", create_if_missing=True)
|
402
|
+
|
403
|
+
app = modal.App()
|
404
|
+
|
405
|
+
# Volume refers to the same object, even across instances of `app`.
|
406
|
+
@app.function(volumes={"/data": vol})
|
407
|
+
def f():
|
408
|
+
pass
|
409
|
+
```
|
410
|
+
"""
|
411
|
+
...
|
412
|
+
|
138
413
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
139
414
|
def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
|
140
415
|
@property
|
@@ -146,7 +421,22 @@ class Volume(modal.object.Object):
|
|
146
421
|
environment_name: typing.Optional[str] = None,
|
147
422
|
version: typing.Optional[int] = None,
|
148
423
|
_heartbeat_sleep: float = 300,
|
149
|
-
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Volume]:
|
424
|
+
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Volume]:
|
425
|
+
"""Creates a new ephemeral volume within a context manager:
|
426
|
+
|
427
|
+
Usage:
|
428
|
+
```python
|
429
|
+
import modal
|
430
|
+
with modal.Volume.ephemeral() as vol:
|
431
|
+
assert vol.listdir("/") == []
|
432
|
+
```
|
433
|
+
|
434
|
+
```python notest
|
435
|
+
async with modal.Volume.ephemeral() as vol:
|
436
|
+
assert await vol.listdir("/") == []
|
437
|
+
```
|
438
|
+
"""
|
439
|
+
...
|
150
440
|
|
151
441
|
class __lookup_spec(typing_extensions.Protocol):
|
152
442
|
def __call__(
|
@@ -158,7 +448,22 @@ class Volume(modal.object.Object):
|
|
158
448
|
environment_name: typing.Optional[str] = None,
|
159
449
|
create_if_missing: bool = False,
|
160
450
|
version: typing.Optional[int] = None,
|
161
|
-
) -> Volume:
|
451
|
+
) -> Volume:
|
452
|
+
"""mdmd:hidden
|
453
|
+
Lookup a named Volume.
|
454
|
+
|
455
|
+
DEPRECATED: This method is deprecated in favor of `modal.Volume.from_name`.
|
456
|
+
|
457
|
+
In contrast to `modal.Volume.from_name`, this is an eager method
|
458
|
+
that will hydrate the local object with metadata from Modal servers.
|
459
|
+
|
460
|
+
```python notest
|
461
|
+
vol = modal.Volume.from_name("my-volume")
|
462
|
+
print(vol.listdir("/"))
|
463
|
+
```
|
464
|
+
"""
|
465
|
+
...
|
466
|
+
|
162
467
|
async def aio(
|
163
468
|
self,
|
164
469
|
/,
|
@@ -168,7 +473,21 @@ class Volume(modal.object.Object):
|
|
168
473
|
environment_name: typing.Optional[str] = None,
|
169
474
|
create_if_missing: bool = False,
|
170
475
|
version: typing.Optional[int] = None,
|
171
|
-
) -> Volume:
|
476
|
+
) -> Volume:
|
477
|
+
"""mdmd:hidden
|
478
|
+
Lookup a named Volume.
|
479
|
+
|
480
|
+
DEPRECATED: This method is deprecated in favor of `modal.Volume.from_name`.
|
481
|
+
|
482
|
+
In contrast to `modal.Volume.from_name`, this is an eager method
|
483
|
+
that will hydrate the local object with metadata from Modal servers.
|
484
|
+
|
485
|
+
```python notest
|
486
|
+
vol = modal.Volume.from_name("my-volume")
|
487
|
+
print(vol.listdir("/"))
|
488
|
+
```
|
489
|
+
"""
|
490
|
+
...
|
172
491
|
|
173
492
|
lookup: __lookup_spec
|
174
493
|
|
@@ -181,7 +500,10 @@ class Volume(modal.object.Object):
|
|
181
500
|
client: typing.Optional[modal.client.Client] = None,
|
182
501
|
environment_name: typing.Optional[str] = None,
|
183
502
|
version: typing.Optional[int] = None,
|
184
|
-
) -> str:
|
503
|
+
) -> str:
|
504
|
+
"""mdmd:hidden"""
|
505
|
+
...
|
506
|
+
|
185
507
|
async def aio(
|
186
508
|
self,
|
187
509
|
/,
|
@@ -190,7 +512,9 @@ class Volume(modal.object.Object):
|
|
190
512
|
client: typing.Optional[modal.client.Client] = None,
|
191
513
|
environment_name: typing.Optional[str] = None,
|
192
514
|
version: typing.Optional[int] = None,
|
193
|
-
) -> str:
|
515
|
+
) -> str:
|
516
|
+
"""mdmd:hidden"""
|
517
|
+
...
|
194
518
|
|
195
519
|
create_deployed: __create_deployed_spec
|
196
520
|
|
@@ -201,32 +525,127 @@ class Volume(modal.object.Object):
|
|
201
525
|
_do_reload: ___do_reload_spec[typing_extensions.Self]
|
202
526
|
|
203
527
|
class __commit_spec(typing_extensions.Protocol[SUPERSELF]):
|
204
|
-
def __call__(self, /):
|
205
|
-
|
528
|
+
def __call__(self, /):
|
529
|
+
"""Commit changes to the volume.
|
530
|
+
|
531
|
+
If successful, the changes made are now persisted in durable storage and available to other containers accessing
|
532
|
+
the volume.
|
533
|
+
"""
|
534
|
+
...
|
535
|
+
|
536
|
+
async def aio(self, /):
|
537
|
+
"""Commit changes to the volume.
|
538
|
+
|
539
|
+
If successful, the changes made are now persisted in durable storage and available to other containers accessing
|
540
|
+
the volume.
|
541
|
+
"""
|
542
|
+
...
|
206
543
|
|
207
544
|
commit: __commit_spec[typing_extensions.Self]
|
208
545
|
|
209
546
|
class __reload_spec(typing_extensions.Protocol[SUPERSELF]):
|
210
|
-
def __call__(self, /):
|
211
|
-
|
547
|
+
def __call__(self, /):
|
548
|
+
"""Make latest committed state of volume available in the running container.
|
549
|
+
|
550
|
+
Any uncommitted changes to the volume, such as new or modified files, may implicitly be committed when
|
551
|
+
reloading.
|
552
|
+
|
553
|
+
Reloading will fail if there are open files for the volume.
|
554
|
+
"""
|
555
|
+
...
|
556
|
+
|
557
|
+
async def aio(self, /):
|
558
|
+
"""Make latest committed state of volume available in the running container.
|
559
|
+
|
560
|
+
Any uncommitted changes to the volume, such as new or modified files, may implicitly be committed when
|
561
|
+
reloading.
|
562
|
+
|
563
|
+
Reloading will fail if there are open files for the volume.
|
564
|
+
"""
|
565
|
+
...
|
212
566
|
|
213
567
|
reload: __reload_spec[typing_extensions.Self]
|
214
568
|
|
215
569
|
class __iterdir_spec(typing_extensions.Protocol[SUPERSELF]):
|
216
|
-
def __call__(self, /, path: str, *, recursive: bool = True) -> typing.Iterator[FileEntry]:
|
217
|
-
|
570
|
+
def __call__(self, /, path: str, *, recursive: bool = True) -> typing.Iterator[FileEntry]:
|
571
|
+
"""Iterate over all files in a directory in the volume.
|
572
|
+
|
573
|
+
Passing a directory path lists all files in the directory. For a file path, return only that
|
574
|
+
file's description. If `recursive` is set to True, list all files and folders under the path
|
575
|
+
recursively.
|
576
|
+
"""
|
577
|
+
...
|
578
|
+
|
579
|
+
def aio(self, /, path: str, *, recursive: bool = True) -> collections.abc.AsyncIterator[FileEntry]:
|
580
|
+
"""Iterate over all files in a directory in the volume.
|
581
|
+
|
582
|
+
Passing a directory path lists all files in the directory. For a file path, return only that
|
583
|
+
file's description. If `recursive` is set to True, list all files and folders under the path
|
584
|
+
recursively.
|
585
|
+
"""
|
586
|
+
...
|
218
587
|
|
219
588
|
iterdir: __iterdir_spec[typing_extensions.Self]
|
220
589
|
|
221
590
|
class __listdir_spec(typing_extensions.Protocol[SUPERSELF]):
|
222
|
-
def __call__(self, /, path: str, *, recursive: bool = False) -> list[FileEntry]:
|
223
|
-
|
591
|
+
def __call__(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
|
+
async def aio(self, /, path: str, *, recursive: bool = False) -> list[FileEntry]:
|
601
|
+
"""List all files under a path prefix in the modal.Volume.
|
602
|
+
|
603
|
+
Passing a directory path lists all files in the directory. For a file path, return only that
|
604
|
+
file's description. If `recursive` is set to True, list all files and folders under the path
|
605
|
+
recursively.
|
606
|
+
"""
|
607
|
+
...
|
224
608
|
|
225
609
|
listdir: __listdir_spec[typing_extensions.Self]
|
226
610
|
|
227
611
|
class __read_file_spec(typing_extensions.Protocol[SUPERSELF]):
|
228
|
-
def __call__(self, /, path: str) -> typing.Iterator[bytes]:
|
229
|
-
|
612
|
+
def __call__(self, /, path: str) -> typing.Iterator[bytes]:
|
613
|
+
"""Read a file from the modal.Volume.
|
614
|
+
|
615
|
+
Note - this function is primarily intended to be used outside of a Modal App.
|
616
|
+
For more information on downloading files from a Modal Volume, see
|
617
|
+
[the guide](https://modal.com/docs/guide/volumes).
|
618
|
+
|
619
|
+
**Example:**
|
620
|
+
|
621
|
+
```python notest
|
622
|
+
vol = modal.Volume.from_name("my-modal-volume")
|
623
|
+
data = b""
|
624
|
+
for chunk in vol.read_file("1mb.csv"):
|
625
|
+
data += chunk
|
626
|
+
print(len(data)) # == 1024 * 1024
|
627
|
+
```
|
628
|
+
"""
|
629
|
+
...
|
630
|
+
|
631
|
+
def aio(self, /, path: str) -> collections.abc.AsyncIterator[bytes]:
|
632
|
+
"""Read a file from the modal.Volume.
|
633
|
+
|
634
|
+
Note - this function is primarily intended to be used outside of a Modal App.
|
635
|
+
For more information on downloading files from a Modal Volume, see
|
636
|
+
[the guide](https://modal.com/docs/guide/volumes).
|
637
|
+
|
638
|
+
**Example:**
|
639
|
+
|
640
|
+
```python notest
|
641
|
+
vol = modal.Volume.from_name("my-modal-volume")
|
642
|
+
data = b""
|
643
|
+
for chunk in vol.read_file("1mb.csv"):
|
644
|
+
data += chunk
|
645
|
+
print(len(data)) # == 1024 * 1024
|
646
|
+
```
|
647
|
+
"""
|
648
|
+
...
|
230
649
|
|
231
650
|
read_file: __read_file_spec[typing_extensions.Self]
|
232
651
|
|
@@ -237,36 +656,130 @@ class Volume(modal.object.Object):
|
|
237
656
|
path: str,
|
238
657
|
fileobj: typing.IO[bytes],
|
239
658
|
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
240
|
-
) -> int:
|
659
|
+
) -> int:
|
660
|
+
"""mdmd:hidden
|
661
|
+
Read volume file into file-like IO object.
|
662
|
+
"""
|
663
|
+
...
|
664
|
+
|
241
665
|
async def aio(
|
242
666
|
self,
|
243
667
|
/,
|
244
668
|
path: str,
|
245
669
|
fileobj: typing.IO[bytes],
|
246
670
|
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
247
|
-
) -> int:
|
671
|
+
) -> int:
|
672
|
+
"""mdmd:hidden
|
673
|
+
Read volume file into file-like IO object.
|
674
|
+
"""
|
675
|
+
...
|
248
676
|
|
249
677
|
read_file_into_fileobj: __read_file_into_fileobj_spec[typing_extensions.Self]
|
250
678
|
|
251
679
|
class __remove_file_spec(typing_extensions.Protocol[SUPERSELF]):
|
252
|
-
def __call__(self, /, path: str, recursive: bool = False) -> None:
|
253
|
-
|
680
|
+
def __call__(self, /, path: str, recursive: bool = False) -> None:
|
681
|
+
"""Remove a file or directory from a volume."""
|
682
|
+
...
|
683
|
+
|
684
|
+
async def aio(self, /, path: str, recursive: bool = False) -> None:
|
685
|
+
"""Remove a file or directory from a volume."""
|
686
|
+
...
|
254
687
|
|
255
688
|
remove_file: __remove_file_spec[typing_extensions.Self]
|
256
689
|
|
257
690
|
class __copy_files_spec(typing_extensions.Protocol[SUPERSELF]):
|
258
|
-
def __call__(
|
259
|
-
|
260
|
-
|
691
|
+
def __call__(self, /, src_paths: collections.abc.Sequence[str], dst_path: str, recursive: bool = False) -> None:
|
692
|
+
"""Copy files within the volume from src_paths to dst_path.
|
693
|
+
The semantics of the copy operation follow those of the UNIX cp command.
|
694
|
+
|
695
|
+
The `src_paths` parameter is a list. If you want to copy a single file, you should pass a list with a
|
696
|
+
single element.
|
697
|
+
|
698
|
+
`src_paths` and `dst_path` should refer to the desired location *inside* the volume. You do not need to prepend
|
699
|
+
the volume mount path.
|
700
|
+
|
701
|
+
**Usage**
|
702
|
+
|
703
|
+
```python notest
|
704
|
+
vol = modal.Volume.from_name("my-modal-volume")
|
705
|
+
|
706
|
+
vol.copy_files(["bar/example.txt"], "bar2") # Copy files to another directory
|
707
|
+
vol.copy_files(["bar/example.txt"], "bar/example2.txt") # Rename a file by copying
|
708
|
+
```
|
709
|
+
|
710
|
+
Note that if the volume is already mounted on the Modal function, you should use normal filesystem operations
|
711
|
+
like `os.rename()` and then `commit()` the volume. The `copy_files()` method is useful when you don't have
|
712
|
+
the volume mounted as a filesystem, e.g. when running a script on your local computer.
|
713
|
+
"""
|
714
|
+
...
|
715
|
+
|
261
716
|
async def aio(
|
262
717
|
self, /, src_paths: collections.abc.Sequence[str], dst_path: str, recursive: bool = False
|
263
|
-
) -> None:
|
718
|
+
) -> None:
|
719
|
+
"""Copy files within the volume from src_paths to dst_path.
|
720
|
+
The semantics of the copy operation follow those of the UNIX cp command.
|
721
|
+
|
722
|
+
The `src_paths` parameter is a list. If you want to copy a single file, you should pass a list with a
|
723
|
+
single element.
|
724
|
+
|
725
|
+
`src_paths` and `dst_path` should refer to the desired location *inside* the volume. You do not need to prepend
|
726
|
+
the volume mount path.
|
727
|
+
|
728
|
+
**Usage**
|
729
|
+
|
730
|
+
```python notest
|
731
|
+
vol = modal.Volume.from_name("my-modal-volume")
|
732
|
+
|
733
|
+
vol.copy_files(["bar/example.txt"], "bar2") # Copy files to another directory
|
734
|
+
vol.copy_files(["bar/example.txt"], "bar/example2.txt") # Rename a file by copying
|
735
|
+
```
|
736
|
+
|
737
|
+
Note that if the volume is already mounted on the Modal function, you should use normal filesystem operations
|
738
|
+
like `os.rename()` and then `commit()` the volume. The `copy_files()` method is useful when you don't have
|
739
|
+
the volume mounted as a filesystem, e.g. when running a script on your local computer.
|
740
|
+
"""
|
741
|
+
...
|
264
742
|
|
265
743
|
copy_files: __copy_files_spec[typing_extensions.Self]
|
266
744
|
|
267
745
|
class __batch_upload_spec(typing_extensions.Protocol[SUPERSELF]):
|
268
|
-
def __call__(self, /, force: bool = False) -> AbstractVolumeUploadContextManager:
|
269
|
-
|
746
|
+
def __call__(self, /, force: bool = False) -> AbstractVolumeUploadContextManager:
|
747
|
+
"""Initiate a batched upload to a volume.
|
748
|
+
|
749
|
+
To allow overwriting existing files, set `force` to `True` (you cannot overwrite existing directories with
|
750
|
+
uploaded files regardless).
|
751
|
+
|
752
|
+
**Example:**
|
753
|
+
|
754
|
+
```python notest
|
755
|
+
vol = modal.Volume.from_name("my-modal-volume")
|
756
|
+
|
757
|
+
with vol.batch_upload() as batch:
|
758
|
+
batch.put_file("local-path.txt", "/remote-path.txt")
|
759
|
+
batch.put_directory("/local/directory/", "/remote/directory")
|
760
|
+
batch.put_file(io.BytesIO(b"some data"), "/foobar")
|
761
|
+
```
|
762
|
+
"""
|
763
|
+
...
|
764
|
+
|
765
|
+
async def aio(self, /, force: bool = False) -> AbstractVolumeUploadContextManager:
|
766
|
+
"""Initiate a batched upload to a volume.
|
767
|
+
|
768
|
+
To allow overwriting existing files, set `force` to `True` (you cannot overwrite existing directories with
|
769
|
+
uploaded files regardless).
|
770
|
+
|
771
|
+
**Example:**
|
772
|
+
|
773
|
+
```python notest
|
774
|
+
vol = modal.Volume.from_name("my-modal-volume")
|
775
|
+
|
776
|
+
with vol.batch_upload() as batch:
|
777
|
+
batch.put_file("local-path.txt", "/remote-path.txt")
|
778
|
+
batch.put_directory("/local/directory/", "/remote/directory")
|
779
|
+
batch.put_file(io.BytesIO(b"some data"), "/foobar")
|
780
|
+
```
|
781
|
+
"""
|
782
|
+
...
|
270
783
|
|
271
784
|
batch_upload: __batch_upload_spec[typing_extensions.Self]
|
272
785
|
|
@@ -341,7 +854,10 @@ class _AbstractVolumeUploadContextManager:
|
|
341
854
|
) -> _AbstractVolumeUploadContextManager: ...
|
342
855
|
|
343
856
|
class AbstractVolumeUploadContextManager:
|
344
|
-
def __init__(self, /, *args, **kwargs):
|
857
|
+
def __init__(self, /, *args, **kwargs):
|
858
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
859
|
+
...
|
860
|
+
|
345
861
|
def __enter__(self): ...
|
346
862
|
async def __aenter__(self): ...
|
347
863
|
def __exit__(self, exc_type, exc_val, exc_tb): ...
|
@@ -368,6 +884,8 @@ class AbstractVolumeUploadContextManager:
|
|
368
884
|
) -> AbstractVolumeUploadContextManager: ...
|
369
885
|
|
370
886
|
class _VolumeUploadContextManager(_AbstractVolumeUploadContextManager):
|
887
|
+
"""Context manager for batch-uploading files to a Volume."""
|
888
|
+
|
371
889
|
_volume_id: str
|
372
890
|
_client: modal.client._Client
|
373
891
|
_force: bool
|
@@ -382,7 +900,10 @@ class _VolumeUploadContextManager(_AbstractVolumeUploadContextManager):
|
|
382
900
|
client: modal.client._Client,
|
383
901
|
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
384
902
|
force: bool = False,
|
385
|
-
):
|
903
|
+
):
|
904
|
+
"""mdmd:hidden"""
|
905
|
+
...
|
906
|
+
|
386
907
|
async def __aenter__(self): ...
|
387
908
|
async def __aexit__(self, exc_type, exc_val, exc_tb): ...
|
388
909
|
def put_file(
|
@@ -390,18 +911,34 @@ class _VolumeUploadContextManager(_AbstractVolumeUploadContextManager):
|
|
390
911
|
local_file: typing.Union[pathlib.Path, str, typing.BinaryIO, _io.BytesIO],
|
391
912
|
remote_path: typing.Union[pathlib.PurePosixPath, str],
|
392
913
|
mode: typing.Optional[int] = None,
|
393
|
-
):
|
914
|
+
):
|
915
|
+
"""Upload a file from a local file or file-like object.
|
916
|
+
|
917
|
+
Will create any needed parent directories automatically.
|
918
|
+
|
919
|
+
If `local_file` is a file-like object it must remain readable for the lifetime of the batch.
|
920
|
+
"""
|
921
|
+
...
|
922
|
+
|
394
923
|
def put_directory(
|
395
924
|
self,
|
396
925
|
local_path: typing.Union[pathlib.Path, str],
|
397
926
|
remote_path: typing.Union[pathlib.PurePosixPath, str],
|
398
927
|
recursive: bool = True,
|
399
|
-
):
|
928
|
+
):
|
929
|
+
"""Upload all files in a local directory.
|
930
|
+
|
931
|
+
Will create any needed parent directories automatically.
|
932
|
+
"""
|
933
|
+
...
|
934
|
+
|
400
935
|
async def _upload_file(
|
401
936
|
self, file_spec: modal._utils.blob_utils.FileUploadSpec
|
402
937
|
) -> modal_proto.api_pb2.MountFile: ...
|
403
938
|
|
404
939
|
class VolumeUploadContextManager(AbstractVolumeUploadContextManager):
|
940
|
+
"""Context manager for batch-uploading files to a Volume."""
|
941
|
+
|
405
942
|
_volume_id: str
|
406
943
|
_client: modal.client.Client
|
407
944
|
_force: bool
|
@@ -416,7 +953,10 @@ class VolumeUploadContextManager(AbstractVolumeUploadContextManager):
|
|
416
953
|
client: modal.client.Client,
|
417
954
|
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
418
955
|
force: bool = False,
|
419
|
-
):
|
956
|
+
):
|
957
|
+
"""mdmd:hidden"""
|
958
|
+
...
|
959
|
+
|
420
960
|
def __enter__(self): ...
|
421
961
|
async def __aenter__(self): ...
|
422
962
|
def __exit__(self, exc_type, exc_val, exc_tb): ...
|
@@ -426,13 +966,26 @@ class VolumeUploadContextManager(AbstractVolumeUploadContextManager):
|
|
426
966
|
local_file: typing.Union[pathlib.Path, str, typing.BinaryIO, _io.BytesIO],
|
427
967
|
remote_path: typing.Union[pathlib.PurePosixPath, str],
|
428
968
|
mode: typing.Optional[int] = None,
|
429
|
-
):
|
969
|
+
):
|
970
|
+
"""Upload a file from a local file or file-like object.
|
971
|
+
|
972
|
+
Will create any needed parent directories automatically.
|
973
|
+
|
974
|
+
If `local_file` is a file-like object it must remain readable for the lifetime of the batch.
|
975
|
+
"""
|
976
|
+
...
|
977
|
+
|
430
978
|
def put_directory(
|
431
979
|
self,
|
432
980
|
local_path: typing.Union[pathlib.Path, str],
|
433
981
|
remote_path: typing.Union[pathlib.PurePosixPath, str],
|
434
982
|
recursive: bool = True,
|
435
|
-
):
|
983
|
+
):
|
984
|
+
"""Upload all files in a local directory.
|
985
|
+
|
986
|
+
Will create any needed parent directories automatically.
|
987
|
+
"""
|
988
|
+
...
|
436
989
|
|
437
990
|
class ___upload_file_spec(typing_extensions.Protocol[SUPERSELF]):
|
438
991
|
def __call__(self, /, file_spec: modal._utils.blob_utils.FileUploadSpec) -> modal_proto.api_pb2.MountFile: ...
|
@@ -441,6 +994,8 @@ class VolumeUploadContextManager(AbstractVolumeUploadContextManager):
|
|
441
994
|
_upload_file: ___upload_file_spec[typing_extensions.Self]
|
442
995
|
|
443
996
|
class _VolumeUploadContextManager2(_AbstractVolumeUploadContextManager):
|
997
|
+
"""Context manager for batch-uploading files to a Volume version 2."""
|
998
|
+
|
444
999
|
_volume_id: str
|
445
1000
|
_client: modal.client._Client
|
446
1001
|
_progress_cb: collections.abc.Callable[..., typing.Any]
|
@@ -463,7 +1018,10 @@ class _VolumeUploadContextManager2(_AbstractVolumeUploadContextManager):
|
|
463
1018
|
force: bool = False,
|
464
1019
|
hash_concurrency: int = 4,
|
465
1020
|
put_concurrency: int = 128,
|
466
|
-
):
|
1021
|
+
):
|
1022
|
+
"""mdmd:hidden"""
|
1023
|
+
...
|
1024
|
+
|
467
1025
|
async def __aenter__(self): ...
|
468
1026
|
async def __aexit__(self, exc_type, exc_val, exc_tb): ...
|
469
1027
|
def put_file(
|
@@ -471,16 +1029,32 @@ class _VolumeUploadContextManager2(_AbstractVolumeUploadContextManager):
|
|
471
1029
|
local_file: typing.Union[pathlib.Path, str, typing.BinaryIO, _io.BytesIO],
|
472
1030
|
remote_path: typing.Union[pathlib.PurePosixPath, str],
|
473
1031
|
mode: typing.Optional[int] = None,
|
474
|
-
):
|
1032
|
+
):
|
1033
|
+
"""Upload a file from a local file or file-like object.
|
1034
|
+
|
1035
|
+
Will create any needed parent directories automatically.
|
1036
|
+
|
1037
|
+
If `local_file` is a file-like object it must remain readable for the lifetime of the batch.
|
1038
|
+
"""
|
1039
|
+
...
|
1040
|
+
|
475
1041
|
def put_directory(
|
476
1042
|
self,
|
477
1043
|
local_path: typing.Union[pathlib.Path, str],
|
478
1044
|
remote_path: typing.Union[pathlib.PurePosixPath, str],
|
479
1045
|
recursive: bool = True,
|
480
|
-
):
|
1046
|
+
):
|
1047
|
+
"""Upload all files in a local directory.
|
1048
|
+
|
1049
|
+
Will create any needed parent directories automatically.
|
1050
|
+
"""
|
1051
|
+
...
|
1052
|
+
|
481
1053
|
async def _put_file_specs(self, file_specs: list[modal._utils.blob_utils.FileUploadSpec2]): ...
|
482
1054
|
|
483
1055
|
class VolumeUploadContextManager2(AbstractVolumeUploadContextManager):
|
1056
|
+
"""Context manager for batch-uploading files to a Volume version 2."""
|
1057
|
+
|
484
1058
|
_volume_id: str
|
485
1059
|
_client: modal.client.Client
|
486
1060
|
_progress_cb: collections.abc.Callable[..., typing.Any]
|
@@ -503,7 +1077,10 @@ class VolumeUploadContextManager2(AbstractVolumeUploadContextManager):
|
|
503
1077
|
force: bool = False,
|
504
1078
|
hash_concurrency: int = 4,
|
505
1079
|
put_concurrency: int = 128,
|
506
|
-
):
|
1080
|
+
):
|
1081
|
+
"""mdmd:hidden"""
|
1082
|
+
...
|
1083
|
+
|
507
1084
|
def __enter__(self): ...
|
508
1085
|
async def __aenter__(self): ...
|
509
1086
|
def __exit__(self, exc_type, exc_val, exc_tb): ...
|
@@ -513,13 +1090,26 @@ class VolumeUploadContextManager2(AbstractVolumeUploadContextManager):
|
|
513
1090
|
local_file: typing.Union[pathlib.Path, str, typing.BinaryIO, _io.BytesIO],
|
514
1091
|
remote_path: typing.Union[pathlib.PurePosixPath, str],
|
515
1092
|
mode: typing.Optional[int] = None,
|
516
|
-
):
|
1093
|
+
):
|
1094
|
+
"""Upload a file from a local file or file-like object.
|
1095
|
+
|
1096
|
+
Will create any needed parent directories automatically.
|
1097
|
+
|
1098
|
+
If `local_file` is a file-like object it must remain readable for the lifetime of the batch.
|
1099
|
+
"""
|
1100
|
+
...
|
1101
|
+
|
517
1102
|
def put_directory(
|
518
1103
|
self,
|
519
1104
|
local_path: typing.Union[pathlib.Path, str],
|
520
1105
|
remote_path: typing.Union[pathlib.PurePosixPath, str],
|
521
1106
|
recursive: bool = True,
|
522
|
-
):
|
1107
|
+
):
|
1108
|
+
"""Upload all files in a local directory.
|
1109
|
+
|
1110
|
+
Will create any needed parent directories automatically.
|
1111
|
+
"""
|
1112
|
+
...
|
523
1113
|
|
524
1114
|
class ___put_file_specs_spec(typing_extensions.Protocol[SUPERSELF]):
|
525
1115
|
def __call__(self, /, file_specs: list[modal._utils.blob_utils.FileUploadSpec2]): ...
|