modal 1.0.3.dev10__py3-none-any.whl → 1.2.3.dev7__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.

Files changed (160) hide show
  1. modal/__init__.py +0 -2
  2. modal/__main__.py +3 -4
  3. modal/_billing.py +80 -0
  4. modal/_clustered_functions.py +7 -3
  5. modal/_clustered_functions.pyi +15 -3
  6. modal/_container_entrypoint.py +51 -69
  7. modal/_functions.py +508 -240
  8. modal/_grpc_client.py +171 -0
  9. modal/_load_context.py +105 -0
  10. modal/_object.py +81 -21
  11. modal/_output.py +58 -45
  12. modal/_partial_function.py +48 -73
  13. modal/_pty.py +7 -3
  14. modal/_resolver.py +26 -46
  15. modal/_runtime/asgi.py +4 -3
  16. modal/_runtime/container_io_manager.py +358 -220
  17. modal/_runtime/container_io_manager.pyi +296 -101
  18. modal/_runtime/execution_context.py +18 -2
  19. modal/_runtime/execution_context.pyi +64 -7
  20. modal/_runtime/gpu_memory_snapshot.py +262 -57
  21. modal/_runtime/user_code_imports.py +28 -58
  22. modal/_serialization.py +90 -6
  23. modal/_traceback.py +42 -1
  24. modal/_tunnel.pyi +380 -12
  25. modal/_utils/async_utils.py +84 -29
  26. modal/_utils/auth_token_manager.py +111 -0
  27. modal/_utils/blob_utils.py +181 -58
  28. modal/_utils/deprecation.py +19 -0
  29. modal/_utils/function_utils.py +91 -47
  30. modal/_utils/grpc_utils.py +89 -66
  31. modal/_utils/mount_utils.py +26 -1
  32. modal/_utils/name_utils.py +17 -3
  33. modal/_utils/task_command_router_client.py +536 -0
  34. modal/_utils/time_utils.py +34 -6
  35. modal/app.py +256 -88
  36. modal/app.pyi +909 -92
  37. modal/billing.py +5 -0
  38. modal/builder/2025.06.txt +18 -0
  39. modal/builder/PREVIEW.txt +18 -0
  40. modal/builder/base-images.json +58 -0
  41. modal/cli/_download.py +19 -3
  42. modal/cli/_traceback.py +3 -2
  43. modal/cli/app.py +4 -4
  44. modal/cli/cluster.py +15 -7
  45. modal/cli/config.py +5 -3
  46. modal/cli/container.py +7 -6
  47. modal/cli/dict.py +22 -16
  48. modal/cli/entry_point.py +12 -5
  49. modal/cli/environment.py +5 -4
  50. modal/cli/import_refs.py +3 -3
  51. modal/cli/launch.py +102 -5
  52. modal/cli/network_file_system.py +11 -12
  53. modal/cli/profile.py +3 -2
  54. modal/cli/programs/launch_instance_ssh.py +94 -0
  55. modal/cli/programs/run_jupyter.py +1 -1
  56. modal/cli/programs/run_marimo.py +95 -0
  57. modal/cli/programs/vscode.py +1 -1
  58. modal/cli/queues.py +57 -26
  59. modal/cli/run.py +91 -23
  60. modal/cli/secret.py +48 -22
  61. modal/cli/token.py +7 -8
  62. modal/cli/utils.py +4 -7
  63. modal/cli/volume.py +31 -25
  64. modal/client.py +15 -85
  65. modal/client.pyi +183 -62
  66. modal/cloud_bucket_mount.py +5 -3
  67. modal/cloud_bucket_mount.pyi +197 -5
  68. modal/cls.py +200 -126
  69. modal/cls.pyi +446 -68
  70. modal/config.py +29 -11
  71. modal/container_process.py +319 -19
  72. modal/container_process.pyi +190 -20
  73. modal/dict.py +290 -71
  74. modal/dict.pyi +835 -83
  75. modal/environments.py +15 -27
  76. modal/environments.pyi +46 -24
  77. modal/exception.py +14 -2
  78. modal/experimental/__init__.py +194 -40
  79. modal/experimental/flash.py +618 -0
  80. modal/experimental/flash.pyi +380 -0
  81. modal/experimental/ipython.py +11 -7
  82. modal/file_io.py +29 -36
  83. modal/file_io.pyi +251 -53
  84. modal/file_pattern_matcher.py +56 -16
  85. modal/functions.pyi +673 -92
  86. modal/gpu.py +1 -1
  87. modal/image.py +528 -176
  88. modal/image.pyi +1572 -145
  89. modal/io_streams.py +458 -128
  90. modal/io_streams.pyi +433 -52
  91. modal/mount.py +216 -151
  92. modal/mount.pyi +225 -78
  93. modal/network_file_system.py +45 -62
  94. modal/network_file_system.pyi +277 -56
  95. modal/object.pyi +93 -17
  96. modal/parallel_map.py +942 -129
  97. modal/parallel_map.pyi +294 -15
  98. modal/partial_function.py +0 -2
  99. modal/partial_function.pyi +234 -19
  100. modal/proxy.py +17 -8
  101. modal/proxy.pyi +36 -3
  102. modal/queue.py +270 -65
  103. modal/queue.pyi +817 -57
  104. modal/runner.py +115 -101
  105. modal/runner.pyi +205 -49
  106. modal/sandbox.py +512 -136
  107. modal/sandbox.pyi +845 -111
  108. modal/schedule.py +1 -1
  109. modal/secret.py +300 -70
  110. modal/secret.pyi +589 -34
  111. modal/serving.py +7 -11
  112. modal/serving.pyi +7 -8
  113. modal/snapshot.py +11 -8
  114. modal/snapshot.pyi +25 -4
  115. modal/token_flow.py +4 -4
  116. modal/token_flow.pyi +28 -8
  117. modal/volume.py +416 -158
  118. modal/volume.pyi +1117 -121
  119. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/METADATA +10 -9
  120. modal-1.2.3.dev7.dist-info/RECORD +195 -0
  121. modal_docs/mdmd/mdmd.py +17 -4
  122. modal_proto/api.proto +534 -79
  123. modal_proto/api_grpc.py +337 -1
  124. modal_proto/api_pb2.py +1522 -968
  125. modal_proto/api_pb2.pyi +1619 -134
  126. modal_proto/api_pb2_grpc.py +699 -4
  127. modal_proto/api_pb2_grpc.pyi +226 -14
  128. modal_proto/modal_api_grpc.py +175 -154
  129. modal_proto/sandbox_router.proto +145 -0
  130. modal_proto/sandbox_router_grpc.py +105 -0
  131. modal_proto/sandbox_router_pb2.py +149 -0
  132. modal_proto/sandbox_router_pb2.pyi +333 -0
  133. modal_proto/sandbox_router_pb2_grpc.py +203 -0
  134. modal_proto/sandbox_router_pb2_grpc.pyi +75 -0
  135. modal_proto/task_command_router.proto +144 -0
  136. modal_proto/task_command_router_grpc.py +105 -0
  137. modal_proto/task_command_router_pb2.py +149 -0
  138. modal_proto/task_command_router_pb2.pyi +333 -0
  139. modal_proto/task_command_router_pb2_grpc.py +203 -0
  140. modal_proto/task_command_router_pb2_grpc.pyi +75 -0
  141. modal_version/__init__.py +1 -1
  142. modal/requirements/PREVIEW.txt +0 -16
  143. modal/requirements/base-images.json +0 -26
  144. modal-1.0.3.dev10.dist-info/RECORD +0 -179
  145. modal_proto/modal_options_grpc.py +0 -3
  146. modal_proto/options.proto +0 -19
  147. modal_proto/options_grpc.py +0 -3
  148. modal_proto/options_pb2.py +0 -35
  149. modal_proto/options_pb2.pyi +0 -20
  150. modal_proto/options_pb2_grpc.py +0 -4
  151. modal_proto/options_pb2_grpc.pyi +0 -7
  152. /modal/{requirements → builder}/2023.12.312.txt +0 -0
  153. /modal/{requirements → builder}/2023.12.txt +0 -0
  154. /modal/{requirements → builder}/2024.04.txt +0 -0
  155. /modal/{requirements → builder}/2024.10.txt +0 -0
  156. /modal/{requirements → builder}/README.md +0 -0
  157. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/WHEEL +0 -0
  158. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/entry_points.txt +0 -0
  159. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/licenses/LICENSE +0 -0
  160. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/top_level.txt +0 -0
