modal 1.1.2.dev27__py3-none-any.whl → 1.1.2.dev29__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/_utils/blob_utils.py +83 -24
- modal/client.pyi +2 -2
- modal/dict.py +57 -3
- modal/dict.pyi +107 -0
- modal/functions.pyi +6 -6
- modal/queue.py +57 -3
- modal/queue.pyi +107 -0
- modal/secret.py +60 -2
- modal/secret.pyi +116 -0
- modal/volume.py +62 -11
- modal/volume.pyi +104 -0
- {modal-1.1.2.dev27.dist-info → modal-1.1.2.dev29.dist-info}/METADATA +1 -1
- {modal-1.1.2.dev27.dist-info → modal-1.1.2.dev29.dist-info}/RECORD +18 -18
- modal_version/__init__.py +1 -1
- {modal-1.1.2.dev27.dist-info → modal-1.1.2.dev29.dist-info}/WHEEL +0 -0
- {modal-1.1.2.dev27.dist-info → modal-1.1.2.dev29.dist-info}/entry_points.txt +0 -0
- {modal-1.1.2.dev27.dist-info → modal-1.1.2.dev29.dist-info}/licenses/LICENSE +0 -0
- {modal-1.1.2.dev27.dist-info → modal-1.1.2.dev29.dist-info}/top_level.txt +0 -0
modal/_utils/blob_utils.py
CHANGED
@@ -444,14 +444,24 @@ def get_file_upload_spec_from_fileobj(fp: BinaryIO, mount_filename: PurePosixPat
|
|
444
444
|
_FileUploadSource2 = Callable[[], ContextManager[BinaryIO]]
|
445
445
|
|
446
446
|
|
447
|
+
@dataclasses.dataclass
|
448
|
+
class FileUploadBlock:
|
449
|
+
# The start (byte offset, inclusive) of the block within the file
|
450
|
+
start: int
|
451
|
+
# The end (byte offset, exclusive) of the block, after having removed any trailing zeroes
|
452
|
+
end: int
|
453
|
+
# Raw (unencoded 32 byte) SHA256 sum of the block, not including trailing zeroes
|
454
|
+
contents_sha256: bytes
|
455
|
+
|
456
|
+
|
447
457
|
@dataclasses.dataclass
|
448
458
|
class FileUploadSpec2:
|
449
459
|
source: _FileUploadSource2
|
450
460
|
source_description: Union[str, Path]
|
451
461
|
|
452
462
|
path: str
|
453
|
-
#
|
454
|
-
|
463
|
+
# 8MiB file blocks
|
464
|
+
blocks: list[FileUploadBlock]
|
455
465
|
mode: int # file permission bits (last 12 bits of st_mode)
|
456
466
|
size: int
|
457
467
|
|
@@ -522,53 +532,102 @@ class FileUploadSpec2:
|
|
522
532
|
source_fp.seek(0, os.SEEK_END)
|
523
533
|
size = source_fp.tell()
|
524
534
|
|
525
|
-
|
535
|
+
blocks = await _gather_blocks(source, size, hash_semaphore)
|
526
536
|
|
527
537
|
return FileUploadSpec2(
|
528
538
|
source=source,
|
529
539
|
source_description=source_description,
|
530
540
|
path=mount_filename.as_posix(),
|
531
|
-
|
541
|
+
blocks=blocks,
|
532
542
|
mode=mode & 0o7777,
|
533
543
|
size=size,
|
534
544
|
)
|
535
545
|
|
536
546
|
|
537
|
-
async def
|
547
|
+
async def _gather_blocks(
|
538
548
|
source: _FileUploadSource2,
|
539
549
|
size: int,
|
540
550
|
hash_semaphore: asyncio.Semaphore,
|
541
|
-
) -> list[
|
551
|
+
) -> list[FileUploadBlock]:
|
542
552
|
def ceildiv(a: int, b: int) -> int:
|
543
553
|
return -(a // -b)
|
544
554
|
|
545
555
|
num_blocks = ceildiv(size, BLOCK_SIZE)
|
546
556
|
|
547
|
-
def
|
548
|
-
|
549
|
-
|
557
|
+
async def gather_block(block_idx: int) -> FileUploadBlock:
|
558
|
+
async with hash_semaphore:
|
559
|
+
return await asyncio.to_thread(_gather_block, source, block_idx)
|
550
560
|
|
551
|
-
|
552
|
-
|
561
|
+
tasks = (gather_block(idx) for idx in range(num_blocks))
|
562
|
+
return await asyncio.gather(*tasks)
|
553
563
|
|
554
|
-
num_bytes_read = 0
|
555
|
-
while num_bytes_read < BLOCK_SIZE:
|
556
|
-
chunk = block_fp.read(BLOCK_SIZE - num_bytes_read)
|
557
564
|
|
558
|
-
|
559
|
-
|
565
|
+
def _gather_block(source: _FileUploadSource2, block_idx: int) -> FileUploadBlock:
|
566
|
+
start = block_idx * BLOCK_SIZE
|
567
|
+
end = _find_end_of_block(source, start, start + BLOCK_SIZE)
|
568
|
+
contents_sha256 = _hash_range_sha256(source, start, end)
|
569
|
+
return FileUploadBlock(start=start, end=end, contents_sha256=contents_sha256)
|
560
570
|
|
561
|
-
num_bytes_read += len(chunk)
|
562
|
-
sha256_hash.update(chunk)
|
563
571
|
|
564
|
-
|
572
|
+
def _hash_range_sha256(source: _FileUploadSource2, start, end):
|
573
|
+
sha256_hash = hashlib.sha256()
|
574
|
+
range_size = end - start
|
565
575
|
|
566
|
-
|
567
|
-
|
568
|
-
|
576
|
+
with source() as fp:
|
577
|
+
fp.seek(start)
|
578
|
+
|
579
|
+
num_bytes_read = 0
|
580
|
+
while num_bytes_read < range_size:
|
581
|
+
chunk = fp.read(range_size - num_bytes_read)
|
582
|
+
|
583
|
+
if not chunk:
|
584
|
+
break
|
585
|
+
|
586
|
+
num_bytes_read += len(chunk)
|
587
|
+
sha256_hash.update(chunk)
|
588
|
+
|
589
|
+
return sha256_hash.digest()
|
590
|
+
|
591
|
+
|
592
|
+
def _find_end_of_block(source: _FileUploadSource2, start: int, end: int) -> Optional[int]:
|
593
|
+
"""Finds the appropriate end of a block, which is the index of the byte just past the last non-zero byte in the
|
594
|
+
block.
|
595
|
+
|
596
|
+
>>> _find_end_of_block(lambda: BytesIO(b"abc123\0\0\0"), 0, 1024)
|
597
|
+
6
|
598
|
+
>>> _find_end_of_block(lambda: BytesIO(b"abc123\0\0\0"), 3, 1024)
|
599
|
+
6
|
600
|
+
>>> _find_end_of_block(lambda: BytesIO(b"abc123\0\0\0"), 0, 3)
|
601
|
+
4
|
602
|
+
>>> _find_end_of_block(lambda: BytesIO(b"abc123\0\0\0a"), 0, 9)
|
603
|
+
6
|
604
|
+
>>> _find_end_of_block(lambda: BytesIO(b"\0\0\0"), 0, 3)
|
605
|
+
0
|
606
|
+
>>> _find_end_of_block(lambda: BytesIO(b"\0\0\0\0\0\0"), 3, 6)
|
607
|
+
3
|
608
|
+
>>> _find_end_of_block(lambda: BytesIO(b""), 0, 1024)
|
609
|
+
0
|
610
|
+
"""
|
611
|
+
size = end - start
|
612
|
+
new_end = start
|
569
613
|
|
570
|
-
|
571
|
-
|
614
|
+
with source() as block_fp:
|
615
|
+
block_fp.seek(start)
|
616
|
+
|
617
|
+
num_bytes_read = 0
|
618
|
+
while num_bytes_read < size:
|
619
|
+
chunk = block_fp.read(size - num_bytes_read)
|
620
|
+
|
621
|
+
if not chunk:
|
622
|
+
break
|
623
|
+
|
624
|
+
stripped_chunk = chunk.rstrip(b"\0")
|
625
|
+
if stripped_chunk:
|
626
|
+
new_end = start + num_bytes_read + len(stripped_chunk)
|
627
|
+
|
628
|
+
num_bytes_read += len(chunk)
|
629
|
+
|
630
|
+
return new_end
|
572
631
|
|
573
632
|
|
574
633
|
def use_md5(url: str) -> bool:
|
modal/client.pyi
CHANGED
@@ -33,7 +33,7 @@ class _Client:
|
|
33
33
|
server_url: str,
|
34
34
|
client_type: int,
|
35
35
|
credentials: typing.Optional[tuple[str, str]],
|
36
|
-
version: str = "1.1.2.
|
36
|
+
version: str = "1.1.2.dev29",
|
37
37
|
):
|
38
38
|
"""mdmd:hidden
|
39
39
|
The Modal client object is not intended to be instantiated directly by users.
|
@@ -164,7 +164,7 @@ class Client:
|
|
164
164
|
server_url: str,
|
165
165
|
client_type: int,
|
166
166
|
credentials: typing.Optional[tuple[str, str]],
|
167
|
-
version: str = "1.1.2.
|
167
|
+
version: str = "1.1.2.dev29",
|
168
168
|
):
|
169
169
|
"""mdmd:hidden
|
170
170
|
The Modal client object is not intended to be instantiated directly by users.
|
modal/dict.py
CHANGED
@@ -5,7 +5,7 @@ from datetime import datetime
|
|
5
5
|
from typing import Any, Optional, Union
|
6
6
|
|
7
7
|
from google.protobuf.message import Message
|
8
|
-
from grpclib import GRPCError
|
8
|
+
from grpclib import GRPCError, Status
|
9
9
|
from synchronicity import classproperty
|
10
10
|
from synchronicity.async_wrap import asynccontextmanager
|
11
11
|
|
@@ -27,7 +27,7 @@ from ._utils.name_utils import check_object_name
|
|
27
27
|
from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
|
28
28
|
from .client import _Client
|
29
29
|
from .config import logger
|
30
|
-
from .exception import InvalidError, NotFoundError, RequestSizeError
|
30
|
+
from .exception import AlreadyExistsError, InvalidError, NotFoundError, RequestSizeError
|
31
31
|
|
32
32
|
|
33
33
|
def _serialize_dict(data):
|
@@ -49,6 +49,58 @@ class DictInfo:
|
|
49
49
|
class _DictManager:
|
50
50
|
"""Namespace with methods for managing named Dict objects."""
|
51
51
|
|
52
|
+
@staticmethod
|
53
|
+
async def create(
|
54
|
+
name: str, # Name to use for the new Dict
|
55
|
+
*,
|
56
|
+
allow_existing: bool = False, # If True, no-op when the Dict already exists
|
57
|
+
environment_name: Optional[str] = None, # Uses active environment if not specified
|
58
|
+
client: Optional[_Client] = None, # Optional client with Modal credentials
|
59
|
+
) -> None:
|
60
|
+
"""Create a new Dict object.
|
61
|
+
|
62
|
+
**Examples:**
|
63
|
+
|
64
|
+
```python notest
|
65
|
+
modal.Dict.objects.create("my-dict")
|
66
|
+
```
|
67
|
+
|
68
|
+
Dicts will be created in the active environment, or another one can be specified:
|
69
|
+
|
70
|
+
```python notest
|
71
|
+
modal.Dict.objects.create("my-dict", environment_name="dev")
|
72
|
+
```
|
73
|
+
|
74
|
+
By default, an error will be raised if the Dict already exists, but passing
|
75
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
76
|
+
|
77
|
+
```python notest
|
78
|
+
modal.Dict.objects.create("my-dict", allow_existing=True)
|
79
|
+
```
|
80
|
+
|
81
|
+
Note that this method does not return a local instance of the Dict. You can use
|
82
|
+
`modal.Dict.from_name` to perform a lookup after creation.
|
83
|
+
|
84
|
+
"""
|
85
|
+
client = await _Client.from_env() if client is None else client
|
86
|
+
object_creation_type = (
|
87
|
+
api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING
|
88
|
+
if allow_existing
|
89
|
+
else api_pb2.OBJECT_CREATION_TYPE_CREATE_FAIL_IF_EXISTS
|
90
|
+
)
|
91
|
+
req = api_pb2.DictGetOrCreateRequest(
|
92
|
+
deployment_name=name,
|
93
|
+
environment_name=_get_environment_name(environment_name),
|
94
|
+
object_creation_type=object_creation_type,
|
95
|
+
)
|
96
|
+
try:
|
97
|
+
await retry_transient_errors(client.stub.DictGetOrCreate, req)
|
98
|
+
except GRPCError as exc:
|
99
|
+
if exc.status == Status.ALREADY_EXISTS and not allow_existing:
|
100
|
+
raise AlreadyExistsError(exc.message)
|
101
|
+
else:
|
102
|
+
raise
|
103
|
+
|
52
104
|
@staticmethod
|
53
105
|
async def list(
|
54
106
|
*,
|
@@ -89,7 +141,9 @@ class _DictManager:
|
|
89
141
|
async def retrieve_page(created_before: float) -> bool:
|
90
142
|
max_page_size = 100 if max_objects is None else min(100, max_objects - len(items))
|
91
143
|
pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
|
92
|
-
req = api_pb2.DictListRequest(
|
144
|
+
req = api_pb2.DictListRequest(
|
145
|
+
environment_name=_get_environment_name(environment_name), pagination=pagination
|
146
|
+
)
|
93
147
|
resp = await retry_transient_errors(client.stub.DictList, req)
|
94
148
|
items.extend(resp.dicts)
|
95
149
|
finished = (len(resp.dicts) < max_page_size) or (max_objects is not None and len(items) >= max_objects)
|
modal/dict.pyi
CHANGED
@@ -35,6 +35,40 @@ class DictInfo:
|
|
35
35
|
|
36
36
|
class _DictManager:
|
37
37
|
"""Namespace with methods for managing named Dict objects."""
|
38
|
+
@staticmethod
|
39
|
+
async def create(
|
40
|
+
name: str,
|
41
|
+
*,
|
42
|
+
allow_existing: bool = False,
|
43
|
+
environment_name: typing.Optional[str] = None,
|
44
|
+
client: typing.Optional[modal.client._Client] = None,
|
45
|
+
) -> None:
|
46
|
+
"""Create a new Dict object.
|
47
|
+
|
48
|
+
**Examples:**
|
49
|
+
|
50
|
+
```python notest
|
51
|
+
modal.Dict.objects.create("my-dict")
|
52
|
+
```
|
53
|
+
|
54
|
+
Dicts will be created in the active environment, or another one can be specified:
|
55
|
+
|
56
|
+
```python notest
|
57
|
+
modal.Dict.objects.create("my-dict", environment_name="dev")
|
58
|
+
```
|
59
|
+
|
60
|
+
By default, an error will be raised if the Dict already exists, but passing
|
61
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
62
|
+
|
63
|
+
```python notest
|
64
|
+
modal.Dict.objects.create("my-dict", allow_existing=True)
|
65
|
+
```
|
66
|
+
|
67
|
+
Note that this method does not return a local instance of the Dict. You can use
|
68
|
+
`modal.Dict.from_name` to perform a lookup after creation.
|
69
|
+
"""
|
70
|
+
...
|
71
|
+
|
38
72
|
@staticmethod
|
39
73
|
async def list(
|
40
74
|
*,
|
@@ -100,6 +134,79 @@ class DictManager:
|
|
100
134
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
101
135
|
...
|
102
136
|
|
137
|
+
class __create_spec(typing_extensions.Protocol):
|
138
|
+
def __call__(
|
139
|
+
self,
|
140
|
+
/,
|
141
|
+
name: str,
|
142
|
+
*,
|
143
|
+
allow_existing: bool = False,
|
144
|
+
environment_name: typing.Optional[str] = None,
|
145
|
+
client: typing.Optional[modal.client.Client] = None,
|
146
|
+
) -> None:
|
147
|
+
"""Create a new Dict object.
|
148
|
+
|
149
|
+
**Examples:**
|
150
|
+
|
151
|
+
```python notest
|
152
|
+
modal.Dict.objects.create("my-dict")
|
153
|
+
```
|
154
|
+
|
155
|
+
Dicts will be created in the active environment, or another one can be specified:
|
156
|
+
|
157
|
+
```python notest
|
158
|
+
modal.Dict.objects.create("my-dict", environment_name="dev")
|
159
|
+
```
|
160
|
+
|
161
|
+
By default, an error will be raised if the Dict already exists, but passing
|
162
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
163
|
+
|
164
|
+
```python notest
|
165
|
+
modal.Dict.objects.create("my-dict", allow_existing=True)
|
166
|
+
```
|
167
|
+
|
168
|
+
Note that this method does not return a local instance of the Dict. You can use
|
169
|
+
`modal.Dict.from_name` to perform a lookup after creation.
|
170
|
+
"""
|
171
|
+
...
|
172
|
+
|
173
|
+
async def aio(
|
174
|
+
self,
|
175
|
+
/,
|
176
|
+
name: str,
|
177
|
+
*,
|
178
|
+
allow_existing: bool = False,
|
179
|
+
environment_name: typing.Optional[str] = None,
|
180
|
+
client: typing.Optional[modal.client.Client] = None,
|
181
|
+
) -> None:
|
182
|
+
"""Create a new Dict object.
|
183
|
+
|
184
|
+
**Examples:**
|
185
|
+
|
186
|
+
```python notest
|
187
|
+
modal.Dict.objects.create("my-dict")
|
188
|
+
```
|
189
|
+
|
190
|
+
Dicts will be created in the active environment, or another one can be specified:
|
191
|
+
|
192
|
+
```python notest
|
193
|
+
modal.Dict.objects.create("my-dict", environment_name="dev")
|
194
|
+
```
|
195
|
+
|
196
|
+
By default, an error will be raised if the Dict already exists, but passing
|
197
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
198
|
+
|
199
|
+
```python notest
|
200
|
+
modal.Dict.objects.create("my-dict", allow_existing=True)
|
201
|
+
```
|
202
|
+
|
203
|
+
Note that this method does not return a local instance of the Dict. You can use
|
204
|
+
`modal.Dict.from_name` to perform a lookup after creation.
|
205
|
+
"""
|
206
|
+
...
|
207
|
+
|
208
|
+
create: __create_spec
|
209
|
+
|
103
210
|
class __list_spec(typing_extensions.Protocol):
|
104
211
|
def __call__(
|
105
212
|
self,
|
modal/functions.pyi
CHANGED
@@ -433,7 +433,7 @@ class Function(
|
|
433
433
|
|
434
434
|
_call_generator: ___call_generator_spec[typing_extensions.Self]
|
435
435
|
|
436
|
-
class __remote_spec(typing_extensions.Protocol[
|
436
|
+
class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
|
437
437
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
438
438
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
439
439
|
...
|
@@ -442,7 +442,7 @@ class Function(
|
|
442
442
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
443
443
|
...
|
444
444
|
|
445
|
-
remote: __remote_spec[modal._functions.
|
445
|
+
remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
|
446
446
|
|
447
447
|
class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
|
448
448
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
@@ -469,7 +469,7 @@ class Function(
|
|
469
469
|
"""
|
470
470
|
...
|
471
471
|
|
472
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
472
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
|
473
473
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
474
474
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
475
475
|
|
@@ -493,7 +493,7 @@ class Function(
|
|
493
493
|
...
|
494
494
|
|
495
495
|
_experimental_spawn: ___experimental_spawn_spec[
|
496
|
-
modal._functions.
|
496
|
+
modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
|
497
497
|
]
|
498
498
|
|
499
499
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
|
@@ -502,7 +502,7 @@ class Function(
|
|
502
502
|
|
503
503
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
|
504
504
|
|
505
|
-
class __spawn_spec(typing_extensions.Protocol[
|
505
|
+
class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
|
506
506
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
507
507
|
"""Calls the function with the given arguments, without waiting for the results.
|
508
508
|
|
@@ -523,7 +523,7 @@ class Function(
|
|
523
523
|
"""
|
524
524
|
...
|
525
525
|
|
526
|
-
spawn: __spawn_spec[modal._functions.
|
526
|
+
spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
|
527
527
|
|
528
528
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
529
529
|
"""Return the inner Python object wrapped by this Modal Function."""
|
modal/queue.py
CHANGED
@@ -29,7 +29,7 @@ from ._utils.grpc_utils import retry_transient_errors
|
|
29
29
|
from ._utils.name_utils import check_object_name
|
30
30
|
from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
|
31
31
|
from .client import _Client
|
32
|
-
from .exception import InvalidError, NotFoundError, RequestSizeError
|
32
|
+
from .exception import AlreadyExistsError, InvalidError, NotFoundError, RequestSizeError
|
33
33
|
|
34
34
|
|
35
35
|
@dataclass
|
@@ -47,10 +47,62 @@ class QueueInfo:
|
|
47
47
|
class _QueueManager:
|
48
48
|
"""Namespace with methods for managing named Queue objects."""
|
49
49
|
|
50
|
+
@staticmethod
|
51
|
+
async def create(
|
52
|
+
name: str, # Name to use for the new Queue
|
53
|
+
*,
|
54
|
+
allow_existing: bool = False, # If True, no-op when the Queue already exists
|
55
|
+
environment_name: Optional[str] = None, # Uses active environment if not specified
|
56
|
+
client: Optional[_Client] = None, # Optional client with Modal credentials
|
57
|
+
) -> None:
|
58
|
+
"""Create a new Queue object.
|
59
|
+
|
60
|
+
**Examples:**
|
61
|
+
|
62
|
+
```python notest
|
63
|
+
modal.Queue.objects.create("my-queue")
|
64
|
+
```
|
65
|
+
|
66
|
+
Queues will be created in the active environment, or another one can be specified:
|
67
|
+
|
68
|
+
```python notest
|
69
|
+
modal.Queue.objects.create("my-queue", environment_name="dev")
|
70
|
+
```
|
71
|
+
|
72
|
+
By default, an error will be raised if the Queue already exists, but passing
|
73
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
74
|
+
|
75
|
+
```python notest
|
76
|
+
modal.Queue.objects.create("my-queue", allow_existing=True)
|
77
|
+
```
|
78
|
+
|
79
|
+
Note that this method does not return a local instance of the Queue. You can use
|
80
|
+
`modal.Queue.from_name` to perform a lookup after creation.
|
81
|
+
|
82
|
+
"""
|
83
|
+
client = await _Client.from_env() if client is None else client
|
84
|
+
object_creation_type = (
|
85
|
+
api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING
|
86
|
+
if allow_existing
|
87
|
+
else api_pb2.OBJECT_CREATION_TYPE_CREATE_FAIL_IF_EXISTS
|
88
|
+
)
|
89
|
+
req = api_pb2.QueueGetOrCreateRequest(
|
90
|
+
deployment_name=name,
|
91
|
+
environment_name=_get_environment_name(environment_name),
|
92
|
+
object_creation_type=object_creation_type,
|
93
|
+
)
|
94
|
+
try:
|
95
|
+
await retry_transient_errors(client.stub.QueueGetOrCreate, req)
|
96
|
+
except GRPCError as exc:
|
97
|
+
if exc.status == Status.ALREADY_EXISTS and not allow_existing:
|
98
|
+
raise AlreadyExistsError(exc.message)
|
99
|
+
else:
|
100
|
+
raise
|
101
|
+
|
50
102
|
@staticmethod
|
51
103
|
async def list(
|
52
104
|
*,
|
53
|
-
max_objects: Optional[int] = None, # Limit
|
105
|
+
max_objects: Optional[int] = None, # Limit results to this size
|
54
106
|
created_before: Optional[Union[datetime, str]] = None, # Limit based on creation date
|
55
107
|
environment_name: str = "", # Uses active environment if not specified
|
56
108
|
client: Optional[_Client] = None, # Optional client with Modal credentials
|
@@ -87,7 +139,9 @@ class _QueueManager:
|
|
87
139
|
async def retrieve_page(created_before: float) -> bool:
|
88
140
|
max_page_size = 100 if max_objects is None else min(100, max_objects - len(items))
|
89
141
|
pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
|
90
|
-
req = api_pb2.QueueListRequest(
|
142
|
+
req = api_pb2.QueueListRequest(
|
143
|
+
environment_name=_get_environment_name(environment_name), pagination=pagination
|
144
|
+
)
|
91
145
|
resp = await retry_transient_errors(client.stub.QueueList, req)
|
92
146
|
items.extend(resp.queues)
|
93
147
|
finished = (len(resp.queues) < max_page_size) or (max_objects is not None and len(items) >= max_objects)
|
modal/queue.pyi
CHANGED
@@ -33,6 +33,40 @@ class QueueInfo:
|
|
33
33
|
|
34
34
|
class _QueueManager:
|
35
35
|
"""Namespace with methods for managing named Queue objects."""
|
36
|
+
@staticmethod
|
37
|
+
async def create(
|
38
|
+
name: str,
|
39
|
+
*,
|
40
|
+
allow_existing: bool = False,
|
41
|
+
environment_name: typing.Optional[str] = None,
|
42
|
+
client: typing.Optional[modal.client._Client] = None,
|
43
|
+
) -> None:
|
44
|
+
"""Create a new Queue object.
|
45
|
+
|
46
|
+
**Examples:**
|
47
|
+
|
48
|
+
```python notest
|
49
|
+
modal.Queue.objects.create("my-queue")
|
50
|
+
```
|
51
|
+
|
52
|
+
Queues will be created in the active environment, or another one can be specified:
|
53
|
+
|
54
|
+
```python notest
|
55
|
+
modal.Queue.objects.create("my-queue", environment_name="dev")
|
56
|
+
```
|
57
|
+
|
58
|
+
By default, an error will be raised if the Queue already exists, but passing
|
59
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
60
|
+
|
61
|
+
```python notest
|
62
|
+
modal.Queue.objects.create("my-queue", allow_existing=True)
|
63
|
+
```
|
64
|
+
|
65
|
+
Note that this method does not return a local instance of the Queue. You can use
|
66
|
+
`modal.Queue.from_name` to perform a lookup after creation.
|
67
|
+
"""
|
68
|
+
...
|
69
|
+
|
36
70
|
@staticmethod
|
37
71
|
async def list(
|
38
72
|
*,
|
@@ -98,6 +132,79 @@ class QueueManager:
|
|
98
132
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
99
133
|
...
|
100
134
|
|
135
|
+
class __create_spec(typing_extensions.Protocol):
|
136
|
+
def __call__(
|
137
|
+
self,
|
138
|
+
/,
|
139
|
+
name: str,
|
140
|
+
*,
|
141
|
+
allow_existing: bool = False,
|
142
|
+
environment_name: typing.Optional[str] = None,
|
143
|
+
client: typing.Optional[modal.client.Client] = None,
|
144
|
+
) -> None:
|
145
|
+
"""Create a new Queue object.
|
146
|
+
|
147
|
+
**Examples:**
|
148
|
+
|
149
|
+
```python notest
|
150
|
+
modal.Queue.objects.create("my-queue")
|
151
|
+
```
|
152
|
+
|
153
|
+
Queues will be created in the active environment, or another one can be specified:
|
154
|
+
|
155
|
+
```python notest
|
156
|
+
modal.Queue.objects.create("my-queue", environment_name="dev")
|
157
|
+
```
|
158
|
+
|
159
|
+
By default, an error will be raised if the Queue already exists, but passing
|
160
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
161
|
+
|
162
|
+
```python notest
|
163
|
+
modal.Queue.objects.create("my-queue", allow_existing=True)
|
164
|
+
```
|
165
|
+
|
166
|
+
Note that this method does not return a local instance of the Queue. You can use
|
167
|
+
`modal.Queue.from_name` to perform a lookup after creation.
|
168
|
+
"""
|
169
|
+
...
|
170
|
+
|
171
|
+
async def aio(
|
172
|
+
self,
|
173
|
+
/,
|
174
|
+
name: str,
|
175
|
+
*,
|
176
|
+
allow_existing: bool = False,
|
177
|
+
environment_name: typing.Optional[str] = None,
|
178
|
+
client: typing.Optional[modal.client.Client] = None,
|
179
|
+
) -> None:
|
180
|
+
"""Create a new Queue object.
|
181
|
+
|
182
|
+
**Examples:**
|
183
|
+
|
184
|
+
```python notest
|
185
|
+
modal.Queue.objects.create("my-queue")
|
186
|
+
```
|
187
|
+
|
188
|
+
Queues will be created in the active environment, or another one can be specified:
|
189
|
+
|
190
|
+
```python notest
|
191
|
+
modal.Queue.objects.create("my-queue", environment_name="dev")
|
192
|
+
```
|
193
|
+
|
194
|
+
By default, an error will be raised if the Queue already exists, but passing
|
195
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
196
|
+
|
197
|
+
```python notest
|
198
|
+
modal.Queue.objects.create("my-queue", allow_existing=True)
|
199
|
+
```
|
200
|
+
|
201
|
+
Note that this method does not return a local instance of the Queue. You can use
|
202
|
+
`modal.Queue.from_name` to perform a lookup after creation.
|
203
|
+
"""
|
204
|
+
...
|
205
|
+
|
206
|
+
create: __create_spec
|
207
|
+
|
101
208
|
class __list_spec(typing_extensions.Protocol):
|
102
209
|
def __call__(
|
103
210
|
self,
|
modal/secret.py
CHANGED
@@ -19,7 +19,7 @@ from ._utils.grpc_utils import retry_transient_errors
|
|
19
19
|
from ._utils.name_utils import check_object_name
|
20
20
|
from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
|
21
21
|
from .client import _Client
|
22
|
-
from .exception import InvalidError, NotFoundError
|
22
|
+
from .exception import AlreadyExistsError, InvalidError, NotFoundError
|
23
23
|
|
24
24
|
ENV_DICT_WRONG_TYPE_ERR = "the env_dict argument to Secret has to be a dict[str, Union[str, None]]"
|
25
25
|
|
@@ -39,6 +39,62 @@ class SecretInfo:
|
|
39
39
|
class _SecretManager:
|
40
40
|
"""Namespace with methods for managing named Secret objects."""
|
41
41
|
|
42
|
+
@staticmethod
|
43
|
+
async def create(
|
44
|
+
name: str, # Name to use for the new Secret
|
45
|
+
env_dict: dict[str, str], # Key-value pairs to set in the Secret
|
46
|
+
*,
|
47
|
+
allow_existing: bool = False, # If True, no-op when the Secret already exists
|
48
|
+
environment_name: Optional[str] = None, # Uses active environment if not specified
|
49
|
+
client: Optional[_Client] = None, # Optional client with Modal credentials
|
50
|
+
) -> None:
|
51
|
+
"""Create a new Secret object.
|
52
|
+
|
53
|
+
**Examples:**
|
54
|
+
|
55
|
+
```python notest
|
56
|
+
contents = {"MY_KEY": "my-value", "MY_OTHER_KEY": "my-other-value"}
|
57
|
+
modal.Secret.objects.create("my-secret", contents)
|
58
|
+
```
|
59
|
+
|
60
|
+
Secrets will be created in the active environment, or another one can be specified:
|
61
|
+
|
62
|
+
```python notest
|
63
|
+
modal.Secret.objects.create("my-secret", contents, environment_name="dev")
|
64
|
+
```
|
65
|
+
|
66
|
+
By default, an error will be raised if the Secret already exists, but passing
|
67
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
68
|
+
If the `env_dict` data differs from the existing Secret, it will be ignored.
|
69
|
+
|
70
|
+
```python notest
|
71
|
+
modal.Secret.objects.create("my-secret", contents, allow_existing=True)
|
72
|
+
```
|
73
|
+
|
74
|
+
Note that this method does not return a local instance of the Secret. You can use
|
75
|
+
`modal.Secret.from_name` to perform a lookup after creation.
|
76
|
+
|
77
|
+
"""
|
78
|
+
client = await _Client.from_env() if client is None else client
|
79
|
+
object_creation_type = (
|
80
|
+
api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING
|
81
|
+
if allow_existing
|
82
|
+
else api_pb2.OBJECT_CREATION_TYPE_CREATE_FAIL_IF_EXISTS
|
83
|
+
)
|
84
|
+
req = api_pb2.SecretGetOrCreateRequest(
|
85
|
+
deployment_name=name,
|
86
|
+
environment_name=_get_environment_name(environment_name),
|
87
|
+
object_creation_type=object_creation_type,
|
88
|
+
env_dict=env_dict,
|
89
|
+
)
|
90
|
+
try:
|
91
|
+
await retry_transient_errors(client.stub.SecretGetOrCreate, req)
|
92
|
+
except GRPCError as exc:
|
93
|
+
if exc.status == Status.ALREADY_EXISTS and not allow_existing:
|
94
|
+
raise AlreadyExistsError(exc.message)
|
95
|
+
else:
|
96
|
+
raise
|
97
|
+
|
42
98
|
@staticmethod
|
43
99
|
async def list(
|
44
100
|
*,
|
@@ -79,7 +135,9 @@ class _SecretManager:
|
|
79
135
|
async def retrieve_page(created_before: float) -> bool:
|
80
136
|
max_page_size = 100 if max_objects is None else min(100, max_objects - len(items))
|
81
137
|
pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
|
82
|
-
req = api_pb2.SecretListRequest(
|
138
|
+
req = api_pb2.SecretListRequest(
|
139
|
+
environment_name=_get_environment_name(environment_name), pagination=pagination
|
140
|
+
)
|
83
141
|
resp = await retry_transient_errors(client.stub.SecretList, req)
|
84
142
|
items.extend(resp.items)
|
85
143
|
finished = (len(resp.items) < max_page_size) or (max_objects is not None and len(items) >= max_objects)
|
modal/secret.pyi
CHANGED
@@ -31,6 +31,43 @@ class SecretInfo:
|
|
31
31
|
|
32
32
|
class _SecretManager:
|
33
33
|
"""Namespace with methods for managing named Secret objects."""
|
34
|
+
@staticmethod
|
35
|
+
async def create(
|
36
|
+
name: str,
|
37
|
+
env_dict: dict[str, str],
|
38
|
+
*,
|
39
|
+
allow_existing: bool = False,
|
40
|
+
environment_name: typing.Optional[str] = None,
|
41
|
+
client: typing.Optional[modal.client._Client] = None,
|
42
|
+
) -> None:
|
43
|
+
"""Create a new Secret object.
|
44
|
+
|
45
|
+
**Examples:**
|
46
|
+
|
47
|
+
```python notest
|
48
|
+
contents = {"MY_KEY": "my-value", "MY_OTHER_KEY": "my-other-value"}
|
49
|
+
modal.Secret.objects.create("my-secret", contents)
|
50
|
+
```
|
51
|
+
|
52
|
+
Secrets will be created in the active environment, or another one can be specified:
|
53
|
+
|
54
|
+
```python notest
|
55
|
+
modal.Secret.objects.create("my-secret", contents, environment_name="dev")
|
56
|
+
```
|
57
|
+
|
58
|
+
By default, an error will be raised if the Secret already exists, but passing
|
59
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
60
|
+
If the `env_dict` data differs from the existing Secret, it will be ignored.
|
61
|
+
|
62
|
+
```python notest
|
63
|
+
modal.Secret.objects.create("my-secret", contents, allow_existing=True)
|
64
|
+
```
|
65
|
+
|
66
|
+
Note that this method does not return a local instance of the Secret. You can use
|
67
|
+
`modal.Secret.from_name` to perform a lookup after creation.
|
68
|
+
"""
|
69
|
+
...
|
70
|
+
|
34
71
|
@staticmethod
|
35
72
|
async def list(
|
36
73
|
*,
|
@@ -95,6 +132,85 @@ class SecretManager:
|
|
95
132
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
96
133
|
...
|
97
134
|
|
135
|
+
class __create_spec(typing_extensions.Protocol):
|
136
|
+
def __call__(
|
137
|
+
self,
|
138
|
+
/,
|
139
|
+
name: str,
|
140
|
+
env_dict: dict[str, str],
|
141
|
+
*,
|
142
|
+
allow_existing: bool = False,
|
143
|
+
environment_name: typing.Optional[str] = None,
|
144
|
+
client: typing.Optional[modal.client.Client] = None,
|
145
|
+
) -> None:
|
146
|
+
"""Create a new Secret object.
|
147
|
+
|
148
|
+
**Examples:**
|
149
|
+
|
150
|
+
```python notest
|
151
|
+
contents = {"MY_KEY": "my-value", "MY_OTHER_KEY": "my-other-value"}
|
152
|
+
modal.Secret.objects.create("my-secret", contents)
|
153
|
+
```
|
154
|
+
|
155
|
+
Secrets will be created in the active environment, or another one can be specified:
|
156
|
+
|
157
|
+
```python notest
|
158
|
+
modal.Secret.objects.create("my-secret", contents, environment_name="dev")
|
159
|
+
```
|
160
|
+
|
161
|
+
By default, an error will be raised if the Secret already exists, but passing
|
162
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
163
|
+
If the `env_dict` data differs from the existing Secret, it will be ignored.
|
164
|
+
|
165
|
+
```python notest
|
166
|
+
modal.Secret.objects.create("my-secret", contents, allow_existing=True)
|
167
|
+
```
|
168
|
+
|
169
|
+
Note that this method does not return a local instance of the Secret. You can use
|
170
|
+
`modal.Secret.from_name` to perform a lookup after creation.
|
171
|
+
"""
|
172
|
+
...
|
173
|
+
|
174
|
+
async def aio(
|
175
|
+
self,
|
176
|
+
/,
|
177
|
+
name: str,
|
178
|
+
env_dict: dict[str, str],
|
179
|
+
*,
|
180
|
+
allow_existing: bool = False,
|
181
|
+
environment_name: typing.Optional[str] = None,
|
182
|
+
client: typing.Optional[modal.client.Client] = None,
|
183
|
+
) -> None:
|
184
|
+
"""Create a new Secret object.
|
185
|
+
|
186
|
+
**Examples:**
|
187
|
+
|
188
|
+
```python notest
|
189
|
+
contents = {"MY_KEY": "my-value", "MY_OTHER_KEY": "my-other-value"}
|
190
|
+
modal.Secret.objects.create("my-secret", contents)
|
191
|
+
```
|
192
|
+
|
193
|
+
Secrets will be created in the active environment, or another one can be specified:
|
194
|
+
|
195
|
+
```python notest
|
196
|
+
modal.Secret.objects.create("my-secret", contents, environment_name="dev")
|
197
|
+
```
|
198
|
+
|
199
|
+
By default, an error will be raised if the Secret already exists, but passing
|
200
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
201
|
+
If the `env_dict` data differs from the existing Secret, it will be ignored.
|
202
|
+
|
203
|
+
```python notest
|
204
|
+
modal.Secret.objects.create("my-secret", contents, allow_existing=True)
|
205
|
+
```
|
206
|
+
|
207
|
+
Note that this method does not return a local instance of the Secret. You can use
|
208
|
+
`modal.Secret.from_name` to perform a lookup after creation.
|
209
|
+
"""
|
210
|
+
...
|
211
|
+
|
212
|
+
create: __create_spec
|
213
|
+
|
98
214
|
class __list_spec(typing_extensions.Protocol):
|
99
215
|
def __call__(
|
100
216
|
self,
|
modal/volume.py
CHANGED
@@ -30,7 +30,7 @@ from synchronicity.async_wrap import asynccontextmanager
|
|
30
30
|
|
31
31
|
import modal.exception
|
32
32
|
import modal_proto.api_pb2
|
33
|
-
from modal.exception import InvalidError, NotFoundError, VolumeUploadTimeoutError
|
33
|
+
from modal.exception import AlreadyExistsError, InvalidError, NotFoundError, VolumeUploadTimeoutError
|
34
34
|
from modal_proto import api_pb2
|
35
35
|
|
36
36
|
from ._object import (
|
@@ -116,6 +116,57 @@ class VolumeInfo:
|
|
116
116
|
class _VolumeManager:
|
117
117
|
"""Namespace with methods for managing named Volume objects."""
|
118
118
|
|
119
|
+
@staticmethod
|
120
|
+
async def create(
|
121
|
+
name: str, # Name to use for the new Volume
|
122
|
+
*,
|
123
|
+
allow_existing: bool = False, # If True, no-op when the Volume already exists
|
124
|
+
environment_name: Optional[str] = None, # Uses active environment if not specified
|
125
|
+
client: Optional[_Client] = None, # Optional client with Modal credentials
|
126
|
+
) -> None:
|
127
|
+
"""Create a new Volume object.
|
128
|
+
|
129
|
+
**Examples:**
|
130
|
+
|
131
|
+
```python notest
|
132
|
+
modal.Volume.objects.create("my-volume")
|
133
|
+
```
|
134
|
+
|
135
|
+
Volumes will be created in the active environment, or another one can be specified:
|
136
|
+
|
137
|
+
```python notest
|
138
|
+
modal.Volume.objects.create("my-volume", environment_name="dev")
|
139
|
+
```
|
140
|
+
|
141
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
142
|
+
|
143
|
+
```python notest
|
144
|
+
modal.Volume.objects.create("my-volume", allow_existing=True)
|
145
|
+
```
|
146
|
+
|
147
|
+
Note that this method does not return a local instance of the Volume. You can use
|
148
|
+
`modal.Volume.from_name` to perform a lookup after creation.
|
149
|
+
|
150
|
+
"""
|
151
|
+
client = await _Client.from_env() if client is None else client
|
152
|
+
object_creation_type = (
|
153
|
+
api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING
|
154
|
+
if allow_existing
|
155
|
+
else api_pb2.OBJECT_CREATION_TYPE_CREATE_FAIL_IF_EXISTS
|
156
|
+
)
|
157
|
+
req = api_pb2.VolumeGetOrCreateRequest(
|
158
|
+
deployment_name=name,
|
159
|
+
environment_name=_get_environment_name(environment_name),
|
160
|
+
object_creation_type=object_creation_type,
|
161
|
+
)
|
162
|
+
try:
|
163
|
+
await retry_transient_errors(client.stub.VolumeGetOrCreate, req)
|
164
|
+
except GRPCError as exc:
|
165
|
+
if exc.status == Status.ALREADY_EXISTS and not allow_existing:
|
166
|
+
raise AlreadyExistsError(exc.message)
|
167
|
+
else:
|
168
|
+
raise
|
169
|
+
|
119
170
|
@staticmethod
|
120
171
|
async def list(
|
121
172
|
*,
|
@@ -156,7 +207,9 @@ class _VolumeManager:
|
|
156
207
|
async def retrieve_page(created_before: float) -> bool:
|
157
208
|
max_page_size = 100 if max_objects is None else min(100, max_objects - len(items))
|
158
209
|
pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
|
159
|
-
req = api_pb2.VolumeListRequest(
|
210
|
+
req = api_pb2.VolumeListRequest(
|
211
|
+
environment_name=_get_environment_name(environment_name), pagination=pagination
|
212
|
+
)
|
160
213
|
resp = await retry_transient_errors(client.stub.VolumeList, req)
|
161
214
|
items.extend(resp.items)
|
162
215
|
finished = (len(resp.items) < max_page_size) or (max_objects is not None and len(items) >= max_objects)
|
@@ -1122,9 +1175,9 @@ class _VolumeUploadContextManager2(_AbstractVolumeUploadContextManager):
|
|
1122
1175
|
for file_spec in file_specs:
|
1123
1176
|
blocks = [
|
1124
1177
|
api_pb2.VolumePutFiles2Request.Block(
|
1125
|
-
contents_sha256=
|
1178
|
+
contents_sha256=block.contents_sha256, put_response=put_responses.get(block.contents_sha256)
|
1126
1179
|
)
|
1127
|
-
for
|
1180
|
+
for block in file_spec.blocks
|
1128
1181
|
]
|
1129
1182
|
files.append(
|
1130
1183
|
api_pb2.VolumePutFiles2Request.File(
|
@@ -1181,7 +1234,7 @@ async def _put_missing_blocks(
|
|
1181
1234
|
# TODO(dflemstr): Type is `api_pb2.VolumePutFiles2Response.MissingBlock` but synchronicity gets confused
|
1182
1235
|
# by the nested class (?)
|
1183
1236
|
missing_block,
|
1184
|
-
) ->
|
1237
|
+
) -> tuple[bytes, bytes]:
|
1185
1238
|
# Lazily import to keep the eager loading time of this module down
|
1186
1239
|
from ._utils.bytes_io_segment_payload import BytesIOSegmentPayload
|
1187
1240
|
|
@@ -1190,9 +1243,7 @@ async def _put_missing_blocks(
|
|
1190
1243
|
file_spec = file_specs[missing_block.file_index]
|
1191
1244
|
# TODO(dflemstr): What if the underlying file has changed here in the meantime; should we check the
|
1192
1245
|
# hash again just to be sure?
|
1193
|
-
|
1194
|
-
block_start = missing_block.block_index * BLOCK_SIZE
|
1195
|
-
block_length = min(BLOCK_SIZE, file_spec.size - block_start)
|
1246
|
+
block = file_spec.blocks[missing_block.block_index]
|
1196
1247
|
|
1197
1248
|
if file_spec.path not in file_progresses:
|
1198
1249
|
file_task_id = progress_cb(name=file_spec.path, size=file_spec.size)
|
@@ -1216,8 +1267,8 @@ async def _put_missing_blocks(
|
|
1216
1267
|
with file_spec.source() as source_fp:
|
1217
1268
|
payload = BytesIOSegmentPayload(
|
1218
1269
|
source_fp,
|
1219
|
-
|
1220
|
-
|
1270
|
+
block.start,
|
1271
|
+
block.end - block.start,
|
1221
1272
|
# limit chunk size somewhat to not keep event loop busy for too long
|
1222
1273
|
chunk_size=256 * 1024,
|
1223
1274
|
progress_report_cb=task_progress_cb,
|
@@ -1229,7 +1280,7 @@ async def _put_missing_blocks(
|
|
1229
1280
|
if len(file_progress.pending_blocks) == 0:
|
1230
1281
|
task_progress_cb(complete=True)
|
1231
1282
|
|
1232
|
-
return
|
1283
|
+
return block.contents_sha256, resp_data
|
1233
1284
|
|
1234
1285
|
tasks = [asyncio.create_task(put_missing_block(missing_block)) for missing_block in missing_blocks]
|
1235
1286
|
for task_result in asyncio.as_completed(tasks):
|
modal/volume.pyi
CHANGED
@@ -82,6 +82,39 @@ class VolumeInfo:
|
|
82
82
|
|
83
83
|
class _VolumeManager:
|
84
84
|
"""Namespace with methods for managing named Volume objects."""
|
85
|
+
@staticmethod
|
86
|
+
async def create(
|
87
|
+
name: str,
|
88
|
+
*,
|
89
|
+
allow_existing: bool = False,
|
90
|
+
environment_name: typing.Optional[str] = None,
|
91
|
+
client: typing.Optional[modal.client._Client] = None,
|
92
|
+
) -> None:
|
93
|
+
"""Create a new Volume object.
|
94
|
+
|
95
|
+
**Examples:**
|
96
|
+
|
97
|
+
```python notest
|
98
|
+
modal.Volume.objects.create("my-volume")
|
99
|
+
```
|
100
|
+
|
101
|
+
Volumes will be created in the active environment, or another one can be specified:
|
102
|
+
|
103
|
+
```python notest
|
104
|
+
modal.Volume.objects.create("my-volume", environment_name="dev")
|
105
|
+
```
|
106
|
+
|
107
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
108
|
+
|
109
|
+
```python notest
|
110
|
+
modal.Volume.objects.create("my-volume", allow_existing=True)
|
111
|
+
```
|
112
|
+
|
113
|
+
Note that this method does not return a local instance of the Volume. You can use
|
114
|
+
`modal.Volume.from_name` to perform a lookup after creation.
|
115
|
+
"""
|
116
|
+
...
|
117
|
+
|
85
118
|
@staticmethod
|
86
119
|
async def list(
|
87
120
|
*,
|
@@ -147,6 +180,77 @@ class VolumeManager:
|
|
147
180
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
148
181
|
...
|
149
182
|
|
183
|
+
class __create_spec(typing_extensions.Protocol):
|
184
|
+
def __call__(
|
185
|
+
self,
|
186
|
+
/,
|
187
|
+
name: str,
|
188
|
+
*,
|
189
|
+
allow_existing: bool = False,
|
190
|
+
environment_name: typing.Optional[str] = None,
|
191
|
+
client: typing.Optional[modal.client.Client] = None,
|
192
|
+
) -> None:
|
193
|
+
"""Create a new Volume object.
|
194
|
+
|
195
|
+
**Examples:**
|
196
|
+
|
197
|
+
```python notest
|
198
|
+
modal.Volume.objects.create("my-volume")
|
199
|
+
```
|
200
|
+
|
201
|
+
Volumes will be created in the active environment, or another one can be specified:
|
202
|
+
|
203
|
+
```python notest
|
204
|
+
modal.Volume.objects.create("my-volume", environment_name="dev")
|
205
|
+
```
|
206
|
+
|
207
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
208
|
+
|
209
|
+
```python notest
|
210
|
+
modal.Volume.objects.create("my-volume", allow_existing=True)
|
211
|
+
```
|
212
|
+
|
213
|
+
Note that this method does not return a local instance of the Volume. You can use
|
214
|
+
`modal.Volume.from_name` to perform a lookup after creation.
|
215
|
+
"""
|
216
|
+
...
|
217
|
+
|
218
|
+
async def aio(
|
219
|
+
self,
|
220
|
+
/,
|
221
|
+
name: str,
|
222
|
+
*,
|
223
|
+
allow_existing: bool = False,
|
224
|
+
environment_name: typing.Optional[str] = None,
|
225
|
+
client: typing.Optional[modal.client.Client] = None,
|
226
|
+
) -> None:
|
227
|
+
"""Create a new Volume object.
|
228
|
+
|
229
|
+
**Examples:**
|
230
|
+
|
231
|
+
```python notest
|
232
|
+
modal.Volume.objects.create("my-volume")
|
233
|
+
```
|
234
|
+
|
235
|
+
Volumes will be created in the active environment, or another one can be specified:
|
236
|
+
|
237
|
+
```python notest
|
238
|
+
modal.Volume.objects.create("my-volume", environment_name="dev")
|
239
|
+
```
|
240
|
+
|
241
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
242
|
+
|
243
|
+
```python notest
|
244
|
+
modal.Volume.objects.create("my-volume", allow_existing=True)
|
245
|
+
```
|
246
|
+
|
247
|
+
Note that this method does not return a local instance of the Volume. You can use
|
248
|
+
`modal.Volume.from_name` to perform a lookup after creation.
|
249
|
+
"""
|
250
|
+
...
|
251
|
+
|
252
|
+
create: __create_spec
|
253
|
+
|
150
254
|
class __list_spec(typing_extensions.Protocol):
|
151
255
|
def __call__(
|
152
256
|
self,
|
@@ -22,7 +22,7 @@ modal/app.py,sha256=GsClEQIs8i0K-n-DxkCO3flV2xq36sejFd4Dtjxfw0U,47914
|
|
22
22
|
modal/app.pyi,sha256=k0HnXfwV3mEze3PFHmSeqXBqizNqeJWF5oxrqo-P4Wg,43447
|
23
23
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
24
24
|
modal/client.py,sha256=kyAIVB3Ay-XKJizQ_1ufUFB__EagV0MLmHJpyYyJ7J0,18636
|
25
|
-
modal/client.pyi,sha256=
|
25
|
+
modal/client.pyi,sha256=xsvlxqbpdCqw7aGMLza5OOySW89utSNEz1QHfHTi2h0,15831
|
26
26
|
modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
|
27
27
|
modal/cloud_bucket_mount.pyi,sha256=-qSfYAQvIoO_l2wsCCGTG5ZUwQieNKXdAO00yP1-LYU,7394
|
28
28
|
modal/cls.py,sha256=1mBcExFrLDTZwkD3Dzu8F26_CL0CGktOV9pE60Y8g_E,40689
|
@@ -30,8 +30,8 @@ modal/cls.pyi,sha256=TevKBrBez2R0_4Epsx5GB5gyQX_kQV-uVHPxpqEokhQ,28357
|
|
30
30
|
modal/config.py,sha256=tW-SEGjVvAt3D_MNi3LhxXnFKIA9fjLd3UIgbW8uSJE,12121
|
31
31
|
modal/container_process.py,sha256=XkPwNIW-iD_GB9u9yqv9q8y-i5cQ8eBbLZZ_GvEw9t8,6858
|
32
32
|
modal/container_process.pyi,sha256=9m-st3hCUlNN1GOTctfPPvIvoLtEl7FbuGWwif5-7YU,6037
|
33
|
-
modal/dict.py,sha256=
|
34
|
-
modal/dict.pyi,sha256=
|
33
|
+
modal/dict.py,sha256=eKd1I9J345s4kopPjT9Q8L-lDFOpaZKHhHkUwMLQq9E,22557
|
34
|
+
modal/dict.pyi,sha256=F9yxTgBjxLOy6boB3N4wc2BtnGIO1TARmyhpMBC8c80,33317
|
35
35
|
modal/environments.py,sha256=gHFNLG78bqgizpQ4w_elz27QOqmcgAonFsmLs7NjUJ4,6804
|
36
36
|
modal/environments.pyi,sha256=9-KtrzAcUe55cCP4020lSUD7-fWS7OPakAHssq4-bro,4219
|
37
37
|
modal/exception.py,sha256=o0V93PK8Hcg2YQ2aeOB1Y-qWBw4Gz5ATfyokR8GapuQ,5634
|
@@ -39,7 +39,7 @@ modal/file_io.py,sha256=BVqAJ0sgPUfN8QsYztWiGB4j56he60TncM02KsylnCw,21449
|
|
39
39
|
modal/file_io.pyi,sha256=cPT_hsplE5iLCXhYOLn1Sp9eDdk7DxdFmicQHanJZyg,15918
|
40
40
|
modal/file_pattern_matcher.py,sha256=A_Kdkej6q7YQyhM_2-BvpFmPqJ0oHb54B6yf9VqvPVE,8116
|
41
41
|
modal/functions.py,sha256=kcNHvqeGBxPI7Cgd57NIBBghkfbeFJzXO44WW0jSmao,325
|
42
|
-
modal/functions.pyi,sha256=
|
42
|
+
modal/functions.pyi,sha256=65HxorqpspknohUdxFYzKIdL1-P3JYSQLQEhcmhWgpw,36161
|
43
43
|
modal/gpu.py,sha256=Fe5ORvVPDIstSq1xjmM6OoNgLYFWvogP9r5BgmD3hYg,6769
|
44
44
|
modal/image.py,sha256=A83nmo0zfCUwgvJh0LZ7Yc1sYvDnZLl_phbKxN-9HIw,103144
|
45
45
|
modal/image.pyi,sha256=oH-GCHVEwD5fOX0K_IaWN5RKZlYwX82z-K4wxx8aN3c,68541
|
@@ -59,8 +59,8 @@ modal/partial_function.pyi,sha256=lqqOzZ9-QvHTDWKQ_oAYYOvsXgTOBKhO9u-RI98JbUk,13
|
|
59
59
|
modal/proxy.py,sha256=CQydu_NPDgApN2GLdd7rrcg8PM-pXyFdVYcTaGMBRCQ,1491
|
60
60
|
modal/proxy.pyi,sha256=yWGWwADCRGrC2w81B7671UTH4Uv3HMZKy5vVqlJUZoA,1417
|
61
61
|
modal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
62
|
-
modal/queue.py,sha256=
|
63
|
-
modal/queue.pyi,sha256=
|
62
|
+
modal/queue.py,sha256=gzpgtzCah0NgmUAFmUEvnHal5XnTri9Vblf334t2bAE,27013
|
63
|
+
modal/queue.pyi,sha256=BEbAa9Fb77Z6Th644FhMnf3BvfLuoBuUz6iUVCkbD1k,39251
|
64
64
|
modal/retries.py,sha256=IvNLDM0f_GLUDD5VgEDoN09C88yoxSrCquinAuxT1Sc,5205
|
65
65
|
modal/runner.py,sha256=ostdzYpQb-20tlD6dIq7bpWTkZkOhjJBNuMNektqnJA,24068
|
66
66
|
modal/runner.pyi,sha256=lbwLljm1cC8d6PcNvmYQhkE8501V9fg0bYqqKX6G4r4,8489
|
@@ -69,8 +69,8 @@ modal/sandbox.py,sha256=eQd0Cf9yTFCNshnj7oH8WvecbhVIwsEsmuXB9O-REis,40927
|
|
69
69
|
modal/sandbox.pyi,sha256=_ddnvZGauSRG-WelsMB5oPil8KVWb0PSvmuAzAzrLIw,41713
|
70
70
|
modal/schedule.py,sha256=ng0g0AqNY5GQI9KhkXZQ5Wam5G42glbkqVQsNpBtbDE,3078
|
71
71
|
modal/scheduler_placement.py,sha256=BAREdOY5HzHpzSBqt6jDVR6YC_jYfHMVqOzkyqQfngU,1235
|
72
|
-
modal/secret.py,sha256=
|
73
|
-
modal/secret.pyi,sha256=
|
72
|
+
modal/secret.py,sha256=Y6vMT7PUFdwCjVqAe9efxbVwKNjLk2yMLDV3HdnbdxI,18483
|
73
|
+
modal/secret.pyi,sha256=XDmES3SppQQ6Q8L6ySIg7uEv6AhmXFEnkIE-0rK8V2w,20080
|
74
74
|
modal/serving.py,sha256=3I3WBeVbzZY258u9PXBCW_dZBgypq3OhwBuTVvlgubE,4423
|
75
75
|
modal/serving.pyi,sha256=YfixTaWikyYpwhnNxCHMZnDDQiPmV1xJ87QF91U_WGU,1924
|
76
76
|
modal/snapshot.py,sha256=E3oxYQkYVRB_LeFBfmUV1Y6vHz8-azXJfC4x7A1QKnI,1455
|
@@ -78,8 +78,8 @@ modal/snapshot.pyi,sha256=0q83hlmWxAhDu8xwZyL5VmYh0i8Tigf7S60or2k30L8,1682
|
|
78
78
|
modal/stream_type.py,sha256=A6320qoAAWhEfwOCZfGtymQTu5AfLfJXXgARqooTPvY,417
|
79
79
|
modal/token_flow.py,sha256=GWpar0gANs71vm9Bd_Cj87UG1K3ljTURbkEjG3JLsrY,7616
|
80
80
|
modal/token_flow.pyi,sha256=eirYjyqbRiT3GCKMIPHJPpkvBTu8WxDKqSHehWaJI_4,2533
|
81
|
-
modal/volume.py,sha256=
|
82
|
-
modal/volume.pyi,sha256=
|
81
|
+
modal/volume.py,sha256=8Bw6q9_2lwWq5Vsykkor_0_MYMbVGlpH5DSuJ9TDwYw,52006
|
82
|
+
modal/volume.pyi,sha256=k4oEW0xzn6i1eY7nj6ppaGqo72FB3Pqm_OQb3juAgOk,52630
|
83
83
|
modal/_runtime/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
84
84
|
modal/_runtime/asgi.py,sha256=_2xSTsDD27Cit7xnMs4lzkJA2wzer2_N4Oa3BkXFzVA,22521
|
85
85
|
modal/_runtime/container_io_manager.py,sha256=9oqlKKPuLZjE7rYw3zTK30XUZighT3s4ZlA-9oxXOVI,45206
|
@@ -93,7 +93,7 @@ modal/_utils/__init__.py,sha256=waLjl5c6IPDhSsdWAm9Bji4e2PVxamYABKAze6CHVXY,28
|
|
93
93
|
modal/_utils/app_utils.py,sha256=88BT4TPLWfYAQwKTHcyzNQRHg8n9B-QE2UyJs96iV-0,108
|
94
94
|
modal/_utils/async_utils.py,sha256=7uA4KJV7XRgak5nXZSGRE-RN1h91UOyNwK6v_ilUQMQ,29737
|
95
95
|
modal/_utils/auth_token_manager.py,sha256=i-kfLgDd4BMAw6wouO5aKfNGHo27VAZoVOsbEWqDr2I,5252
|
96
|
-
modal/_utils/blob_utils.py,sha256=
|
96
|
+
modal/_utils/blob_utils.py,sha256=N5ZEKdGggJgHkO2SeavT505qvewZrvd-6QV2_7cYFbk,22673
|
97
97
|
modal/_utils/bytes_io_segment_payload.py,sha256=vaXPq8b52-x6G2hwE7SrjS58pg_aRm7gV3bn3yjmTzQ,4261
|
98
98
|
modal/_utils/deprecation.py,sha256=-Bgg7jZdcJU8lROy18YyVnQYbM8hue-hVmwJqlWAGH0,5504
|
99
99
|
modal/_utils/docker_utils.py,sha256=h1uETghR40mp_y3fSWuZAfbIASH1HMzuphJHghAL6DU,3722
|
@@ -153,7 +153,7 @@ modal/experimental/__init__.py,sha256=dPBPpxsmjZMLF3YjRrXoTvT01pl65wxi4UdFZsOem3
|
|
153
153
|
modal/experimental/flash.py,sha256=viXQumCIFp5VFsPFURdFTBTjP_QnsAi8nSWXAMmfjeQ,19744
|
154
154
|
modal/experimental/flash.pyi,sha256=A8_qJGtGoXEzKDdHbvhmCw7oqfneFEvJQK3ZdTOvUdU,10830
|
155
155
|
modal/experimental/ipython.py,sha256=TrCfmol9LGsRZMeDoeMPx3Hv3BFqQhYnmD_iH0pqdhk,2904
|
156
|
-
modal-1.1.2.
|
156
|
+
modal-1.1.2.dev29.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
157
157
|
modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
|
158
158
|
modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
|
159
159
|
modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
|
@@ -176,10 +176,10 @@ modal_proto/options_pb2.pyi,sha256=l7DBrbLO7q3Ir-XDkWsajm0d0TQqqrfuX54i4BMpdQg,1
|
|
176
176
|
modal_proto/options_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
177
177
|
modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0yJSI,247
|
178
178
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
179
|
-
modal_version/__init__.py,sha256=
|
179
|
+
modal_version/__init__.py,sha256=k5tQPaZpe6NyxMJNAQPjEsO3ssJzq0380En8g8V2o6s,121
|
180
180
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
181
|
-
modal-1.1.2.
|
182
|
-
modal-1.1.2.
|
183
|
-
modal-1.1.2.
|
184
|
-
modal-1.1.2.
|
185
|
-
modal-1.1.2.
|
181
|
+
modal-1.1.2.dev29.dist-info/METADATA,sha256=BimC9Hd1P4EOaSbXatNspB8KKPkENmbmbXhFPwm5eDw,2460
|
182
|
+
modal-1.1.2.dev29.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
183
|
+
modal-1.1.2.dev29.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
184
|
+
modal-1.1.2.dev29.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
|
185
|
+
modal-1.1.2.dev29.dist-info/RECORD,,
|
modal_version/__init__.py
CHANGED
File without changes
|
File without changes
|
File without changes
|
File without changes
|