modal 0.67.1__py3-none-any.whl → 0.67.33__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.
Files changed (113) hide show
  1. modal/_clustered_functions.py +2 -2
  2. modal/_clustered_functions.pyi +2 -2
  3. modal/_container_entrypoint.py +8 -5
  4. modal/_output.py +29 -28
  5. modal/_pty.py +2 -2
  6. modal/_resolver.py +6 -5
  7. modal/_resources.py +3 -3
  8. modal/_runtime/asgi.py +46 -6
  9. modal/_runtime/container_io_manager.py +22 -26
  10. modal/_runtime/execution_context.py +2 -2
  11. modal/_runtime/telemetry.py +1 -2
  12. modal/_runtime/user_code_imports.py +12 -14
  13. modal/_serialization.py +3 -7
  14. modal/_traceback.py +5 -5
  15. modal/_tunnel.py +5 -4
  16. modal/_tunnel.pyi +2 -2
  17. modal/_utils/async_utils.py +53 -17
  18. modal/_utils/blob_utils.py +22 -7
  19. modal/_utils/function_utils.py +20 -10
  20. modal/_utils/grpc_testing.py +7 -6
  21. modal/_utils/grpc_utils.py +2 -3
  22. modal/_utils/hash_utils.py +2 -2
  23. modal/_utils/mount_utils.py +5 -4
  24. modal/_utils/package_utils.py +2 -3
  25. modal/_utils/pattern_matcher.py +6 -6
  26. modal/_utils/rand_pb_testing.py +3 -3
  27. modal/_utils/shell_utils.py +2 -1
  28. modal/_vendor/a2wsgi_wsgi.py +62 -72
  29. modal/_vendor/cloudpickle.py +1 -1
  30. modal/_watcher.py +8 -7
  31. modal/app.py +68 -62
  32. modal/app.pyi +104 -99
  33. modal/call_graph.py +6 -6
  34. modal/cli/_download.py +3 -2
  35. modal/cli/_traceback.py +4 -4
  36. modal/cli/app.py +4 -4
  37. modal/cli/container.py +4 -4
  38. modal/cli/dict.py +1 -1
  39. modal/cli/environment.py +2 -3
  40. modal/cli/import_refs.py +1 -1
  41. modal/cli/launch.py +2 -2
  42. modal/cli/network_file_system.py +1 -1
  43. modal/cli/profile.py +1 -1
  44. modal/cli/programs/run_jupyter.py +2 -2
  45. modal/cli/programs/vscode.py +3 -3
  46. modal/cli/queues.py +1 -1
  47. modal/cli/run.py +6 -6
  48. modal/cli/secret.py +3 -3
  49. modal/cli/utils.py +2 -1
  50. modal/cli/volume.py +3 -3
  51. modal/client.py +6 -11
  52. modal/client.pyi +18 -27
  53. modal/cloud_bucket_mount.py +3 -3
  54. modal/cloud_bucket_mount.pyi +2 -2
  55. modal/cls.py +100 -47
  56. modal/cls.pyi +40 -40
  57. modal/config.py +3 -2
  58. modal/container_process.py +6 -2
  59. modal/dict.py +6 -3
  60. modal/dict.pyi +10 -9
  61. modal/environments.py +3 -3
  62. modal/environments.pyi +3 -3
  63. modal/exception.py +2 -3
  64. modal/functions.py +112 -104
  65. modal/functions.pyi +77 -58
  66. modal/image.py +59 -57
  67. modal/image.pyi +104 -103
  68. modal/io_streams.py +20 -12
  69. modal/io_streams.pyi +24 -14
  70. modal/mount.py +24 -24
  71. modal/mount.pyi +28 -29
  72. modal/network_file_system.py +14 -11
  73. modal/network_file_system.pyi +12 -11
  74. modal/object.py +9 -8
  75. modal/object.pyi +47 -34
  76. modal/output.py +2 -1
  77. modal/parallel_map.py +4 -4
  78. modal/partial_function.py +10 -14
  79. modal/partial_function.pyi +17 -18
  80. modal/queue.py +11 -8
  81. modal/queue.pyi +23 -22
  82. modal/retries.py +38 -0
  83. modal/runner.py +8 -7
  84. modal/runner.pyi +8 -14
  85. modal/running_app.py +3 -3
  86. modal/sandbox.py +20 -13
  87. modal/sandbox.pyi +73 -72
  88. modal/scheduler_placement.py +2 -1
  89. modal/secret.py +7 -7
  90. modal/secret.pyi +12 -12
  91. modal/serving.py +4 -3
  92. modal/serving.pyi +5 -4
  93. modal/token_flow.py +3 -2
  94. modal/token_flow.pyi +3 -3
  95. modal/volume.py +16 -23
  96. modal/volume.pyi +17 -16
  97. {modal-0.67.1.dist-info → modal-0.67.33.dist-info}/METADATA +2 -2
  98. modal-0.67.33.dist-info/RECORD +168 -0
  99. modal_docs/mdmd/signatures.py +1 -2
  100. modal_global_objects/mounts/python_standalone.py +1 -1
  101. modal_proto/api.proto +15 -0
  102. modal_proto/api_grpc.py +32 -0
  103. modal_proto/api_pb2.py +674 -654
  104. modal_proto/api_pb2.pyi +45 -1
  105. modal_proto/api_pb2_grpc.py +66 -0
  106. modal_proto/api_pb2_grpc.pyi +20 -0
  107. modal_proto/modal_api_grpc.py +2 -0
  108. modal_version/_version_generated.py +1 -1
  109. modal-0.67.1.dist-info/RECORD +0 -168
  110. {modal-0.67.1.dist-info → modal-0.67.33.dist-info}/LICENSE +0 -0
  111. {modal-0.67.1.dist-info → modal-0.67.33.dist-info}/WHEEL +0 -0
  112. {modal-0.67.1.dist-info → modal-0.67.33.dist-info}/entry_points.txt +0 -0
  113. {modal-0.67.1.dist-info → modal-0.67.33.dist-info}/top_level.txt +0 -0
