modal 1.0.6.dev31__py3-none-any.whl → 1.0.6.dev34__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

modal/_functions.py CHANGED
@@ -401,9 +401,7 @@ class _InputPlaneInvocation:
401
401
  parent_input_id=current_input_id() or "",
402
402
  input=input_item,
403
403
  )
404
- metadata: list[tuple[str, str]] = []
405
- if input_plane_region and input_plane_region != "":
406
- metadata.append(("x-modal-input-plane-region", input_plane_region))
404
+ metadata = await _InputPlaneInvocation._get_metadata(input_plane_region, client)
407
405
  response = await retry_transient_errors(stub.AttemptStart, request, metadata=metadata)
408
406
  attempt_token = response.attempt_token
409
407
 
@@ -419,9 +417,7 @@ class _InputPlaneInvocation:
419
417
  timeout_secs=OUTPUTS_TIMEOUT,
420
418
  requested_at=time.time(),
421
419
  )
422
- metadata: list[tuple[str, str]] = []
423
- if self.input_plane_region and self.input_plane_region != "":
424
- metadata.append(("x-modal-input-plane-region", self.input_plane_region))
420
+ metadata = await self._get_metadata(self.input_plane_region, self.client)
425
421
  await_response: api_pb2.AttemptAwaitResponse = await retry_transient_errors(
426
422
  self.stub.AttemptAwait,
427
423
  await_request,
@@ -457,6 +453,12 @@ class _InputPlaneInvocation:
457
453
  await_response.output.result, await_response.output.data_format, control_plane_stub, self.client
458
454
  )
459
455
 
456
+ @staticmethod
457
+ async def _get_metadata(input_plane_region: str, client: _Client) -> list[tuple[str, str]]:
458
+ if not input_plane_region:
459
+ return []
460
+ token = await client._auth_token_manager.get_token()
461
+ return [("x-modal-input-plane-region", input_plane_region), ("x-modal-auth-token", token)]
460
462
 
461
463
  # Wrapper type for api_pb2.FunctionStats
462
464
  @dataclass(frozen=True)
