modal 1.0.6.dev61__py3-none-any.whl → 1.1.1__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/__main__.py +2 -2
- modal/_clustered_functions.py +3 -0
- modal/_clustered_functions.pyi +3 -2
- modal/_functions.py +78 -26
- modal/_object.py +9 -1
- modal/_output.py +14 -25
- modal/_runtime/gpu_memory_snapshot.py +158 -54
- modal/_utils/async_utils.py +6 -4
- modal/_utils/auth_token_manager.py +1 -1
- modal/_utils/blob_utils.py +16 -21
- modal/_utils/function_utils.py +16 -4
- modal/_utils/time_utils.py +8 -4
- modal/app.py +0 -4
- modal/app.pyi +0 -4
- modal/cli/_traceback.py +3 -2
- modal/cli/app.py +4 -4
- modal/cli/cluster.py +4 -4
- modal/cli/config.py +2 -2
- modal/cli/container.py +2 -2
- modal/cli/dict.py +4 -4
- modal/cli/entry_point.py +2 -2
- modal/cli/import_refs.py +3 -3
- modal/cli/network_file_system.py +8 -9
- modal/cli/profile.py +2 -2
- modal/cli/queues.py +5 -5
- modal/cli/secret.py +5 -5
- modal/cli/utils.py +3 -4
- modal/cli/volume.py +8 -9
- modal/client.py +8 -1
- modal/client.pyi +9 -10
- modal/container_process.py +2 -2
- modal/dict.py +47 -3
- modal/dict.pyi +55 -0
- modal/exception.py +4 -0
- modal/experimental/__init__.py +1 -1
- modal/experimental/flash.py +18 -2
- modal/experimental/flash.pyi +19 -0
- modal/functions.pyi +6 -7
- modal/image.py +26 -10
- modal/image.pyi +12 -4
- modal/mount.py +1 -1
- modal/object.pyi +4 -0
- modal/parallel_map.py +432 -4
- modal/parallel_map.pyi +28 -0
- modal/queue.py +46 -3
- modal/queue.pyi +53 -0
- modal/sandbox.py +105 -25
- modal/sandbox.pyi +108 -18
- modal/secret.py +48 -5
- modal/secret.pyi +55 -0
- modal/token_flow.py +3 -3
- modal/volume.py +49 -18
- modal/volume.pyi +50 -8
- {modal-1.0.6.dev61.dist-info → modal-1.1.1.dist-info}/METADATA +2 -2
- {modal-1.0.6.dev61.dist-info → modal-1.1.1.dist-info}/RECORD +75 -75
- modal_proto/api.proto +140 -14
- modal_proto/api_grpc.py +80 -0
- modal_proto/api_pb2.py +927 -756
- modal_proto/api_pb2.pyi +488 -34
- modal_proto/api_pb2_grpc.py +166 -0
- modal_proto/api_pb2_grpc.pyi +52 -0
- modal_proto/modal_api_grpc.py +5 -0
- modal_version/__init__.py +1 -1
- /modal/{requirements → builder}/2023.12.312.txt +0 -0
- /modal/{requirements → builder}/2023.12.txt +0 -0
- /modal/{requirements → builder}/2024.04.txt +0 -0
- /modal/{requirements → builder}/2024.10.txt +0 -0
- /modal/{requirements → builder}/2025.06.txt +0 -0
- /modal/{requirements → builder}/PREVIEW.txt +0 -0
- /modal/{requirements → builder}/README.md +0 -0
- /modal/{requirements → builder}/base-images.json +0 -0
- {modal-1.0.6.dev61.dist-info → modal-1.1.1.dist-info}/WHEEL +0 -0
- {modal-1.0.6.dev61.dist-info → modal-1.1.1.dist-info}/entry_points.txt +0 -0
- {modal-1.0.6.dev61.dist-info → modal-1.1.1.dist-info}/licenses/LICENSE +0 -0
- {modal-1.0.6.dev61.dist-info → modal-1.1.1.dist-info}/top_level.txt +0 -0
modal/sandbox.pyi
CHANGED
|
@@ -25,7 +25,18 @@ import os
|
|
|
25
25
|
import typing
|
|
26
26
|
import typing_extensions
|
|
27
27
|
|
|
28
|
-
def _validate_exec_args(
|
|
28
|
+
def _validate_exec_args(args: collections.abc.Sequence[str]) -> None: ...
|
|
29
|
+
|
|
30
|
+
class DefaultSandboxNameOverride(str):
|
|
31
|
+
"""A singleton class that represents the default sandbox name override.
|
|
32
|
+
|
|
33
|
+
It is used to indicate that the sandbox name should not be overridden.
|
|
34
|
+
"""
|
|
35
|
+
def __repr__(self) -> str:
|
|
36
|
+
"""Return repr(self)."""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
_DEFAULT_SANDBOX_NAME_OVERRIDE: DefaultSandboxNameOverride
|
|
29
40
|
|
|
30
41
|
class _Sandbox(modal._object._Object):
|
|
31
42
|
"""A `Sandbox` object lets you interact with a running sandbox. This API is similar to Python's
|
|
@@ -44,9 +55,10 @@ class _Sandbox(modal._object._Object):
|
|
|
44
55
|
|
|
45
56
|
@staticmethod
|
|
46
57
|
def _new(
|
|
47
|
-
|
|
58
|
+
args: collections.abc.Sequence[str],
|
|
48
59
|
image: modal.image._Image,
|
|
49
60
|
secrets: collections.abc.Sequence[modal.secret._Secret],
|
|
61
|
+
name: typing.Optional[str] = None,
|
|
50
62
|
timeout: typing.Optional[int] = None,
|
|
51
63
|
workdir: typing.Optional[str] = None,
|
|
52
64
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
@@ -67,6 +79,7 @@ class _Sandbox(modal._object._Object):
|
|
|
67
79
|
h2_ports: collections.abc.Sequence[int] = [],
|
|
68
80
|
unencrypted_ports: collections.abc.Sequence[int] = [],
|
|
69
81
|
proxy: typing.Optional[modal.proxy._Proxy] = None,
|
|
82
|
+
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
70
83
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
71
84
|
enable_snapshot: bool = False,
|
|
72
85
|
verbose: bool = False,
|
|
@@ -76,8 +89,9 @@ class _Sandbox(modal._object._Object):
|
|
|
76
89
|
|
|
77
90
|
@staticmethod
|
|
78
91
|
async def create(
|
|
79
|
-
*
|
|
92
|
+
*args: str,
|
|
80
93
|
app: typing.Optional[modal.app._App] = None,
|
|
94
|
+
name: typing.Optional[str] = None,
|
|
81
95
|
image: typing.Optional[modal.image._Image] = None,
|
|
82
96
|
secrets: collections.abc.Sequence[modal.secret._Secret] = (),
|
|
83
97
|
network_file_systems: dict[typing.Union[str, os.PathLike], modal.network_file_system._NetworkFileSystem] = {},
|
|
@@ -100,6 +114,7 @@ class _Sandbox(modal._object._Object):
|
|
|
100
114
|
unencrypted_ports: collections.abc.Sequence[int] = [],
|
|
101
115
|
proxy: typing.Optional[modal.proxy._Proxy] = None,
|
|
102
116
|
verbose: bool = False,
|
|
117
|
+
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
103
118
|
_experimental_enable_snapshot: bool = False,
|
|
104
119
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
105
120
|
client: typing.Optional[modal.client._Client] = None,
|
|
@@ -121,8 +136,9 @@ class _Sandbox(modal._object._Object):
|
|
|
121
136
|
|
|
122
137
|
@staticmethod
|
|
123
138
|
async def _create(
|
|
124
|
-
*
|
|
139
|
+
*args: str,
|
|
125
140
|
app: typing.Optional[modal.app._App] = None,
|
|
141
|
+
name: typing.Optional[str] = None,
|
|
126
142
|
image: typing.Optional[modal.image._Image] = None,
|
|
127
143
|
secrets: collections.abc.Sequence[modal.secret._Secret] = (),
|
|
128
144
|
mounts: collections.abc.Sequence[modal.mount._Mount] = (),
|
|
@@ -145,12 +161,28 @@ class _Sandbox(modal._object._Object):
|
|
|
145
161
|
h2_ports: collections.abc.Sequence[int] = [],
|
|
146
162
|
unencrypted_ports: collections.abc.Sequence[int] = [],
|
|
147
163
|
proxy: typing.Optional[modal.proxy._Proxy] = None,
|
|
164
|
+
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
148
165
|
_experimental_enable_snapshot: bool = False,
|
|
149
166
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
150
167
|
client: typing.Optional[modal.client._Client] = None,
|
|
151
168
|
verbose: bool = False,
|
|
152
169
|
): ...
|
|
153
170
|
def _hydrate_metadata(self, handle_metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
171
|
+
@staticmethod
|
|
172
|
+
async def from_name(
|
|
173
|
+
app_name: str,
|
|
174
|
+
name: str,
|
|
175
|
+
*,
|
|
176
|
+
environment_name: typing.Optional[str] = None,
|
|
177
|
+
client: typing.Optional[modal.client._Client] = None,
|
|
178
|
+
) -> _Sandbox:
|
|
179
|
+
"""Get a running Sandbox by name from the given app.
|
|
180
|
+
|
|
181
|
+
Raises a modal.exception.NotFoundError if no running sandbox is found with the given name.
|
|
182
|
+
A Sandbox's name is the `name` argument passed to `Sandbox.create`.
|
|
183
|
+
"""
|
|
184
|
+
...
|
|
185
|
+
|
|
154
186
|
@staticmethod
|
|
155
187
|
async def from_id(sandbox_id: str, client: typing.Optional[modal.client._Client] = None) -> _Sandbox:
|
|
156
188
|
"""Construct a Sandbox from an id and look up the Sandbox result.
|
|
@@ -212,7 +244,7 @@ class _Sandbox(modal._object._Object):
|
|
|
212
244
|
@typing.overload
|
|
213
245
|
async def exec(
|
|
214
246
|
self,
|
|
215
|
-
*
|
|
247
|
+
*args: str,
|
|
216
248
|
pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
|
|
217
249
|
stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
|
218
250
|
stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
|
@@ -226,7 +258,7 @@ class _Sandbox(modal._object._Object):
|
|
|
226
258
|
@typing.overload
|
|
227
259
|
async def exec(
|
|
228
260
|
self,
|
|
229
|
-
*
|
|
261
|
+
*args: str,
|
|
230
262
|
pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
|
|
231
263
|
stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
|
232
264
|
stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
|
@@ -240,7 +272,10 @@ class _Sandbox(modal._object._Object):
|
|
|
240
272
|
async def _experimental_snapshot(self) -> modal.snapshot._SandboxSnapshot: ...
|
|
241
273
|
@staticmethod
|
|
242
274
|
async def _experimental_from_snapshot(
|
|
243
|
-
snapshot: modal.snapshot._SandboxSnapshot,
|
|
275
|
+
snapshot: modal.snapshot._SandboxSnapshot,
|
|
276
|
+
client: typing.Optional[modal.client._Client] = None,
|
|
277
|
+
*,
|
|
278
|
+
name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
|
|
244
279
|
): ...
|
|
245
280
|
@typing.overload
|
|
246
281
|
async def open(self, path: str, mode: _typeshed.OpenTextMode) -> modal.file_io._FileIO[str]: ...
|
|
@@ -329,9 +364,10 @@ class Sandbox(modal.object.Object):
|
|
|
329
364
|
|
|
330
365
|
@staticmethod
|
|
331
366
|
def _new(
|
|
332
|
-
|
|
367
|
+
args: collections.abc.Sequence[str],
|
|
333
368
|
image: modal.image.Image,
|
|
334
369
|
secrets: collections.abc.Sequence[modal.secret.Secret],
|
|
370
|
+
name: typing.Optional[str] = None,
|
|
335
371
|
timeout: typing.Optional[int] = None,
|
|
336
372
|
workdir: typing.Optional[str] = None,
|
|
337
373
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
@@ -351,6 +387,7 @@ class Sandbox(modal.object.Object):
|
|
|
351
387
|
h2_ports: collections.abc.Sequence[int] = [],
|
|
352
388
|
unencrypted_ports: collections.abc.Sequence[int] = [],
|
|
353
389
|
proxy: typing.Optional[modal.proxy.Proxy] = None,
|
|
390
|
+
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
354
391
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
355
392
|
enable_snapshot: bool = False,
|
|
356
393
|
verbose: bool = False,
|
|
@@ -362,8 +399,9 @@ class Sandbox(modal.object.Object):
|
|
|
362
399
|
def __call__(
|
|
363
400
|
self,
|
|
364
401
|
/,
|
|
365
|
-
*
|
|
402
|
+
*args: str,
|
|
366
403
|
app: typing.Optional[modal.app.App] = None,
|
|
404
|
+
name: typing.Optional[str] = None,
|
|
367
405
|
image: typing.Optional[modal.image.Image] = None,
|
|
368
406
|
secrets: collections.abc.Sequence[modal.secret.Secret] = (),
|
|
369
407
|
network_file_systems: dict[
|
|
@@ -388,6 +426,7 @@ class Sandbox(modal.object.Object):
|
|
|
388
426
|
unencrypted_ports: collections.abc.Sequence[int] = [],
|
|
389
427
|
proxy: typing.Optional[modal.proxy.Proxy] = None,
|
|
390
428
|
verbose: bool = False,
|
|
429
|
+
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
391
430
|
_experimental_enable_snapshot: bool = False,
|
|
392
431
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
393
432
|
client: typing.Optional[modal.client.Client] = None,
|
|
@@ -410,8 +449,9 @@ class Sandbox(modal.object.Object):
|
|
|
410
449
|
async def aio(
|
|
411
450
|
self,
|
|
412
451
|
/,
|
|
413
|
-
*
|
|
452
|
+
*args: str,
|
|
414
453
|
app: typing.Optional[modal.app.App] = None,
|
|
454
|
+
name: typing.Optional[str] = None,
|
|
415
455
|
image: typing.Optional[modal.image.Image] = None,
|
|
416
456
|
secrets: collections.abc.Sequence[modal.secret.Secret] = (),
|
|
417
457
|
network_file_systems: dict[
|
|
@@ -436,6 +476,7 @@ class Sandbox(modal.object.Object):
|
|
|
436
476
|
unencrypted_ports: collections.abc.Sequence[int] = [],
|
|
437
477
|
proxy: typing.Optional[modal.proxy.Proxy] = None,
|
|
438
478
|
verbose: bool = False,
|
|
479
|
+
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
439
480
|
_experimental_enable_snapshot: bool = False,
|
|
440
481
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
441
482
|
client: typing.Optional[modal.client.Client] = None,
|
|
@@ -461,8 +502,9 @@ class Sandbox(modal.object.Object):
|
|
|
461
502
|
def __call__(
|
|
462
503
|
self,
|
|
463
504
|
/,
|
|
464
|
-
*
|
|
505
|
+
*args: str,
|
|
465
506
|
app: typing.Optional[modal.app.App] = None,
|
|
507
|
+
name: typing.Optional[str] = None,
|
|
466
508
|
image: typing.Optional[modal.image.Image] = None,
|
|
467
509
|
secrets: collections.abc.Sequence[modal.secret.Secret] = (),
|
|
468
510
|
mounts: collections.abc.Sequence[modal.mount.Mount] = (),
|
|
@@ -487,6 +529,7 @@ class Sandbox(modal.object.Object):
|
|
|
487
529
|
h2_ports: collections.abc.Sequence[int] = [],
|
|
488
530
|
unencrypted_ports: collections.abc.Sequence[int] = [],
|
|
489
531
|
proxy: typing.Optional[modal.proxy.Proxy] = None,
|
|
532
|
+
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
490
533
|
_experimental_enable_snapshot: bool = False,
|
|
491
534
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
492
535
|
client: typing.Optional[modal.client.Client] = None,
|
|
@@ -495,8 +538,9 @@ class Sandbox(modal.object.Object):
|
|
|
495
538
|
async def aio(
|
|
496
539
|
self,
|
|
497
540
|
/,
|
|
498
|
-
*
|
|
541
|
+
*args: str,
|
|
499
542
|
app: typing.Optional[modal.app.App] = None,
|
|
543
|
+
name: typing.Optional[str] = None,
|
|
500
544
|
image: typing.Optional[modal.image.Image] = None,
|
|
501
545
|
secrets: collections.abc.Sequence[modal.secret.Secret] = (),
|
|
502
546
|
mounts: collections.abc.Sequence[modal.mount.Mount] = (),
|
|
@@ -521,6 +565,7 @@ class Sandbox(modal.object.Object):
|
|
|
521
565
|
h2_ports: collections.abc.Sequence[int] = [],
|
|
522
566
|
unencrypted_ports: collections.abc.Sequence[int] = [],
|
|
523
567
|
proxy: typing.Optional[modal.proxy.Proxy] = None,
|
|
568
|
+
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
524
569
|
_experimental_enable_snapshot: bool = False,
|
|
525
570
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
526
571
|
client: typing.Optional[modal.client.Client] = None,
|
|
@@ -531,6 +576,41 @@ class Sandbox(modal.object.Object):
|
|
|
531
576
|
|
|
532
577
|
def _hydrate_metadata(self, handle_metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
533
578
|
|
|
579
|
+
class __from_name_spec(typing_extensions.Protocol):
|
|
580
|
+
def __call__(
|
|
581
|
+
self,
|
|
582
|
+
/,
|
|
583
|
+
app_name: str,
|
|
584
|
+
name: str,
|
|
585
|
+
*,
|
|
586
|
+
environment_name: typing.Optional[str] = None,
|
|
587
|
+
client: typing.Optional[modal.client.Client] = None,
|
|
588
|
+
) -> Sandbox:
|
|
589
|
+
"""Get a running Sandbox by name from the given app.
|
|
590
|
+
|
|
591
|
+
Raises a modal.exception.NotFoundError if no running sandbox is found with the given name.
|
|
592
|
+
A Sandbox's name is the `name` argument passed to `Sandbox.create`.
|
|
593
|
+
"""
|
|
594
|
+
...
|
|
595
|
+
|
|
596
|
+
async def aio(
|
|
597
|
+
self,
|
|
598
|
+
/,
|
|
599
|
+
app_name: str,
|
|
600
|
+
name: str,
|
|
601
|
+
*,
|
|
602
|
+
environment_name: typing.Optional[str] = None,
|
|
603
|
+
client: typing.Optional[modal.client.Client] = None,
|
|
604
|
+
) -> Sandbox:
|
|
605
|
+
"""Get a running Sandbox by name from the given app.
|
|
606
|
+
|
|
607
|
+
Raises a modal.exception.NotFoundError if no running sandbox is found with the given name.
|
|
608
|
+
A Sandbox's name is the `name` argument passed to `Sandbox.create`.
|
|
609
|
+
"""
|
|
610
|
+
...
|
|
611
|
+
|
|
612
|
+
from_name: __from_name_spec
|
|
613
|
+
|
|
534
614
|
class __from_id_spec(typing_extensions.Protocol):
|
|
535
615
|
def __call__(self, /, sandbox_id: str, client: typing.Optional[modal.client.Client] = None) -> Sandbox:
|
|
536
616
|
"""Construct a Sandbox from an id and look up the Sandbox result.
|
|
@@ -678,7 +758,7 @@ class Sandbox(modal.object.Object):
|
|
|
678
758
|
def __call__(
|
|
679
759
|
self,
|
|
680
760
|
/,
|
|
681
|
-
*
|
|
761
|
+
*args: str,
|
|
682
762
|
pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
|
|
683
763
|
stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
|
684
764
|
stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
|
@@ -693,7 +773,7 @@ class Sandbox(modal.object.Object):
|
|
|
693
773
|
def __call__(
|
|
694
774
|
self,
|
|
695
775
|
/,
|
|
696
|
-
*
|
|
776
|
+
*args: str,
|
|
697
777
|
pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
|
|
698
778
|
stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
|
699
779
|
stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
|
@@ -708,7 +788,7 @@ class Sandbox(modal.object.Object):
|
|
|
708
788
|
async def aio(
|
|
709
789
|
self,
|
|
710
790
|
/,
|
|
711
|
-
*
|
|
791
|
+
*args: str,
|
|
712
792
|
pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
|
|
713
793
|
stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
|
714
794
|
stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
|
@@ -723,7 +803,7 @@ class Sandbox(modal.object.Object):
|
|
|
723
803
|
async def aio(
|
|
724
804
|
self,
|
|
725
805
|
/,
|
|
726
|
-
*
|
|
806
|
+
*args: str,
|
|
727
807
|
pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
|
|
728
808
|
stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
|
729
809
|
stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
|
@@ -745,10 +825,20 @@ class Sandbox(modal.object.Object):
|
|
|
745
825
|
|
|
746
826
|
class ___experimental_from_snapshot_spec(typing_extensions.Protocol):
|
|
747
827
|
def __call__(
|
|
748
|
-
self,
|
|
828
|
+
self,
|
|
829
|
+
/,
|
|
830
|
+
snapshot: modal.snapshot.SandboxSnapshot,
|
|
831
|
+
client: typing.Optional[modal.client.Client] = None,
|
|
832
|
+
*,
|
|
833
|
+
name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
|
|
749
834
|
): ...
|
|
750
835
|
async def aio(
|
|
751
|
-
self,
|
|
836
|
+
self,
|
|
837
|
+
/,
|
|
838
|
+
snapshot: modal.snapshot.SandboxSnapshot,
|
|
839
|
+
client: typing.Optional[modal.client.Client] = None,
|
|
840
|
+
*,
|
|
841
|
+
name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
|
|
752
842
|
): ...
|
|
753
843
|
|
|
754
844
|
_experimental_from_snapshot: ___experimental_from_snapshot_spec
|
modal/secret.py
CHANGED
|
@@ -1,24 +1,40 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
2
|
import os
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime
|
|
3
5
|
from typing import Optional, Union
|
|
4
6
|
|
|
7
|
+
from google.protobuf.message import Message
|
|
5
8
|
from grpclib import GRPCError, Status
|
|
6
9
|
|
|
7
10
|
from modal_proto import api_pb2
|
|
8
11
|
|
|
9
|
-
from ._object import _get_environment_name, _Object
|
|
12
|
+
from ._object import _get_environment_name, _Object, live_method
|
|
10
13
|
from ._resolver import Resolver
|
|
11
14
|
from ._runtime.execution_context import is_local
|
|
12
15
|
from ._utils.async_utils import synchronize_api
|
|
13
16
|
from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
|
|
14
17
|
from ._utils.grpc_utils import retry_transient_errors
|
|
15
18
|
from ._utils.name_utils import check_object_name
|
|
19
|
+
from ._utils.time_utils import timestamp_to_localized_dt
|
|
16
20
|
from .client import _Client
|
|
17
21
|
from .exception import InvalidError, NotFoundError
|
|
18
22
|
|
|
19
23
|
ENV_DICT_WRONG_TYPE_ERR = "the env_dict argument to Secret has to be a dict[str, Union[str, None]]"
|
|
20
24
|
|
|
21
25
|
|
|
26
|
+
@dataclass
|
|
27
|
+
class SecretInfo:
|
|
28
|
+
"""Information about the Secret object."""
|
|
29
|
+
|
|
30
|
+
# This dataclass should be limited to information that is unchanging over the lifetime of the Secret,
|
|
31
|
+
# since it is transmitted from the server when the object is hydrated and could be stale when accessed.
|
|
32
|
+
|
|
33
|
+
name: Optional[str]
|
|
34
|
+
created_at: datetime
|
|
35
|
+
created_by: Optional[str]
|
|
36
|
+
|
|
37
|
+
|
|
22
38
|
class _Secret(_Object, type_prefix="st"):
|
|
23
39
|
"""Secrets provide a dictionary of environment variables for images.
|
|
24
40
|
|
|
@@ -29,6 +45,22 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
29
45
|
See [the secrets guide page](https://modal.com/docs/guide/secrets) for more information.
|
|
30
46
|
"""
|
|
31
47
|
|
|
48
|
+
_metadata: Optional[api_pb2.SecretMetadata] = None
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def name(self) -> Optional[str]:
|
|
52
|
+
return self._name
|
|
53
|
+
|
|
54
|
+
def _hydrate_metadata(self, metadata: Optional[Message]):
|
|
55
|
+
if metadata:
|
|
56
|
+
assert isinstance(metadata, api_pb2.SecretMetadata)
|
|
57
|
+
self._metadata = metadata
|
|
58
|
+
self._name = metadata.name
|
|
59
|
+
|
|
60
|
+
def _get_metadata(self) -> api_pb2.SecretMetadata:
|
|
61
|
+
assert self._metadata
|
|
62
|
+
return self._metadata
|
|
63
|
+
|
|
32
64
|
@staticmethod
|
|
33
65
|
def from_dict(
|
|
34
66
|
env_dict: dict[
|
|
@@ -73,7 +105,7 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
73
105
|
if exc.status == Status.FAILED_PRECONDITION:
|
|
74
106
|
raise InvalidError(exc.message)
|
|
75
107
|
raise
|
|
76
|
-
self._hydrate(resp.secret_id, resolver.client,
|
|
108
|
+
self._hydrate(resp.secret_id, resolver.client, resp.metadata)
|
|
77
109
|
|
|
78
110
|
rep = f"Secret.from_dict([{', '.join(env_dict.keys())}])"
|
|
79
111
|
return _Secret._from_loader(_load, rep, hydrate_lazily=True)
|
|
@@ -157,7 +189,7 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
157
189
|
)
|
|
158
190
|
resp = await resolver.client.stub.SecretGetOrCreate(req)
|
|
159
191
|
|
|
160
|
-
self._hydrate(resp.secret_id, resolver.client,
|
|
192
|
+
self._hydrate(resp.secret_id, resolver.client, resp.metadata)
|
|
161
193
|
|
|
162
194
|
return _Secret._from_loader(_load, "Secret.from_dotenv()", hydrate_lazily=True)
|
|
163
195
|
|
|
@@ -200,9 +232,9 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
200
232
|
raise NotFoundError(exc.message)
|
|
201
233
|
else:
|
|
202
234
|
raise
|
|
203
|
-
self._hydrate(response.secret_id, resolver.client,
|
|
235
|
+
self._hydrate(response.secret_id, resolver.client, response.metadata)
|
|
204
236
|
|
|
205
|
-
return _Secret._from_loader(_load, "Secret()", hydrate_lazily=True)
|
|
237
|
+
return _Secret._from_loader(_load, "Secret()", hydrate_lazily=True, name=name)
|
|
206
238
|
|
|
207
239
|
@staticmethod
|
|
208
240
|
async def lookup(
|
|
@@ -261,5 +293,16 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
261
293
|
resp = await retry_transient_errors(client.stub.SecretGetOrCreate, request)
|
|
262
294
|
return resp.secret_id
|
|
263
295
|
|
|
296
|
+
@live_method
|
|
297
|
+
async def info(self) -> SecretInfo:
|
|
298
|
+
"""Return information about the Secret object."""
|
|
299
|
+
metadata = self._get_metadata()
|
|
300
|
+
creation_info = metadata.creation_info
|
|
301
|
+
return SecretInfo(
|
|
302
|
+
name=metadata.name or None,
|
|
303
|
+
created_at=timestamp_to_localized_dt(creation_info.created_at),
|
|
304
|
+
created_by=creation_info.created_by or None,
|
|
305
|
+
)
|
|
306
|
+
|
|
264
307
|
|
|
265
308
|
Secret = synchronize_api(_Secret)
|
modal/secret.pyi
CHANGED
|
@@ -1,9 +1,33 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import google.protobuf.message
|
|
1
3
|
import modal._object
|
|
2
4
|
import modal.client
|
|
3
5
|
import modal.object
|
|
6
|
+
import modal_proto.api_pb2
|
|
4
7
|
import typing
|
|
5
8
|
import typing_extensions
|
|
6
9
|
|
|
10
|
+
class SecretInfo:
|
|
11
|
+
"""Information about the Secret object."""
|
|
12
|
+
|
|
13
|
+
name: typing.Optional[str]
|
|
14
|
+
created_at: datetime.datetime
|
|
15
|
+
created_by: typing.Optional[str]
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self, name: typing.Optional[str], created_at: datetime.datetime, created_by: typing.Optional[str]
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
def __repr__(self):
|
|
24
|
+
"""Return repr(self)."""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
def __eq__(self, other):
|
|
28
|
+
"""Return self==value."""
|
|
29
|
+
...
|
|
30
|
+
|
|
7
31
|
class _Secret(modal._object._Object):
|
|
8
32
|
"""Secrets provide a dictionary of environment variables for images.
|
|
9
33
|
|
|
@@ -13,6 +37,13 @@ class _Secret(modal._object._Object):
|
|
|
13
37
|
|
|
14
38
|
See [the secrets guide page](https://modal.com/docs/guide/secrets) for more information.
|
|
15
39
|
"""
|
|
40
|
+
|
|
41
|
+
_metadata: typing.Optional[modal_proto.api_pb2.SecretMetadata]
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def name(self) -> typing.Optional[str]: ...
|
|
45
|
+
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
46
|
+
def _get_metadata(self) -> modal_proto.api_pb2.SecretMetadata: ...
|
|
16
47
|
@staticmethod
|
|
17
48
|
def from_dict(env_dict: dict[str, typing.Optional[str]] = {}) -> _Secret:
|
|
18
49
|
"""Create a secret from a str-str dictionary. Values can also be `None`, which is ignored.
|
|
@@ -104,6 +135,12 @@ class _Secret(modal._object._Object):
|
|
|
104
135
|
"""mdmd:hidden"""
|
|
105
136
|
...
|
|
106
137
|
|
|
138
|
+
async def info(self) -> SecretInfo:
|
|
139
|
+
"""Return information about the Secret object."""
|
|
140
|
+
...
|
|
141
|
+
|
|
142
|
+
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
|
143
|
+
|
|
107
144
|
class Secret(modal.object.Object):
|
|
108
145
|
"""Secrets provide a dictionary of environment variables for images.
|
|
109
146
|
|
|
@@ -113,10 +150,17 @@ class Secret(modal.object.Object):
|
|
|
113
150
|
|
|
114
151
|
See [the secrets guide page](https://modal.com/docs/guide/secrets) for more information.
|
|
115
152
|
"""
|
|
153
|
+
|
|
154
|
+
_metadata: typing.Optional[modal_proto.api_pb2.SecretMetadata]
|
|
155
|
+
|
|
116
156
|
def __init__(self, *args, **kwargs):
|
|
117
157
|
"""mdmd:hidden"""
|
|
118
158
|
...
|
|
119
159
|
|
|
160
|
+
@property
|
|
161
|
+
def name(self) -> typing.Optional[str]: ...
|
|
162
|
+
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
163
|
+
def _get_metadata(self) -> modal_proto.api_pb2.SecretMetadata: ...
|
|
120
164
|
@staticmethod
|
|
121
165
|
def from_dict(env_dict: dict[str, typing.Optional[str]] = {}) -> Secret:
|
|
122
166
|
"""Create a secret from a str-str dictionary. Values can also be `None`, which is ignored.
|
|
@@ -240,3 +284,14 @@ class Secret(modal.object.Object):
|
|
|
240
284
|
...
|
|
241
285
|
|
|
242
286
|
create_deployed: __create_deployed_spec
|
|
287
|
+
|
|
288
|
+
class __info_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
289
|
+
def __call__(self, /) -> SecretInfo:
|
|
290
|
+
"""Return information about the Secret object."""
|
|
291
|
+
...
|
|
292
|
+
|
|
293
|
+
async def aio(self, /) -> SecretInfo:
|
|
294
|
+
"""Return information about the Secret object."""
|
|
295
|
+
...
|
|
296
|
+
|
|
297
|
+
info: __info_spec[typing_extensions.Self]
|
modal/token_flow.py
CHANGED
|
@@ -6,11 +6,11 @@ from collections.abc import AsyncGenerator
|
|
|
6
6
|
from typing import Optional
|
|
7
7
|
|
|
8
8
|
import aiohttp.web
|
|
9
|
-
from rich.console import Console
|
|
10
9
|
from synchronicity.async_wrap import asynccontextmanager
|
|
11
10
|
|
|
12
11
|
from modal_proto import api_pb2
|
|
13
12
|
|
|
13
|
+
from ._output import make_console
|
|
14
14
|
from ._utils.async_utils import synchronize_api
|
|
15
15
|
from ._utils.http_utils import run_temporary_http_server
|
|
16
16
|
from .client import _Client
|
|
@@ -76,7 +76,7 @@ async def _new_token(
|
|
|
76
76
|
):
|
|
77
77
|
server_url = config.get("server_url", profile=profile)
|
|
78
78
|
|
|
79
|
-
console =
|
|
79
|
+
console = make_console()
|
|
80
80
|
|
|
81
81
|
result: Optional[api_pb2.TokenFlowWaitResponse] = None
|
|
82
82
|
async with _Client.anonymous(server_url) as client:
|
|
@@ -133,7 +133,7 @@ async def _set_token(
|
|
|
133
133
|
):
|
|
134
134
|
# TODO add server_url as a parameter for verification?
|
|
135
135
|
server_url = config.get("server_url", profile=profile)
|
|
136
|
-
console =
|
|
136
|
+
console = make_console()
|
|
137
137
|
if verify:
|
|
138
138
|
console.print(f"Verifying token against [blue]{server_url}[/blue]")
|
|
139
139
|
await _Client.verify(server_url, (token_id, token_secret))
|
modal/volume.py
CHANGED
|
@@ -11,6 +11,7 @@ import time
|
|
|
11
11
|
import typing
|
|
12
12
|
from collections.abc import AsyncGenerator, AsyncIterator, Generator, Sequence
|
|
13
13
|
from dataclasses import dataclass
|
|
14
|
+
from datetime import datetime
|
|
14
15
|
from io import BytesIO
|
|
15
16
|
from pathlib import Path, PurePosixPath
|
|
16
17
|
from typing import (
|
|
@@ -54,6 +55,7 @@ from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
|
|
|
54
55
|
from ._utils.grpc_utils import retry_transient_errors
|
|
55
56
|
from ._utils.http_utils import ClientSessionRegistry
|
|
56
57
|
from ._utils.name_utils import check_object_name
|
|
58
|
+
from ._utils.time_utils import timestamp_to_localized_dt
|
|
57
59
|
from .client import _Client
|
|
58
60
|
from .config import logger
|
|
59
61
|
|
|
@@ -92,6 +94,18 @@ class FileEntry:
|
|
|
92
94
|
)
|
|
93
95
|
|
|
94
96
|
|
|
97
|
+
@dataclass
|
|
98
|
+
class VolumeInfo:
|
|
99
|
+
"""Information about the Volume object."""
|
|
100
|
+
|
|
101
|
+
# This dataclass should be limited to information that is unchanging over the lifetime of the Volume,
|
|
102
|
+
# since it is transmitted from the server when the object is hydrated and could be stale when accessed.
|
|
103
|
+
|
|
104
|
+
name: Optional[str]
|
|
105
|
+
created_at: datetime
|
|
106
|
+
created_by: Optional[str]
|
|
107
|
+
|
|
108
|
+
|
|
95
109
|
class _Volume(_Object, type_prefix="vo"):
|
|
96
110
|
"""A writeable volume that can be used to share files between one or more Modal functions.
|
|
97
111
|
|
|
@@ -167,6 +181,19 @@ class _Volume(_Object, type_prefix="vo"):
|
|
|
167
181
|
obj = _Volume._from_loader(_load, "Volume()", hydrate_lazily=True, deps=lambda: [self])
|
|
168
182
|
return obj
|
|
169
183
|
|
|
184
|
+
@property
|
|
185
|
+
def name(self) -> Optional[str]:
|
|
186
|
+
return self._name
|
|
187
|
+
|
|
188
|
+
def _hydrate_metadata(self, metadata: Optional[Message]):
|
|
189
|
+
if metadata:
|
|
190
|
+
assert isinstance(metadata, api_pb2.VolumeMetadata)
|
|
191
|
+
self._metadata = metadata
|
|
192
|
+
self._name = metadata.name
|
|
193
|
+
|
|
194
|
+
def _get_metadata(self) -> Optional[Message]:
|
|
195
|
+
return self._metadata
|
|
196
|
+
|
|
170
197
|
async def _get_lock(self):
|
|
171
198
|
# To (mostly*) prevent multiple concurrent operations on the same volume, which can cause problems under
|
|
172
199
|
# some unlikely circumstances.
|
|
@@ -181,6 +208,14 @@ class _Volume(_Object, type_prefix="vo"):
|
|
|
181
208
|
self._lock = asyncio.Lock()
|
|
182
209
|
return self._lock
|
|
183
210
|
|
|
211
|
+
@property
|
|
212
|
+
def _is_v1(self) -> bool:
|
|
213
|
+
return self._metadata.version in [
|
|
214
|
+
None,
|
|
215
|
+
api_pb2.VolumeFsVersion.VOLUME_FS_VERSION_UNSPECIFIED,
|
|
216
|
+
api_pb2.VolumeFsVersion.VOLUME_FS_VERSION_V1,
|
|
217
|
+
]
|
|
218
|
+
|
|
184
219
|
@staticmethod
|
|
185
220
|
def from_name(
|
|
186
221
|
name: str,
|
|
@@ -220,24 +255,7 @@ class _Volume(_Object, type_prefix="vo"):
|
|
|
220
255
|
response = await resolver.client.stub.VolumeGetOrCreate(req)
|
|
221
256
|
self._hydrate(response.volume_id, resolver.client, response.metadata)
|
|
222
257
|
|
|
223
|
-
return _Volume._from_loader(_load, "Volume()", hydrate_lazily=True)
|
|
224
|
-
|
|
225
|
-
def _hydrate_metadata(self, metadata: Optional[Message]):
|
|
226
|
-
if metadata and isinstance(metadata, api_pb2.VolumeMetadata):
|
|
227
|
-
self._metadata = metadata
|
|
228
|
-
else:
|
|
229
|
-
raise TypeError("_hydrate_metadata() requires an `api_pb2.VolumeMetadata` to determine volume version")
|
|
230
|
-
|
|
231
|
-
def _get_metadata(self) -> Optional[Message]:
|
|
232
|
-
return self._metadata
|
|
233
|
-
|
|
234
|
-
@property
|
|
235
|
-
def _is_v1(self) -> bool:
|
|
236
|
-
return self._metadata.version in [
|
|
237
|
-
None,
|
|
238
|
-
api_pb2.VolumeFsVersion.VOLUME_FS_VERSION_UNSPECIFIED,
|
|
239
|
-
api_pb2.VolumeFsVersion.VOLUME_FS_VERSION_V1,
|
|
240
|
-
]
|
|
258
|
+
return _Volume._from_loader(_load, "Volume()", hydrate_lazily=True, name=name)
|
|
241
259
|
|
|
242
260
|
@classmethod
|
|
243
261
|
@asynccontextmanager
|
|
@@ -338,6 +356,19 @@ class _Volume(_Object, type_prefix="vo"):
|
|
|
338
356
|
resp = await retry_transient_errors(client.stub.VolumeGetOrCreate, request)
|
|
339
357
|
return resp.volume_id
|
|
340
358
|
|
|
359
|
+
@live_method
|
|
360
|
+
async def info(self) -> VolumeInfo:
|
|
361
|
+
"""Return information about the Volume object."""
|
|
362
|
+
metadata = self._get_metadata()
|
|
363
|
+
if not metadata:
|
|
364
|
+
return VolumeInfo()
|
|
365
|
+
creation_info = metadata.creation_info
|
|
366
|
+
return VolumeInfo(
|
|
367
|
+
name=metadata.name or None,
|
|
368
|
+
created_at=timestamp_to_localized_dt(creation_info.created_at),
|
|
369
|
+
created_by=creation_info.created_by or None,
|
|
370
|
+
)
|
|
371
|
+
|
|
341
372
|
@live_method
|
|
342
373
|
async def _do_reload(self, lock=True):
|
|
343
374
|
async with (await self._get_lock()) if lock else asyncnullcontext():
|