modal 1.1.5.dev66__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 +171 -138
- 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 +30 -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/name_utils.py +2 -3
- 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 +285 -105
- modal/app.pyi +216 -53
- 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/programs/run_jupyter.py +1 -1
- modal/cli/programs/vscode.py +1 -1
- 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 +24 -53
- 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 +82 -47
- modal/image.pyi +51 -30
- modal/io_streams.py +500 -149
- modal/io_streams.pyi +279 -189
- modal/mount.py +60 -46
- 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 -107
- modal/sandbox.pyi +226 -60
- 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.dev66.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 +141 -70
- modal_proto/api_grpc.py +42 -26
- modal_proto/api_pb2.py +1123 -1103
- modal_proto/api_pb2.pyi +331 -83
- modal_proto/api_pb2_grpc.py +80 -48
- modal_proto/api_pb2_grpc.pyi +26 -18
- modal_proto/modal_api_grpc.py +175 -174
- modal_proto/task_command_router.proto +164 -0
- 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} +148 -57
- 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.dev66.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.proto +0 -125
- modal_proto/sandbox_router_grpc.py +0 -89
- modal_proto/sandbox_router_pb2.py +0 -128
- modal_proto/sandbox_router_pb2_grpc.py +0 -169
- modal_proto/sandbox_router_pb2_grpc.pyi +0 -63
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/WHEEL +0 -0
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/entry_points.txt +0 -0
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/licenses/LICENSE +0 -0
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/top_level.txt +0 -0
modal/exception.py
CHANGED
|
@@ -1,7 +1,45 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
|
+
"""
|
|
3
|
+
Modal-specific exception types.
|
|
4
|
+
|
|
5
|
+
## Notes on `grpclib.GRPCError` migration
|
|
6
|
+
|
|
7
|
+
Historically, the Modal SDK could propagate `grpclib.GRPCError` exceptions out
|
|
8
|
+
to user code. As of v1.3, we are in the process of gracefully migrating to
|
|
9
|
+
always raising a Modal exception type in these cases. To avoid breaking user
|
|
10
|
+
code that relies on catching `grpclib.GRPCError`, a subset of Modal exception
|
|
11
|
+
types temporarily inherit from `grpclib.GRPCError`.
|
|
12
|
+
|
|
13
|
+
We encourage users to migrate any code that currently catches `grpclib.GRPCError`
|
|
14
|
+
to instead catch the appropriate Modal exception type. The following mapping
|
|
15
|
+
between GRPCError status codes and Modal exception types is currently in use:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
CANCELLED -> ServiceError
|
|
19
|
+
UNKNOWN -> ServiceError
|
|
20
|
+
INVALID_ARGUMENT -> InvalidError
|
|
21
|
+
DEADLINE_EXCEEDED -> ServiceError
|
|
22
|
+
NOT_FOUND -> NotFoundError
|
|
23
|
+
ALREADY_EXISTS -> AlreadyExistsError
|
|
24
|
+
PERMISSION_DENIED -> PermissionDeniedError
|
|
25
|
+
RESOURCE_EXHAUSTED -> ResourceExhaustedError
|
|
26
|
+
FAILED_PRECONDITION -> ConflictError
|
|
27
|
+
ABORTED -> ConflictError
|
|
28
|
+
OUT_OF_RANGE -> InvalidError
|
|
29
|
+
UNIMPLEMENTED -> UnimplementedError
|
|
30
|
+
INTERNAL -> InternalError
|
|
31
|
+
UNAVAILABLE -> ServiceError
|
|
32
|
+
DATA_LOSS -> DataLossError
|
|
33
|
+
UNAUTHENTICATED -> AuthError
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
|
|
2
38
|
import random
|
|
3
39
|
import signal
|
|
40
|
+
from typing import Any, Optional
|
|
4
41
|
|
|
42
|
+
import grpclib
|
|
5
43
|
import synchronicity.exceptions
|
|
6
44
|
|
|
7
45
|
UserCodeException = synchronicity.exceptions.UserCodeException # Deprecated type used for return_exception wrapping
|
|
@@ -26,10 +64,116 @@ class Error(Exception):
|
|
|
26
64
|
"""
|
|
27
65
|
|
|
28
66
|
|
|
29
|
-
class
|
|
67
|
+
class _GRPCErrorWrapper(grpclib.GRPCError):
|
|
68
|
+
"""This transitional class helps us migrate away from propagating `grpclib.GRPCError` to users.
|
|
69
|
+
|
|
70
|
+
It serves two purposes:
|
|
71
|
+
- It avoids abruptly breaking user code that catches `grpclib.GRPCError`
|
|
72
|
+
- It actively warns when users access attributes defined by `grpclib.GRPCError`
|
|
73
|
+
|
|
74
|
+
This won't catch all cases (users might react indiscriminately to GRPCError without checking the status).
|
|
75
|
+
|
|
76
|
+
The mapping between GRPCError status codes and our error types is defined in `modal._grpc_client`.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
# These will be set on the instance in our error handling middleware
|
|
81
|
+
_grpc_message: str
|
|
82
|
+
_grpc_status: grpclib.Status
|
|
83
|
+
_grpc_details: Any
|
|
84
|
+
|
|
85
|
+
def __init__(self, message: Optional[str] = None):
|
|
86
|
+
# Override GRPCError's init and repr to behave more like a regular Exception
|
|
87
|
+
# (We don't customize these anywhere in our custom error types currently).
|
|
88
|
+
self._message = message or ""
|
|
89
|
+
|
|
90
|
+
def __repr__(self) -> str:
|
|
91
|
+
return f"{type(self).__name__}({self._message!r})"
|
|
92
|
+
|
|
93
|
+
def _warn_on_grpc_error_attribute_access(self) -> None:
|
|
94
|
+
from ._utils.deprecation import deprecation_warning # Avoid circular import
|
|
95
|
+
|
|
96
|
+
exc_type = type(self).__name__
|
|
97
|
+
deprecation_warning(
|
|
98
|
+
(2025, 12, 9),
|
|
99
|
+
"Modal will stop propagating the `grpclib.GRPCError` type in the future. "
|
|
100
|
+
f"Update your code so that it catches `modal.exception.{exc_type}` directly "
|
|
101
|
+
"to avoid changes to error handling behavior in the future.",
|
|
102
|
+
pending=True,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def message(self) -> str:
|
|
107
|
+
self._warn_on_grpc_error_attribute_access()
|
|
108
|
+
return self._grpc_message
|
|
109
|
+
|
|
110
|
+
@message.setter
|
|
111
|
+
def message(self, value: str) -> None:
|
|
112
|
+
self._grpc_message = value
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def status(self) -> grpclib.Status:
|
|
116
|
+
self._warn_on_grpc_error_attribute_access()
|
|
117
|
+
return self._grpc_status
|
|
118
|
+
|
|
119
|
+
@status.setter
|
|
120
|
+
def status(self, value: grpclib.Status) -> None:
|
|
121
|
+
self._grpc_status = value
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def details(self) -> Any:
|
|
125
|
+
self._warn_on_grpc_error_attribute_access()
|
|
126
|
+
return self._grpc_details
|
|
127
|
+
|
|
128
|
+
@details.setter
|
|
129
|
+
def details(self, value: Any) -> None:
|
|
130
|
+
self._grpc_details = value
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class AlreadyExistsError(Error, _GRPCErrorWrapper):
|
|
30
134
|
"""Raised when a resource creation conflicts with an existing resource."""
|
|
31
135
|
|
|
32
136
|
|
|
137
|
+
class AuthError(Error, _GRPCErrorWrapper):
|
|
138
|
+
"""Raised when a client has missing or invalid authentication."""
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class InternalError(Error, _GRPCErrorWrapper):
|
|
142
|
+
"""Raised when an internal error occurs in the Modal system."""
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class InvalidError(Error, _GRPCErrorWrapper):
|
|
146
|
+
"""Raised when user does something invalid."""
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class ConflictError(InvalidError, _GRPCErrorWrapper):
|
|
150
|
+
"""Raised when a resource conflict occurs between the request and current system state."""
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class DataLossError(Error, _GRPCErrorWrapper):
|
|
154
|
+
"""Raised when data is lost or corrupted."""
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class NotFoundError(Error, _GRPCErrorWrapper):
|
|
158
|
+
"""Raised when a requested resource was not found."""
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class PermissionDeniedError(Error, _GRPCErrorWrapper):
|
|
162
|
+
"""Raised when a user does not have permission to perform the requested operation."""
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class ResourceExhaustedError(Error, _GRPCErrorWrapper):
|
|
166
|
+
"""Raised when a server-side resource has been exhausted, e.g. a quota or rate limit."""
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class ServiceError(Error, _GRPCErrorWrapper):
|
|
170
|
+
"""Raised when an error occurs in basic client/server communication."""
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class UnimplementedError(Error, _GRPCErrorWrapper):
|
|
174
|
+
"""Raised when a requested operation is not implemented or not supported."""
|
|
175
|
+
|
|
176
|
+
|
|
33
177
|
class RemoteError(Error):
|
|
34
178
|
"""Raised when an error occurs on the Modal server."""
|
|
35
179
|
|
|
@@ -42,6 +186,10 @@ class SandboxTimeoutError(TimeoutError):
|
|
|
42
186
|
"""Raised when a Sandbox exceeds its execution duration limit and times out."""
|
|
43
187
|
|
|
44
188
|
|
|
189
|
+
class ExecTimeoutError(TimeoutError):
|
|
190
|
+
"""Raised when a container process exceeds its execution duration limit and times out."""
|
|
191
|
+
|
|
192
|
+
|
|
45
193
|
class SandboxTerminatedError(Error):
|
|
46
194
|
"""Raised when a Sandbox is terminated for an internal reason."""
|
|
47
195
|
|
|
@@ -66,26 +214,14 @@ class OutputExpiredError(TimeoutError):
|
|
|
66
214
|
"""Raised when the Output exceeds expiration and times out."""
|
|
67
215
|
|
|
68
216
|
|
|
69
|
-
class AuthError(Error):
|
|
70
|
-
"""Raised when a client has missing or invalid authentication."""
|
|
71
|
-
|
|
72
|
-
|
|
73
217
|
class ConnectionError(Error):
|
|
74
218
|
"""Raised when an issue occurs while connecting to the Modal servers."""
|
|
75
219
|
|
|
76
220
|
|
|
77
|
-
class InvalidError(Error):
|
|
78
|
-
"""Raised when user does something invalid."""
|
|
79
|
-
|
|
80
|
-
|
|
81
221
|
class VersionError(Error):
|
|
82
222
|
"""Raised when the current client version of Modal is unsupported."""
|
|
83
223
|
|
|
84
224
|
|
|
85
|
-
class NotFoundError(Error):
|
|
86
|
-
"""Raised when a requested resource was not found."""
|
|
87
|
-
|
|
88
|
-
|
|
89
225
|
class ExecutionError(Error):
|
|
90
226
|
"""Raised when something unexpected happened during runtime."""
|
|
91
227
|
|
|
@@ -116,10 +252,12 @@ class ServerWarning(UserWarning):
|
|
|
116
252
|
"""Warning originating from the Modal server and re-issued in client code."""
|
|
117
253
|
|
|
118
254
|
|
|
255
|
+
class AsyncUsageWarning(UserWarning):
|
|
256
|
+
"""Warning emitted when a blocking Modal interface is used in an async context."""
|
|
257
|
+
|
|
258
|
+
|
|
119
259
|
class InternalFailure(Error):
|
|
120
|
-
"""
|
|
121
|
-
Retriable internal error.
|
|
122
|
-
"""
|
|
260
|
+
"""Retriable internal error."""
|
|
123
261
|
|
|
124
262
|
|
|
125
263
|
class _CliUserExecutionError(Exception):
|
modal/experimental/__init__.py
CHANGED
|
@@ -13,15 +13,18 @@ from .._object import _get_environment_name
|
|
|
13
13
|
from .._partial_function import _clustered
|
|
14
14
|
from .._runtime.container_io_manager import _ContainerIOManager
|
|
15
15
|
from .._utils.async_utils import synchronize_api, synchronizer
|
|
16
|
-
from .._utils.deprecation import deprecation_warning
|
|
17
|
-
from .._utils.grpc_utils import retry_transient_errors
|
|
18
16
|
from ..app import _App
|
|
19
17
|
from ..client import _Client
|
|
20
|
-
from ..cls import _Cls
|
|
18
|
+
from ..cls import _Cls
|
|
21
19
|
from ..exception import InvalidError
|
|
22
20
|
from ..image import DockerfileSpec, ImageBuilderVersion, _Image, _ImageRegistryConfig
|
|
23
21
|
from ..secret import _Secret
|
|
24
|
-
from .flash import
|
|
22
|
+
from .flash import ( # noqa: F401
|
|
23
|
+
flash_forward,
|
|
24
|
+
flash_get_containers,
|
|
25
|
+
flash_prometheus_autoscaler,
|
|
26
|
+
http_server,
|
|
27
|
+
)
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
def stop_fetching_inputs():
|
|
@@ -87,6 +90,19 @@ async def list_deployed_apps(environment_name: str = "", client: Optional[_Clien
|
|
|
87
90
|
return app_infos
|
|
88
91
|
|
|
89
92
|
|
|
93
|
+
@synchronizer.create_blocking
|
|
94
|
+
async def stop_app(name: str, *, environment_name: Optional[str] = None, client: Optional[_Client] = None) -> None:
|
|
95
|
+
"""Stop a deployed App.
|
|
96
|
+
|
|
97
|
+
This interface is experimental and may change in the future,
|
|
98
|
+
although the functionality will continue to be supported.
|
|
99
|
+
"""
|
|
100
|
+
client_ = client or await _Client.from_env()
|
|
101
|
+
app = await _App.lookup(name, environment_name=environment_name, client=client_)
|
|
102
|
+
req = api_pb2.AppStopRequest(app_id=app.app_id, source=api_pb2.APP_STOP_SOURCE_PYTHON_CLIENT)
|
|
103
|
+
await client_.stub.AppStop(req)
|
|
104
|
+
|
|
105
|
+
|
|
90
106
|
@synchronizer.create_blocking
|
|
91
107
|
async def get_app_objects(
|
|
92
108
|
app_name: str, *, environment_name: Optional[str] = None, client: Optional[_Client] = None
|
|
@@ -117,7 +133,7 @@ async def get_app_objects(
|
|
|
117
133
|
|
|
118
134
|
app = await _App.lookup(app_name, environment_name=environment_name, client=client)
|
|
119
135
|
req = api_pb2.AppGetLayoutRequest(app_id=app.app_id)
|
|
120
|
-
app_layout_resp = await
|
|
136
|
+
app_layout_resp = await client.stub.AppGetLayout(req)
|
|
121
137
|
|
|
122
138
|
app_objects: dict[str, Union[_Function, _Cls]] = {}
|
|
123
139
|
|
|
@@ -340,52 +356,6 @@ async def notebook_base_image(*, python_version: Optional[str] = None, force_bui
|
|
|
340
356
|
)
|
|
341
357
|
|
|
342
358
|
|
|
343
|
-
@synchronizer.create_blocking
|
|
344
|
-
async def update_autoscaler(
|
|
345
|
-
obj: Union[_Function, _Obj],
|
|
346
|
-
*,
|
|
347
|
-
min_containers: Optional[int] = None,
|
|
348
|
-
max_containers: Optional[int] = None,
|
|
349
|
-
buffer_containers: Optional[int] = None,
|
|
350
|
-
scaledown_window: Optional[int] = None,
|
|
351
|
-
client: Optional[_Client] = None,
|
|
352
|
-
) -> None:
|
|
353
|
-
"""Update the autoscaler settings for a Function or Obj (instance of a Cls).
|
|
354
|
-
|
|
355
|
-
This is an experimental interface for a feature that we will be adding to
|
|
356
|
-
replace the existing `.keep_warm()` method. The stable form of this interface
|
|
357
|
-
may look different (i.e., it may be a standalone function or a method).
|
|
358
|
-
|
|
359
|
-
"""
|
|
360
|
-
deprecation_warning(
|
|
361
|
-
(2025, 5, 5),
|
|
362
|
-
"The modal.experimental.update_autoscaler(...) function is now deprecated in favor of"
|
|
363
|
-
" a stable `.update_autoscaler(...) method on the corresponding object.",
|
|
364
|
-
show_source=True,
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
settings = api_pb2.AutoscalerSettings(
|
|
368
|
-
min_containers=min_containers,
|
|
369
|
-
max_containers=max_containers,
|
|
370
|
-
buffer_containers=buffer_containers,
|
|
371
|
-
scaledown_window=scaledown_window,
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
if client is None:
|
|
375
|
-
client = await _Client.from_env()
|
|
376
|
-
|
|
377
|
-
if isinstance(obj, _Function):
|
|
378
|
-
f = obj
|
|
379
|
-
else:
|
|
380
|
-
assert obj._cls._class_service_function is not None
|
|
381
|
-
await obj._cls._class_service_function.hydrate(client=client)
|
|
382
|
-
f = obj._cached_service_function()
|
|
383
|
-
await f.hydrate(client=client)
|
|
384
|
-
|
|
385
|
-
request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=f.object_id, settings=settings)
|
|
386
|
-
await retry_transient_errors(client.stub.FunctionUpdateSchedulingParams, request)
|
|
387
|
-
|
|
388
|
-
|
|
389
359
|
@synchronizer.create_blocking
|
|
390
360
|
async def image_delete(
|
|
391
361
|
image_id: str,
|
|
@@ -394,7 +364,8 @@ async def image_delete(
|
|
|
394
364
|
) -> None:
|
|
395
365
|
"""Delete an Image by its ID.
|
|
396
366
|
|
|
397
|
-
Deletion is irreversible and will prevent
|
|
367
|
+
Deletion is irreversible and will prevent Functions/Sandboxes from using
|
|
368
|
+
the Image.
|
|
398
369
|
|
|
399
370
|
This is an experimental interface for a feature that we will be adding to
|
|
400
371
|
the main Image class. The stable form of this interface may look different.
|
|
@@ -408,4 +379,4 @@ async def image_delete(
|
|
|
408
379
|
client = await _Client.from_env()
|
|
409
380
|
|
|
410
381
|
req = api_pb2.ImageDeleteRequest(image_id=image_id)
|
|
411
|
-
await
|
|
382
|
+
await client.stub.ImageDelete(req)
|