@@ -0,0 +1,114 @@
1
+ # Copyright Modal Labs 2025
2
+ import asyncio
3
+ import base64
4
+ import json
5
+ import time
6
+ import typing
7
+ from typing import Any
8
+
9
+ from modal.exception import ExecutionError
10
+ from modal_proto import api_pb2, modal_api_grpc
11
+
12
+ from .grpc_utils import retry_transient_errors
13
+ from .logger import logger
14
+
15
+
16
+ class _AuthTokenManager:
17
+ """Handles fetching and refreshing of the input plane auth token."""
18
+
19
+ # Start refreshing this many seconds before the token expires
20
+ REFRESH_WINDOW = 5 * 60
21
+ # If the token doesn't have an expiry field, default to current time plus this value (not expected).
22
+ DEFAULT_EXPIRY_OFFSET = 20 * 60
23
+
24
+ def __init__(self, stub: "modal_api_grpc.ModalClientModal"):
25
+ self._stub = stub
26
+ self._token = ""
27
+ self._expiry = 0.0
28
+ self._lock: typing.Union[asyncio.Lock, None] = None
29
+
30
+ async def get_token(self):
31
+ """
32
+ When called, the AuthTokenManager can be in one of three states:
33
+ 1. Has a valid cached token. It is returned to the caller.
34
+ 2. Has no cached token, or the token is expired. We fetch a new one and cache it. If `get_token` is called
35
+ concurrently by multiple coroutines, all requests will block until the token has been fetched. But only one
36
+ coroutine will actually make a request to the control plane to fetch the new token. This ensures we do not hit
37
+ the control plane with more requests than needed.
38
+ 3. Has a valid cached token, but it is going to expire in the next 5 minutes. In this case we fetch a new token
39
+ and cache it. If `get_token` is called concurrently, only one request will fetch the new token, and the others
40
+ will be given the old (but still valid) token - i.e. they will not block.
41
+ """
42
+ if not self._token or self._is_expired():
43
+ # We either have no token or it is expired - block everyone until we get a new token
44
+ await self._refresh_token()
45
+ elif self._needs_refresh():
46
+ # The token hasn't expired yet, but will soon, so it needs a refresh.
47
+ lock = await self._get_lock()
48
+ if lock.locked():
49
+ # The lock is taken, so someone else is refreshing. Continue to use the old token.
50
+ return self._token
51
+ else:
52
+ # The lock is not taken, so we need to fetch a new token.
53
+ await self._refresh_token()
54
+
55
+ return self._token
56
+
57
+ async def _refresh_token(self):
58
+ """
59
+ Fetch a new token from the control plane. If called concurrently, only one coroutine will make a request for a
60
+ new token. The others will block on a lock, until the first coroutine has fetched the new token.
61
+ """
62
+ lock = await self._get_lock()
63
+ async with lock:
64
+ # Double check inside lock - maybe another coroutine refreshed already. This happens the first time we fetch
65
+ # the token. The first coroutine will fetch the token, while the others block on the lock, waiting for the
66
+ # new token. Once we have a new token, the other coroutines will unblock and return from here.
67
+ if self._token and not self._needs_refresh():
68
+ return
69
+ resp: api_pb2.AuthTokenGetResponse = await retry_transient_errors(
70
+ self._stub.AuthTokenGet, api_pb2.AuthTokenGetRequest()
71
+ )
72
+ if not resp.token:
73
+ # Not expected
74
+ raise ExecutionError(
75
+ "Internal error: Did not receive auth token from server. Please contact Modal support."
76
+ )
77
+
78
+ self._token = resp.token
79
+ if exp := self._decode_jwt(resp.token).get("exp"):
80
+ self._expiry = float(exp)
81
+ else:
82
+ # This should never happen.
83
+ logger.warning("x-modal-auth-token does not contain exp field")
84
+ # We'll use the token, and set the expiry to 20 min from now.
85
+ self._expiry = time.time() + self.DEFAULT_EXPIRY_OFFSET
86
+
87
+ async def _get_lock(self) -> asyncio.Lock:
88
+ # Note: this function runs no async code but is marked as async to ensure it's
89
+ # being run inside the synchronicity event loop and binds the lock to the
90
+ # correct event loop on Python 3.9 which eagerly assigns event loops on
91
+ # constructions of locks
92
+ if self._lock is None:
93
+ self._lock = asyncio.Lock()
94
+ return self._lock
95
+
96
+ @staticmethod
97
+ def _decode_jwt(token: str) -> dict[str, Any]:
98
+ """
99
+ Decodes a JWT into a dict without verifying signature. We do this manually instead of using a library to avoid
100
+ adding another dependency to the client.
101
+ """
102
+ try:
103
+ payload = token.split(".")[1]
104
+ padding = "=" * (-len(payload) % 4)
105
+ decoded_bytes = base64.urlsafe_b64decode(payload + padding)
106
+ return json.loads(decoded_bytes)
107
+ except Exception as e:
108
+ raise ValueError("Internal error: Cannot parse auth token. Please contact Modal support.") from e
109
+
110
+ def _needs_refresh(self):
111
+ return time.time() >= (self._expiry - self.REFRESH_WINDOW)
112
+
113
+ def _is_expired(self):
114
+ return time.time() >= self._expiry
@@ -149,21 +149,7 @@ def create_channel(
149
149
 
150
150
  logger.debug(f"Sending request to {event.method_name}")
151
151
 
152
- async def recv_initial_metadata(initial_metadata: grpclib.events.RecvInitialMetadata) -> None:
153
- # If we receive an auth token from the server, include it in all future requests.
154
- # TODO(nathan): This isn't perfect because the metadata isn't propagated when the
155
- # process is forked and a new channel is created. This is OK for now since this
156
- # token is only used by the experimental input plane
157
- if token := initial_metadata.metadata.get("x-modal-auth-token"):
158
- metadata["x-modal-auth-token"] = str(token)
159
-
160
- async def recv_trailing_metadata(trailing_metadata: grpclib.events.RecvTrailingMetadata) -> None:
161
- if token := trailing_metadata.metadata.get("x-modal-auth-token"):
162
- metadata["x-modal-auth-token"] = str(token)
163
-
164
152
  grpclib.events.listen(channel, grpclib.events.SendRequest, send_request)
165
- grpclib.events.listen(channel, grpclib.events.RecvInitialMetadata, recv_initial_metadata)
166
- grpclib.events.listen(channel, grpclib.events.RecvTrailingMetadata, recv_trailing_metadata)
167
153
 
168
154
  return channel
169
155
 
modal/client.py CHANGED
@@ -27,6 +27,7 @@ from modal_version import __version__
27
27
  from ._traceback import print_server_warnings
28
28
  from ._utils import async_utils
29
29
  from ._utils.async_utils import TaskContext, synchronize_api
30
+ from ._utils.auth_token_manager import _AuthTokenManager
30
31
  from ._utils.grpc_utils import ConnectionManager, retry_transient_errors
31
32
  from .config import _check_config, _is_remote, config, logger
32
33
  from .exception import AuthError, ClientClosed
@@ -78,6 +79,7 @@ class _Client:
78
79
  _cancellation_context: TaskContext
79
80
  _cancellation_context_event_loop: asyncio.AbstractEventLoop = None
80
81
  _stub: Optional[api_grpc.ModalClientStub]
82
+ _auth_token_manager: _AuthTokenManager = None
81
83
  _snapshotted: bool
82
84
 
83
85
  def __init__(
@@ -96,6 +98,7 @@ class _Client:
96
98
  self.version = version
97
99
  self._closed = False
98
100
  self._stub: Optional[modal_api_grpc.ModalClientModal] = None
101
+ self._auth_token_manager: Optional[_AuthTokenManager] = None
99
102
  self._snapshotted = False
100
103
  self._owner_pid = None
101
104
 
@@ -133,9 +136,9 @@ class _Client:
133
136
  self._cancellation_context = TaskContext(grace=0.5) # allow running rpcs to finish in 0.5s when closing client
134
137
  self._cancellation_context_event_loop = asyncio.get_running_loop()
135
138
  await self._cancellation_context.__aenter__()
136
-
137
139
  self._connection_manager = ConnectionManager(client=self, metadata=metadata)
138
140
  self._stub = await self.get_stub(self.server_url)
141
+ self._auth_token_manager = _AuthTokenManager(self.stub)
139
142
  self._owner_pid = os.getpid()
140
143
 
141
144
  async def _close(self, prep_for_restore: bool = False):
@@ -424,3 +427,4 @@ class UnaryStreamWrapper(Generic[RequestType, ResponseType]):
424
427
  self.wrapped_method.channel = await self.client._get_channel(self.server_url)
425
428
  async for response in self.client._call_stream(self.wrapped_method, request, metadata=metadata):
426
429
  yield response
430
+
modal/client.pyi CHANGED
@@ -4,6 +4,7 @@ import collections.abc
4
4
  import google.protobuf.message
5
5
  import grpclib.client
6
6
  import modal._utils.async_utils
7
+ import modal._utils.auth_token_manager
7
8
  import modal_proto.api_grpc
8
9
  import modal_proto.modal_api_grpc
9
10
  import synchronicity.combined_types
@@ -24,6 +25,7 @@ class _Client:
24
25
  _cancellation_context: modal._utils.async_utils.TaskContext
25
26
  _cancellation_context_event_loop: asyncio.events.AbstractEventLoop
26
27
  _stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
28
+ _auth_token_manager: modal._utils.auth_token_manager._AuthTokenManager
27
29
  _snapshotted: bool
28
30
 
29
31
  def __init__(
@@ -31,7 +33,7 @@ class _Client:
31
33
  server_url: str,
32
34
  client_type: int,
33
35
  credentials: typing.Optional[tuple[str, str]],
34
- version: str = "1.0.6.dev31",
36
+ version: str = "1.0.6.dev34",
35
37
  ):
36
38
  """mdmd:hidden
37
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -153,6 +155,7 @@ class Client:
153
155
  _cancellation_context: modal._utils.async_utils.TaskContext
154
156
  _cancellation_context_event_loop: asyncio.events.AbstractEventLoop
155
157
  _stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
158
+ _auth_token_manager: modal._utils.auth_token_manager._AuthTokenManager
156
159
  _snapshotted: bool
157
160
 
158
161
  def __init__(
@@ -160,7 +163,7 @@ class Client:
160
163
  server_url: str,
161
164
  client_type: int,
162
165
  credentials: typing.Optional[tuple[str, str]],
163
- version: str = "1.0.6.dev31",
166
+ version: str = "1.0.6.dev34",
164
167
  ):
165
168
  """mdmd:hidden
166
169
  The Modal client object is not intended to be instantiated directly by users.
modal/file_io.py CHANGED
@@ -117,10 +117,15 @@ class FileWatchEvent:
117
117
  # The FileIO class is designed to mimic Python's io.FileIO
118
118
  # See https://github.com/python/cpython/blob/main/Lib/_pyio.py#L1459
119
119
  class _FileIO(Generic[T]):
120
- """FileIO handle, used in the Sandbox filesystem API.
120
+ """[Alpha] FileIO handle, used in the Sandbox filesystem API.
121
121
 
122
122
  The API is designed to mimic Python's io.FileIO.
123
123
 
124
+ Currently this API is in Alpha and is subject to change. File I/O operations
125
+ may be limited in size to 100 MiB, and the throughput of requests is
126
+ restricted in the current implementation. For our recommendations on large file transfers
127
+ see the Sandbox [filesystem access guide](https://modal.com/docs/guide/sandbox-files).
128
+
124
129
  **Usage**
125
130
 
126
131
  ```python
@@ -144,7 +149,7 @@ class _FileIO(Generic[T]):
144
149
  _task_id: str = ""
145
150
  _file_descriptor: str = ""
146
151
  _client: _Client
147
- _watch_output_buffer: list[Union[Optional[bytes],Exception]] = []
152
+ _watch_output_buffer: list[Union[Optional[bytes], Exception]] = []
148
153
 
149
154
  def __init__(self, client: _Client, task_id: str) -> None:
150
155
  self._client = client
modal/file_io.pyi CHANGED
@@ -51,10 +51,15 @@ class FileWatchEvent:
51
51
  ...
52
52
 
53
53
  class _FileIO(typing.Generic[T]):
54
- """FileIO handle, used in the Sandbox filesystem API.
54
+ """[Alpha] FileIO handle, used in the Sandbox filesystem API.
55
55
 
56
56
  The API is designed to mimic Python's io.FileIO.
57
57
 
58
+ Currently this API is in Alpha and is subject to change. File I/O operations
59
+ may be limited in size to 100 MiB, and the throughput of requests is
60
+ restricted in the current implementation. For our recommendations on large file transfers
61
+ see the Sandbox [filesystem access guide](https://modal.com/docs/guide/sandbox-files).
62
+
58
63
  **Usage**
59
64
 
60
65
  ```python
@@ -216,10 +221,15 @@ SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
216
221
  T_INNER = typing.TypeVar("T_INNER", covariant=True)
217
222
 
218
223
  class FileIO(typing.Generic[T]):
219
- """FileIO handle, used in the Sandbox filesystem API.
224
+ """[Alpha] FileIO handle, used in the Sandbox filesystem API.
220
225
 
221
226
  The API is designed to mimic Python's io.FileIO.
222
227
 
228
+ Currently this API is in Alpha and is subject to change. File I/O operations
229
+ may be limited in size to 100 MiB, and the throughput of requests is
230
+ restricted in the current implementation. For our recommendations on large file transfers
231
+ see the Sandbox [filesystem access guide](https://modal.com/docs/guide/sandbox-files).
232
+
223
233
  **Usage**
224
234
 
225
235
  ```python
modal/sandbox.py CHANGED
@@ -578,7 +578,9 @@ class _Sandbox(_Object, type_prefix="sb"):
578
578
 
579
579
  async def _get_task_id(self) -> str:
580
580
  while not self._task_id:
581
- resp = await self._client.stub.SandboxGetTaskId(api_pb2.SandboxGetTaskIdRequest(sandbox_id=self.object_id))
581
+ resp = await retry_transient_errors(
582
+ self._client.stub.SandboxGetTaskId, api_pb2.SandboxGetTaskIdRequest(sandbox_id=self.object_id)
583
+ )
582
584
  self._task_id = resp.task_id
583
585
  if not self._task_id:
584
586
  await asyncio.sleep(0.5)
@@ -745,7 +747,7 @@ class _Sandbox(_Object, type_prefix="sb"):
745
747
  path: str,
746
748
  mode: Union["_typeshed.OpenTextMode", "_typeshed.OpenBinaryMode"] = "r",
747
749
  ):
748
- """Open a file in the Sandbox and return a FileIO handle.
750
+ """[Alpha] Open a file in the Sandbox and return a FileIO handle.
749
751
 
750
752
  See the [`FileIO`](https://modal.com/docs/reference/modal.file_io#modalfile_iofileio) docs for more information.
751
753
 
@@ -762,17 +764,17 @@ class _Sandbox(_Object, type_prefix="sb"):
762
764
  return await _FileIO.create(path, mode, self._client, task_id)
763
765
 
764
766
  async def ls(self, path: str) -> list[str]:
765
- """List the contents of a directory in the Sandbox."""
767
+ """[Alpha] List the contents of a directory in the Sandbox."""
766
768
  task_id = await self._get_task_id()
767
769
  return await _FileIO.ls(path, self._client, task_id)
768
770
 
769
771
  async def mkdir(self, path: str, parents: bool = False) -> None:
770
- """Create a new directory in the Sandbox."""
772
+ """[Alpha] Create a new directory in the Sandbox."""
771
773
  task_id = await self._get_task_id()
772
774
  return await _FileIO.mkdir(path, self._client, task_id, parents)
773
775
 
774
776
  async def rm(self, path: str, recursive: bool = False) -> None:
775
- """Remove a file or directory in the Sandbox."""
777
+ """[Alpha] Remove a file or directory in the Sandbox."""
776
778
  task_id = await self._get_task_id()
777
779
  return await _FileIO.rm(path, self._client, task_id, recursive)
778
780
 
@@ -783,7 +785,7 @@ class _Sandbox(_Object, type_prefix="sb"):
783
785
  recursive: Optional[bool] = None,
784
786
  timeout: Optional[int] = None,
785
787
  ) -> AsyncIterator[FileWatchEvent]:
786
- """Watch a file or directory in the Sandbox for changes."""
788
+ """[Alpha] Watch a file or directory in the Sandbox for changes."""
787
789
  task_id = await self._get_task_id()
788
790
  async for event in _FileIO.watch(path, self._client, task_id, filter, recursive, timeout):
789
791
  yield event
modal/sandbox.pyi CHANGED
@@ -245,15 +245,15 @@ class _Sandbox(modal._object._Object):
245
245
  @typing.overload
246
246
  async def open(self, path: str, mode: _typeshed.OpenBinaryMode) -> modal.file_io._FileIO[bytes]: ...
247
247
  async def ls(self, path: str) -> list[str]:
248
- """List the contents of a directory in the Sandbox."""
248
+ """[Alpha] List the contents of a directory in the Sandbox."""
249
249
  ...
250
250
 
251
251
  async def mkdir(self, path: str, parents: bool = False) -> None:
252
- """Create a new directory in the Sandbox."""
252
+ """[Alpha] Create a new directory in the Sandbox."""
253
253
  ...
254
254
 
255
255
  async def rm(self, path: str, recursive: bool = False) -> None:
256
- """Remove a file or directory in the Sandbox."""
256
+ """[Alpha] Remove a file or directory in the Sandbox."""
257
257
  ...
258
258
 
259
259
  def watch(
@@ -263,7 +263,7 @@ class _Sandbox(modal._object._Object):
263
263
  recursive: typing.Optional[bool] = None,
264
264
  timeout: typing.Optional[int] = None,
265
265
  ) -> typing.AsyncIterator[modal.file_io.FileWatchEvent]:
266
- """Watch a file or directory in the Sandbox for changes."""
266
+ """[Alpha] Watch a file or directory in the Sandbox for changes."""
267
267
  ...
268
268
 
269
269
  @property
@@ -761,33 +761,33 @@ class Sandbox(modal.object.Object):
761
761
 
762
762
  class __ls_spec(typing_extensions.Protocol[SUPERSELF]):
763
763
  def __call__(self, /, path: str) -> list[str]:
764
- """List the contents of a directory in the Sandbox."""
764
+ """[Alpha] List the contents of a directory in the Sandbox."""
765
765
  ...
766
766
 
767
767
  async def aio(self, /, path: str) -> list[str]:
768
- """List the contents of a directory in the Sandbox."""
768
+ """[Alpha] List the contents of a directory in the Sandbox."""
769
769
  ...
770
770
 
771
771
  ls: __ls_spec[typing_extensions.Self]
772
772
 
773
773
  class __mkdir_spec(typing_extensions.Protocol[SUPERSELF]):
774
774
  def __call__(self, /, path: str, parents: bool = False) -> None:
775
- """Create a new directory in the Sandbox."""
775
+ """[Alpha] Create a new directory in the Sandbox."""
776
776
  ...
777
777
 
778
778
  async def aio(self, /, path: str, parents: bool = False) -> None:
779
- """Create a new directory in the Sandbox."""
779
+ """[Alpha] Create a new directory in the Sandbox."""
780
780
  ...
781
781
 
782
782
  mkdir: __mkdir_spec[typing_extensions.Self]
783
783
 
784
784
  class __rm_spec(typing_extensions.Protocol[SUPERSELF]):
785
785
  def __call__(self, /, path: str, recursive: bool = False) -> None:
786
- """Remove a file or directory in the Sandbox."""
786
+ """[Alpha] Remove a file or directory in the Sandbox."""
787
787
  ...
788
788
 
789
789
  async def aio(self, /, path: str, recursive: bool = False) -> None:
790
- """Remove a file or directory in the Sandbox."""
790
+ """[Alpha] Remove a file or directory in the Sandbox."""
791
791
  ...
792
792
 
793
793
  rm: __rm_spec[typing_extensions.Self]
@@ -801,7 +801,7 @@ class Sandbox(modal.object.Object):
801
801
  recursive: typing.Optional[bool] = None,
802
802
  timeout: typing.Optional[int] = None,
803
803
  ) -> typing.Iterator[modal.file_io.FileWatchEvent]:
804
- """Watch a file or directory in the Sandbox for changes."""
804
+ """[Alpha] Watch a file or directory in the Sandbox for changes."""
805
805
  ...
806
806
 
807
807
  def aio(
@@ -812,7 +812,7 @@ class Sandbox(modal.object.Object):
812
812
  recursive: typing.Optional[bool] = None,
813
813
  timeout: typing.Optional[int] = None,
814
814
  ) -> typing.AsyncIterator[modal.file_io.FileWatchEvent]:
815
- """Watch a file or directory in the Sandbox for changes."""
815
+ """[Alpha] Watch a file or directory in the Sandbox for changes."""
816
816
  ...
817
817
 
818
818
  watch: __watch_spec[typing_extensions.Self]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.0.6.dev31
3
+ Version: 1.0.6.dev34
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -3,7 +3,7 @@ modal/__main__.py,sha256=sTJcc9EbDuCKSwg3tL6ZckFw9WWdlkXW8mId1IvJCNc,2846
3
3
  modal/_clustered_functions.py,sha256=kTf-9YBXY88NutC1akI-gCbvf01RhMPCw-zoOI_YIUE,2700
4
4
  modal/_clustered_functions.pyi,sha256=_QKM87tdYwcALSGth8a0-9qXl02fZK6zMfEGEoYz7eA,1007
5
5
  modal/_container_entrypoint.py,sha256=1qBMNY_E9ICC_sRCtillMxmKPsmxJl1J0_qOAG8rH-0,28288
6
- modal/_functions.py,sha256=aA4wnPgZ2-ftUhyQEQl4rsW8L6Q8HS_RXuBiUnalEdY,82307
6
+ modal/_functions.py,sha256=hQ92Vv8wlyecDw2I_essrmYO1Sa6AIPySBogf14Dkr0,82416
7
7
  modal/_ipython.py,sha256=TW1fkVOmZL3YYqdS2YlM1hqpf654Yf8ZyybHdBnlhSw,301
8
8
  modal/_location.py,sha256=joiX-0ZeutEUDTrrqLF1GHXCdVLF-rHzstocbMcd_-k,366
9
9
  modal/_object.py,sha256=QWyUGjrGLupITkyvJru2cekizsaVdteAhwMQlw_tE4k,11172
@@ -21,8 +21,8 @@ modal/_watcher.py,sha256=K6LYnlmSGQB4tWWI9JADv-tvSvQ1j522FwT71B51CX8,3584
21
21
  modal/app.py,sha256=aH4PWc0TBTq9r5RLHVlNuh7szaob2AhTSXMweIrMfBo,46750
22
22
  modal/app.pyi,sha256=yBMZpnPxhDkPJPNtAiepkZU6XEe2uyhOBt0wa0HRrsg,42331
23
23
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
24
- modal/client.py,sha256=OwISJvkgMb-rHm9Gc4i-7YcDgGiZgwJ7F_PzwZH7a6Q,16847
25
- modal/client.pyi,sha256=ZxMDrhEq7DzlY8KVLOf9Z2hHvjtJKuqNUmFq2ncreCM,15081
24
+ modal/client.py,sha256=5QyM7VJjsFbHf6E91ar3A2KY9mx03wdtGlNJvfTKUVs,17087
25
+ modal/client.pyi,sha256=FBWq_zLWS8SVmBExtoRzf6B4Nq3fPWmSePe0eP4mDaw,15270
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=EFrM949jNXJpmwB2G_1d28b8IpHShfKIEIaiPkZqeOU,39881
@@ -35,8 +35,8 @@ modal/dict.pyi,sha256=gs3J7X5yG3J1L6rW0s3_7yRn8qAfY0f4n5-sqaDZY2g,20853
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=RjfKTJH7-Gcf_33BGkvDch-ry1Zx9u6-0QLViNxNTaU,5520
38
- modal/file_io.py,sha256=SCBfLk5gRieqdTVlA_f-2YHHtRp7Iy_sA6iR1zPsO3c,21100
39
- modal/file_io.pyi,sha256=_Hm-59MrppfuBYxtzdJkA2Jf9zI5LlbPh_0gURk0_7s,15222
38
+ modal/file_io.py,sha256=BVqAJ0sgPUfN8QsYztWiGB4j56he60TncM02KsylnCw,21449
39
+ modal/file_io.pyi,sha256=cPT_hsplE5iLCXhYOLn1Sp9eDdk7DxdFmicQHanJZyg,15918
40
40
  modal/file_pattern_matcher.py,sha256=urAue8es8jxqX94k9EYoZxxhtfgOlsEES8lbFHOorzc,7734
41
41
  modal/functions.py,sha256=kcNHvqeGBxPI7Cgd57NIBBghkfbeFJzXO44WW0jSmao,325
42
42
  modal/functions.pyi,sha256=FJe_91dSrMCRNVT-YV1UhtxFKzIvL_C5q8xdk08-wT8,34840
@@ -65,8 +65,8 @@ modal/retries.py,sha256=IvNLDM0f_GLUDD5VgEDoN09C88yoxSrCquinAuxT1Sc,5205
65
65
  modal/runner.py,sha256=ostdzYpQb-20tlD6dIq7bpWTkZkOhjJBNuMNektqnJA,24068
66
66
  modal/runner.pyi,sha256=lbwLljm1cC8d6PcNvmYQhkE8501V9fg0bYqqKX6G4r4,8489
67
67
  modal/running_app.py,sha256=v61mapYNV1-O-Uaho5EfJlryMLvIT9We0amUOSvSGx8,1188
68
- modal/sandbox.py,sha256=NS2ShX-JxsRYczCe-hz7Iu6TKxJq8OXLMWGx04FJHls,37489
69
- modal/sandbox.pyi,sha256=AiZlmZdYHrlqnT8Ba8K5BxNWI1W_oIIkNMhQHF2zqIU,38469
68
+ modal/sandbox.py,sha256=q7kpGNustlL6Lw7KQbzhw_XwDFn0qBfCoEP7lTW3wYY,37583
69
+ modal/sandbox.pyi,sha256=AyROza8ZUUxs6MO1f3l8zDjTkp6O46H132xUwBUixIc,38565
70
70
  modal/schedule.py,sha256=ng0g0AqNY5GQI9KhkXZQ5Wam5G42glbkqVQsNpBtbDE,3078
71
71
  modal/scheduler_placement.py,sha256=BAREdOY5HzHpzSBqt6jDVR6YC_jYfHMVqOzkyqQfngU,1235
72
72
  modal/secret.py,sha256=bpgtv0urwaBOmmJpMTZIwVWUraQlpeu4hW8pbJiGcOA,10546
@@ -92,6 +92,7 @@ modal/_runtime/user_code_imports.py,sha256=78wJyleqY2RVibqcpbDQyfWVBVT9BjyHPeoV9
92
92
  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=MhSCsCL8GqIVFWoHubU_899IH-JBZAiiqadG9Wri2l4,29361
95
+ modal/_utils/auth_token_manager.py,sha256=4YS0pBfwbuNFy5DoAIOnBNCcYjS9rNCMv4zSVGybiOw,5245
95
96
  modal/_utils/blob_utils.py,sha256=v2NAQVVGx1AQjHQ7-2T64x5rYtwjFFykxDXb-0grrzA,21022
96
97
  modal/_utils/bytes_io_segment_payload.py,sha256=vaXPq8b52-x6G2hwE7SrjS58pg_aRm7gV3bn3yjmTzQ,4261
97
98
  modal/_utils/deprecation.py,sha256=-Bgg7jZdcJU8lROy18YyVnQYbM8hue-hVmwJqlWAGH0,5504
@@ -99,7 +100,7 @@ modal/_utils/docker_utils.py,sha256=h1uETghR40mp_y3fSWuZAfbIASH1HMzuphJHghAL6DU,
99
100
  modal/_utils/function_utils.py,sha256=wFJcfmGC8RuYeQUORGKg6soLj31Mzw9qfay6CyPSAZE,28130
100
101
  modal/_utils/git_utils.py,sha256=qtUU6JAttF55ZxYq51y55OR58B0tDPZsZWK5dJe6W5g,3182
101
102
  modal/_utils/grpc_testing.py,sha256=H1zHqthv19eGPJz2HKXDyWXWGSqO4BRsxah3L5Xaa8A,8619
102
- modal/_utils/grpc_utils.py,sha256=aFDJIK3Idn9r0iqLRmQqCKsPhRCueyeaA64mZUvDNKA,11118
103
+ modal/_utils/grpc_utils.py,sha256=HBZdMcBHCk6uozILYTjGnR0mV8fg7WOdJldoyZ-ZhSg,10137
103
104
  modal/_utils/hash_utils.py,sha256=zg3J6OGxTFGSFri1qQ12giDz90lWk8bzaxCTUCRtiX4,3034
104
105
  modal/_utils/http_utils.py,sha256=yeTFsXYr0rYMEhB7vBP7audG9Uc7OLhzKBANFDZWVt0,2451
105
106
  modal/_utils/jwt_utils.py,sha256=fxH9plyrbAemTbjSsQtzIdDXE9QXxvMC4DiUZ16G0aA,1360
@@ -148,7 +149,7 @@ modal/requirements/2025.06.txt,sha256=KxDaVTOwatHvboDo4lorlgJ7-n-MfAwbPwxJ0zcJqr
148
149
  modal/requirements/PREVIEW.txt,sha256=KxDaVTOwatHvboDo4lorlgJ7-n-MfAwbPwxJ0zcJqrs,312
149
150
  modal/requirements/README.md,sha256=9tK76KP0Uph7O0M5oUgsSwEZDj5y-dcUPsnpR0Sc-Ik,854
150
151
  modal/requirements/base-images.json,sha256=JYSDAgHTl-WrV_TZW5icY-IJEnbe2eQ4CZ_KN6EOZKU,1304
151
- modal-1.0.6.dev31.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
152
+ modal-1.0.6.dev34.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
152
153
  modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
153
154
  modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
154
155
  modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
@@ -171,10 +172,10 @@ modal_proto/options_pb2.pyi,sha256=l7DBrbLO7q3Ir-XDkWsajm0d0TQqqrfuX54i4BMpdQg,1
171
172
  modal_proto/options_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
172
173
  modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0yJSI,247
173
174
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
174
- modal_version/__init__.py,sha256=Ycv08h4fVUHXZ_mWaE5vVe7IACFP7lb25l9LqYkoCLg,121
175
+ modal_version/__init__.py,sha256=15eHLyF3_7SZY-ilZsb7AvYsRe9kitXAYg6n5Q5LSWY,121
175
176
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
176
- modal-1.0.6.dev31.dist-info/METADATA,sha256=FXoHmDms-M3zW7wuZymWRF4Va4TArQm4NTX21gA45aY,2462
177
- modal-1.0.6.dev31.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
178
- modal-1.0.6.dev31.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
179
- modal-1.0.6.dev31.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
180
- modal-1.0.6.dev31.dist-info/RECORD,,
177
+ modal-1.0.6.dev34.dist-info/METADATA,sha256=OCokLD9bPprvZlpLVjEpxET7bjYfLRYAAHog4XyrJrc,2462
178
+ modal-1.0.6.dev34.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
179
+ modal-1.0.6.dev34.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
180
+ modal-1.0.6.dev34.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
181
+ modal-1.0.6.dev34.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.0.6.dev31"
4
+ __version__ = "1.0.6.dev34"