modal/environments.py CHANGED
@@ -8,11 +8,10 @@ from google.protobuf.wrappers_pb2 import StringValue
8
8
 
9
9
  from modal_proto import api_pb2
10
10
 
11
+ from ._load_context import LoadContext
11
12
  from ._object import _Object
12
13
  from ._resolver import Resolver
13
14
  from ._utils.async_utils import synchronize_api, synchronizer
14
- from ._utils.deprecation import deprecation_warning
15
- from ._utils.grpc_utils import retry_transient_errors
16
15
  from ._utils.name_utils import check_object_name
17
16
  from .client import _Client
18
17
  from .config import config, logger
@@ -54,6 +53,7 @@ class _Environment(_Object, type_prefix="en"):
54
53
  name: str,
55
54
  *,
56
55
  create_if_missing: bool = False,
56
+ client: Optional[_Client] = None,
57
57
  ):
58
58
  if name:
59
59
  # Allow null names for the case where we want to look up the "default" environment,
@@ -63,7 +63,9 @@ class _Environment(_Object, type_prefix="en"):
63
63
  # environments as part of public API when we make this class more useful.
64
64
  check_object_name(name, "Environment")
65
65
 
66
- async def _load(self: _Environment, resolver: Resolver, existing_object_id: Optional[str]):
66
+ async def _load(
67
+ self: _Environment, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
68
+ ):
67
69
  request = api_pb2.EnvironmentGetOrCreateRequest(
68
70
  deployment_name=name,
69
71
  object_creation_type=(
@@ -72,31 +74,17 @@ class _Environment(_Object, type_prefix="en"):
72
74
  else api_pb2.OBJECT_CREATION_TYPE_UNSPECIFIED
73
75
  ),
74
76
  )
