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/app.pyi
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import collections.abc
|
|
2
2
|
import modal._functions
|
|
3
|
+
import modal._load_context
|
|
3
4
|
import modal._partial_function
|
|
4
5
|
import modal._utils.function_utils
|
|
5
6
|
import modal.client
|
|
@@ -74,6 +75,42 @@ class _FunctionDecoratorType:
|
|
|
74
75
|
self, func: collections.abc.Callable[P, ReturnType]
|
|
75
76
|
) -> modal.functions.Function[P, ReturnType, ReturnType]: ...
|
|
76
77
|
|
|
78
|
+
class _LocalAppState:
|
|
79
|
+
"""All state for apps that's part of the local/definition state"""
|
|
80
|
+
|
|
81
|
+
functions: dict[str, modal._functions._Function]
|
|
82
|
+
classes: dict[str, modal.cls._Cls]
|
|
83
|
+
image_default: typing.Optional[modal.image._Image]
|
|
84
|
+
web_endpoints: list[str]
|
|
85
|
+
local_entrypoints: dict[str, _LocalEntrypoint]
|
|
86
|
+
tags: dict[str, str]
|
|
87
|
+
include_source_default: bool
|
|
88
|
+
secrets_default: collections.abc.Sequence[modal.secret._Secret]
|
|
89
|
+
volumes_default: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume]
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
functions: dict[str, modal._functions._Function],
|
|
94
|
+
classes: dict[str, modal.cls._Cls],
|
|
95
|
+
image_default: typing.Optional[modal.image._Image],
|
|
96
|
+
web_endpoints: list[str],
|
|
97
|
+
local_entrypoints: dict[str, _LocalEntrypoint],
|
|
98
|
+
tags: dict[str, str],
|
|
99
|
+
include_source_default: bool,
|
|
100
|
+
secrets_default: collections.abc.Sequence[modal.secret._Secret],
|
|
101
|
+
volumes_default: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume],
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
104
|
+
...
|
|
105
|
+
|
|
106
|
+
def __repr__(self):
|
|
107
|
+
"""Return repr(self)."""
|
|
108
|
+
...
|
|
109
|
+
|
|
110
|
+
def __eq__(self, other):
|
|
111
|
+
"""Return self==value."""
|
|
112
|
+
...
|
|
113
|
+
|
|
77
114
|
class _App:
|
|
78
115
|
"""A Modal App is a group of functions and classes that are deployed together.
|
|
79
116
|
|
|
@@ -110,18 +147,16 @@ class _App:
|
|
|
110
147
|
_container_app: typing.ClassVar[typing.Optional[_App]]
|
|
111
148
|
_name: typing.Optional[str]
|
|
112
149
|
_description: typing.Optional[str]
|
|
113
|
-
|
|
114
|
-
_functions: dict[str, modal._functions._Function]
|
|
115
|
-
_classes: dict[str, modal.cls._Cls]
|
|
116
|
-
_image: typing.Optional[modal.image._Image]
|
|
117
|
-
_secrets: collections.abc.Sequence[modal.secret._Secret]
|
|
118
|
-
_volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume]
|
|
119
|
-
_web_endpoints: list[str]
|
|
120
|
-
_local_entrypoints: dict[str, _LocalEntrypoint]
|
|
150
|
+
_local_state_attr: typing.Optional[_LocalAppState]
|
|
121
151
|
_app_id: typing.Optional[str]
|
|
122
152
|
_running_app: typing.Optional[modal.running_app.RunningApp]
|
|
123
153
|
_client: typing.Optional[modal.client._Client]
|
|
124
|
-
|
|
154
|
+
_root_load_context: modal._load_context.LoadContext
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def _local_state(self) -> _LocalAppState:
|
|
158
|
+
"""For internal use only. Do not use this property directly."""
|
|
159
|
+
...
|
|
125
160
|
|
|
126
161
|
def __init__(
|
|
127
162
|
self,
|
|
@@ -452,14 +487,14 @@ class _App:
|
|
|
452
487
|
is_generator: typing.Optional[bool] = None,
|
|
453
488
|
cloud: typing.Optional[str] = None,
|
|
454
489
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
490
|
+
nonpreemptible: bool = False,
|
|
455
491
|
enable_memory_snapshot: bool = False,
|
|
456
492
|
block_network: bool = False,
|
|
457
493
|
restrict_modal_access: bool = False,
|
|
458
|
-
|
|
494
|
+
single_use_containers: bool = False,
|
|
459
495
|
i6pn: typing.Optional[bool] = None,
|
|
460
496
|
include_source: typing.Optional[bool] = None,
|
|
461
497
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
462
|
-
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
463
498
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
|
464
499
|
_experimental_custom_scaling_factor: typing.Optional[float] = None,
|
|
465
500
|
_experimental_restrict_output: bool = False,
|
|
@@ -467,7 +502,9 @@ class _App:
|
|
|
467
502
|
concurrency_limit: typing.Optional[int] = None,
|
|
468
503
|
container_idle_timeout: typing.Optional[int] = None,
|
|
469
504
|
allow_concurrent_inputs: typing.Optional[int] = None,
|
|
505
|
+
max_inputs: typing.Optional[int] = None,
|
|
470
506
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
|
507
|
+
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
471
508
|
) -> _FunctionDecoratorType:
|
|
472
509
|
"""Decorator to register a new Modal Function with this App."""
|
|
473
510
|
...
|
|
@@ -505,14 +542,14 @@ class _App:
|
|
|
505
542
|
startup_timeout: typing.Optional[int] = None,
|
|
506
543
|
cloud: typing.Optional[str] = None,
|
|
507
544
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
545
|
+
nonpreemptible: bool = False,
|
|
508
546
|
enable_memory_snapshot: bool = False,
|
|
509
547
|
block_network: bool = False,
|
|
510
548
|
restrict_modal_access: bool = False,
|
|
511
|
-
|
|
549
|
+
single_use_containers: bool = False,
|
|
512
550
|
i6pn: typing.Optional[bool] = None,
|
|
513
551
|
include_source: typing.Optional[bool] = None,
|
|
514
552
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
515
|
-
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
516
553
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
|
517
554
|
_experimental_custom_scaling_factor: typing.Optional[float] = None,
|
|
518
555
|
_experimental_restrict_output: bool = False,
|
|
@@ -520,7 +557,9 @@ class _App:
|
|
|
520
557
|
concurrency_limit: typing.Optional[int] = None,
|
|
521
558
|
container_idle_timeout: typing.Optional[int] = None,
|
|
522
559
|
allow_concurrent_inputs: typing.Optional[int] = None,
|
|
560
|
+
max_inputs: typing.Optional[int] = None,
|
|
523
561
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
|
562
|
+
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
524
563
|
) -> collections.abc.Callable[[typing.Union[CLS_T, modal._partial_function._PartialFunction]], CLS_T]:
|
|
525
564
|
"""Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App."""
|
|
526
565
|
...
|
|
@@ -630,18 +669,11 @@ class App:
|
|
|
630
669
|
_container_app: typing.ClassVar[typing.Optional[App]]
|
|
631
670
|
_name: typing.Optional[str]
|
|
632
671
|
_description: typing.Optional[str]
|
|
633
|
-
|
|
634
|
-
_functions: dict[str, modal.functions.Function]
|
|
635
|
-
_classes: dict[str, modal.cls.Cls]
|
|
636
|
-
_image: typing.Optional[modal.image.Image]
|
|
637
|
-
_secrets: collections.abc.Sequence[modal.secret.Secret]
|
|
638
|
-
_volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume.Volume]
|
|
639
|
-
_web_endpoints: list[str]
|
|
640
|
-
_local_entrypoints: dict[str, LocalEntrypoint]
|
|
672
|
+
_local_state_attr: typing.Optional[_LocalAppState]
|
|
641
673
|
_app_id: typing.Optional[str]
|
|
642
674
|
_running_app: typing.Optional[modal.running_app.RunningApp]
|
|
643
675
|
_client: typing.Optional[modal.client.Client]
|
|
644
|
-
|
|
676
|
+
_root_load_context: modal._load_context.LoadContext
|
|
645
677
|
|
|
646
678
|
def __init__(
|
|
647
679
|
self,
|
|
@@ -664,6 +696,11 @@ class App:
|
|
|
664
696
|
"""
|
|
665
697
|
...
|
|
666
698
|
|
|
699
|
+
@property
|
|
700
|
+
def _local_state(self) -> _LocalAppState:
|
|
701
|
+
"""For internal use only. Do not use this property directly."""
|
|
702
|
+
...
|
|
703
|
+
|
|
667
704
|
@property
|
|
668
705
|
def name(self) -> typing.Optional[str]:
|
|
669
706
|
"""The user-provided name of the App."""
|
|
@@ -733,7 +770,7 @@ class App:
|
|
|
733
770
|
"""
|
|
734
771
|
...
|
|
735
772
|
|
|
736
|
-
lookup: __lookup_spec
|
|
773
|
+
lookup: typing.ClassVar[__lookup_spec]
|
|
737
774
|
|
|
738
775
|
def set_description(self, description: str):
|
|
739
776
|
"""mdmd:hidden
|
|
@@ -761,7 +798,7 @@ class App:
|
|
|
761
798
|
|
|
762
799
|
def _uncreate_all_objects(self): ...
|
|
763
800
|
|
|
764
|
-
class ___set_local_app_spec(typing_extensions.Protocol
|
|
801
|
+
class ___set_local_app_spec(typing_extensions.Protocol):
|
|
765
802
|
def __call__(
|
|
766
803
|
self, /, client: modal.client.Client, running_app: modal.running_app.RunningApp
|
|
767
804
|
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[None]: ...
|
|
@@ -769,9 +806,9 @@ class App:
|
|
|
769
806
|
self, /, client: modal.client.Client, running_app: modal.running_app.RunningApp
|
|
770
807
|
) -> typing.AsyncContextManager[None]: ...
|
|
771
808
|
|
|
772
|
-
_set_local_app: ___set_local_app_spec
|
|
809
|
+
_set_local_app: ___set_local_app_spec
|
|
773
810
|
|
|
774
|
-
class __run_spec(typing_extensions.Protocol
|
|
811
|
+
class __run_spec(typing_extensions.Protocol):
|
|
775
812
|
def __call__(
|
|
776
813
|
self,
|
|
777
814
|
/,
|
|
@@ -870,7 +907,7 @@ class App:
|
|
|
870
907
|
"""
|
|
871
908
|
...
|
|
872
909
|
|
|
873
|
-
run: __run_spec
|
|
910
|
+
run: __run_spec
|
|
874
911
|
|
|
875
912
|
class __deploy_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
876
913
|
def __call__(
|
|
@@ -1115,14 +1152,14 @@ class App:
|
|
|
1115
1152
|
is_generator: typing.Optional[bool] = None,
|
|
1116
1153
|
cloud: typing.Optional[str] = None,
|
|
1117
1154
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
1155
|
+
nonpreemptible: bool = False,
|
|
1118
1156
|
enable_memory_snapshot: bool = False,
|
|
1119
1157
|
block_network: bool = False,
|
|
1120
1158
|
restrict_modal_access: bool = False,
|
|
1121
|
-
|
|
1159
|
+
single_use_containers: bool = False,
|
|
1122
1160
|
i6pn: typing.Optional[bool] = None,
|
|
1123
1161
|
include_source: typing.Optional[bool] = None,
|
|
1124
1162
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
1125
|
-
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
1126
1163
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
|
1127
1164
|
_experimental_custom_scaling_factor: typing.Optional[float] = None,
|
|
1128
1165
|
_experimental_restrict_output: bool = False,
|
|
@@ -1130,7 +1167,9 @@ class App:
|
|
|
1130
1167
|
concurrency_limit: typing.Optional[int] = None,
|
|
1131
1168
|
container_idle_timeout: typing.Optional[int] = None,
|
|
1132
1169
|
allow_concurrent_inputs: typing.Optional[int] = None,
|
|
1170
|
+
max_inputs: typing.Optional[int] = None,
|
|
1133
1171
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
|
1172
|
+
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
1134
1173
|
) -> _FunctionDecoratorType:
|
|
1135
1174
|
"""Decorator to register a new Modal Function with this App."""
|
|
1136
1175
|
...
|
|
@@ -1168,14 +1207,14 @@ class App:
|
|
|
1168
1207
|
startup_timeout: typing.Optional[int] = None,
|
|
1169
1208
|
cloud: typing.Optional[str] = None,
|
|
1170
1209
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
1210
|
+
nonpreemptible: bool = False,
|
|
1171
1211
|
enable_memory_snapshot: bool = False,
|
|
1172
1212
|
block_network: bool = False,
|
|
1173
1213
|
restrict_modal_access: bool = False,
|
|
1174
|
-
|
|
1214
|
+
single_use_containers: bool = False,
|
|
1175
1215
|
i6pn: typing.Optional[bool] = None,
|
|
1176
1216
|
include_source: typing.Optional[bool] = None,
|
|
1177
1217
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
1178
|
-
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
1179
1218
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
|
1180
1219
|
_experimental_custom_scaling_factor: typing.Optional[float] = None,
|
|
1181
1220
|
_experimental_restrict_output: bool = False,
|
|
@@ -1183,7 +1222,9 @@ class App:
|
|
|
1183
1222
|
concurrency_limit: typing.Optional[int] = None,
|
|
1184
1223
|
container_idle_timeout: typing.Optional[int] = None,
|
|
1185
1224
|
allow_concurrent_inputs: typing.Optional[int] = None,
|
|
1225
|
+
max_inputs: typing.Optional[int] = None,
|
|
1186
1226
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
|
1227
|
+
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
1187
1228
|
) -> collections.abc.Callable[[typing.Union[CLS_T, modal.partial_function.PartialFunction]], CLS_T]:
|
|
1188
1229
|
"""Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App."""
|
|
1189
1230
|
...
|
|
@@ -1217,7 +1258,7 @@ class App:
|
|
|
1217
1258
|
"""
|
|
1218
1259
|
...
|
|
1219
1260
|
|
|
1220
|
-
class __set_tags_spec(typing_extensions.Protocol
|
|
1261
|
+
class __set_tags_spec(typing_extensions.Protocol):
|
|
1221
1262
|
def __call__(
|
|
1222
1263
|
self, /, tags: collections.abc.Mapping[str, str], *, client: typing.Optional[modal.client.Client] = None
|
|
1223
1264
|
) -> None:
|
|
@@ -1246,9 +1287,9 @@ class App:
|
|
|
1246
1287
|
"""
|
|
1247
1288
|
...
|
|
1248
1289
|
|
|
1249
|
-
set_tags: __set_tags_spec
|
|
1290
|
+
set_tags: __set_tags_spec
|
|
1250
1291
|
|
|
1251
|
-
class __get_tags_spec(typing_extensions.Protocol
|
|
1292
|
+
class __get_tags_spec(typing_extensions.Protocol):
|
|
1252
1293
|
def __call__(self, /, *, client: typing.Optional[modal.client.Client] = None) -> dict[str, str]:
|
|
1253
1294
|
"""Get the tags that are currently attached to the App."""
|
|
1254
1295
|
...
|
|
@@ -1257,9 +1298,9 @@ class App:
|
|
|
1257
1298
|
"""Get the tags that are currently attached to the App."""
|
|
1258
1299
|
...
|
|
1259
1300
|
|
|
1260
|
-
get_tags: __get_tags_spec
|
|
1301
|
+
get_tags: __get_tags_spec
|
|
1261
1302
|
|
|
1262
|
-
class ___logs_spec(typing_extensions.Protocol
|
|
1303
|
+
class ___logs_spec(typing_extensions.Protocol):
|
|
1263
1304
|
def __call__(self, /, client: typing.Optional[modal.client.Client] = None) -> typing.Generator[str, None, None]:
|
|
1264
1305
|
"""Stream logs from the app.
|
|
1265
1306
|
|
|
@@ -1276,7 +1317,7 @@ class App:
|
|
|
1276
1317
|
"""
|
|
1277
1318
|
...
|
|
1278
1319
|
|
|
1279
|
-
_logs: ___logs_spec
|
|
1320
|
+
_logs: ___logs_spec
|
|
1280
1321
|
|
|
1281
1322
|
@classmethod
|
|
1282
1323
|
def _get_container_app(cls) -> typing.Optional[App]:
|
modal/billing.py
ADDED
modal/builder/2025.06.txt
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
aiohappyeyeballs==2.6.1
|
|
2
|
-
aiohttp==3.12.7
|
|
3
|
-
|
|
2
|
+
aiohttp==3.12.7 ; python_version < "3.14"
|
|
3
|
+
aiohttp==3.13.2 ; python_version >= "3.14"
|
|
4
|
+
aiosignal==1.3.2 ; python_version < "3.14"
|
|
5
|
+
aiosignal==1.4.0 ; python_version >= "3.14"
|
|
4
6
|
async-timeout==5.0.1 ; python_version < "3.11"
|
|
5
7
|
attrs==25.3.0
|
|
6
8
|
cbor2==5.7.0
|
|
7
9
|
certifi==2025.4.26
|
|
8
10
|
frozenlist==1.6.0
|
|
9
|
-
grpclib==0.4.8
|
|
11
|
+
grpclib==0.4.8 ; python_version < "3.14"
|
|
12
|
+
grpclib==0.4.9 ; python_version >= "3.14"
|
|
10
13
|
h2==4.2.0
|
|
11
14
|
hpack==4.1.0
|
|
12
15
|
hyperframe==6.1.0
|
modal/builder/PREVIEW.txt
CHANGED
modal/builder/base-images.json
CHANGED
modal/cli/_download.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2023
|
|
2
2
|
import asyncio
|
|
3
3
|
import functools
|
|
4
|
+
import multiprocessing
|
|
4
5
|
import os
|
|
5
6
|
import shutil
|
|
6
7
|
import sys
|
|
@@ -23,12 +24,22 @@ async def _volume_download(
|
|
|
23
24
|
remote_path: str,
|
|
24
25
|
local_destination: Path,
|
|
25
26
|
overwrite: bool,
|
|
26
|
-
|
|
27
|
+
concurrency: Optional[int] = None,
|
|
28
|
+
progress_cb: Optional[Callable] = None,
|
|
27
29
|
):
|
|
30
|
+
if progress_cb is None:
|
|
31
|
+
|
|
32
|
+
def progress_cb(*_, **__):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
if concurrency is None:
|
|
36
|
+
concurrency = max(128, 2 * multiprocessing.cpu_count())
|
|
37
|
+
|
|
28
38
|
is_pipe = local_destination == PIPE_PATH
|
|
29
39
|
|
|
30
40
|
q: asyncio.Queue[tuple[Optional[Path], Optional[FileEntry]]] = asyncio.Queue()
|
|
31
|
-
num_consumers = 1 if is_pipe else
|
|
41
|
+
num_consumers = 1 if is_pipe else concurrency # concurrency limit for downloading files
|
|
42
|
+
download_semaphore = asyncio.Semaphore(concurrency)
|
|
32
43
|
|
|
33
44
|
async def producer():
|
|
34
45
|
iterator: AsyncIterator[FileEntry]
|
|
@@ -86,7 +97,12 @@ async def _volume_download(
|
|
|
86
97
|
|
|
87
98
|
with output_path.open("wb") as fp:
|
|
88
99
|
if isinstance(volume, _Volume):
|
|
89
|
-
b = await volume.
|
|
100
|
+
b = await volume._read_file_into_fileobj(
|
|
101
|
+
path=entry.path,
|
|
102
|
+
fileobj=fp,
|
|
103
|
+
download_semaphore=download_semaphore,
|
|
104
|
+
progress_cb=file_progress_cb,
|
|
105
|
+
)
|
|
90
106
|
else:
|
|
91
107
|
b = 0
|
|
92
108
|
async for chunk in volume.read_file(entry.path):
|
modal/cli/cluster.py
CHANGED
|
@@ -83,7 +83,9 @@ async def shell(
|
|
|
83
83
|
)
|
|
84
84
|
exec_res: api_pb2.ContainerExecResponse = await client.stub.ContainerExec(req)
|
|
85
85
|
if pty:
|
|
86
|
-
await _ContainerProcess(exec_res.exec_id, client).attach()
|
|
86
|
+
await _ContainerProcess(exec_res.exec_id, task_id, client).attach()
|
|
87
87
|
else:
|
|
88
88
|
# TODO: redirect stderr to its own stream?
|
|
89
|
-
await _ContainerProcess(
|
|
89
|
+
await _ContainerProcess(
|
|
90
|
+
exec_res.exec_id, task_id, client, stdout=StreamType.STDOUT, stderr=StreamType.STDOUT
|
|
91
|
+
).wait()
|
modal/cli/config.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
|
+
import json
|
|
3
|
+
|
|
2
4
|
import typer
|
|
3
5
|
|
|
4
6
|
from modal._output import make_console
|
|
@@ -25,7 +27,7 @@ def show(redact: bool = typer.Option(True, help="Redact the `token_secret` value
|
|
|
25
27
|
config_dict["token_secret"] = "***"
|
|
26
28
|
|
|
27
29
|
console = make_console()
|
|
28
|
-
console.
|
|
30
|
+
console.print_json(json.dumps(config_dict))
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
SET_DEFAULT_ENV_HELP = """Set the default Modal environment for the active profile
|
modal/cli/container.py
CHANGED
|
@@ -7,7 +7,6 @@ from rich.text import Text
|
|
|
7
7
|
from modal._object import _get_environment_name
|
|
8
8
|
from modal._pty import get_pty_info
|
|
9
9
|
from modal._utils.async_utils import synchronizer
|
|
10
|
-
from modal._utils.grpc_utils import retry_transient_errors
|
|
11
10
|
from modal._utils.time_utils import timestamp_to_localized_str
|
|
12
11
|
from modal.cli.utils import ENV_OPTION, display_table, is_tty, stream_app_logs
|
|
13
12
|
from modal.client import _Client
|
|
@@ -80,10 +79,12 @@ async def exec(
|
|
|
80
79
|
res: api_pb2.ContainerExecResponse = await client.stub.ContainerExec(req)
|
|
81
80
|
|
|
82
81
|
if pty:
|
|
83
|
-
await _ContainerProcess(res.exec_id, client).attach()
|
|
82
|
+
await _ContainerProcess(res.exec_id, container_id, client).attach()
|
|
84
83
|
else:
|
|
85
84
|
# TODO: redirect stderr to its own stream?
|
|
86
|
-
await _ContainerProcess(
|
|
85
|
+
await _ContainerProcess(
|
|
86
|
+
res.exec_id, container_id, client, stdout=StreamType.STDOUT, stderr=StreamType.STDOUT
|
|
87
|
+
).wait()
|
|
87
88
|
|
|
88
89
|
|
|
89
90
|
@container_cli.command("stop")
|
|
@@ -95,4 +96,4 @@ async def stop(container_id: str = typer.Argument(help="Container ID")):
|
|
|
95
96
|
"""
|
|
96
97
|
client = await _Client.from_env()
|
|
97
98
|
request = api_pb2.ContainerStopRequest(task_id=container_id)
|
|
98
|
-
await
|
|
99
|
+
await client.stub.ContainerStop(request)
|
modal/cli/dict.py
CHANGED
|
@@ -4,6 +4,7 @@ from typing import Optional
|
|
|
4
4
|
import typer
|
|
5
5
|
from typer import Argument, Option, Typer
|
|
6
6
|
|
|
7
|
+
from modal._load_context import LoadContext
|
|
7
8
|
from modal._output import make_console
|
|
8
9
|
from modal._resolver import Resolver
|
|
9
10
|
from modal._utils.async_utils import synchronizer
|
|
@@ -29,8 +30,10 @@ async def create(name: str, *, env: Optional[str] = ENV_OPTION):
|
|
|
29
30
|
"""
|
|
30
31
|
d = _Dict.from_name(name, environment_name=env, create_if_missing=True)
|
|
31
32
|
client = await _Client.from_env()
|
|
32
|
-
resolver = Resolver(
|
|
33
|
-
|
|
33
|
+
resolver = Resolver()
|
|
34
|
+
|
|
35
|
+
load_context = LoadContext(client=client, environment_name=env)
|
|
36
|
+
await resolver.load(d, load_context)
|
|
34
37
|
|
|
35
38
|
|
|
36
39
|
@dict_cli.command(name="list", rich_help_panel="Management")
|
modal/cli/entry_point.py
CHANGED
|
@@ -8,7 +8,7 @@ from rich.rule import Rule
|
|
|
8
8
|
from modal._output import make_console
|
|
9
9
|
from modal._utils.async_utils import synchronizer
|
|
10
10
|
|
|
11
|
-
from . import run
|
|
11
|
+
from . import run, shell as shell_module
|
|
12
12
|
from .app import app_cli
|
|
13
13
|
from .cluster import cluster_cli
|
|
14
14
|
from .config import config_cli
|
|
@@ -36,6 +36,7 @@ entrypoint_cli_typer = typer.Typer(
|
|
|
36
36
|
no_args_is_help=False,
|
|
37
37
|
add_completion=False,
|
|
38
38
|
rich_markup_mode="markdown",
|
|
39
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
39
40
|
help="""
|
|
40
41
|
Modal is the fastest way to run code in the cloud.
|
|
41
42
|
|
|
@@ -86,6 +87,29 @@ def check_path():
|
|
|
86
87
|
async def setup(profile: Optional[str] = None):
|
|
87
88
|
check_path()
|
|
88
89
|
|
|
90
|
+
art = """
|
|
91
|
+
############# #############
|
|
92
|
+
#### ## #### ##
|
|
93
|
+
## ## ## ## ## ##
|
|
94
|
+
## ## ## ## ## ##
|
|
95
|
+
## ## #### ## ##
|
|
96
|
+
## ############# ## ##
|
|
97
|
+
## ## #### ## ##
|
|
98
|
+
## ## ## ## ## ##
|
|
99
|
+
## ## ## ## ## ##
|
|
100
|
+
## ## ## ## ## ##
|
|
101
|
+
## ## ## ## ## ##
|
|
102
|
+
## ## ## ## #############
|
|
103
|
+
## ## ## ## ## ##
|
|
104
|
+
## ## ## ## ## ##
|
|
105
|
+
## ## ## ## ## ##
|
|
106
|
+
#### ## #### ##
|
|
107
|
+
############# #############
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
console = make_console()
|
|
111
|
+
console.print(art, style="green")
|
|
112
|
+
|
|
89
113
|
# Fetch a new token (same as `modal token new` but redirect to /home once finishes)
|
|
90
114
|
await _new_token(profile=profile, next_url="/home")
|
|
91
115
|
|
|
@@ -93,7 +117,7 @@ async def setup(profile: Optional[str] = None):
|
|
|
93
117
|
# Commands
|
|
94
118
|
entrypoint_cli_typer.command("deploy", no_args_is_help=True)(run.deploy)
|
|
95
119
|
entrypoint_cli_typer.command("serve", no_args_is_help=True)(run.serve)
|
|
96
|
-
entrypoint_cli_typer.command("shell")(
|
|
120
|
+
entrypoint_cli_typer.command("shell")(shell_module.shell)
|
|
97
121
|
entrypoint_cli_typer.add_typer(launch_cli)
|
|
98
122
|
|
|
99
123
|
# Deployments
|
modal/cli/environment.py
CHANGED
|
@@ -3,14 +3,12 @@ from typing import Annotated, Optional, Union
|
|
|
3
3
|
|
|
4
4
|
import typer
|
|
5
5
|
from click import UsageError
|
|
6
|
-
from grpclib import GRPCError, Status
|
|
7
6
|
from rich.text import Text
|
|
8
7
|
|
|
9
8
|
from modal import environments
|
|
10
9
|
from modal._utils.name_utils import check_environment_name
|
|
11
10
|
from modal.cli.utils import YES_OPTION, display_table
|
|
12
11
|
from modal.config import config
|
|
13
|
-
from modal.exception import InvalidError
|
|
14
12
|
|
|
15
13
|
ENVIRONMENT_HELP_TEXT = """Create and interact with Environments
|
|
16
14
|
|
|
@@ -61,13 +59,7 @@ ENVIRONMENT_CREATE_HELP = """Create a new environment in the current workspace""
|
|
|
61
59
|
@environment_cli.command(name="create", help=ENVIRONMENT_CREATE_HELP)
|
|
62
60
|
def create(name: Annotated[str, typer.Argument(help="Name of the new environment. Must be unique. Case sensitive")]):
|
|
63
61
|
check_environment_name(name)
|
|
64
|
-
|
|
65
|
-
try:
|
|
66
|
-
environments.create_environment(name)
|
|
67
|
-
except GRPCError as exc:
|
|
68
|
-
if exc.status == Status.INVALID_ARGUMENT:
|
|
69
|
-
raise InvalidError(exc.message)
|
|
70
|
-
raise
|
|
62
|
+
environments.create_environment(name)
|
|
71
63
|
typer.echo(f"Environment created: {name}")
|
|
72
64
|
|
|
73
65
|
|
|
@@ -114,11 +106,5 @@ def update(
|
|
|
114
106
|
if set_name:
|
|
115
107
|
check_environment_name(set_name)
|
|
116
108
|
|
|
117
|
-
|
|
118
|
-
environments.update_environment(current_name, new_name=set_name, new_web_suffix=set_web_suffix)
|
|
119
|
-
except GRPCError as exc:
|
|
120
|
-
if exc.status == Status.INVALID_ARGUMENT:
|
|
121
|
-
raise InvalidError(exc.message)
|
|
122
|
-
raise
|
|
123
|
-
|
|
109
|
+
environments.update_environment(current_name, new_name=set_name, new_web_suffix=set_web_suffix)
|
|
124
110
|
typer.echo("Environment updated")
|
modal/cli/launch.py
CHANGED
|
@@ -3,8 +3,6 @@ import asyncio
|
|
|
3
3
|
import inspect
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
-
import subprocess
|
|
7
|
-
import tempfile
|
|
8
6
|
from pathlib import Path
|
|
9
7
|
from typing import Any, Optional
|
|
10
8
|
|
|
@@ -23,8 +21,7 @@ launch_cli = Typer(
|
|
|
23
21
|
no_args_is_help=True,
|
|
24
22
|
rich_markup_mode="markdown",
|
|
25
23
|
help="""
|
|
26
|
-
Open a serverless app instance on Modal.
|
|
27
|
-
>⚠️ `modal launch` is **experimental** and may change in the future.
|
|
24
|
+
[Experimental] Open a serverless app instance on Modal.
|
|
28
25
|
""",
|
|
29
26
|
)
|
|
30
27
|
|
|
@@ -121,75 +118,3 @@ def vscode(
|
|
|
121
118
|
"volume": volume,
|
|
122
119
|
}
|
|
123
120
|
_launch_program("vscode", "vscode.py", detach, args)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
@launch_cli.command(name="machine", help="Start an instance on Modal, with direct SSH access.", hidden=True)
|
|
127
|
-
def machine(
|
|
128
|
-
name: str, # Name of the machine App.
|
|
129
|
-
cpu: int = 8, # Reservation of CPU cores (can burst above this value).
|
|
130
|
-
memory: int = 32768, # Reservation of memory in MiB (can burst above this value).
|
|
131
|
-
gpu: Optional[str] = None, # GPU type and count, e.g. "t4" or "h100:2".
|
|
132
|
-
image: Optional[str] = None, # Image tag to use from registry. Defaults to the notebook base image.
|
|
133
|
-
timeout: int = 3600 * 24, # Timeout in seconds for the instance.
|
|
134
|
-
volume: str = "machine-vol", # Attach a persisted `modal.Volume` at /workspace (created if missing).
|
|
135
|
-
):
|
|
136
|
-
tempdir = Path(tempfile.gettempdir())
|
|
137
|
-
key_path = tempdir / "modal-machine-keyfile.pem"
|
|
138
|
-
# Generate a new SSH key pair for this machine instance.
|
|
139
|
-
if not key_path.exists():
|
|
140
|
-
subprocess.run(
|
|
141
|
-
["ssh-keygen", "-t", "ed25519", "-f", str(key_path), "-N", ""],
|
|
142
|
-
check=True,
|
|
143
|
-
stdout=subprocess.DEVNULL,
|
|
144
|
-
)
|
|
145
|
-
# Add the key with expiry 1d to ssh agent.
|
|
146
|
-
subprocess.run(
|
|
147
|
-
["ssh-add", "-t", "1d", str(key_path)],
|
|
148
|
-
check=True,
|
|
149
|
-
stdout=subprocess.DEVNULL,
|
|
150
|
-
stderr=subprocess.DEVNULL,
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
os.environ["SSH_PUBLIC_KEY"] = Path(str(key_path) + ".pub").read_text()
|
|
154
|
-
os.environ["MODAL_LOGS_TIMEOUT"] = "0" # hack to work with --detach
|
|
155
|
-
|
|
156
|
-
args = {
|
|
157
|
-
"cpu": cpu,
|
|
158
|
-
"memory": memory,
|
|
159
|
-
"gpu": gpu,
|
|
160
|
-
"image": image,
|
|
161
|
-
"timeout": timeout,
|
|
162
|
-
"volume": volume,
|
|
163
|
-
}
|
|
164
|
-
_launch_program(
|
|
165
|
-
"machine",
|
|
166
|
-
"launch_instance_ssh.py",
|
|
167
|
-
True,
|
|
168
|
-
args,
|
|
169
|
-
description=name,
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
@launch_cli.command(name="marimo", help="Start a remote Marimo notebook on Modal.", hidden=True)
|
|
174
|
-
def marimo(
|
|
175
|
-
cpu: int = 8,
|
|
176
|
-
memory: int = 32768,
|
|
177
|
-
gpu: Optional[str] = None,
|
|
178
|
-
image: str = "debian:12",
|
|
179
|
-
timeout: int = 3600,
|
|
180
|
-
add_python: Optional[str] = "3.12",
|
|
181
|
-
mount: Optional[str] = None, # Create a `modal.Mount` from a local directory.
|
|
182
|
-
volume: Optional[str] = None, # Attach a persisted `modal.Volume` by name (creating if missing).
|
|
183
|
-
detach: bool = False, # Run the app in "detached" mode to persist after local client disconnects
|
|
184
|
-
):
|
|
185
|
-
args = {
|
|
186
|
-
"cpu": cpu,
|
|
187
|
-
"memory": memory,
|
|
188
|
-
"gpu": gpu,
|
|
189
|
-
"timeout": timeout,
|
|
190
|
-
"image": image,
|
|
191
|
-
"add_python": add_python,
|
|
192
|
-
"mount": mount,
|
|
193
|
-
"volume": volume,
|
|
194
|
-
}
|
|
195
|
-
_launch_program("marimo", "run_marimo.py", detach, args)
|