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.
@@ -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
- # Raw (unencoded 32 byte) SHA256 sum per 8MiB file block
454
- blocks_sha256: list[bytes]
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
- blocks_sha256 = await hash_blocks_sha256(source, size, hash_semaphore)
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
- blocks_sha256=blocks_sha256,
541
+ blocks=blocks,
532
542
  mode=mode & 0o7777,
533
543
  size=size,
534
544
  )
535
545
 
536
546
 
537
- async def hash_blocks_sha256(
547
+ async def _gather_blocks(
538
548
  source: _FileUploadSource2,
539
549
  size: int,
540
550
  hash_semaphore: asyncio.Semaphore,
541
- ) -> list[bytes]:
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 blocking_hash_block_sha256(block_idx: int) -> bytes:
548
- sha256_hash = hashlib.sha256()
549
- block_start = block_idx * BLOCK_SIZE
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
- with source() as block_fp:
552
- block_fp.seek(block_start)
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
- if not chunk:
559
- break
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
- return sha256_hash.digest()
572
+ def _hash_range_sha256(source: _FileUploadSource2, start, end):
573
+ sha256_hash = hashlib.sha256()
574
+ range_size = end - start
565
575
 
566
- async def hash_block_sha256(block_idx: int) -> bytes:
567
- async with hash_semaphore:
568
- return await asyncio.to_thread(blocking_hash_block_sha256, block_idx)
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
- tasks = (hash_block_sha256(idx) for idx in range(num_blocks))
571
- return await asyncio.gather(*tasks)
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.dev27",
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.dev27",
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(environment_name=environment_name, pagination=pagination)
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[ReturnType_INNER, P_INNER, SUPERSELF]):
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.ReturnType, modal._functions.P, typing_extensions.Self]
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[ReturnType_INNER, P_INNER, SUPERSELF]):
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.ReturnType, modal._functions.P, typing_extensions.Self
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[ReturnType_INNER, P_INNER, SUPERSELF]):
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.ReturnType, modal._functions.P, typing_extensions.Self]
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 requests to this size
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(environment_name=environment_name, pagination=pagination)
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(environment_name=environment_name, pagination=pagination)
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(environment_name=environment_name, pagination=pagination)
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=block_sha256, put_response=put_responses.get(block_sha256)
1178
+ contents_sha256=block.contents_sha256, put_response=put_responses.get(block.contents_sha256)
1126
1179
  )
1127
- for block_sha256 in file_spec.blocks_sha256
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
- ) -> (bytes, bytes):
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
- block_sha256 = file_spec.blocks_sha256[missing_block.block_index]
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
- block_start,
1220
- block_length,
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 block_sha256, resp_data
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,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.2.dev27
3
+ Version: 1.1.2.dev29
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -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=TANRVFKqJGvNTQVQg5aKkw5wkGsTUGwdX9Q2UO3vnFc,15831
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=wqgbV0K0wp-jPF6JLQGOnC7hYEJeV1BfoaqGyNy3DvY,20538
34
- modal/dict.pyi,sha256=zHj9XK1uv8PR8Vvd9-RmMMd52002SxoNjNsrK2Bn1OE,29914
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=s3PQtacOfSeHukLR7Xz3qGD2sVh-CEgfpjgimv2gCCo,36161
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=TJWkFtOVxg1gtlc40-wGVsYzY7PqQpaJXYSsI_-Rquw,24988
63
- modal/queue.pyi,sha256=cnnd5ZxZA3X0xT-igJbLC3p0TXATNwkJaXC8qCeFphQ,35815
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=S_7Ck9J0yVqfBiaF2BUpqQ9NRBhxre19bWggl8PS7_Q,16146
73
- modal/secret.pyi,sha256=5YTMzaybIoBtOFhK2T2SuhhvHBbo4lDm8sBqH-rITtk,15912
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=XCAq7kY9z1bYXgyU8VJf3hnY26NNdhzuXzWTKJUUUH0,50166
82
- modal/volume.pyi,sha256=fnHhR152qCh5St7XT-PReQK_tPAQ0hmcXKoezOulEl4,49427
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=bySVr9M7hlFzZo-u4ikovxMdcdEE8yfGOs94Zex2k4o,20913
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.dev27.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
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=fY3poOeCUpqsevQvZoyjCXsrjR6mpgHAIGV5AwiTc8E,121
179
+ modal_version/__init__.py,sha256=k5tQPaZpe6NyxMJNAQPjEsO3ssJzq0380En8g8V2o6s,121
180
180
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
181
- modal-1.1.2.dev27.dist-info/METADATA,sha256=SkxMH7rjheb8Y9Jh3T7qDd-IVPvokWQ0rffL1VxOWx8,2460
182
- modal-1.1.2.dev27.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
183
- modal-1.1.2.dev27.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
184
- modal-1.1.2.dev27.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
185
- modal-1.1.2.dev27.dist-info/RECORD,,
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
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.1.2.dev27"
4
+ __version__ = "1.1.2.dev29"