modal/image.py CHANGED
@@ -7,18 +7,15 @@ import shlex
7
7
  import sys
8
8
  import typing
9
9
  import warnings
10
+ from collections.abc import Sequence
10
11
  from dataclasses import dataclass
11
12
  from inspect import isfunction
12
13
  from pathlib import Path, PurePosixPath
13
14
  from typing import (
14
15
  Any,
15
16
  Callable,
16
- Dict,
17
- List,
18
17
  Literal,
19
18
  Optional,
20
- Sequence,
21
- Set,
22
19
  Union,
23
20
  cast,
24
21
  get_args,
@@ -59,7 +56,7 @@ ImageBuilderVersion = Literal["2023.12", "2024.04", "2024.10"]
59
56
  # so that we fail fast / clearly in unsupported containers. Additionally, we enumerate the supported
60
57
  # Python versions in mount.py where we specify the "standalone Python versions" we create mounts for.
61
58
  # Consider consolidating these multiple sources of truth?
62
- SUPPORTED_PYTHON_SERIES: Dict[ImageBuilderVersion, List[str]] = {
59
+ SUPPORTED_PYTHON_SERIES: dict[ImageBuilderVersion, list[str]] = {
63
60
  "2024.10": ["3.9", "3.10", "3.11", "3.12", "3.13"],
64
61
  "2024.04": ["3.9", "3.10", "3.11", "3.12"],
65
62
  "2023.12": ["3.9", "3.10", "3.11", "3.12"],
@@ -74,7 +71,7 @@ def _validate_python_version(
74
71
  ) -> str:
75
72
  if python_version is None:
76
73
  # If Python version is unspecified, match the local version, up to the minor component
77
- python_version = series_version = "{0}.{1}".format(*sys.version_info)
74
+ python_version = series_version = "{}.{}".format(*sys.version_info)
78
75
  elif not isinstance(python_version, str):
79
76
  raise InvalidError(f"Python version must be specified as a string, not {type(python_version).__name__}")
80
77
  elif not re.match(r"^3(?:\.\d{1,2}){1,2}(rc\d*)?$", python_version):
@@ -86,7 +83,7 @@ def _validate_python_version(
86
83
  "Python version must be specified as 'major.minor' for this interface;"
87
84
  f" micro-level specification ({python_version!r}) is not valid."
88
85
  )
89
- series_version = "{0}.{1}".format(*components)
86
+ series_version = "{}.{}".format(*components)
90
87
 
91
88
  supported_series = SUPPORTED_PYTHON_SERIES[builder_version]
92
89
  if series_version not in supported_series:
@@ -111,13 +108,13 @@ def _dockerhub_python_version(builder_version: ImageBuilderVersion, python_versi
111
108
  # This allows us to publish one pre-built debian-slim image per Python series.
112
109
  python_versions = _base_image_config("python", builder_version)
113
110
  series_to_micro_version = dict(tuple(v.rsplit(".", 1)) for v in python_versions)
114
- python_series_requested = "{0}.{1}".format(*version_components)
111
+ python_series_requested = "{}.{}".format(*version_components)
115
112
  micro_version = series_to_micro_version[python_series_requested]
116
113
  return f"{python_series_requested}.{micro_version}"
117
114
 
118
115
 
119
116
  def _base_image_config(group: str, builder_version: ImageBuilderVersion) -> Any:
120
- with open(LOCAL_REQUIREMENTS_DIR / "base-images.json", "r") as f:
117
+ with open(LOCAL_REQUIREMENTS_DIR / "base-images.json") as f:
121
118
  data = json.load(f)
122
119
  return data[group][builder_version]
123
120
 
@@ -146,7 +143,7 @@ def _get_modal_requirements_command(version: ImageBuilderVersion) -> str:
146
143
  return f"{prefix} -r {CONTAINER_REQUIREMENTS_PATH}"
147
144
 
148
145
 
149
- def _flatten_str_args(function_name: str, arg_name: str, args: Sequence[Union[str, List[str]]]) -> List[str]:
146
+ def _flatten_str_args(function_name: str, arg_name: str, args: Sequence[Union[str, list[str]]]) -> list[str]:
150
147
  """Takes a sequence of strings, or string lists, and flattens it.
151
148
 
152
149
  Raises an error if any of the elements are not strings or string lists.
@@ -155,7 +152,7 @@ def _flatten_str_args(function_name: str, arg_name: str, args: Sequence[Union[st
155
152
  def is_str_list(x):
156
153
  return isinstance(x, list) and all(isinstance(y, str) for y in x)
157
154
 
158
- ret: List[str] = []
155
+ ret: list[str] = []
159
156
  for x in args:
160
157
  if isinstance(x, str):
161
158
  ret.append(x)
@@ -166,7 +163,7 @@ def _flatten_str_args(function_name: str, arg_name: str, args: Sequence[Union[st
166
163
  return ret
167
164
 
168
165
 
169
- def _validate_packages(packages: List[str]) -> bool:
166
+ def _validate_packages(packages: list[str]) -> bool:
170
167
  """Validates that a list of packages does not contain any command-line options."""
171
168
  return not any(pkg.startswith("-") for pkg in packages)
172
169
 
@@ -219,7 +216,7 @@ def _get_image_builder_version(server_version: ImageBuilderVersion) -> ImageBuil
219
216
  version_source = ""
220
217
  version = server_version
221
218
 
222
- supported_versions: Set[ImageBuilderVersion] = set(get_args(ImageBuilderVersion))
219
+ supported_versions: set[ImageBuilderVersion] = set(get_args(ImageBuilderVersion))
223
220
  if version not in supported_versions:
224
221
  if local_config_version is not None:
225
222
  update_suggestion = "or remove your local configuration"
@@ -259,8 +256,8 @@ class _ImageRegistryConfig:
259
256
  @dataclass
260
257
  class DockerfileSpec:
261
258
  # Ideally we would use field() with default_factory=, but doesn't work with synchronicity type-stub gen
262
- commands: List[str]
263
- context_files: Dict[str, str]
259
+ commands: list[str]
260
+ context_files: dict[str, str]
264
261
 
265
262
 
266
263
  async def _image_await_build_result(image_id: str, client: _Client) -> api_pb2.ImageJoinStreamingResponse:
@@ -310,8 +307,8 @@ class _Image(_Object, type_prefix="im"):
310
307
  """
311
308
 
312
309
  force_build: bool
313
- inside_exceptions: List[Exception]
314
- _serve_mounts: typing.FrozenSet[_Mount] # used for mounts watching in `modal serve`
310
+ inside_exceptions: list[Exception]
311
+ _serve_mounts: frozenset[_Mount] # used for mounts watching in `modal serve`
315
312
  _deferred_mounts: Sequence[
316
313
  _Mount
317
314
  ] # added as mounts on any container referencing the Image, see `def _mount_layers`
@@ -375,7 +372,7 @@ class _Image(_Object, type_prefix="im"):
375
372
  "\n"
376
373
  "Run `image.add_local_*` commands last in your image build to avoid rebuilding images with every local "
377
374
  "file change. Modal will then add these files to containers on startup instead, saving build time.\n"
378
- "If you need to run other build steps after adding local files, set `copy=True` to copy the files"
375
+ "If you need to run other build steps after adding local files, set `copy=True` to copy the files "
379
376
  "directly into the image, at the expense of some added build time.\n"
380
377
  "\n"
381
378
  "Example:\n"
@@ -390,7 +387,7 @@ class _Image(_Object, type_prefix="im"):
390
387
  @staticmethod
391
388
  def _from_args(
392
389
  *,
393
- base_images: Optional[Dict[str, "_Image"]] = None,
390
+ base_images: Optional[dict[str, "_Image"]] = None,
394
391
  dockerfile_function: Optional[Callable[[ImageBuilderVersion], DockerfileSpec]] = None,
395
392
  secrets: Optional[Sequence[_Secret]] = None,
396
393
  gpu_config: Optional[api_pb2.GPUConfig] = None,
@@ -680,13 +677,13 @@ class _Image(_Object, type_prefix="im"):
680
677
  context_mount=mount,
681
678
  )
682
679
 
683
- def _add_local_python_packages(self, *packages: str, copy: bool = False) -> "_Image":
684
- """Adds Python package files to containers
680
+ def add_local_python_source(self, *modules: str, copy: bool = False) -> "_Image":
681
+ """Adds locally available Python packages/modules to containers
685
682
 
686
- Adds all files from the specified Python packages to containers running the Image.
683
+ Adds all files from the specified Python package or module to containers running the Image.
687
684
 
688
685
  Packages are added to the `/root` directory of containers, which is on the `PYTHONPATH`
689
- of any executed Modal Functions.
686
+ of any executed Modal Functions, enabling import of the module by that name.
690
687
 
691
688
  By default (`copy=False`), the files are added to containers on startup and are not built into the actual Image,
692
689
  which speeds up deployment.
@@ -696,9 +693,14 @@ class _Image(_Object, type_prefix="im"):
696
693
  required if you want to run additional build steps after this one.
697
694
 
698
695
  **Note:** This excludes all dot-prefixed subdirectories or files and all `.pyc`/`__pycache__` files.
699
- To add full directories with finer control, use `.add_local_dir()` instead.
696
+ To add full directories with finer control, use `.add_local_dir()` instead and specify `/root` as
697
+ the destination directory.
700
698
  """
701
- mount = _Mount.from_local_python_packages(*packages)
699
+
700
+ def only_py_files(filename):
701
+ return filename.endswith(".py")
702
+
703
+ mount = _Mount.from_local_python_packages(*modules, condition=only_py_files)
702
704
  return self._add_mount_layer_or_copy(mount, copy=copy)
703
705
 
704
706
  def copy_local_dir(self, local_path: Union[str, Path], remote_path: Union[str, Path] = ".") -> "_Image":
@@ -720,7 +722,7 @@ class _Image(_Object, type_prefix="im"):
720
722
 
721
723
  def pip_install(
722
724
  self,
723
- *packages: Union[str, List[str]], # A list of Python packages, eg. ["numpy", "matplotlib>=3.5.0"]
725
+ *packages: Union[str, list[str]], # A list of Python packages, eg. ["numpy", "matplotlib>=3.5.0"]
724
726
  find_links: Optional[str] = None, # Passes -f (--find-links) pip install
725
727
  index_url: Optional[str] = None, # Passes -i (--index-url) to pip install
726
728
  extra_index_url: Optional[str] = None, # Passes --extra-index-url to pip install
@@ -762,7 +764,7 @@ class _Image(_Object, type_prefix="im"):
762
764
  return self
763
765
 
764
766
  def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
765
- package_args = " ".join(shlex.quote(pkg) for pkg in sorted(pkgs))
767
+ package_args = shlex.join(sorted(pkgs))
766
768
  extra_args = _make_pip_install_args(find_links, index_url, extra_index_url, pre, extra_options)
767
769
  commands = ["FROM base", f"RUN python -m pip install {package_args} {extra_args}"]
768
770
  if not _validate_packages(pkgs):
@@ -924,7 +926,7 @@ class _Image(_Object, type_prefix="im"):
924
926
  def pip_install_from_pyproject(
925
927
  self,
926
928
  pyproject_toml: str,
927
- optional_dependencies: List[str] = [],
929
+ optional_dependencies: list[str] = [],
928
930
  *,
929
931
  find_links: Optional[str] = None, # Passes -f (--find-links) pip install
930
932
  index_url: Optional[str] = None, # Passes -i (--index-url) to pip install
@@ -968,7 +970,7 @@ class _Image(_Object, type_prefix="im"):
968
970
  dependencies.extend(optionals[dep_group_name])
969
971
 
970
972
  extra_args = _make_pip_install_args(find_links, index_url, extra_index_url, pre, extra_options)
971
- package_args = " ".join(shlex.quote(pkg) for pkg in sorted(dependencies))
973
+ package_args = shlex.join(sorted(dependencies))
972
974
  commands = ["FROM base", f"RUN python -m pip install {package_args} {extra_args}"]
973
975
  if version > "2023.12": # Back-compat for legacy trailing space
974
976
  commands = [cmd.strip() for cmd in commands]
@@ -994,11 +996,11 @@ class _Image(_Object, type_prefix="im"):
994
996
  old_installer: bool = False,
995
997
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
996
998
  # Selected optional dependency groups to install (See https://python-poetry.org/docs/cli/#install)
997
- with_: List[str] = [],
999
+ with_: list[str] = [],
998
1000
  # Selected optional dependency groups to exclude (See https://python-poetry.org/docs/cli/#install)
999
- without: List[str] = [],
1001
+ without: list[str] = [],
1000
1002
  # Only install dependency groups specifed in this list.
1001
- only: List[str] = [],
1003
+ only: list[str] = [],
1002
1004
  *,
1003
1005
  secrets: Sequence[_Secret] = [],
1004
1006
  gpu: GPU_T = None,
@@ -1008,8 +1010,8 @@ class _Image(_Object, type_prefix="im"):
1008
1010
  If not provided as argument the path to the lockfile is inferred. However, the
1009
1011
  file has to exist, unless `ignore_lockfile` is set to `True`.
1010
1012
 
1011
- Note that the root project of the poetry project is not installed,
1012
- only the dependencies. For including local packages see `modal.Mount.from_local_python_packages`
1013
+ Note that the root project of the poetry project is not installed, only the dependencies.
1014
+ For including local python source files see `add_local_python_source`
1013
1015
  """
1014
1016
 
1015
1017
  def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
@@ -1066,8 +1068,8 @@ class _Image(_Object, type_prefix="im"):
1066
1068
 
1067
1069
  def dockerfile_commands(
1068
1070
  self,
1069
- *dockerfile_commands: Union[str, List[str]],
1070
- context_files: Dict[str, str] = {},
1071
+ *dockerfile_commands: Union[str, list[str]],
1072
+ context_files: dict[str, str] = {},
1071
1073
  secrets: Sequence[_Secret] = [],
1072
1074
  gpu: GPU_T = None,
1073
1075
  # modal.Mount with local files to supply as build context for COPY commands
@@ -1093,7 +1095,7 @@ class _Image(_Object, type_prefix="im"):
1093
1095
 
1094
1096
  def entrypoint(
1095
1097
  self,
1096
- entrypoint_commands: List[str],
1098
+ entrypoint_commands: list[str],
1097
1099
  ) -> "_Image":
1098
1100
  """Set the entrypoint for the image."""
1099
1101
  args_str = _flatten_str_args("entrypoint", "entrypoint_files", entrypoint_commands)
@@ -1104,7 +1106,7 @@ class _Image(_Object, type_prefix="im"):
1104
1106
 
1105
1107
  def shell(
1106
1108
  self,
1107
- shell_commands: List[str],
1109
+ shell_commands: list[str],
1108
1110
  ) -> "_Image":
1109
1111
  """Overwrite default shell for the image."""
1110
1112
  args_str = _flatten_str_args("shell", "shell_commands", shell_commands)
@@ -1115,7 +1117,7 @@ class _Image(_Object, type_prefix="im"):
1115
1117
 
1116
1118
  def run_commands(
1117
1119
  self,
1118
- *commands: Union[str, List[str]],
1120
+ *commands: Union[str, list[str]],
1119
1121
  secrets: Sequence[_Secret] = [],
1120
1122
  gpu: GPU_T = None,
1121
1123
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
@@ -1147,8 +1149,8 @@ class _Image(_Object, type_prefix="im"):
1147
1149
 
1148
1150
  def conda_install(
1149
1151
  self,
1150
- *packages: Union[str, List[str]], # A list of Python packages, eg. ["numpy", "matplotlib>=3.5.0"]
1151
- channels: List[str] = [], # A list of Conda channels, eg. ["conda-forge", "nvidia"]
1152
+ *packages: Union[str, list[str]], # A list of Python packages, eg. ["numpy", "matplotlib>=3.5.0"]
1153
+ channels: list[str] = [], # A list of Conda channels, eg. ["conda-forge", "nvidia"]
1152
1154
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1153
1155
  secrets: Sequence[_Secret] = [],
1154
1156
  gpu: GPU_T = None,
@@ -1208,11 +1210,11 @@ class _Image(_Object, type_prefix="im"):
1208
1210
  def micromamba_install(
1209
1211
  self,
1210
1212
  # A list of Python packages, eg. ["numpy", "matplotlib>=3.5.0"]
1211
- *packages: Union[str, List[str]],
1213
+ *packages: Union[str, list[str]],
1212
1214
  # A local path to a file containing package specifications
1213
1215
  spec_file: Optional[str] = None,
1214
1216
  # A list of Conda channels, eg. ["conda-forge", "nvidia"].
1215
- channels: List[str] = [],
1217
+ channels: list[str] = [],
1216
1218
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1217
1219
  secrets: Sequence[_Secret] = [],
1218
1220
  gpu: GPU_T = None,
@@ -1223,7 +1225,7 @@ class _Image(_Object, type_prefix="im"):
1223
1225
  return self
1224
1226
 
1225
1227
  def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
1226
- package_args = " ".join(shlex.quote(pkg) for pkg in pkgs)
1228
+ package_args = shlex.join(pkgs)
1227
1229
  channel_args = "".join(f" -c {channel}" for channel in channels)
1228
1230
 
1229
1231
  space = " " if package_args else ""
@@ -1251,10 +1253,10 @@ class _Image(_Object, type_prefix="im"):
1251
1253
  def _registry_setup_commands(
1252
1254
  tag: str,
1253
1255
  builder_version: ImageBuilderVersion,
1254
- setup_commands: List[str],
1256
+ setup_commands: list[str],
1255
1257
  add_python: Optional[str] = None,
1256
- ) -> List[str]:
1257
- add_python_commands: List[str] = []
1258
+ ) -> list[str]:
1259
+ add_python_commands: list[str] = []
1258
1260
  if add_python:
1259
1261
  _validate_python_version(add_python, builder_version, allow_micro_granularity=False)
1260
1262
  add_python_commands = [
@@ -1285,7 +1287,7 @@ class _Image(_Object, type_prefix="im"):
1285
1287
  tag: str,
1286
1288
  *,
1287
1289
  secret: Optional[_Secret] = None,
1288
- setup_dockerfile_commands: List[str] = [],
1290
+ setup_dockerfile_commands: list[str] = [],
1289
1291
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1290
1292
  add_python: Optional[str] = None,
1291
1293
  **kwargs,
@@ -1344,7 +1346,7 @@ class _Image(_Object, type_prefix="im"):
1344
1346
  tag: str,
1345
1347
  secret: Optional[_Secret] = None,
1346
1348
  *,
1347
- setup_dockerfile_commands: List[str] = [],
1349
+ setup_dockerfile_commands: list[str] = [],
1348
1350
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1349
1351
  add_python: Optional[str] = None,
1350
1352
  **kwargs,
@@ -1395,7 +1397,7 @@ class _Image(_Object, type_prefix="im"):
1395
1397
  tag: str,
1396
1398
  secret: Optional[_Secret] = None,
1397
1399
  *,
1398
- setup_dockerfile_commands: List[str] = [],
1400
+ setup_dockerfile_commands: list[str] = [],
1399
1401
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1400
1402
  add_python: Optional[str] = None,
1401
1403
  **kwargs,
@@ -1550,7 +1552,7 @@ class _Image(_Object, type_prefix="im"):
1550
1552
 
1551
1553
  def apt_install(
1552
1554
  self,
1553
- *packages: Union[str, List[str]], # A list of packages, e.g. ["ssh", "libpq-dev"]
1555
+ *packages: Union[str, list[str]], # A list of packages, e.g. ["ssh", "libpq-dev"]
1554
1556
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1555
1557
  secrets: Sequence[_Secret] = [],
1556
1558
  gpu: GPU_T = None,
@@ -1567,7 +1569,7 @@ class _Image(_Object, type_prefix="im"):
1567
1569
  if not pkgs:
1568
1570
  return self
1569
1571
 
1570
- package_args = " ".join(shlex.quote(pkg) for pkg in pkgs)
1572
+ package_args = shlex.join(pkgs)
1571
1573
 
1572
1574
  def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
1573
1575
  commands = [
@@ -1590,11 +1592,11 @@ class _Image(_Object, type_prefix="im"):
1590
1592
  raw_f: Callable[..., Any],
1591
1593
  secrets: Sequence[_Secret] = (), # Optional Modal Secret objects with environment variables for the container
1592
1594
  gpu: Union[
1593
- GPU_T, List[GPU_T]
1595
+ GPU_T, list[GPU_T]
1594
1596
  ] = None, # GPU request as string ("any", "T4", ...), object (`modal.GPU.A100()`, ...), or a list of either
1595
1597
  mounts: Sequence[_Mount] = (), # Mounts attached to the function
1596
- volumes: Dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {}, # Volume mount paths
1597
- network_file_systems: Dict[Union[str, PurePosixPath], _NetworkFileSystem] = {}, # NFS mount paths
1598
+ volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {}, # Volume mount paths
1599
+ network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem] = {}, # NFS mount paths
1598
1600
  cpu: Optional[float] = None, # How many CPU cores to request. This is a soft limit.
1599
1601
  memory: Optional[int] = None, # How much memory to request, in MiB. This is a soft limit.
1600
1602
  timeout: Optional[int] = 60 * 60, # Maximum execution time of the function in seconds.
@@ -1602,7 +1604,7 @@ class _Image(_Object, type_prefix="im"):
1602
1604
  cloud: Optional[str] = None, # Cloud provider to run the function on. Possible values are aws, gcp, oci, auto.
1603
1605
  region: Optional[Union[str, Sequence[str]]] = None, # Region or regions to run the function on.
1604
1606
  args: Sequence[Any] = (), # Positional arguments to the function.
1605
- kwargs: Dict[str, Any] = {}, # Keyword arguments to the function.
1607
+ kwargs: dict[str, Any] = {}, # Keyword arguments to the function.
1606
1608
  ) -> "_Image":
1607
1609
  """Run user-defined function `raw_f` as an image build step. The function runs just like an ordinary Modal
1608
1610
  function, and any kwargs accepted by `@app.function` (such as `Mount`s, `NetworkFileSystem`s,
@@ -1676,7 +1678,7 @@ class _Image(_Object, type_prefix="im"):
1676
1678
  force_build=self.force_build or force_build,
1677
1679
  )
1678
1680
 
1679
- def env(self, vars: Dict[str, str]) -> "_Image":
1681
+ def env(self, vars: dict[str, str]) -> "_Image":
1680
1682
  """Sets the environment variables in an Image.
1681
1683
 
1682
1684
  **Example**