modal 0.62.115__py3-none-any.whl → 0.72.13__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.
- modal/__init__.py +13 -9
- modal/__main__.py +41 -3
- modal/_clustered_functions.py +80 -0
- modal/_clustered_functions.pyi +22 -0
- modal/_container_entrypoint.py +402 -398
- modal/_ipython.py +3 -13
- modal/_location.py +17 -10
- modal/_output.py +243 -99
- modal/_pty.py +2 -2
- modal/_resolver.py +55 -60
- modal/_resources.py +26 -7
- modal/_runtime/__init__.py +1 -0
- modal/_runtime/asgi.py +519 -0
- modal/_runtime/container_io_manager.py +1025 -0
- modal/{execution_context.py → _runtime/execution_context.py} +11 -2
- modal/_runtime/telemetry.py +169 -0
- modal/_runtime/user_code_imports.py +356 -0
- modal/_serialization.py +123 -6
- modal/_traceback.py +47 -187
- modal/_tunnel.py +50 -14
- modal/_tunnel.pyi +19 -36
- modal/_utils/app_utils.py +3 -17
- modal/_utils/async_utils.py +386 -104
- modal/_utils/blob_utils.py +157 -186
- modal/_utils/bytes_io_segment_payload.py +97 -0
- modal/_utils/deprecation.py +89 -0
- modal/_utils/docker_utils.py +98 -0
- modal/_utils/function_utils.py +299 -98
- modal/_utils/grpc_testing.py +47 -34
- modal/_utils/grpc_utils.py +54 -21
- modal/_utils/hash_utils.py +51 -10
- modal/_utils/http_utils.py +39 -9
- modal/_utils/logger.py +2 -1
- modal/_utils/mount_utils.py +34 -16
- modal/_utils/name_utils.py +58 -0
- modal/_utils/package_utils.py +14 -1
- modal/_utils/pattern_utils.py +205 -0
- modal/_utils/rand_pb_testing.py +3 -3
- modal/_utils/shell_utils.py +15 -49
- modal/_vendor/a2wsgi_wsgi.py +62 -72
- modal/_vendor/cloudpickle.py +1 -1
- modal/_watcher.py +12 -10
- modal/app.py +561 -323
- modal/app.pyi +474 -262
- modal/call_graph.py +7 -6
- modal/cli/_download.py +22 -6
- modal/cli/_traceback.py +200 -0
- modal/cli/app.py +203 -42
- modal/cli/config.py +12 -5
- modal/cli/container.py +61 -13
- modal/cli/dict.py +128 -0
- modal/cli/entry_point.py +26 -13
- modal/cli/environment.py +40 -9
- modal/cli/import_refs.py +21 -48
- modal/cli/launch.py +28 -14
- modal/cli/network_file_system.py +57 -21
- modal/cli/profile.py +1 -1
- modal/cli/programs/run_jupyter.py +34 -9
- modal/cli/programs/vscode.py +58 -8
- modal/cli/queues.py +131 -0
- modal/cli/run.py +199 -96
- modal/cli/secret.py +5 -4
- modal/cli/token.py +7 -2
- modal/cli/utils.py +74 -8
- modal/cli/volume.py +97 -56
- modal/client.py +248 -144
- modal/client.pyi +156 -124
- modal/cloud_bucket_mount.py +43 -30
- modal/cloud_bucket_mount.pyi +32 -25
- modal/cls.py +528 -141
- modal/cls.pyi +189 -145
- modal/config.py +32 -15
- modal/container_process.py +177 -0
- modal/container_process.pyi +82 -0
- modal/dict.py +50 -54
- modal/dict.pyi +120 -164
- modal/environments.py +106 -5
- modal/environments.pyi +77 -25
- modal/exception.py +30 -43
- modal/experimental.py +62 -2
- modal/file_io.py +537 -0
- modal/file_io.pyi +235 -0
- modal/file_pattern_matcher.py +196 -0
- modal/functions.py +846 -428
- modal/functions.pyi +446 -387
- modal/gpu.py +57 -44
- modal/image.py +943 -417
- modal/image.pyi +584 -245
- modal/io_streams.py +434 -0
- modal/io_streams.pyi +122 -0
- modal/mount.py +223 -90
- modal/mount.pyi +241 -243
- modal/network_file_system.py +85 -86
- modal/network_file_system.pyi +151 -110
- modal/object.py +66 -36
- modal/object.pyi +166 -143
- modal/output.py +63 -0
- modal/parallel_map.py +73 -47
- modal/parallel_map.pyi +51 -63
- modal/partial_function.py +272 -107
- modal/partial_function.pyi +219 -120
- modal/proxy.py +15 -12
- modal/proxy.pyi +3 -8
- modal/queue.py +96 -72
- modal/queue.pyi +210 -135
- modal/requirements/2024.04.txt +2 -1
- modal/requirements/2024.10.txt +16 -0
- modal/requirements/README.md +21 -0
- modal/requirements/base-images.json +22 -0
- modal/retries.py +45 -4
- modal/runner.py +325 -203
- modal/runner.pyi +124 -110
- modal/running_app.py +27 -4
- modal/sandbox.py +509 -231
- modal/sandbox.pyi +396 -169
- modal/schedule.py +2 -2
- modal/scheduler_placement.py +20 -3
- modal/secret.py +41 -25
- modal/secret.pyi +62 -42
- modal/serving.py +39 -49
- modal/serving.pyi +37 -43
- modal/stream_type.py +15 -0
- modal/token_flow.py +5 -3
- modal/token_flow.pyi +37 -32
- modal/volume.py +123 -137
- modal/volume.pyi +228 -221
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/METADATA +5 -5
- modal-0.72.13.dist-info/RECORD +174 -0
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/top_level.txt +0 -1
- modal_docs/gen_reference_docs.py +3 -1
- modal_docs/mdmd/mdmd.py +0 -1
- modal_docs/mdmd/signatures.py +1 -2
- modal_global_objects/images/base_images.py +28 -0
- modal_global_objects/mounts/python_standalone.py +2 -2
- modal_proto/__init__.py +1 -1
- modal_proto/api.proto +1231 -531
- modal_proto/api_grpc.py +750 -430
- modal_proto/api_pb2.py +2102 -1176
- modal_proto/api_pb2.pyi +8859 -0
- modal_proto/api_pb2_grpc.py +1329 -675
- modal_proto/api_pb2_grpc.pyi +1416 -0
- modal_proto/modal_api_grpc.py +149 -0
- modal_proto/modal_options_grpc.py +3 -0
- modal_proto/options_pb2.pyi +20 -0
- modal_proto/options_pb2_grpc.pyi +7 -0
- modal_proto/py.typed +0 -0
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +2 -2
- modal/_asgi.py +0 -370
- modal/_container_exec.py +0 -128
- modal/_container_io_manager.py +0 -646
- modal/_container_io_manager.pyi +0 -412
- modal/_sandbox_shell.py +0 -49
- modal/app_utils.py +0 -20
- modal/app_utils.pyi +0 -17
- modal/execution_context.pyi +0 -37
- modal/shared_volume.py +0 -23
- modal/shared_volume.pyi +0 -24
- modal-0.62.115.dist-info/RECORD +0 -207
- modal_global_objects/images/conda.py +0 -15
- modal_global_objects/images/debian_slim.py +0 -15
- modal_global_objects/images/micromamba.py +0 -15
- test/__init__.py +0 -1
- test/aio_test.py +0 -12
- test/async_utils_test.py +0 -279
- test/blob_test.py +0 -67
- test/cli_imports_test.py +0 -149
- test/cli_test.py +0 -674
- test/client_test.py +0 -203
- test/cloud_bucket_mount_test.py +0 -22
- test/cls_test.py +0 -636
- test/config_test.py +0 -149
- test/conftest.py +0 -1485
- test/container_app_test.py +0 -50
- test/container_test.py +0 -1405
- test/cpu_test.py +0 -23
- test/decorator_test.py +0 -85
- test/deprecation_test.py +0 -34
- test/dict_test.py +0 -51
- test/e2e_test.py +0 -68
- test/error_test.py +0 -7
- test/function_serialization_test.py +0 -32
- test/function_test.py +0 -791
- test/function_utils_test.py +0 -101
- test/gpu_test.py +0 -159
- test/grpc_utils_test.py +0 -82
- test/helpers.py +0 -47
- test/image_test.py +0 -814
- test/live_reload_test.py +0 -80
- test/lookup_test.py +0 -70
- test/mdmd_test.py +0 -329
- test/mount_test.py +0 -162
- test/mounted_files_test.py +0 -327
- test/network_file_system_test.py +0 -188
- test/notebook_test.py +0 -66
- test/object_test.py +0 -41
- test/package_utils_test.py +0 -25
- test/queue_test.py +0 -115
- test/resolver_test.py +0 -59
- test/retries_test.py +0 -67
- test/runner_test.py +0 -85
- test/sandbox_test.py +0 -191
- test/schedule_test.py +0 -15
- test/scheduler_placement_test.py +0 -57
- test/secret_test.py +0 -89
- test/serialization_test.py +0 -50
- test/stub_composition_test.py +0 -10
- test/stub_test.py +0 -361
- test/test_asgi_wrapper.py +0 -234
- test/token_flow_test.py +0 -18
- test/traceback_test.py +0 -135
- test/tunnel_test.py +0 -29
- test/utils_test.py +0 -88
- test/version_test.py +0 -14
- test/volume_test.py +0 -397
- test/watcher_test.py +0 -58
- test/webhook_test.py +0 -145
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/LICENSE +0 -0
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/WHEEL +0 -0
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/entry_points.txt +0 -0
modal/cls.pyi
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
import collections.abc
|
1
2
|
import google.protobuf.message
|
2
|
-
import
|
3
|
+
import inspect
|
3
4
|
import modal.app
|
4
5
|
import modal.client
|
5
6
|
import modal.functions
|
@@ -16,167 +17,210 @@ import typing_extensions
|
|
16
17
|
|
17
18
|
T = typing.TypeVar("T")
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
_local_obj_constr: typing.Union[typing.Callable[[], typing.Any], None]
|
25
|
-
|
26
|
-
def __init__(self, user_cls: type, output_mgr: typing.Union[modal._output.OutputManager, None], base_functions: typing.Dict[str, modal.functions._Function], from_other_workspace: bool, options: typing.Union[modal_proto.api_pb2.FunctionOptions, None], args, kwargs):
|
27
|
-
...
|
28
|
-
|
29
|
-
def get_obj(self):
|
30
|
-
...
|
31
|
-
|
32
|
-
def get_local_obj(self):
|
33
|
-
...
|
34
|
-
|
35
|
-
def enter(self):
|
36
|
-
...
|
20
|
+
def _use_annotation_parameters(user_cls: type) -> bool: ...
|
21
|
+
def _get_class_constructor_signature(user_cls: type) -> inspect.Signature: ...
|
22
|
+
def _bind_instance_method(
|
23
|
+
service_function: modal.functions._Function, class_bound_method: modal.functions._Function
|
24
|
+
): ...
|
37
25
|
|
26
|
+
class _Obj:
|
27
|
+
_cls: _Cls
|
28
|
+
_functions: dict[str, modal.functions._Function]
|
29
|
+
_has_entered: bool
|
30
|
+
_user_cls_instance: typing.Optional[typing.Any]
|
31
|
+
_args: tuple[typing.Any, ...]
|
32
|
+
_kwargs: dict[str, typing.Any]
|
33
|
+
_instance_service_function: typing.Optional[modal.functions._Function]
|
34
|
+
|
35
|
+
def _uses_common_service_function(self): ...
|
36
|
+
def __init__(
|
37
|
+
self,
|
38
|
+
cls: _Cls,
|
39
|
+
user_cls: typing.Optional[type],
|
40
|
+
options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
|
41
|
+
args,
|
42
|
+
kwargs,
|
43
|
+
): ...
|
44
|
+
def _cached_service_function(self) -> modal.functions._Function: ...
|
45
|
+
def _get_parameter_values(self) -> dict[str, typing.Any]: ...
|
46
|
+
def _new_user_cls_instance(self): ...
|
47
|
+
async def keep_warm(self, warm_pool_size: int) -> None: ...
|
48
|
+
def _cached_user_cls_instance(self): ...
|
49
|
+
def _enter(self): ...
|
38
50
|
@property
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
def
|
44
|
-
...
|
45
|
-
|
46
|
-
async def aenter(self):
|
47
|
-
...
|
48
|
-
|
49
|
-
def __getattr__(self, k):
|
50
|
-
...
|
51
|
-
|
51
|
+
def _entered(self) -> bool: ...
|
52
|
+
@_entered.setter
|
53
|
+
def _entered(self, val: bool): ...
|
54
|
+
async def _aenter(self): ...
|
55
|
+
def __getattr__(self, k): ...
|
52
56
|
|
53
57
|
class Obj:
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
58
|
+
_cls: Cls
|
59
|
+
_functions: dict[str, modal.functions.Function]
|
60
|
+
_has_entered: bool
|
61
|
+
_user_cls_instance: typing.Optional[typing.Any]
|
62
|
+
_args: tuple[typing.Any, ...]
|
63
|
+
_kwargs: dict[str, typing.Any]
|
64
|
+
_instance_service_function: typing.Optional[modal.functions.Function]
|
65
|
+
|
66
|
+
def __init__(
|
67
|
+
self,
|
68
|
+
cls: Cls,
|
69
|
+
user_cls: typing.Optional[type],
|
70
|
+
options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
|
71
|
+
args,
|
72
|
+
kwargs,
|
73
|
+
): ...
|
74
|
+
def _uses_common_service_function(self): ...
|
75
|
+
def _cached_service_function(self) -> modal.functions.Function: ...
|
76
|
+
def _get_parameter_values(self) -> dict[str, typing.Any]: ...
|
77
|
+
def _new_user_cls_instance(self): ...
|
78
|
+
|
79
|
+
class __keep_warm_spec(typing_extensions.Protocol):
|
80
|
+
def __call__(self, warm_pool_size: int) -> None: ...
|
81
|
+
async def aio(self, warm_pool_size: int) -> None: ...
|
82
|
+
|
83
|
+
keep_warm: __keep_warm_spec
|
84
|
+
|
85
|
+
def _cached_user_cls_instance(self): ...
|
86
|
+
def _enter(self): ...
|
72
87
|
@property
|
73
|
-
def
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
def
|
78
|
-
...
|
79
|
-
|
80
|
-
async def aenter(self):
|
81
|
-
...
|
82
|
-
|
83
|
-
def __getattr__(self, k):
|
84
|
-
...
|
85
|
-
|
88
|
+
def _entered(self) -> bool: ...
|
89
|
+
@_entered.setter
|
90
|
+
def _entered(self, val: bool): ...
|
91
|
+
async def _aenter(self): ...
|
92
|
+
def __getattr__(self, k): ...
|
86
93
|
|
87
94
|
class _Cls(modal.object._Object):
|
88
|
-
_user_cls: typing.
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
_app: typing.
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
def
|
99
|
-
|
100
|
-
|
101
|
-
def _set_output_mgr(self, output_mgr: modal._output.OutputManager):
|
102
|
-
...
|
103
|
-
|
104
|
-
def _hydrate_metadata(self, metadata: google.protobuf.message.Message):
|
105
|
-
...
|
106
|
-
|
107
|
-
def _get_metadata(self) -> modal_proto.api_pb2.ClassHandleMetadata:
|
108
|
-
...
|
109
|
-
|
95
|
+
_user_cls: typing.Optional[type]
|
96
|
+
_class_service_function: typing.Optional[modal.functions._Function]
|
97
|
+
_method_functions: typing.Optional[dict[str, modal.functions._Function]]
|
98
|
+
_options: typing.Optional[modal_proto.api_pb2.FunctionOptions]
|
99
|
+
_callables: dict[str, typing.Callable[..., typing.Any]]
|
100
|
+
_app: typing.Optional[modal.app._App]
|
101
|
+
_name: typing.Optional[str]
|
102
|
+
|
103
|
+
def _initialize_from_empty(self): ...
|
104
|
+
def _initialize_from_other(self, other: _Cls): ...
|
105
|
+
def _get_partial_functions(self) -> dict[str, modal.partial_function._PartialFunction]: ...
|
106
|
+
def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
|
110
107
|
@staticmethod
|
111
|
-
def
|
112
|
-
|
113
|
-
|
108
|
+
def validate_construction_mechanism(user_cls): ...
|
109
|
+
@staticmethod
|
110
|
+
def from_local(user_cls, app: modal.app._App, class_service_function: modal.functions._Function) -> _Cls: ...
|
111
|
+
def _uses_common_service_function(self): ...
|
114
112
|
@classmethod
|
115
|
-
def from_name(
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
113
|
+
def from_name(
|
114
|
+
cls: type[_Cls],
|
115
|
+
app_name: str,
|
116
|
+
name: str,
|
117
|
+
namespace=1,
|
118
|
+
environment_name: typing.Optional[str] = None,
|
119
|
+
workspace: typing.Optional[str] = None,
|
120
|
+
) -> _Cls: ...
|
121
|
+
def with_options(
|
122
|
+
self: _Cls,
|
123
|
+
cpu: typing.Union[float, tuple[float, float], None] = None,
|
124
|
+
memory: typing.Union[int, tuple[int, int], None] = None,
|
125
|
+
gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
|
126
|
+
secrets: collections.abc.Collection[modal.secret._Secret] = (),
|
127
|
+
volumes: dict[typing.Union[str, os.PathLike], modal.volume._Volume] = {},
|
128
|
+
retries: typing.Union[int, modal.retries.Retries, None] = None,
|
129
|
+
timeout: typing.Optional[int] = None,
|
130
|
+
concurrency_limit: typing.Optional[int] = None,
|
131
|
+
allow_concurrent_inputs: typing.Optional[int] = None,
|
132
|
+
container_idle_timeout: typing.Optional[int] = None,
|
133
|
+
) -> _Cls: ...
|
121
134
|
@staticmethod
|
122
|
-
async def lookup(
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
135
|
+
async def lookup(
|
136
|
+
app_name: str,
|
137
|
+
name: str,
|
138
|
+
namespace=1,
|
139
|
+
client: typing.Optional[modal.client._Client] = None,
|
140
|
+
environment_name: typing.Optional[str] = None,
|
141
|
+
workspace: typing.Optional[str] = None,
|
142
|
+
) -> _Cls: ...
|
143
|
+
def __call__(self, *args, **kwargs) -> _Obj: ...
|
144
|
+
def __getattr__(self, k): ...
|
131
145
|
|
132
146
|
class Cls(modal.object.Object):
|
133
|
-
_user_cls: typing.
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
_app: typing.
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
def
|
144
|
-
|
145
|
-
|
146
|
-
def _initialize_from_other(self, other: Cls):
|
147
|
-
...
|
148
|
-
|
149
|
-
def _set_output_mgr(self, output_mgr: modal._output.OutputManager):
|
150
|
-
...
|
151
|
-
|
152
|
-
def _hydrate_metadata(self, metadata: google.protobuf.message.Message):
|
153
|
-
...
|
154
|
-
|
155
|
-
def _get_metadata(self) -> modal_proto.api_pb2.ClassHandleMetadata:
|
156
|
-
...
|
157
|
-
|
147
|
+
_user_cls: typing.Optional[type]
|
148
|
+
_class_service_function: typing.Optional[modal.functions.Function]
|
149
|
+
_method_functions: typing.Optional[dict[str, modal.functions.Function]]
|
150
|
+
_options: typing.Optional[modal_proto.api_pb2.FunctionOptions]
|
151
|
+
_callables: dict[str, typing.Callable[..., typing.Any]]
|
152
|
+
_app: typing.Optional[modal.app.App]
|
153
|
+
_name: typing.Optional[str]
|
154
|
+
|
155
|
+
def __init__(self, *args, **kwargs): ...
|
156
|
+
def _initialize_from_empty(self): ...
|
157
|
+
def _initialize_from_other(self, other: Cls): ...
|
158
|
+
def _get_partial_functions(self) -> dict[str, modal.partial_function.PartialFunction]: ...
|
159
|
+
def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
|
158
160
|
@staticmethod
|
159
|
-
def
|
160
|
-
|
161
|
-
|
161
|
+
def validate_construction_mechanism(user_cls): ...
|
162
|
+
@staticmethod
|
163
|
+
def from_local(user_cls, app: modal.app.App, class_service_function: modal.functions.Function) -> Cls: ...
|
164
|
+
def _uses_common_service_function(self): ...
|
162
165
|
@classmethod
|
163
|
-
def from_name(
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
166
|
+
def from_name(
|
167
|
+
cls: type[Cls],
|
168
|
+
app_name: str,
|
169
|
+
name: str,
|
170
|
+
namespace=1,
|
171
|
+
environment_name: typing.Optional[str] = None,
|
172
|
+
workspace: typing.Optional[str] = None,
|
173
|
+
) -> Cls: ...
|
174
|
+
def with_options(
|
175
|
+
self: Cls,
|
176
|
+
cpu: typing.Union[float, tuple[float, float], None] = None,
|
177
|
+
memory: typing.Union[int, tuple[int, int], None] = None,
|
178
|
+
gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
|
179
|
+
secrets: collections.abc.Collection[modal.secret.Secret] = (),
|
180
|
+
volumes: dict[typing.Union[str, os.PathLike], modal.volume.Volume] = {},
|
181
|
+
retries: typing.Union[int, modal.retries.Retries, None] = None,
|
182
|
+
timeout: typing.Optional[int] = None,
|
183
|
+
concurrency_limit: typing.Optional[int] = None,
|
184
|
+
allow_concurrent_inputs: typing.Optional[int] = None,
|
185
|
+
container_idle_timeout: typing.Optional[int] = None,
|
186
|
+
) -> Cls: ...
|
168
187
|
|
169
188
|
class __lookup_spec(typing_extensions.Protocol):
|
170
|
-
def __call__(
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
189
|
+
def __call__(
|
190
|
+
self,
|
191
|
+
app_name: str,
|
192
|
+
name: str,
|
193
|
+
namespace=1,
|
194
|
+
client: typing.Optional[modal.client.Client] = None,
|
195
|
+
environment_name: typing.Optional[str] = None,
|
196
|
+
workspace: typing.Optional[str] = None,
|
197
|
+
) -> Cls: ...
|
198
|
+
async def aio(
|
199
|
+
self,
|
200
|
+
app_name: str,
|
201
|
+
name: str,
|
202
|
+
namespace=1,
|
203
|
+
client: typing.Optional[modal.client.Client] = None,
|
204
|
+
environment_name: typing.Optional[str] = None,
|
205
|
+
workspace: typing.Optional[str] = None,
|
206
|
+
) -> Cls: ...
|
175
207
|
|
176
208
|
lookup: __lookup_spec
|
177
209
|
|
178
|
-
def __call__(self, *args, **kwargs) -> Obj:
|
179
|
-
|
210
|
+
def __call__(self, *args, **kwargs) -> Obj: ...
|
211
|
+
def __getattr__(self, k): ...
|
212
|
+
|
213
|
+
class _NO_DEFAULT:
|
214
|
+
def __repr__(self): ...
|
215
|
+
|
216
|
+
_no_default: _NO_DEFAULT
|
217
|
+
|
218
|
+
class _Parameter:
|
219
|
+
default: typing.Any
|
220
|
+
init: bool
|
221
|
+
|
222
|
+
def __init__(self, default: typing.Any, init: bool): ...
|
223
|
+
def __get__(self, obj, obj_type=None) -> typing.Any: ...
|
180
224
|
|
181
|
-
|
182
|
-
|
225
|
+
def is_parameter(p: typing.Any) -> bool: ...
|
226
|
+
def parameter(*, default: typing.Any = modal.cls._NO_DEFAULT(), init: bool = True) -> typing.Any: ...
|
modal/config.py
CHANGED
@@ -29,7 +29,7 @@ Setting tokens using the CLI
|
|
29
29
|
|
30
30
|
You can set a token by running the command::
|
31
31
|
|
32
|
-
```
|
32
|
+
```
|
33
33
|
modal token set \
|
34
34
|
--token-id <token id> \
|
35
35
|
--token-secret <token secret>
|
@@ -55,6 +55,11 @@ Other possible configuration options are:
|
|
55
55
|
Defaults to True.
|
56
56
|
By default, Modal automatically mounts modules imported in the current scope, that
|
57
57
|
are deemed to be "local". This can be turned off by setting this to False.
|
58
|
+
* `force_build` (in the .toml file) / `MODAL_FORCE_BUILD` (as an env var).
|
59
|
+
Defaults to False.
|
60
|
+
When set, ignores the Image cache and builds all Image layers. Note that this
|
61
|
+
will break the cache for all images based on the rebuilt layers, so other images
|
62
|
+
may rebuild on subsequent runs / deploys even if the config is reverted.
|
58
63
|
* `traceback` (in the .toml file) / `MODAL_TRACEBACK` (as an env var).
|
59
64
|
Defaults to False. Enables printing full tracebacks on unexpected CLI
|
60
65
|
errors, which can be useful for debugging client issues.
|
@@ -75,14 +80,15 @@ import os
|
|
75
80
|
import typing
|
76
81
|
import warnings
|
77
82
|
from textwrap import dedent
|
78
|
-
from typing import Any,
|
83
|
+
from typing import Any, Optional
|
79
84
|
|
80
85
|
from google.protobuf.empty_pb2 import Empty
|
81
86
|
|
82
87
|
from modal_proto import api_pb2
|
83
88
|
|
89
|
+
from ._utils.deprecation import deprecation_error
|
84
90
|
from ._utils.logger import configure_logger
|
85
|
-
from .exception import InvalidError
|
91
|
+
from .exception import InvalidError
|
86
92
|
|
87
93
|
# Locate config file and read it
|
88
94
|
|
@@ -98,14 +104,25 @@ def _is_remote() -> bool:
|
|
98
104
|
|
99
105
|
|
100
106
|
def _read_user_config():
|
107
|
+
config_data = {}
|
101
108
|
if not _is_remote() and os.path.exists(user_config_path):
|
102
109
|
# Defer toml import so we don't need it in the container runtime environment
|
103
110
|
import toml
|
104
111
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
112
|
+
try:
|
113
|
+
with open(user_config_path) as f:
|
114
|
+
config_data = toml.load(f)
|
115
|
+
except Exception as exc:
|
116
|
+
config_problem = str(exc)
|
117
|
+
else:
|
118
|
+
if not all(isinstance(e, dict) for e in config_data.values()):
|
119
|
+
config_problem = "TOML file must contain table sections for each profile."
|
120
|
+
else:
|
121
|
+
config_problem = ""
|
122
|
+
if config_problem:
|
123
|
+
message = f"\nError when reading the modal configuration from `{user_config_path}`.\n\n{config_problem}"
|
124
|
+
raise InvalidError(message)
|
125
|
+
return config_data
|
109
126
|
|
110
127
|
|
111
128
|
_user_config = _read_user_config()
|
@@ -161,11 +178,9 @@ def _check_config() -> None:
|
|
161
178
|
Support for using an implicit 'default' profile is deprecated.
|
162
179
|
Please use `modal profile activate` to activate one of your profiles.
|
163
180
|
(Use `modal profile list` to see the options.)
|
164
|
-
|
165
|
-
This will become an error in a future update.
|
166
181
|
"""
|
167
182
|
)
|
168
|
-
|
183
|
+
deprecation_error((2024, 2, 6), message)
|
169
184
|
|
170
185
|
|
171
186
|
_profile = os.environ.get("MODAL_PROFILE") or _config_active_profile()
|
@@ -189,16 +204,15 @@ _SETTINGS = {
|
|
189
204
|
"token_id": _Setting(),
|
190
205
|
"token_secret": _Setting(),
|
191
206
|
"task_id": _Setting(),
|
192
|
-
"task_secret": _Setting(),
|
193
207
|
"serve_timeout": _Setting(transform=float),
|
194
208
|
"sync_entrypoint": _Setting(),
|
195
209
|
"logs_timeout": _Setting(10, float),
|
196
210
|
"image_id": _Setting(),
|
197
211
|
"automount": _Setting(True, transform=_to_boolean),
|
198
|
-
"profiling_enabled": _Setting(False, transform=_to_boolean),
|
199
212
|
"heartbeat_interval": _Setting(15, float),
|
200
213
|
"function_runtime": _Setting(),
|
201
214
|
"function_runtime_debug": _Setting(False, transform=_to_boolean), # For internal debugging use.
|
215
|
+
"runtime_perf_record": _Setting(False, transform=_to_boolean), # For internal debugging use.
|
202
216
|
"environment": _Setting(),
|
203
217
|
"default_cloud": _Setting(None, transform=lambda x: x if x else None),
|
204
218
|
"worker_id": _Setting(), # For internal debugging use.
|
@@ -206,6 +220,9 @@ _SETTINGS = {
|
|
206
220
|
"force_build": _Setting(False, transform=_to_boolean),
|
207
221
|
"traceback": _Setting(False, transform=_to_boolean),
|
208
222
|
"image_builder_version": _Setting(),
|
223
|
+
"strict_parameters": _Setting(False, transform=_to_boolean), # For internal/experimental use
|
224
|
+
"snapshot_debug": _Setting(False, transform=_to_boolean),
|
225
|
+
"client_retries": _Setting(False, transform=_to_boolean), # For internal testing.
|
209
226
|
}
|
210
227
|
|
211
228
|
|
@@ -243,7 +260,7 @@ class Config:
|
|
243
260
|
os.environ["MODAL_" + key.upper()] = value
|
244
261
|
except KeyError:
|
245
262
|
# Override env vars not available in config, e.g. NVIDIA_VISIBLE_DEVICES.
|
246
|
-
# This is used for restoring env vars from a
|
263
|
+
# This is used for restoring env vars from a memory snapshot.
|
247
264
|
os.environ[key.upper()] = value
|
248
265
|
|
249
266
|
def __getitem__(self, key):
|
@@ -253,7 +270,7 @@ class Config:
|
|
253
270
|
return repr(self.to_dict())
|
254
271
|
|
255
272
|
def to_dict(self):
|
256
|
-
return {key: self.get(key) for key in _SETTINGS
|
273
|
+
return {key: self.get(key) for key in sorted(_SETTINGS)}
|
257
274
|
|
258
275
|
|
259
276
|
config = Config()
|
@@ -267,7 +284,7 @@ configure_logger(logger, config["loglevel"], config["log_format"])
|
|
267
284
|
|
268
285
|
|
269
286
|
def _store_user_config(
|
270
|
-
new_settings:
|
287
|
+
new_settings: dict[str, Any], profile: Optional[str] = None, active_profile: Optional[str] = None
|
271
288
|
):
|
272
289
|
"""Internal method, used by the CLI to set tokens."""
|
273
290
|
if profile is None:
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# Copyright Modal Labs 2024
|
2
|
+
import asyncio
|
3
|
+
import platform
|
4
|
+
from typing import Generic, Optional, TypeVar
|
5
|
+
|
6
|
+
from modal_proto import api_pb2
|
7
|
+
|
8
|
+
from ._utils.async_utils import TaskContext, synchronize_api
|
9
|
+
from ._utils.deprecation import deprecation_error
|
10
|
+
from ._utils.grpc_utils import retry_transient_errors
|
11
|
+
from ._utils.shell_utils import stream_from_stdin, write_to_fd
|
12
|
+
from .client import _Client
|
13
|
+
from .exception import InteractiveTimeoutError, InvalidError
|
14
|
+
from .io_streams import _StreamReader, _StreamWriter
|
15
|
+
from .stream_type import StreamType
|
16
|
+
|
17
|
+
T = TypeVar("T", str, bytes)
|
18
|
+
|
19
|
+
|
20
|
+
class _ContainerProcess(Generic[T]):
|
21
|
+
_process_id: Optional[str] = None
|
22
|
+
_stdout: _StreamReader[T]
|
23
|
+
_stderr: _StreamReader[T]
|
24
|
+
_stdin: _StreamWriter
|
25
|
+
_text: bool
|
26
|
+
_by_line: bool
|
27
|
+
_returncode: Optional[int] = None
|
28
|
+
|
29
|
+
def __init__(
|
30
|
+
self,
|
31
|
+
process_id: str,
|
32
|
+
client: _Client,
|
33
|
+
stdout: StreamType = StreamType.PIPE,
|
34
|
+
stderr: StreamType = StreamType.PIPE,
|
35
|
+
text: bool = True,
|
36
|
+
by_line: bool = False,
|
37
|
+
) -> None:
|
38
|
+
self._process_id = process_id
|
39
|
+
self._client = client
|
40
|
+
self._text = text
|
41
|
+
self._by_line = by_line
|
42
|
+
self._stdout = _StreamReader[T](
|
43
|
+
api_pb2.FILE_DESCRIPTOR_STDOUT,
|
44
|
+
process_id,
|
45
|
+
"container_process",
|
46
|
+
self._client,
|
47
|
+
stream_type=stdout,
|
48
|
+
text=text,
|
49
|
+
by_line=by_line,
|
50
|
+
)
|
51
|
+
self._stderr = _StreamReader[T](
|
52
|
+
api_pb2.FILE_DESCRIPTOR_STDERR,
|
53
|
+
process_id,
|
54
|
+
"container_process",
|
55
|
+
self._client,
|
56
|
+
stream_type=stderr,
|
57
|
+
text=text,
|
58
|
+
by_line=by_line,
|
59
|
+
)
|
60
|
+
self._stdin = _StreamWriter(process_id, "container_process", self._client)
|
61
|
+
|
62
|
+
@property
|
63
|
+
def stdout(self) -> _StreamReader[T]:
|
64
|
+
"""StreamReader for the container process's stdout stream."""
|
65
|
+
return self._stdout
|
66
|
+
|
67
|
+
@property
|
68
|
+
def stderr(self) -> _StreamReader[T]:
|
69
|
+
"""StreamReader for the container process's stderr stream."""
|
70
|
+
return self._stderr
|
71
|
+
|
72
|
+
@property
|
73
|
+
def stdin(self) -> _StreamWriter:
|
74
|
+
"""StreamWriter for the container process's stdin stream."""
|
75
|
+
return self._stdin
|
76
|
+
|
77
|
+
@property
|
78
|
+
def returncode(self) -> int:
|
79
|
+
if self._returncode is None:
|
80
|
+
raise InvalidError(
|
81
|
+
"You must call wait() before accessing the returncode. "
|
82
|
+
"To poll for the status of a running process, use poll() instead."
|
83
|
+
)
|
84
|
+
return self._returncode
|
85
|
+
|
86
|
+
async def poll(self) -> Optional[int]:
|
87
|
+
"""Check if the container process has finished running.
|
88
|
+
|
89
|
+
Returns `None` if the process is still running, else returns the exit code.
|
90
|
+
"""
|
91
|
+
if self._returncode is not None:
|
92
|
+
return self._returncode
|
93
|
+
|
94
|
+
req = api_pb2.ContainerExecWaitRequest(exec_id=self._process_id, timeout=0)
|
95
|
+
resp: api_pb2.ContainerExecWaitResponse = await retry_transient_errors(self._client.stub.ContainerExecWait, req)
|
96
|
+
|
97
|
+
if resp.completed:
|
98
|
+
self._returncode = resp.exit_code
|
99
|
+
return self._returncode
|
100
|
+
|
101
|
+
return None
|
102
|
+
|
103
|
+
async def wait(self) -> int:
|
104
|
+
"""Wait for the container process to finish running. Returns the exit code."""
|
105
|
+
|
106
|
+
if self._returncode is not None:
|
107
|
+
return self._returncode
|
108
|
+
|
109
|
+
while True:
|
110
|
+
req = api_pb2.ContainerExecWaitRequest(exec_id=self._process_id, timeout=50)
|
111
|
+
resp: api_pb2.ContainerExecWaitResponse = await retry_transient_errors(
|
112
|
+
self._client.stub.ContainerExecWait, req
|
113
|
+
)
|
114
|
+
if resp.completed:
|
115
|
+
self._returncode = resp.exit_code
|
116
|
+
return self._returncode
|
117
|
+
|
118
|
+
async def attach(self, *, pty: Optional[bool] = None):
|
119
|
+
if platform.system() == "Windows":
|
120
|
+
print("interactive exec is not currently supported on Windows.")
|
121
|
+
return
|
122
|
+
|
123
|
+
if pty is not None:
|
124
|
+
deprecation_error(
|
125
|
+
(2024, 12, 9),
|
126
|
+
"The `pty` argument to `modal.container_process.attach(pty=...)` is deprecated, "
|
127
|
+
"as only PTY mode is supported. Please remove the argument.",
|
128
|
+
)
|
129
|
+
|
130
|
+
from rich.console import Console
|
131
|
+
|
132
|
+
console = Console()
|
133
|
+
|
134
|
+
connecting_status = console.status("Connecting...")
|
135
|
+
connecting_status.start()
|
136
|
+
on_connect = asyncio.Event()
|
137
|
+
|
138
|
+
async def _write_to_fd_loop(stream: _StreamReader):
|
139
|
+
# Don't skip empty messages so we can detect when the process has booted.
|
140
|
+
async for chunk in stream._get_logs(skip_empty_messages=False):
|
141
|
+
if chunk is None:
|
142
|
+
break
|
143
|
+
|
144
|
+
if not on_connect.is_set():
|
145
|
+
connecting_status.stop()
|
146
|
+
on_connect.set()
|
147
|
+
|
148
|
+
await write_to_fd(stream.file_descriptor, chunk)
|
149
|
+
|
150
|
+
async def _handle_input(data: bytes, message_index: int):
|
151
|
+
self.stdin.write(data)
|
152
|
+
await self.stdin.drain()
|
153
|
+
|
154
|
+
async with TaskContext() as tc:
|
155
|
+
stdout_task = tc.create_task(_write_to_fd_loop(self.stdout))
|
156
|
+
stderr_task = tc.create_task(_write_to_fd_loop(self.stderr))
|
157
|
+
|
158
|
+
try:
|
159
|
+
# time out if we can't connect to the server fast enough
|
160
|
+
await asyncio.wait_for(on_connect.wait(), timeout=60)
|
161
|
+
|
162
|
+
async with stream_from_stdin(_handle_input, use_raw_terminal=True):
|
163
|
+
await stdout_task
|
164
|
+
await stderr_task
|
165
|
+
|
166
|
+
# TODO: this doesn't work right now.
|
167
|
+
# if exit_status != 0:
|
168
|
+
# raise ExecutionError(f"Process exited with status code {exit_status}")
|
169
|
+
|
170
|
+
except (asyncio.TimeoutError, TimeoutError):
|
171
|
+
connecting_status.stop()
|
172
|
+
stdout_task.cancel()
|
173
|
+
stderr_task.cancel()
|
174
|
+
raise InteractiveTimeoutError("Failed to establish connection to container. Please try again.")
|
175
|
+
|
176
|
+
|
177
|
+
ContainerProcess = synchronize_api(_ContainerProcess)
|