75
- response = await retry_transient_errors(resolver.client.stub.EnvironmentGetOrCreate, request)
77
+ response = await load_context.client.stub.EnvironmentGetOrCreate(request)
76
78
  logger.debug(f"Created environment with id {response.environment_id}")
77
- self._hydrate(response.environment_id, resolver.client, response.metadata)
78
-
79
- # TODO environment name (and id?) in the repr? (We should make reprs consistently more useful)
80
- return _Environment._from_loader(_load, "Environment()", is_another_app=True, hydrate_lazily=True)
81
-
82
- @staticmethod
83
- async def lookup(
84
- name: str,
85
- client: Optional[_Client] = None,
86
- create_if_missing: bool = False,
87
- ):
88
- deprecation_warning(
89
- (2025, 1, 27),
90
- "`modal.Environment.lookup` is deprecated and will be removed in a future release."
91
- " It can be replaced with `modal.Environment.from_name`."
92
- "\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
79
+ self._hydrate(response.environment_id, load_context.client, response.metadata)
80
+
81
+ return _Environment._from_loader(
82
+ _load,
83
+ f"Environment.from_name({name!r})",
84
+ is_another_app=True,
85
+ hydrate_lazily=True,
86
+ load_context_overrides=LoadContext(client=client),
93
87
  )
94
- obj = _Environment.from_name(name, create_if_missing=create_if_missing)
95
- if client is None:
96
- client = await _Client.from_env()
97
- resolver = Resolver(client=client)
98
- await resolver.load(obj)
99
- return obj
100
88
 
101
89
 
102
90
  Environment = synchronize_api(_Environment)
@@ -109,7 +97,7 @@ ENVIRONMENT_CACHE: dict[str, _Environment] = {}
109
97
  async def _get_environment_cached(name: str, client: _Client) -> _Environment:
110
98
  if name in ENVIRONMENT_CACHE:
111
99
  return ENVIRONMENT_CACHE[name]
112
- environment = await _Environment.from_name(name).hydrate(client)
100
+ environment = await _Environment.from_name(name, client=client).hydrate()
113
101
  ENVIRONMENT_CACHE[name] = environment
114
102
  return environment
115
103
 
modal/environments.pyi CHANGED
@@ -7,45 +7,60 @@ import typing
7
7
  import typing_extensions
8
8
 
9
9
  class EnvironmentSettings:
10
+ """EnvironmentSettings(image_builder_version: str, webhook_suffix: str)"""
11
+
10
12
  image_builder_version: str
11
13
  webhook_suffix: str
12
14
 
13
- def __init__(self, image_builder_version: str, webhook_suffix: str) -> None: ...
14
- def __repr__(self): ...
15
- def __eq__(self, other): ...
16
- def __setattr__(self, name, value): ...
17
- def __delattr__(self, name): ...
18
- def __hash__(self): ...
15
+ def __init__(self, image_builder_version: str, webhook_suffix: str) -> None:
16
+ """Initialize self. See help(type(self)) for accurate signature."""
17
+ ...
18
+
19
+ def __repr__(self):
20
+ """Return repr(self)."""
21
+ ...
22
+
23
+ def __eq__(self, other):
24
+ """Return self==value."""
25
+ ...
26
+
27
+ def __setattr__(self, name, value):
28
+ """Implement setattr(self, name, value)."""
29
+ ...
30
+
31
+ def __delattr__(self, name):
32
+ """Implement delattr(self, name)."""
33
+ ...
34
+
35
+ def __hash__(self):
36
+ """Return hash(self)."""
37
+ ...
19
38
 
20
39
  class _Environment(modal._object._Object):
21
40
  _settings: EnvironmentSettings
22
41
 
23
- def __init__(self): ...
42
+ def __init__(self):
43
+ """mdmd:hidden"""
44
+ ...
45
+
24
46
  def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
25
47
  @staticmethod
26
- def from_name(name: str, *, create_if_missing: bool = False): ...
27
- @staticmethod
28
- async def lookup(
29
- name: str, client: typing.Optional[modal.client._Client] = None, create_if_missing: bool = False
48
+ def from_name(
49
+ name: str, *, create_if_missing: bool = False, client: typing.Optional[modal.client._Client] = None
30
50
  ): ...
31
51
 
32
52
  class Environment(modal.object.Object):
33
53
  _settings: EnvironmentSettings
34
54
 
35
- def __init__(self): ...
55
+ def __init__(self):
56
+ """mdmd:hidden"""
57
+ ...
58
+
36
59
  def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
37
60
  @staticmethod
38
- def from_name(name: str, *, create_if_missing: bool = False): ...
39
-
40
- class __lookup_spec(typing_extensions.Protocol):
41
- def __call__(
42
- self, /, name: str, client: typing.Optional[modal.client.Client] = None, create_if_missing: bool = False
43
- ): ...
44
- async def aio(
45
- self, /, name: str, client: typing.Optional[modal.client.Client] = None, create_if_missing: bool = False
46
- ): ...
47
-
48
- lookup: __lookup_spec
61
+ def from_name(
62
+ name: str, *, create_if_missing: bool = False, client: typing.Optional[modal.client.Client] = None
63
+ ): ...
49
64
 
50
65
  async def _get_environment_cached(name: str, client: modal.client._Client) -> _Environment: ...
51
66
 
@@ -93,6 +108,13 @@ class __list_environments_spec(typing_extensions.Protocol):
93
108
 
94
109
  list_environments: __list_environments_spec
95
110
 
96
- def ensure_env(environment_name: typing.Optional[str] = None) -> str: ...
111
+ def ensure_env(environment_name: typing.Optional[str] = None) -> str:
112
+ """Override config environment with environment from environment_name
113
+
114
+ This is necessary since a cli command that runs Modal code, without explicit
115
+ environment specification wouldn't pick up the environment specified in a
116
+ command line flag otherwise, e.g. when doing `modal run --env=foo`
117
+ """
118
+ ...
97
119
 
98
120
  ENVIRONMENT_CACHE: dict[str, _Environment]
modal/exception.py CHANGED
@@ -2,11 +2,15 @@
2
2
  import random
3
3
  import signal
4
4
 
5
+ import synchronicity.exceptions
6
+
7
+ UserCodeException = synchronicity.exceptions.UserCodeException # Deprecated type used for return_exception wrapping
8
+
5
9
 
6
10
  class Error(Exception):
7
11
  """
8
- Base class for all Modal errors. See [`modal.exception`](/docs/reference/modal.exception) for the specialized
9
- error classes.
12
+ Base class for all Modal errors. See [`modal.exception`](https://modal.com/docs/reference/modal.exception)
13
+ for the specialized error classes.
10
14
 
11
15
  **Usage**
12
16
 
@@ -22,6 +26,10 @@ class Error(Exception):
22
26
  """
23
27
 
24
28
 
29
+ class AlreadyExistsError(Error):
30
+ """Raised when a resource creation conflicts with an existing resource."""
31
+
32
+
25
33
  class RemoteError(Error):
26
34
  """Raised when an error occurs on the Modal server."""
27
35
 
@@ -34,6 +42,10 @@ class SandboxTimeoutError(TimeoutError):
34
42
  """Raised when a Sandbox exceeds its execution duration limit and times out."""
35
43
 
36
44
 
45
+ class ExecTimeoutError(TimeoutError):
46
+ """Raised when a container process exceeds its execution duration limit and times out."""
47
+
48
+
37
49
  class SandboxTerminatedError(Error):
38
50
  """Raised when a Sandbox is terminated for an internal reason."""
39
51
 
@@ -1,5 +1,6 @@
1
1
  # Copyright Modal Labs 2025
2
2
  import os
3
+ import shlex
3
4
  from dataclasses import dataclass
4
5
  from pathlib import Path
5
6
  from typing import Literal, Optional, Union
@@ -12,13 +13,13 @@ from .._object import _get_environment_name
12
13
  from .._partial_function import _clustered
13
14
  from .._runtime.container_io_manager import _ContainerIOManager
14
15
  from .._utils.async_utils import synchronize_api, synchronizer
15
- from .._utils.deprecation import deprecation_warning
16
- from .._utils.grpc_utils import retry_transient_errors
16
+ from ..app import _App
17
17
  from ..client import _Client
18
- from ..cls import _Obj
18
+ from ..cls import _Cls
19
19
  from ..exception import InvalidError
20
20
  from ..image import DockerfileSpec, ImageBuilderVersion, _Image, _ImageRegistryConfig
21
21
  from ..secret import _Secret
22
+ from .flash import flash_forward, flash_get_containers, flash_prometheus_autoscaler # noqa: F401
22
23
 
23
24
 
24
25
  def stop_fetching_inputs():
@@ -84,6 +85,51 @@ async def list_deployed_apps(environment_name: str = "", client: Optional[_Clien
84
85
  return app_infos
85
86
 
86
87
 
88
+ @synchronizer.create_blocking
89
+ async def get_app_objects(
90
+ app_name: str, *, environment_name: Optional[str] = None, client: Optional[_Client] = None
91
+ ) -> dict[str, Union[_Function, _Cls]]:
92
+ """Experimental interface for retrieving a dictionary of the Functions / Clses in an App.
93
+
94
+ The return value is a dictionary mapping names to unhydrated Function or Cls objects.
95
+
96
+ We plan to support this functionality through a stable API in the future. It's likely that
97
+ the stable API will look different (it will probably be a method on the App object itself).
98
+
99
+ """
100
+ # This is implemented through a somewhat odd mixture of internal RPCs and public APIs.
101
+ # While AppGetLayout provides the object ID and metadata for each object in the App, it's
102
+ # currently somewhere between very awkward and impossible to hydrate a modal.Cls with just
103
+ # that information, since the "class service function" needs to be loaded first
104
+ # (and it's not always possible to do that without knowledge of the parameterization).
105
+ # So instead we just use AppGetLayout to retrieve the names of the Functions / Clsices on
106
+ # the App and then use the public .from_name constructors to return unhydrated handles.
107
+
108
+ # Additionally, since we need to know the environment name to use `.from_name`, and the App's
109
+ # environment name isn't stored anywhere on the App (and cannot be retrieved via an RPC), the
110
+ # experimental function is parameterized by an App name while the stable API would instead
111
+ # be a method on the App itself.
112
+
113
+ if client is None:
114
+ client = await _Client.from_env()
115
+
116
+ app = await _App.lookup(app_name, environment_name=environment_name, client=client)
117
+ req = api_pb2.AppGetLayoutRequest(app_id=app.app_id)
118
+ app_layout_resp = await client.stub.AppGetLayout(req)
119
+
120
+ app_objects: dict[str, Union[_Function, _Cls]] = {}
121
+
122
+ for cls_name in app_layout_resp.app_layout.class_ids:
123
+ app_objects[cls_name] = _Cls.from_name(app_name, cls_name, environment_name=environment_name)
124
+
125
+ for func_name in app_layout_resp.app_layout.function_ids:
126
+ if func_name.endswith(".*"):
127
+ continue # TODO explain
128
+ app_objects[func_name] = _Function.from_name(app_name, func_name, environment_name=environment_name)
129
+
130
+ return app_objects
131
+
132
+
87
133
  @synchronizer.create_blocking
88
134
  async def raw_dockerfile_image(
89
135
  path: Union[str, Path],
@@ -94,8 +140,9 @@ async def raw_dockerfile_image(
94
140
 
95
141
  Unlike for `modal.Image.from_dockerfile`, the provided recipe will not be embellished with
96
142
  steps to install dependencies for the Modal client package. As a consequence, the resulting
97
- Image cannot be used with a modal Function unless those dependencies are added in a subsequent
98
- layer. It _can_ be directly used with a modal Sandbox, which does not need the Modal client.
143
+ Image cannot be used with a modal Function unless those dependencies are already included
144
+ as part of the base Dockerfile recipe or are added in a subsequent layer. The Image _can_ be
145
+ directly used with a modal Sandbox, which does not need the Modal client.
99
146
 
100
147
  We expect to support this experimental function until the `2025.04` Modal Image Builder is
101
148
  stable, at which point Modal Image recipes will no longer install the client dependencies
@@ -127,8 +174,9 @@ async def raw_registry_image(
127
174
 
128
175
  Unlike for `modal.Image.from_registry`, the provided recipe will not be embellished with
129
176
  steps to install dependencies for the Modal client package. As a consequence, the resulting
130
- Image cannot be used with a modal Function unless those dependencies are added in a subsequent
131
- layer. It _can_ be directly used with a modal Sandbox, which does not need the Modal client.
177
+ Image cannot be used with a modal Function unless those dependencies are already included
178
+ as part of the registry Image or are added in a subsequent layer. The Image _can_ be
179
+ directly used with a modal Sandbox, which does not need the Modal client.
132
180
 
133
181
  We expect to support this experimental function until the `2025.04` Modal Image Builder is
134
182
  stable, at which point Modal Image recipes will no longer install the client dependencies
@@ -163,47 +211,153 @@ async def raw_registry_image(
163
211
  )
164
212
 
165
213
 
214
+ def _install_cuda_command() -> str:
215
+ """Command to install CUDA Toolkit (nvcc) inside a container."""
216
+ arch = "x86_64" # instruction set architecture for the CPU, all Modal machines are x86_64
217
+ distro = "debian12" # the distribution and version number of our OS (GNU/Linux)
218
+ filename = "cuda-keyring_1.1-1_all.deb" # NVIDIA signing key file
219
+ cuda_keyring_url = f"https://developer.download.nvidia.com/compute/cuda/repos/{distro}/{arch}/{filename}"
220
+
221
+ major, minor = 12, 8
222
+ max_cuda_version = f"{major}-{minor}"
223
+
224
+ return (
225
+ f"wget {cuda_keyring_url} && "
226
+ + f"dpkg -i {filename} && "
227
+ + f"rm -f {filename} && "
228
+ + f"apt-get update && apt-get install -y cuda-nvcc-{max_cuda_version}"
229
+ )
230
+
231
+
166
232
  @synchronizer.create_blocking
167
- async def update_autoscaler(
168
- obj: Union[_Function, _Obj],
233
+ async def notebook_base_image(*, python_version: Optional[str] = None, force_build: bool = False) -> _Image:
234
+ """Default image used for Modal notebook kernels, with common libraries.
235
+
236
+ This can be used to bootstrap development workflows quickly. We don't
237
+ recommend using this image for production Modal Functions though, as it may
238
+ change at any time in the future.
239
+ """
240
+ # Include several common packages, as well as kernelshim dependencies (except 'modal').
241
+ # These packages aren't pinned, so they may change over time with builds.
242
+ #
243
+ # We plan to use `--exclude-newer` in the future, with date-specific image builds.
244
+ base_image = _Image.debian_slim(python_version=python_version)
245
+
246
+ environment_packages: list[str] = [
247
+ "accelerate",
248
+ "aiohttp",
249
+ "altair",
250
+ "anthropic",
251
+ "asyncpg",
252
+ "beautifulsoup4",
253
+ "bokeh",
254
+ "boto3[crt]",
255
+ "click",
256
+ "diffusers[torch,flax]",
257
+ "dm-sonnet",
258
+ "flax",
259
+ "ftfy",
260
+ "h5py",
261
+ "urllib3",
262
+ "httpx",
263
+ "huggingface-hub",
264
+ "ipywidgets",
265
+ "jax[cuda12]",
266
+ "keras",
267
+ "matplotlib",
268
+ "nbformat",
269
+ "numba",
270
+ "numpy",
271
+ "openai",
272
+ "optax",
273
+ "pandas",
274
+ "plotly[express]",
275
+ "polars",
276
+ "psycopg2",
277
+ "requests",
278
+ "safetensors",
279
+ "scikit-image",
280
+ "scikit-learn",
281
+ "scipy",
282
+ "seaborn",
283
+ "sentencepiece",
284
+ "sqlalchemy",
285
+ "statsmodels",
286
+ "sympy",
287
+ "tabulate",
288
+ "tensorboard",
289
+ "toml",
290
+ "transformers",
291
+ "triton",
292
+ "typer",
293
+ "vega-datasets",
294
+ "watchfiles",
295
+ "websockets",
296
+ ]
297
+
298
+ # Kernelshim dependencies. (see NOTEBOOK_KERNELSHIM_DEPENDENCIES)
299
+ kernelshim_packages: list[str] = [
300
+ "authlib>=1.3",
301
+ "basedpyright>=1.28",
302
+ "fastapi>=0.100",
303
+ "ipykernel>=6",
304
+ "pydantic>=2",
305
+ "pyzmq>=26",
306
+ "ruff>=0.11",
307
+ "uvicorn>=0.32",
308
+ ]
309
+
310
+ commands: list[str] = [
311
+ "apt-get update",
312
+ "apt-get install -y "
313
+ + "libpq-dev pkg-config cmake git curl wget unzip zip libsqlite3-dev openssh-server vim ffmpeg",
314
+ _install_cuda_command(),
315
+ # Install uv since it's faster than pip for installing packages.
316
+ "pip install uv",
317
+ # https://github.com/astral-sh/uv/issues/11480
318
+ "pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu129",
319
+ f"uv pip install --system {shlex.join(sorted(environment_packages))}",
320
+ f"uv pip install --system {shlex.join(sorted(kernelshim_packages))}",
321
+ ]
322
+
323
+ def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
324
+ return DockerfileSpec(
325
+ commands=[
326
+ "FROM base",
327
+ *(f"RUN {cmd}" for cmd in commands),
328
+ "ENV PATH=/usr/local/cuda/bin:$PATH",
329
+ ],
330
+ context_files={},
331
+ )
332
+
333
+ return _Image._from_args(
334
+ base_images={"base": base_image},
335
+ dockerfile_function=build_dockerfile,
336
+ force_build=force_build,
337
+ _namespace=api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL,
338
+ )
339
+
340
+
341
+ @synchronizer.create_blocking
342
+ async def image_delete(
343
+ image_id: str,
169
344
  *,
170
- min_containers: Optional[int] = None,
171
- max_containers: Optional[int] = None,
172
- buffer_containers: Optional[int] = None,
173
- scaledown_window: Optional[int] = None,
174
345
  client: Optional[_Client] = None,
175
346
  ) -> None:
176
- """Update the autoscaler settings for a Function or Obj (instance of a Cls).
347
+ """Delete an Image by its ID.
348
+
349
+ Deletion is irreversible and will prevent Apps from using the Image.
177
350
 
178
351
  This is an experimental interface for a feature that we will be adding to
179
- replace the existing `.keep_warm()` method. The stable form of this interface
180
- may look different (i.e., it may be a standalone function or a method).
352
+ the main Image class. The stable form of this interface may look different.
181
353
 
354
+ Note: When building an Image, each chained method call will create an
355
+ intermediate Image layer, each with its own ID. Deleting an Image will not
356
+ delete any of its intermediate layers, only the image identified by the
357
+ provided ID.
182
358
  """
183
- deprecation_warning(
184
- (2025, 5, 5),
185
- "The modal.experimental.update_autoscaler(...) function is now deprecated in favor of"
186
- " a stable `.update_autoscaler(...) method on the corresponding object.",
187
- show_source=True,
188
- )
189
-
190
- settings = api_pb2.AutoscalerSettings(
191
- min_containers=min_containers,
192
- max_containers=max_containers,
193
- buffer_containers=buffer_containers,
194
- scaledown_window=scaledown_window,
195
- )
196
-
197
359
  if client is None:
198
360
  client = await _Client.from_env()
199
361
 
200
- if isinstance(obj, _Function):
201
- f = obj
202
- else:
203
- assert obj._cls._class_service_function is not None
204
- await obj._cls._class_service_function.hydrate(client=client)
205
- f = obj._cached_service_function()
206
- await f.hydrate(client=client)
207
-
208
- request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=f.object_id, settings=settings)
209
- await retry_transient_errors(client.stub.FunctionUpdateSchedulingParams, request)
362
+ req = api_pb2.ImageDeleteRequest(image_id=image_id)
363
+ await client.stub.ImageDelete(req)