modal 1.1.5.dev83__py3-none-any.whl → 1.3.1.dev8__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/__init__.py +4 -4
- modal/__main__.py +4 -29
- modal/_billing.py +84 -0
- modal/_clustered_functions.py +1 -3
- modal/_container_entrypoint.py +33 -208
- modal/_functions.py +146 -121
- modal/_grpc_client.py +191 -0
- modal/_ipython.py +16 -6
- modal/_load_context.py +106 -0
- modal/_object.py +72 -21
- modal/_output.py +12 -14
- modal/_partial_function.py +31 -4
- modal/_resolver.py +44 -57
- modal/_runtime/container_io_manager.py +26 -28
- modal/_runtime/container_io_manager.pyi +42 -44
- modal/_runtime/gpu_memory_snapshot.py +9 -7
- modal/_runtime/user_code_event_loop.py +80 -0
- modal/_runtime/user_code_imports.py +236 -10
- modal/_serialization.py +2 -1
- modal/_traceback.py +4 -13
- modal/_tunnel.py +16 -11
- modal/_tunnel.pyi +25 -3
- modal/_utils/async_utils.py +337 -10
- modal/_utils/auth_token_manager.py +1 -4
- modal/_utils/blob_utils.py +29 -22
- modal/_utils/function_utils.py +20 -21
- modal/_utils/grpc_testing.py +6 -3
- modal/_utils/grpc_utils.py +223 -64
- modal/_utils/mount_utils.py +26 -1
- modal/_utils/package_utils.py +0 -1
- modal/_utils/rand_pb_testing.py +8 -1
- modal/_utils/task_command_router_client.py +524 -0
- modal/_vendor/cloudpickle.py +144 -48
- modal/app.py +215 -96
- modal/app.pyi +78 -37
- modal/billing.py +5 -0
- modal/builder/2025.06.txt +6 -3
- modal/builder/PREVIEW.txt +2 -1
- modal/builder/base-images.json +4 -2
- modal/cli/_download.py +19 -3
- modal/cli/cluster.py +4 -2
- modal/cli/config.py +3 -1
- modal/cli/container.py +5 -4
- modal/cli/dict.py +5 -2
- modal/cli/entry_point.py +26 -2
- modal/cli/environment.py +2 -16
- modal/cli/launch.py +1 -76
- modal/cli/network_file_system.py +5 -20
- modal/cli/queues.py +5 -4
- modal/cli/run.py +24 -204
- modal/cli/secret.py +1 -2
- modal/cli/shell.py +375 -0
- modal/cli/utils.py +1 -13
- modal/cli/volume.py +11 -17
- modal/client.py +16 -125
- modal/client.pyi +94 -144
- modal/cloud_bucket_mount.py +3 -1
- modal/cloud_bucket_mount.pyi +4 -0
- modal/cls.py +101 -64
- modal/cls.pyi +9 -8
- modal/config.py +21 -1
- modal/container_process.py +288 -12
- modal/container_process.pyi +99 -38
- modal/dict.py +72 -33
- modal/dict.pyi +88 -57
- modal/environments.py +16 -8
- modal/environments.pyi +6 -2
- modal/exception.py +154 -16
- modal/experimental/__init__.py +23 -5
- modal/experimental/flash.py +161 -74
- modal/experimental/flash.pyi +97 -49
- modal/file_io.py +50 -92
- modal/file_io.pyi +117 -89
- modal/functions.pyi +70 -87
- modal/image.py +73 -47
- modal/image.pyi +33 -30
- modal/io_streams.py +500 -149
- modal/io_streams.pyi +279 -189
- modal/mount.py +60 -45
- modal/mount.pyi +41 -17
- modal/network_file_system.py +19 -11
- modal/network_file_system.pyi +72 -39
- modal/object.pyi +114 -22
- modal/parallel_map.py +42 -44
- modal/parallel_map.pyi +9 -17
- modal/partial_function.pyi +4 -2
- modal/proxy.py +14 -6
- modal/proxy.pyi +10 -2
- modal/queue.py +45 -38
- modal/queue.pyi +88 -52
- modal/runner.py +96 -96
- modal/runner.pyi +44 -27
- modal/sandbox.py +225 -108
- modal/sandbox.pyi +226 -63
- modal/secret.py +58 -56
- modal/secret.pyi +28 -13
- modal/serving.py +7 -11
- modal/serving.pyi +7 -8
- modal/snapshot.py +29 -15
- modal/snapshot.pyi +18 -10
- modal/token_flow.py +1 -1
- modal/token_flow.pyi +4 -6
- modal/volume.py +102 -55
- modal/volume.pyi +125 -66
- {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/METADATA +10 -9
- modal-1.3.1.dev8.dist-info/RECORD +189 -0
- modal_proto/api.proto +86 -30
- modal_proto/api_grpc.py +10 -25
- modal_proto/api_pb2.py +1080 -1047
- modal_proto/api_pb2.pyi +253 -79
- modal_proto/api_pb2_grpc.py +14 -48
- modal_proto/api_pb2_grpc.pyi +6 -18
- modal_proto/modal_api_grpc.py +175 -176
- modal_proto/{sandbox_router.proto → task_command_router.proto} +62 -45
- modal_proto/task_command_router_grpc.py +138 -0
- modal_proto/task_command_router_pb2.py +180 -0
- modal_proto/{sandbox_router_pb2.pyi → task_command_router_pb2.pyi} +110 -63
- modal_proto/task_command_router_pb2_grpc.py +272 -0
- modal_proto/task_command_router_pb2_grpc.pyi +100 -0
- modal_version/__init__.py +1 -1
- modal_version/__main__.py +1 -1
- modal/cli/programs/launch_instance_ssh.py +0 -94
- modal/cli/programs/run_marimo.py +0 -95
- modal-1.1.5.dev83.dist-info/RECORD +0 -191
- modal_proto/modal_options_grpc.py +0 -3
- modal_proto/options.proto +0 -19
- modal_proto/options_grpc.py +0 -3
- modal_proto/options_pb2.py +0 -35
- modal_proto/options_pb2.pyi +0 -20
- modal_proto/options_pb2_grpc.py +0 -4
- modal_proto/options_pb2_grpc.pyi +0 -7
- modal_proto/sandbox_router_grpc.py +0 -105
- modal_proto/sandbox_router_pb2.py +0 -148
- modal_proto/sandbox_router_pb2_grpc.py +0 -203
- modal_proto/sandbox_router_pb2_grpc.pyi +0 -75
- {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/WHEEL +0 -0
- {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/entry_points.txt +0 -0
- {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/licenses/LICENSE +0 -0
- {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/top_level.txt +0 -0
modal/secret.py
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
|
+
import builtins
|
|
2
3
|
import os
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from typing import Optional, Union
|
|
6
7
|
|
|
7
8
|
from google.protobuf.message import Message
|
|
8
|
-
from grpclib import GRPCError, Status
|
|
9
9
|
from synchronicity import classproperty
|
|
10
10
|
|
|
11
11
|
from modal_proto import api_pb2
|
|
12
12
|
|
|
13
|
+
from ._load_context import LoadContext
|
|
13
14
|
from ._object import _get_environment_name, _Object, live_method
|
|
14
15
|
from ._resolver import Resolver
|
|
15
16
|
from ._runtime.execution_context import is_local
|
|
16
17
|
from ._utils.async_utils import synchronize_api
|
|
17
18
|
from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
|
|
18
|
-
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
|
|
@@ -91,11 +91,9 @@ class _SecretManager:
|
|
|
91
91
|
env_dict=env_dict,
|
|
92
92
|
)
|
|
93
93
|
try:
|
|
94
|
-
await
|
|
95
|
-
except
|
|
96
|
-
if
|
|
97
|
-
raise AlreadyExistsError(exc.message)
|
|
98
|
-
else:
|
|
94
|
+
await client.stub.SecretGetOrCreate(req)
|
|
95
|
+
except AlreadyExistsError:
|
|
96
|
+
if not allow_existing:
|
|
99
97
|
raise
|
|
100
98
|
|
|
101
99
|
@staticmethod
|
|
@@ -105,7 +103,7 @@ class _SecretManager:
|
|
|
105
103
|
created_before: Optional[Union[datetime, str]] = None, # Limit based on creation date
|
|
106
104
|
environment_name: str = "", # Uses active environment if not specified
|
|
107
105
|
client: Optional[_Client] = None, # Optional client with Modal credentials
|
|
108
|
-
) -> list["_Secret"]:
|
|
106
|
+
) -> builtins.list["_Secret"]:
|
|
109
107
|
"""Return a list of hydrated Secret objects.
|
|
110
108
|
|
|
111
109
|
**Examples:**
|
|
@@ -143,7 +141,7 @@ class _SecretManager:
|
|
|
143
141
|
req = api_pb2.SecretListRequest(
|
|
144
142
|
environment_name=_get_environment_name(environment_name), pagination=pagination
|
|
145
143
|
)
|
|
146
|
-
resp = await
|
|
144
|
+
resp = await client.stub.SecretList(req)
|
|
147
145
|
items.extend(resp.items)
|
|
148
146
|
finished = (len(resp.items) < max_page_size) or (max_objects is not None and len(items) >= max_objects)
|
|
149
147
|
return finished
|
|
@@ -200,12 +198,32 @@ class _SecretManager:
|
|
|
200
198
|
raise
|
|
201
199
|
else:
|
|
202
200
|
req = api_pb2.SecretDeleteRequest(secret_id=obj.object_id)
|
|
203
|
-
await
|
|
201
|
+
await obj._client.stub.SecretDelete(req)
|
|
204
202
|
|
|
205
203
|
|
|
206
204
|
SecretManager = synchronize_api(_SecretManager)
|
|
207
205
|
|
|
208
206
|
|
|
207
|
+
async def _load_from_env_dict(instance: "_Secret", load_context: LoadContext, env_dict: dict[str, str]):
|
|
208
|
+
"""helper method for loaders .from_dict and .from_dotenv etc."""
|
|
209
|
+
if load_context.app_id is not None:
|
|
210
|
+
req = api_pb2.SecretGetOrCreateRequest(
|
|
211
|
+
object_creation_type=api_pb2.OBJECT_CREATION_TYPE_ANONYMOUS_OWNED_BY_APP,
|
|
212
|
+
env_dict=env_dict,
|
|
213
|
+
app_id=load_context.app_id,
|
|
214
|
+
environment_name=load_context.environment_name,
|
|
215
|
+
)
|
|
216
|
+
else:
|
|
217
|
+
req = api_pb2.SecretGetOrCreateRequest(
|
|
218
|
+
object_creation_type=api_pb2.OBJECT_CREATION_TYPE_EPHEMERAL,
|
|
219
|
+
env_dict=env_dict,
|
|
220
|
+
environment_name=load_context.environment_name,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
resp = await load_context.client.stub.SecretGetOrCreate(req)
|
|
224
|
+
instance._hydrate(resp.secret_id, load_context.client, resp.metadata)
|
|
225
|
+
|
|
226
|
+
|
|
209
227
|
class _Secret(_Object, type_prefix="st"):
|
|
210
228
|
"""Secrets provide a dictionary of environment variables for images.
|
|
211
229
|
|
|
@@ -260,30 +278,14 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
260
278
|
if not all(isinstance(v, str) for v in env_dict_filtered.values()):
|
|
261
279
|
raise InvalidError(ENV_DICT_WRONG_TYPE_ERR)
|
|
262
280
|
|
|
263
|
-
async def _load(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
object_creation_type = api_pb2.OBJECT_CREATION_TYPE_EPHEMERAL
|
|
268
|
-
|
|
269
|
-
req = api_pb2.SecretGetOrCreateRequest(
|
|
270
|
-
object_creation_type=object_creation_type,
|
|
271
|
-
env_dict=env_dict_filtered,
|
|
272
|
-
app_id=resolver.app_id,
|
|
273
|
-
environment_name=resolver.environment_name,
|
|
274
|
-
)
|
|
275
|
-
try:
|
|
276
|
-
resp = await resolver.client.stub.SecretGetOrCreate(req)
|
|
277
|
-
except GRPCError as exc:
|
|
278
|
-
if exc.status == Status.INVALID_ARGUMENT:
|
|
279
|
-
raise InvalidError(exc.message)
|
|
280
|
-
if exc.status == Status.FAILED_PRECONDITION:
|
|
281
|
-
raise InvalidError(exc.message)
|
|
282
|
-
raise
|
|
283
|
-
self._hydrate(resp.secret_id, resolver.client, resp.metadata)
|
|
281
|
+
async def _load(
|
|
282
|
+
self: _Secret, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
283
|
+
):
|
|
284
|
+
await _load_from_env_dict(self, load_context, env_dict_filtered)
|
|
284
285
|
|
|
285
286
|
rep = f"Secret.from_dict([{', '.join(env_dict.keys())}])"
|
|
286
|
-
|
|
287
|
+
# TODO: scoping - these should probably not be lazily hydrated without having an app and/or sandbox association
|
|
288
|
+
return _Secret._from_loader(_load, rep, hydrate_lazily=True, load_context_overrides=LoadContext.empty())
|
|
287
289
|
|
|
288
290
|
@staticmethod
|
|
289
291
|
def from_local_environ(
|
|
@@ -303,7 +305,7 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
303
305
|
return _Secret.from_dict({})
|
|
304
306
|
|
|
305
307
|
@staticmethod
|
|
306
|
-
def from_dotenv(path=None, *, filename=".env") -> "_Secret":
|
|
308
|
+
def from_dotenv(path=None, *, filename=".env", client: Optional[_Client] = None) -> "_Secret":
|
|
307
309
|
"""Create secrets from a .env file automatically.
|
|
308
310
|
|
|
309
311
|
If no argument is provided, it will use the current working directory as the starting
|
|
@@ -331,7 +333,9 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
331
333
|
```
|
|
332
334
|
"""
|
|
333
335
|
|
|
334
|
-
async def _load(
|
|
336
|
+
async def _load(
|
|
337
|
+
self: _Secret, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
338
|
+
):
|
|
335
339
|
try:
|
|
336
340
|
from dotenv import dotenv_values, find_dotenv
|
|
337
341
|
from dotenv.main import _walk_to_root
|
|
@@ -355,18 +359,13 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
355
359
|
# To simplify this, we just support the cwd and don't do any automatic path inference.
|
|
356
360
|
dotenv_path = find_dotenv(filename, usecwd=True)
|
|
357
361
|
|
|
358
|
-
env_dict = dotenv_values(dotenv_path)
|
|
359
|
-
|
|
360
|
-
req = api_pb2.SecretGetOrCreateRequest(
|
|
361
|
-
object_creation_type=api_pb2.OBJECT_CREATION_TYPE_ANONYMOUS_OWNED_BY_APP,
|
|
362
|
-
env_dict=env_dict,
|
|
363
|
-
app_id=resolver.app_id,
|
|
364
|
-
)
|
|
365
|
-
resp = await resolver.client.stub.SecretGetOrCreate(req)
|
|
362
|
+
env_dict = {k: v or "" for k, v in dotenv_values(dotenv_path).items()}
|
|
366
363
|
|
|
367
|
-
self
|
|
364
|
+
await _load_from_env_dict(self, load_context, env_dict)
|
|
368
365
|
|
|
369
|
-
return _Secret._from_loader(
|
|
366
|
+
return _Secret._from_loader(
|
|
367
|
+
_load, "Secret.from_dotenv()", hydrate_lazily=True, load_context_overrides=LoadContext(client=client)
|
|
368
|
+
)
|
|
370
369
|
|
|
371
370
|
@staticmethod
|
|
372
371
|
def from_name(
|
|
@@ -377,6 +376,7 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
377
376
|
required_keys: list[
|
|
378
377
|
str
|
|
379
378
|
] = [], # Optionally, a list of required environment variables (will be asserted server-side)
|
|
379
|
+
client: Optional[_Client] = None,
|
|
380
380
|
) -> "_Secret":
|
|
381
381
|
"""Reference a Secret by its name.
|
|
382
382
|
|
|
@@ -394,23 +394,25 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
394
394
|
"""
|
|
395
395
|
warn_if_passing_namespace(namespace, "modal.Secret.from_name")
|
|
396
396
|
|
|
397
|
-
async def _load(
|
|
397
|
+
async def _load(
|
|
398
|
+
self: _Secret, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
399
|
+
):
|
|
398
400
|
req = api_pb2.SecretGetOrCreateRequest(
|
|
399
401
|
deployment_name=name,
|
|
400
|
-
environment_name=
|
|
402
|
+
environment_name=load_context.environment_name,
|
|
401
403
|
required_keys=required_keys,
|
|
402
404
|
)
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
except GRPCError as exc:
|
|
406
|
-
if exc.status == Status.NOT_FOUND:
|
|
407
|
-
raise NotFoundError(exc.message)
|
|
408
|
-
else:
|
|
409
|
-
raise
|
|
410
|
-
self._hydrate(response.secret_id, resolver.client, response.metadata)
|
|
405
|
+
response = await load_context.client.stub.SecretGetOrCreate(req)
|
|
406
|
+
self._hydrate(response.secret_id, load_context.client, response.metadata)
|
|
411
407
|
|
|
412
408
|
rep = _Secret._repr(name, environment_name)
|
|
413
|
-
return _Secret._from_loader(
|
|
409
|
+
return _Secret._from_loader(
|
|
410
|
+
_load,
|
|
411
|
+
rep,
|
|
412
|
+
hydrate_lazily=True,
|
|
413
|
+
name=name,
|
|
414
|
+
load_context_overrides=LoadContext(environment_name=environment_name, client=client),
|
|
415
|
+
)
|
|
414
416
|
|
|
415
417
|
@staticmethod
|
|
416
418
|
async def create_deployed(
|
|
@@ -454,7 +456,7 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
454
456
|
object_creation_type=object_creation_type,
|
|
455
457
|
env_dict=env_dict,
|
|
456
458
|
)
|
|
457
|
-
resp = await
|
|
459
|
+
resp = await client.stub.SecretGetOrCreate(request)
|
|
458
460
|
return resp.secret_id
|
|
459
461
|
|
|
460
462
|
@live_method
|
modal/secret.pyi
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import google.protobuf.message
|
|
3
|
+
import modal._load_context
|
|
3
4
|
import modal._object
|
|
4
5
|
import modal.client
|
|
5
6
|
import modal.object
|
|
@@ -219,7 +220,7 @@ class SecretManager:
|
|
|
219
220
|
"""
|
|
220
221
|
...
|
|
221
222
|
|
|
222
|
-
create: __create_spec
|
|
223
|
+
create: typing.ClassVar[__create_spec]
|
|
223
224
|
|
|
224
225
|
class __list_spec(typing_extensions.Protocol):
|
|
225
226
|
def __call__(
|
|
@@ -292,7 +293,7 @@ class SecretManager:
|
|
|
292
293
|
"""
|
|
293
294
|
...
|
|
294
295
|
|
|
295
|
-
list: __list_spec
|
|
296
|
+
list: typing.ClassVar[__list_spec]
|
|
296
297
|
|
|
297
298
|
class __delete_spec(typing_extensions.Protocol):
|
|
298
299
|
def __call__(
|
|
@@ -353,7 +354,13 @@ class SecretManager:
|
|
|
353
354
|
"""
|
|
354
355
|
...
|
|
355
356
|
|
|
356
|
-
delete: __delete_spec
|
|
357
|
+
delete: typing.ClassVar[__delete_spec]
|
|
358
|
+
|
|
359
|
+
async def _load_from_env_dict(
|
|
360
|
+
instance: _Secret, load_context: modal._load_context.LoadContext, env_dict: dict[str, str]
|
|
361
|
+
):
|
|
362
|
+
"""helper method for loaders .from_dict and .from_dotenv etc."""
|
|
363
|
+
...
|
|
357
364
|
|
|
358
365
|
class _Secret(modal._object._Object):
|
|
359
366
|
"""Secrets provide a dictionary of environment variables for images.
|
|
@@ -392,7 +399,7 @@ class _Secret(modal._object._Object):
|
|
|
392
399
|
...
|
|
393
400
|
|
|
394
401
|
@staticmethod
|
|
395
|
-
def from_dotenv(path=None, *, filename=".env") -> _Secret:
|
|
402
|
+
def from_dotenv(path=None, *, filename=".env", client: typing.Optional[modal.client._Client] = None) -> _Secret:
|
|
396
403
|
"""Create secrets from a .env file automatically.
|
|
397
404
|
|
|
398
405
|
If no argument is provided, it will use the current working directory as the starting
|
|
@@ -423,7 +430,12 @@ class _Secret(modal._object._Object):
|
|
|
423
430
|
|
|
424
431
|
@staticmethod
|
|
425
432
|
def from_name(
|
|
426
|
-
name: str,
|
|
433
|
+
name: str,
|
|
434
|
+
*,
|
|
435
|
+
namespace=None,
|
|
436
|
+
environment_name: typing.Optional[str] = None,
|
|
437
|
+
required_keys: list[str] = [],
|
|
438
|
+
client: typing.Optional[modal.client._Client] = None,
|
|
427
439
|
) -> _Secret:
|
|
428
440
|
"""Reference a Secret by its name.
|
|
429
441
|
|
|
@@ -469,8 +481,6 @@ class _Secret(modal._object._Object):
|
|
|
469
481
|
"""Return information about the Secret object."""
|
|
470
482
|
...
|
|
471
483
|
|
|
472
|
-
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
|
473
|
-
|
|
474
484
|
class Secret(modal.object.Object):
|
|
475
485
|
"""Secrets provide a dictionary of environment variables for images.
|
|
476
486
|
|
|
@@ -512,7 +522,7 @@ class Secret(modal.object.Object):
|
|
|
512
522
|
...
|
|
513
523
|
|
|
514
524
|
@staticmethod
|
|
515
|
-
def from_dotenv(path=None, *, filename=".env") -> Secret:
|
|
525
|
+
def from_dotenv(path=None, *, filename=".env", client: typing.Optional[modal.client.Client] = None) -> Secret:
|
|
516
526
|
"""Create secrets from a .env file automatically.
|
|
517
527
|
|
|
518
528
|
If no argument is provided, it will use the current working directory as the starting
|
|
@@ -543,7 +553,12 @@ class Secret(modal.object.Object):
|
|
|
543
553
|
|
|
544
554
|
@staticmethod
|
|
545
555
|
def from_name(
|
|
546
|
-
name: str,
|
|
556
|
+
name: str,
|
|
557
|
+
*,
|
|
558
|
+
namespace=None,
|
|
559
|
+
environment_name: typing.Optional[str] = None,
|
|
560
|
+
required_keys: list[str] = [],
|
|
561
|
+
client: typing.Optional[modal.client.Client] = None,
|
|
547
562
|
) -> Secret:
|
|
548
563
|
"""Reference a Secret by its name.
|
|
549
564
|
|
|
@@ -588,7 +603,7 @@ class Secret(modal.object.Object):
|
|
|
588
603
|
"""mdmd:hidden"""
|
|
589
604
|
...
|
|
590
605
|
|
|
591
|
-
create_deployed: __create_deployed_spec
|
|
606
|
+
create_deployed: typing.ClassVar[__create_deployed_spec]
|
|
592
607
|
|
|
593
608
|
class ___create_deployed_spec(typing_extensions.Protocol):
|
|
594
609
|
def __call__(
|
|
@@ -617,9 +632,9 @@ class Secret(modal.object.Object):
|
|
|
617
632
|
"""mdmd:hidden"""
|
|
618
633
|
...
|
|
619
634
|
|
|
620
|
-
_create_deployed: ___create_deployed_spec
|
|
635
|
+
_create_deployed: typing.ClassVar[___create_deployed_spec]
|
|
621
636
|
|
|
622
|
-
class __info_spec(typing_extensions.Protocol
|
|
637
|
+
class __info_spec(typing_extensions.Protocol):
|
|
623
638
|
def __call__(self, /) -> SecretInfo:
|
|
624
639
|
"""Return information about the Secret object."""
|
|
625
640
|
...
|
|
@@ -628,4 +643,4 @@ class Secret(modal.object.Object):
|
|
|
628
643
|
"""Return information about the Secret object."""
|
|
629
644
|
...
|
|
630
645
|
|
|
631
|
-
info: __info_spec
|
|
646
|
+
info: __info_spec
|
modal/serving.py
CHANGED
|
@@ -4,13 +4,13 @@ import platform
|
|
|
4
4
|
from collections.abc import AsyncGenerator
|
|
5
5
|
from multiprocessing.context import SpawnProcess
|
|
6
6
|
from multiprocessing.synchronize import Event
|
|
7
|
-
from typing import TYPE_CHECKING, Optional
|
|
7
|
+
from typing import TYPE_CHECKING, Optional
|
|
8
8
|
|
|
9
9
|
from synchronicity.async_wrap import asynccontextmanager
|
|
10
10
|
|
|
11
11
|
from modal._output import OutputManager
|
|
12
12
|
|
|
13
|
-
from ._utils.async_utils import TaskContext, asyncify, synchronize_api
|
|
13
|
+
from ._utils.async_utils import TaskContext, asyncify, synchronize_api
|
|
14
14
|
from ._utils.logger import logger
|
|
15
15
|
from ._watcher import watch
|
|
16
16
|
from .cli.import_refs import ImportRef, import_app_from_ref
|
|
@@ -20,20 +20,16 @@ from .output import _get_output_manager, enable_output
|
|
|
20
20
|
from .runner import _run_app, serve_update
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
|
-
|
|
24
|
-
else:
|
|
25
|
-
_App = TypeVar("_App")
|
|
23
|
+
import modal.app
|
|
26
24
|
|
|
27
25
|
|
|
28
26
|
def _run_serve(
|
|
29
27
|
import_ref: ImportRef, existing_app_id: str, is_ready: Event, environment_name: str, show_progress: bool
|
|
30
28
|
):
|
|
31
|
-
|
|
32
|
-
_app = import_app_from_ref(import_ref, base_cmd="modal serve")
|
|
33
|
-
blocking_app = synchronizer._translate_out(_app)
|
|
29
|
+
app = import_app_from_ref(import_ref, base_cmd="modal serve")
|
|
34
30
|
|
|
35
31
|
with enable_output(show_progress=show_progress):
|
|
36
|
-
serve_update(
|
|
32
|
+
serve_update(app, existing_app_id, is_ready, environment_name)
|
|
37
33
|
|
|
38
34
|
|
|
39
35
|
async def _restart_serve(
|
|
@@ -97,12 +93,12 @@ async def _run_watch_loop(
|
|
|
97
93
|
|
|
98
94
|
@asynccontextmanager
|
|
99
95
|
async def _serve_app(
|
|
100
|
-
app: "_App",
|
|
96
|
+
app: "modal.app._App",
|
|
101
97
|
import_ref: ImportRef,
|
|
102
98
|
*,
|
|
103
99
|
_watcher: Optional[AsyncGenerator[set[str], None]] = None, # for testing
|
|
104
100
|
environment_name: Optional[str] = None,
|
|
105
|
-
) -> AsyncGenerator["_App", None]:
|
|
101
|
+
) -> AsyncGenerator["modal.app._App", None]:
|
|
106
102
|
if environment_name is None:
|
|
107
103
|
environment_name = config.get("environment")
|
|
108
104
|
|
modal/serving.pyi
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import collections.abc
|
|
2
|
+
import modal.app
|
|
2
3
|
import modal.cli.import_refs
|
|
3
4
|
import multiprocessing.context
|
|
4
5
|
import multiprocessing.synchronize
|
|
@@ -6,8 +7,6 @@ import synchronicity.combined_types
|
|
|
6
7
|
import typing
|
|
7
8
|
import typing_extensions
|
|
8
9
|
|
|
9
|
-
_App = typing.TypeVar("_App")
|
|
10
|
-
|
|
11
10
|
def _run_serve(
|
|
12
11
|
import_ref: modal.cli.import_refs.ImportRef,
|
|
13
12
|
existing_app_id: str,
|
|
@@ -27,31 +26,31 @@ async def _run_watch_loop(
|
|
|
27
26
|
environment_name: str,
|
|
28
27
|
): ...
|
|
29
28
|
def _serve_app(
|
|
30
|
-
app: _App,
|
|
29
|
+
app: modal.app._App,
|
|
31
30
|
import_ref: modal.cli.import_refs.ImportRef,
|
|
32
31
|
*,
|
|
33
32
|
_watcher: typing.Optional[collections.abc.AsyncGenerator[set[str], None]] = None,
|
|
34
33
|
environment_name: typing.Optional[str] = None,
|
|
35
|
-
) -> typing.AsyncContextManager[_App]: ...
|
|
34
|
+
) -> typing.AsyncContextManager[modal.app._App]: ...
|
|
36
35
|
|
|
37
36
|
class __serve_app_spec(typing_extensions.Protocol):
|
|
38
37
|
def __call__(
|
|
39
38
|
self,
|
|
40
39
|
/,
|
|
41
|
-
app:
|
|
40
|
+
app: modal.app.App,
|
|
42
41
|
import_ref: modal.cli.import_refs.ImportRef,
|
|
43
42
|
*,
|
|
44
43
|
_watcher: typing.Optional[typing.Generator[set[str], None, None]] = None,
|
|
45
44
|
environment_name: typing.Optional[str] = None,
|
|
46
|
-
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[
|
|
45
|
+
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[modal.app.App]: ...
|
|
47
46
|
def aio(
|
|
48
47
|
self,
|
|
49
48
|
/,
|
|
50
|
-
app:
|
|
49
|
+
app: modal.app.App,
|
|
51
50
|
import_ref: modal.cli.import_refs.ImportRef,
|
|
52
51
|
*,
|
|
53
52
|
_watcher: typing.Optional[collections.abc.AsyncGenerator[set[str], None]] = None,
|
|
54
53
|
environment_name: typing.Optional[str] = None,
|
|
55
|
-
) -> typing.AsyncContextManager[
|
|
54
|
+
) -> typing.AsyncContextManager[modal.app.App]: ...
|
|
56
55
|
|
|
57
56
|
serve_app: __serve_app_spec
|
modal/snapshot.py
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# Copyright Modal Labs 2024
|
|
2
|
-
from typing import Optional
|
|
2
|
+
from typing import Optional, cast
|
|
3
3
|
|
|
4
|
+
import typing_extensions
|
|
5
|
+
|
|
6
|
+
import modal.client
|
|
4
7
|
from modal_proto import api_pb2
|
|
5
8
|
|
|
9
|
+
from ._load_context import LoadContext
|
|
6
10
|
from ._object import _Object
|
|
7
11
|
from ._resolver import Resolver
|
|
8
|
-
from ._utils.async_utils import synchronize_api
|
|
9
|
-
from ._utils.grpc_utils import retry_transient_errors
|
|
12
|
+
from ._utils.async_utils import deprecate_aio_usage, synchronize_api, synchronizer
|
|
10
13
|
from .client import _Client
|
|
11
14
|
|
|
12
15
|
|
|
@@ -19,24 +22,35 @@ class _SandboxSnapshot(_Object, type_prefix="sn"):
|
|
|
19
22
|
the original Sandbox at the time the snapshot was taken.
|
|
20
23
|
"""
|
|
21
24
|
|
|
22
|
-
@
|
|
23
|
-
|
|
25
|
+
@deprecate_aio_usage((2025, 12, 5), "SandboxSnapshot.from_id")
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_id(
|
|
28
|
+
cls, sandbox_snapshot_id: str, client: Optional["modal.client.Client"] = None
|
|
29
|
+
) -> typing_extensions.Self:
|
|
24
30
|
"""
|
|
25
31
|
Construct a `SandboxSnapshot` object from a sandbox snapshot ID.
|
|
26
32
|
"""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
_client = cast(_Client, synchronizer._translate_in(client))
|
|
34
|
+
|
|
35
|
+
async def _load(
|
|
36
|
+
self: _SandboxSnapshot, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
37
|
+
):
|
|
38
|
+
# hydration doesn't actually do much apart from validating the existance of the id
|
|
39
|
+
# which is implicitly done by trying to start a sandbox from the snapshot as well
|
|
40
|
+
resp: api_pb2.SandboxSnapshotGetResponse = await load_context.client.stub.SandboxSnapshotGet(
|
|
41
|
+
api_pb2.SandboxSnapshotGetRequest(snapshot_id=sandbox_snapshot_id)
|
|
33
42
|
)
|
|
43
|
+
self._hydrate(resp.snapshot_id, load_context.client, None)
|
|
34
44
|
|
|
35
45
|
rep = "SandboxSnapshot()"
|
|
36
|
-
obj = _SandboxSnapshot._from_loader(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
obj = _SandboxSnapshot._from_loader(
|
|
47
|
+
_load, rep, load_context_overrides=LoadContext(client=_client), hydrate_lazily=True
|
|
48
|
+
)
|
|
49
|
+
# Setting the object id directly is a bit hacky, but
|
|
50
|
+
# it avoids hydrating the object fully if it's going
|
|
51
|
+
# to be used only for its object id anyway
|
|
52
|
+
obj._object_id = sandbox_snapshot_id
|
|
53
|
+
return cast(typing_extensions.Self, synchronizer._translate_out(obj))
|
|
40
54
|
|
|
41
55
|
|
|
42
56
|
SandboxSnapshot = synchronize_api(_SandboxSnapshot)
|
modal/snapshot.pyi
CHANGED
|
@@ -4,6 +4,8 @@ import modal.object
|
|
|
4
4
|
import typing
|
|
5
5
|
import typing_extensions
|
|
6
6
|
|
|
7
|
+
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
|
8
|
+
|
|
7
9
|
class _SandboxSnapshot(modal._object._Object):
|
|
8
10
|
"""> Sandbox memory snapshots are in **early preview**.
|
|
9
11
|
|
|
@@ -11,10 +13,16 @@ class _SandboxSnapshot(modal._object._Object):
|
|
|
11
13
|
`._experimental_snapshot()` on a Sandbox instance. This includes both the filesystem and memory state of
|
|
12
14
|
the original Sandbox at the time the snapshot was taken.
|
|
13
15
|
"""
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
class __from_id_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
17
|
+
def __call__(
|
|
18
|
+
self, /, sandbox_snapshot_id: str, client: typing.Optional[modal.client.Client] = None
|
|
19
|
+
) -> SUPERSELF:
|
|
20
|
+
"""Construct a `SandboxSnapshot` object from a sandbox snapshot ID."""
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
async def aio(self, /, sandbox_snapshot_id: str, client: typing.Optional[modal.client.Client] = None): ...
|
|
24
|
+
|
|
25
|
+
from_id: typing.ClassVar[__from_id_spec[typing_extensions.Self]]
|
|
18
26
|
|
|
19
27
|
class SandboxSnapshot(modal.object.Object):
|
|
20
28
|
"""> Sandbox memory snapshots are in **early preview**.
|
|
@@ -27,13 +35,13 @@ class SandboxSnapshot(modal.object.Object):
|
|
|
27
35
|
"""mdmd:hidden"""
|
|
28
36
|
...
|
|
29
37
|
|
|
30
|
-
class __from_id_spec(typing_extensions.Protocol):
|
|
31
|
-
def __call__(
|
|
38
|
+
class __from_id_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
39
|
+
def __call__(
|
|
40
|
+
self, /, sandbox_snapshot_id: str, client: typing.Optional[modal.client.Client] = None
|
|
41
|
+
) -> SUPERSELF:
|
|
32
42
|
"""Construct a `SandboxSnapshot` object from a sandbox snapshot ID."""
|
|
33
43
|
...
|
|
34
44
|
|
|
35
|
-
async def aio(self, /, sandbox_snapshot_id: str, client: typing.Optional[modal.client.Client] = None):
|
|
36
|
-
"""Construct a `SandboxSnapshot` object from a sandbox snapshot ID."""
|
|
37
|
-
...
|
|
45
|
+
async def aio(self, /, sandbox_snapshot_id: str, client: typing.Optional[modal.client.Client] = None): ...
|
|
38
46
|
|
|
39
|
-
from_id: __from_id_spec
|
|
47
|
+
from_id: typing.ClassVar[__from_id_spec[typing_extensions.Self]]
|
modal/token_flow.py
CHANGED
|
@@ -56,7 +56,7 @@ class _TokenFlow:
|
|
|
56
56
|
req = api_pb2.TokenFlowWaitRequest(
|
|
57
57
|
token_flow_id=self.token_flow_id, timeout=timeout, wait_secret=self.wait_secret
|
|
58
58
|
)
|
|
59
|
-
resp = await self.stub.TokenFlowWait(req, timeout=
|
|
59
|
+
resp = await self.stub.TokenFlowWait(req, retry=None, timeout=timeout + grpc_extra_timeout)
|
|
60
60
|
if not resp.timeout:
|
|
61
61
|
return resp
|
|
62
62
|
else:
|
modal/token_flow.pyi
CHANGED
|
@@ -21,12 +21,10 @@ class _TokenFlow:
|
|
|
21
21
|
"""mdmd:hidden"""
|
|
22
22
|
...
|
|
23
23
|
|
|
24
|
-
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
|
25
|
-
|
|
26
24
|
class TokenFlow:
|
|
27
25
|
def __init__(self, client: modal.client.Client): ...
|
|
28
26
|
|
|
29
|
-
class __start_spec(typing_extensions.Protocol
|
|
27
|
+
class __start_spec(typing_extensions.Protocol):
|
|
30
28
|
def __call__(
|
|
31
29
|
self, /, utm_source: typing.Optional[str] = None, next_url: typing.Optional[str] = None
|
|
32
30
|
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[tuple[str, str, str]]:
|
|
@@ -39,9 +37,9 @@ class TokenFlow:
|
|
|
39
37
|
"""mdmd:hidden"""
|
|
40
38
|
...
|
|
41
39
|
|
|
42
|
-
start: __start_spec
|
|
40
|
+
start: __start_spec
|
|
43
41
|
|
|
44
|
-
class __finish_spec(typing_extensions.Protocol
|
|
42
|
+
class __finish_spec(typing_extensions.Protocol):
|
|
45
43
|
def __call__(
|
|
46
44
|
self, /, timeout: float = 40.0, grpc_extra_timeout: float = 5.0
|
|
47
45
|
) -> typing.Optional[modal_proto.api_pb2.TokenFlowWaitResponse]:
|
|
@@ -54,7 +52,7 @@ class TokenFlow:
|
|
|
54
52
|
"""mdmd:hidden"""
|
|
55
53
|
...
|
|
56
54
|
|
|
57
|
-
finish: __finish_spec
|
|
55
|
+
finish: __finish_spec
|
|
58
56
|
|
|
59
57
|
async def _new_token(
|
|
60
58
|
*,
|