modal 0.67.6__py3-none-any.whl → 0.67.11__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 (103) hide show
  1. modal/_clustered_functions.py +2 -2
  2. modal/_clustered_functions.pyi +2 -2
  3. modal/_container_entrypoint.py +5 -4
  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 +7 -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 +4 -3
  16. modal/_tunnel.pyi +2 -2
  17. modal/_utils/async_utils.py +8 -15
  18. modal/_utils/blob_utils.py +4 -3
  19. modal/_utils/function_utils.py +14 -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 +29 -34
  32. modal/app.pyi +102 -97
  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/launch.py +2 -2
  41. modal/cli/network_file_system.py +1 -1
  42. modal/cli/profile.py +1 -1
  43. modal/cli/programs/run_jupyter.py +2 -2
  44. modal/cli/programs/vscode.py +3 -3
  45. modal/cli/queues.py +1 -1
  46. modal/cli/run.py +6 -6
  47. modal/cli/secret.py +3 -3
  48. modal/cli/utils.py +2 -1
  49. modal/cli/volume.py +3 -3
  50. modal/client.py +6 -11
  51. modal/client.pyi +18 -27
  52. modal/cloud_bucket_mount.py +3 -3
  53. modal/cloud_bucket_mount.pyi +2 -2
  54. modal/cls.py +30 -30
  55. modal/cls.pyi +35 -34
  56. modal/config.py +3 -2
  57. modal/dict.py +4 -3
  58. modal/dict.pyi +10 -9
  59. modal/environments.py +3 -3
  60. modal/environments.pyi +3 -3
  61. modal/exception.py +2 -3
  62. modal/functions.py +105 -35
  63. modal/functions.pyi +71 -48
  64. modal/image.py +45 -48
  65. modal/image.pyi +102 -101
  66. modal/io_streams.py +4 -7
  67. modal/io_streams.pyi +14 -13
  68. modal/mount.py +23 -22
  69. modal/mount.pyi +28 -29
  70. modal/network_file_system.py +7 -6
  71. modal/network_file_system.pyi +12 -11
  72. modal/object.py +9 -8
  73. modal/object.pyi +47 -34
  74. modal/output.py +2 -1
  75. modal/parallel_map.py +4 -4
  76. modal/partial_function.py +9 -13
  77. modal/partial_function.pyi +17 -18
  78. modal/queue.py +9 -8
  79. modal/queue.pyi +23 -22
  80. modal/retries.py +38 -0
  81. modal/runner.py +8 -7
  82. modal/runner.pyi +8 -14
  83. modal/running_app.py +3 -3
  84. modal/sandbox.py +14 -13
  85. modal/sandbox.pyi +67 -72
  86. modal/scheduler_placement.py +2 -1
  87. modal/secret.py +7 -7
  88. modal/secret.pyi +12 -12
  89. modal/serving.py +4 -3
  90. modal/serving.pyi +5 -4
  91. modal/token_flow.py +3 -2
  92. modal/token_flow.pyi +3 -3
  93. modal/volume.py +7 -12
  94. modal/volume.pyi +17 -16
  95. {modal-0.67.6.dist-info → modal-0.67.11.dist-info}/METADATA +1 -1
  96. modal-0.67.11.dist-info/RECORD +168 -0
  97. modal_docs/mdmd/signatures.py +1 -2
  98. modal_version/_version_generated.py +1 -1
  99. modal-0.67.6.dist-info/RECORD +0 -168
  100. {modal-0.67.6.dist-info → modal-0.67.11.dist-info}/LICENSE +0 -0
  101. {modal-0.67.6.dist-info → modal-0.67.11.dist-info}/WHEEL +0 -0
  102. {modal-0.67.6.dist-info → modal-0.67.11.dist-info}/entry_points.txt +0 -0
  103. {modal-0.67.6.dist-info → modal-0.67.11.dist-info}/top_level.txt +0 -0
modal/functions.pyi CHANGED
@@ -1,3 +1,4 @@
1
+ import collections.abc
1
2
  import google.protobuf.message
2
3
  import modal._utils.async_utils
3
4
  import modal._utils.function_utils
@@ -25,11 +26,35 @@ import pathlib
25
26
  import typing
26
27
  import typing_extensions
27
28
 
29
+ class _RetryContext:
30
+ function_call_invocation_type: int
31
+ retry_policy: modal_proto.api_pb2.FunctionRetryPolicy
32
+ function_call_jwt: str
33
+ input_jwt: str
34
+ input_id: str
35
+ item: modal_proto.api_pb2.FunctionPutInputsItem
36
+
37
+ def __init__(
38
+ self,
39
+ function_call_invocation_type: int,
40
+ retry_policy: modal_proto.api_pb2.FunctionRetryPolicy,
41
+ function_call_jwt: str,
42
+ input_jwt: str,
43
+ input_id: str,
44
+ item: modal_proto.api_pb2.FunctionPutInputsItem,
45
+ ) -> None: ...
46
+ def __repr__(self): ...
47
+ def __eq__(self, other): ...
48
+
28
49
  class _Invocation:
29
50
  stub: modal_proto.modal_api_grpc.ModalClientModal
30
51
 
31
52
  def __init__(
32
- self, stub: modal_proto.modal_api_grpc.ModalClientModal, function_call_id: str, client: modal.client._Client
53
+ self,
54
+ stub: modal_proto.modal_api_grpc.ModalClientModal,
55
+ function_call_id: str,
56
+ client: modal.client._Client,
57
+ retry_context: typing.Optional[_RetryContext] = None,
33
58
  ): ...
34
59
  @staticmethod
35
60
  async def create(
@@ -38,6 +63,8 @@ class _Invocation:
38
63
  async def pop_function_call_outputs(
39
64
  self, timeout: typing.Optional[float], clear_on_success: bool
40
65
  ) -> modal_proto.api_pb2.FunctionGetOutputsResponse: ...
66
+ async def _retry_input(self) -> None: ...
67
+ async def _get_single_output(self) -> typing.Any: ...
41
68
  async def run_function(self) -> typing.Any: ...
42
69
  async def poll_function(self, timeout: typing.Optional[float] = None): ...
43
70
  def run_generator(self): ...
@@ -60,42 +87,38 @@ def _parse_retries(
60
87
 
61
88
  class _FunctionSpec:
62
89
  image: typing.Optional[modal.image._Image]
63
- mounts: typing.Sequence[modal.mount._Mount]
64
- secrets: typing.Sequence[modal.secret._Secret]
65
- network_file_systems: typing.Dict[
66
- typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem
67
- ]
68
- volumes: typing.Dict[
90
+ mounts: collections.abc.Sequence[modal.mount._Mount]
91
+ secrets: collections.abc.Sequence[modal.secret._Secret]
92
+ network_file_systems: dict[typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem]
93
+ volumes: dict[
69
94
  typing.Union[str, pathlib.PurePosixPath],
70
95
  typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
71
96
  ]
72
- gpus: typing.Union[
73
- None, bool, str, modal.gpu._GPUConfig, typing.List[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
74
- ]
97
+ gpus: typing.Union[None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]]
75
98
  cloud: typing.Optional[str]
76
99
  cpu: typing.Optional[float]
77
- memory: typing.Union[int, typing.Tuple[int, int], None]
100
+ memory: typing.Union[int, tuple[int, int], None]
78
101
  ephemeral_disk: typing.Optional[int]
79
102
  scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement]
80
103
 
81
104
  def __init__(
82
105
  self,
83
106
  image: typing.Optional[modal.image._Image],
84
- mounts: typing.Sequence[modal.mount._Mount],
85
- secrets: typing.Sequence[modal.secret._Secret],
86
- network_file_systems: typing.Dict[
107
+ mounts: collections.abc.Sequence[modal.mount._Mount],
108
+ secrets: collections.abc.Sequence[modal.secret._Secret],
109
+ network_file_systems: dict[
87
110
  typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem
88
111
  ],
89
- volumes: typing.Dict[
112
+ volumes: dict[
90
113
  typing.Union[str, pathlib.PurePosixPath],
91
114
  typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
92
115
  ],
93
116
  gpus: typing.Union[
94
- None, bool, str, modal.gpu._GPUConfig, typing.List[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
117
+ None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
95
118
  ],
96
119
  cloud: typing.Optional[str],
97
120
  cpu: typing.Optional[float],
98
- memory: typing.Union[int, typing.Tuple[int, int], None],
121
+ memory: typing.Union[int, tuple[int, int], None],
99
122
  ephemeral_disk: typing.Optional[int],
100
123
  scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement],
101
124
  ) -> None: ...
@@ -110,7 +133,7 @@ OriginalReturnType = typing.TypeVar("OriginalReturnType", covariant=True)
110
133
 
111
134
  class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object._Object):
112
135
  _info: typing.Optional[modal._utils.function_utils.FunctionInfo]
113
- _serve_mounts: typing.FrozenSet[modal.mount._Mount]
136
+ _serve_mounts: frozenset[modal.mount._Mount]
114
137
  _app: typing.Optional[modal.app._App]
115
138
  _obj: typing.Optional[modal.cls._Obj]
116
139
  _web_url: typing.Optional[str]
@@ -124,7 +147,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
124
147
  _cluster_size: typing.Optional[int]
125
148
  _use_method_name: str
126
149
  _class_parameter_info: typing.Optional[modal_proto.api_pb2.ClassParameterInfo]
127
- _method_handle_metadata: typing.Optional[typing.Dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
150
+ _method_handle_metadata: typing.Optional[dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
128
151
 
129
152
  def _bind_method(self, user_cls, method_name: str, partial_function: modal.partial_function._PartialFunction): ...
130
153
  def _bind_instance_method(self, class_bound_method: _Function): ...
@@ -133,23 +156,23 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
133
156
  info: modal._utils.function_utils.FunctionInfo,
134
157
  app,
135
158
  image: modal.image._Image,
136
- secrets: typing.Sequence[modal.secret._Secret] = (),
159
+ secrets: collections.abc.Sequence[modal.secret._Secret] = (),
137
160
  schedule: typing.Optional[modal.schedule.Schedule] = None,
138
161
  is_generator=False,
139
162
  gpu: typing.Union[
140
- None, bool, str, modal.gpu._GPUConfig, typing.List[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
163
+ None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
141
164
  ] = None,
142
- mounts: typing.Collection[modal.mount._Mount] = (),
143
- network_file_systems: typing.Dict[
165
+ mounts: collections.abc.Collection[modal.mount._Mount] = (),
166
+ network_file_systems: dict[
144
167
  typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem
145
168
  ] = {},
146
169
  allow_cross_region_volumes: bool = False,
147
- volumes: typing.Dict[
170
+ volumes: dict[
148
171
  typing.Union[str, pathlib.PurePosixPath],
149
172
  typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
150
173
  ] = {},
151
174
  webhook_config: typing.Optional[modal_proto.api_pb2.WebhookConfig] = None,
152
- memory: typing.Union[int, typing.Tuple[int, int], None] = None,
175
+ memory: typing.Union[int, tuple[int, int], None] = None,
153
176
  proxy: typing.Optional[modal.proxy._Proxy] = None,
154
177
  retries: typing.Union[int, modal.retries.Retries, None] = None,
155
178
  timeout: typing.Optional[int] = None,
@@ -179,13 +202,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
179
202
  obj: modal.cls._Obj,
180
203
  from_other_workspace: bool,
181
204
  options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
182
- args: typing.Sized,
183
- kwargs: typing.Dict[str, typing.Any],
205
+ args: collections.abc.Sized,
206
+ kwargs: dict[str, typing.Any],
184
207
  ) -> _Function: ...
185
208
  async def keep_warm(self, warm_pool_size: int) -> None: ...
186
209
  @classmethod
187
210
  def from_name(
188
- cls: typing.Type[_Function], app_name: str, tag: str, namespace=1, environment_name: typing.Optional[str] = None
211
+ cls: type[_Function], app_name: str, tag: str, namespace=1, environment_name: typing.Optional[str] = None
189
212
  ) -> _Function: ...
190
213
  @staticmethod
191
214
  async def lookup(
@@ -218,13 +241,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
218
241
  def cluster_size(self) -> int: ...
219
242
  def _map(
220
243
  self, input_queue: modal.parallel_map._SynchronizedQueue, order_outputs: bool, return_exceptions: bool
221
- ) -> typing.AsyncGenerator[typing.Any, None]: ...
244
+ ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
222
245
  async def _call_function(self, args, kwargs) -> ReturnType: ...
223
246
  async def _call_function_nowait(self, args, kwargs, function_call_invocation_type: int) -> _Invocation: ...
224
247
  def _call_generator(self, args, kwargs): ...
225
248
  async def _call_generator_nowait(self, args, kwargs): ...
226
249
  async def remote(self, *args: P.args, **kwargs: P.kwargs) -> ReturnType: ...
227
- def remote_gen(self, *args, **kwargs) -> typing.AsyncGenerator[typing.Any, None]: ...
250
+ def remote_gen(self, *args, **kwargs) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
228
251
  def _get_info(self) -> modal._utils.function_utils.FunctionInfo: ...
229
252
  def _get_obj(self) -> typing.Optional[modal.cls._Obj]: ...
230
253
  def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
@@ -279,7 +302,7 @@ P_INNER = typing_extensions.ParamSpec("P_INNER")
279
302
 
280
303
  class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.Object):
281
304
  _info: typing.Optional[modal._utils.function_utils.FunctionInfo]
282
- _serve_mounts: typing.FrozenSet[modal.mount.Mount]
305
+ _serve_mounts: frozenset[modal.mount.Mount]
283
306
  _app: typing.Optional[modal.app.App]
284
307
  _obj: typing.Optional[modal.cls.Obj]
285
308
  _web_url: typing.Optional[str]
@@ -293,7 +316,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
293
316
  _cluster_size: typing.Optional[int]
294
317
  _use_method_name: str
295
318
  _class_parameter_info: typing.Optional[modal_proto.api_pb2.ClassParameterInfo]
296
- _method_handle_metadata: typing.Optional[typing.Dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
319
+ _method_handle_metadata: typing.Optional[dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
297
320
 
298
321
  def __init__(self, *args, **kwargs): ...
299
322
  def _bind_method(self, user_cls, method_name: str, partial_function: modal.partial_function.PartialFunction): ...
@@ -303,23 +326,23 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
303
326
  info: modal._utils.function_utils.FunctionInfo,
304
327
  app,
305
328
  image: modal.image.Image,
306
- secrets: typing.Sequence[modal.secret.Secret] = (),
329
+ secrets: collections.abc.Sequence[modal.secret.Secret] = (),
307
330
  schedule: typing.Optional[modal.schedule.Schedule] = None,
308
331
  is_generator=False,
309
332
  gpu: typing.Union[
310
- None, bool, str, modal.gpu._GPUConfig, typing.List[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
333
+ None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
311
334
  ] = None,
312
- mounts: typing.Collection[modal.mount.Mount] = (),
313
- network_file_systems: typing.Dict[
335
+ mounts: collections.abc.Collection[modal.mount.Mount] = (),
336
+ network_file_systems: dict[
314
337
  typing.Union[str, pathlib.PurePosixPath], modal.network_file_system.NetworkFileSystem
315
338
  ] = {},
316
339
  allow_cross_region_volumes: bool = False,
317
- volumes: typing.Dict[
340
+ volumes: dict[
318
341
  typing.Union[str, pathlib.PurePosixPath],
319
342
  typing.Union[modal.volume.Volume, modal.cloud_bucket_mount.CloudBucketMount],
320
343
  ] = {},
321
344
  webhook_config: typing.Optional[modal_proto.api_pb2.WebhookConfig] = None,
322
- memory: typing.Union[int, typing.Tuple[int, int], None] = None,
345
+ memory: typing.Union[int, tuple[int, int], None] = None,
323
346
  proxy: typing.Optional[modal.proxy.Proxy] = None,
324
347
  retries: typing.Union[int, modal.retries.Retries, None] = None,
325
348
  timeout: typing.Optional[int] = None,
@@ -349,8 +372,8 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
349
372
  obj: modal.cls.Obj,
350
373
  from_other_workspace: bool,
351
374
  options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
352
- args: typing.Sized,
353
- kwargs: typing.Dict[str, typing.Any],
375
+ args: collections.abc.Sized,
376
+ kwargs: dict[str, typing.Any],
354
377
  ) -> Function: ...
355
378
 
356
379
  class __keep_warm_spec(typing_extensions.Protocol):
@@ -361,7 +384,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
361
384
 
362
385
  @classmethod
363
386
  def from_name(
364
- cls: typing.Type[Function], app_name: str, tag: str, namespace=1, environment_name: typing.Optional[str] = None
387
+ cls: type[Function], app_name: str, tag: str, namespace=1, environment_name: typing.Optional[str] = None
365
388
  ) -> Function: ...
366
389
 
367
390
  class __lookup_spec(typing_extensions.Protocol):
@@ -412,7 +435,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
412
435
  ) -> typing.Generator[typing.Any, None, None]: ...
413
436
  def aio(
414
437
  self, input_queue: modal.parallel_map.SynchronizedQueue, order_outputs: bool, return_exceptions: bool
415
- ) -> typing.AsyncGenerator[typing.Any, None]: ...
438
+ ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
416
439
 
417
440
  _map: ___map_spec
418
441
 
@@ -444,7 +467,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
444
467
 
445
468
  class __remote_gen_spec(typing_extensions.Protocol):
446
469
  def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
447
- def aio(self, *args, **kwargs) -> typing.AsyncGenerator[typing.Any, None]: ...
470
+ def aio(self, *args, **kwargs) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
448
471
 
449
472
  remote_gen: __remote_gen_spec
450
473
 
@@ -517,8 +540,8 @@ class _FunctionCall(typing.Generic[ReturnType], modal.object._Object):
517
540
 
518
541
  def _invocation(self): ...
519
542
  async def get(self, timeout: typing.Optional[float] = None) -> ReturnType: ...
520
- def get_gen(self) -> typing.AsyncGenerator[typing.Any, None]: ...
521
- async def get_call_graph(self) -> typing.List[modal.call_graph.InputInfo]: ...
543
+ def get_gen(self) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
544
+ async def get_call_graph(self) -> list[modal.call_graph.InputInfo]: ...
522
545
  async def cancel(self, terminate_containers: bool = False): ...
523
546
  @staticmethod
524
547
  async def from_id(
@@ -539,13 +562,13 @@ class FunctionCall(typing.Generic[ReturnType], modal.object.Object):
539
562
 
540
563
  class __get_gen_spec(typing_extensions.Protocol):
541
564
  def __call__(self) -> typing.Generator[typing.Any, None, None]: ...
542
- def aio(self) -> typing.AsyncGenerator[typing.Any, None]: ...
565
+ def aio(self) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
543
566
 
544
567
  get_gen: __get_gen_spec
545
568
 
546
569
  class __get_call_graph_spec(typing_extensions.Protocol):
547
- def __call__(self) -> typing.List[modal.call_graph.InputInfo]: ...
548
- async def aio(self) -> typing.List[modal.call_graph.InputInfo]: ...
570
+ def __call__(self) -> list[modal.call_graph.InputInfo]: ...
571
+ async def aio(self) -> list[modal.call_graph.InputInfo]: ...
549
572
 
550
573
  get_call_graph: __get_call_graph_spec
551
574
 
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`
@@ -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,
@@ -720,7 +717,7 @@ class _Image(_Object, type_prefix="im"):
720
717
 
721
718
  def pip_install(
722
719
  self,
723
- *packages: Union[str, List[str]], # A list of Python packages, eg. ["numpy", "matplotlib>=3.5.0"]
720
+ *packages: Union[str, list[str]], # A list of Python packages, eg. ["numpy", "matplotlib>=3.5.0"]
724
721
  find_links: Optional[str] = None, # Passes -f (--find-links) pip install
725
722
  index_url: Optional[str] = None, # Passes -i (--index-url) to pip install
726
723
  extra_index_url: Optional[str] = None, # Passes --extra-index-url to pip install
@@ -762,7 +759,7 @@ class _Image(_Object, type_prefix="im"):
762
759
  return self
763
760
 
764
761
  def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
765
- package_args = " ".join(shlex.quote(pkg) for pkg in sorted(pkgs))
762
+ package_args = shlex.join(sorted(pkgs))
766
763
  extra_args = _make_pip_install_args(find_links, index_url, extra_index_url, pre, extra_options)
767
764
  commands = ["FROM base", f"RUN python -m pip install {package_args} {extra_args}"]
768
765
  if not _validate_packages(pkgs):
@@ -924,7 +921,7 @@ class _Image(_Object, type_prefix="im"):
924
921
  def pip_install_from_pyproject(
925
922
  self,
926
923
  pyproject_toml: str,
927
- optional_dependencies: List[str] = [],
924
+ optional_dependencies: list[str] = [],
928
925
  *,
929
926
  find_links: Optional[str] = None, # Passes -f (--find-links) pip install
930
927
  index_url: Optional[str] = None, # Passes -i (--index-url) to pip install
@@ -968,7 +965,7 @@ class _Image(_Object, type_prefix="im"):
968
965
  dependencies.extend(optionals[dep_group_name])
969
966
 
970
967
  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))
968
+ package_args = shlex.join(sorted(dependencies))
972
969
  commands = ["FROM base", f"RUN python -m pip install {package_args} {extra_args}"]
973
970
  if version > "2023.12": # Back-compat for legacy trailing space
974
971
  commands = [cmd.strip() for cmd in commands]
@@ -994,11 +991,11 @@ class _Image(_Object, type_prefix="im"):
994
991
  old_installer: bool = False,
995
992
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
996
993
  # Selected optional dependency groups to install (See https://python-poetry.org/docs/cli/#install)
997
- with_: List[str] = [],
994
+ with_: list[str] = [],
998
995
  # Selected optional dependency groups to exclude (See https://python-poetry.org/docs/cli/#install)
999
- without: List[str] = [],
996
+ without: list[str] = [],
1000
997
  # Only install dependency groups specifed in this list.
1001
- only: List[str] = [],
998
+ only: list[str] = [],
1002
999
  *,
1003
1000
  secrets: Sequence[_Secret] = [],
1004
1001
  gpu: GPU_T = None,
@@ -1066,8 +1063,8 @@ class _Image(_Object, type_prefix="im"):
1066
1063
 
1067
1064
  def dockerfile_commands(
1068
1065
  self,
1069
- *dockerfile_commands: Union[str, List[str]],
1070
- context_files: Dict[str, str] = {},
1066
+ *dockerfile_commands: Union[str, list[str]],
1067
+ context_files: dict[str, str] = {},
1071
1068
  secrets: Sequence[_Secret] = [],
1072
1069
  gpu: GPU_T = None,
1073
1070
  # modal.Mount with local files to supply as build context for COPY commands
@@ -1093,7 +1090,7 @@ class _Image(_Object, type_prefix="im"):
1093
1090
 
1094
1091
  def entrypoint(
1095
1092
  self,
1096
- entrypoint_commands: List[str],
1093
+ entrypoint_commands: list[str],
1097
1094
  ) -> "_Image":
1098
1095
  """Set the entrypoint for the image."""
1099
1096
  args_str = _flatten_str_args("entrypoint", "entrypoint_files", entrypoint_commands)
@@ -1104,7 +1101,7 @@ class _Image(_Object, type_prefix="im"):
1104
1101
 
1105
1102
  def shell(
1106
1103
  self,
1107
- shell_commands: List[str],
1104
+ shell_commands: list[str],
1108
1105
  ) -> "_Image":
1109
1106
  """Overwrite default shell for the image."""
1110
1107
  args_str = _flatten_str_args("shell", "shell_commands", shell_commands)
@@ -1115,7 +1112,7 @@ class _Image(_Object, type_prefix="im"):
1115
1112
 
1116
1113
  def run_commands(
1117
1114
  self,
1118
- *commands: Union[str, List[str]],
1115
+ *commands: Union[str, list[str]],
1119
1116
  secrets: Sequence[_Secret] = [],
1120
1117
  gpu: GPU_T = None,
1121
1118
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
@@ -1147,8 +1144,8 @@ class _Image(_Object, type_prefix="im"):
1147
1144
 
1148
1145
  def conda_install(
1149
1146
  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"]
1147
+ *packages: Union[str, list[str]], # A list of Python packages, eg. ["numpy", "matplotlib>=3.5.0"]
1148
+ channels: list[str] = [], # A list of Conda channels, eg. ["conda-forge", "nvidia"]
1152
1149
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1153
1150
  secrets: Sequence[_Secret] = [],
1154
1151
  gpu: GPU_T = None,
@@ -1208,11 +1205,11 @@ class _Image(_Object, type_prefix="im"):
1208
1205
  def micromamba_install(
1209
1206
  self,
1210
1207
  # A list of Python packages, eg. ["numpy", "matplotlib>=3.5.0"]
1211
- *packages: Union[str, List[str]],
1208
+ *packages: Union[str, list[str]],
1212
1209
  # A local path to a file containing package specifications
1213
1210
  spec_file: Optional[str] = None,
1214
1211
  # A list of Conda channels, eg. ["conda-forge", "nvidia"].
1215
- channels: List[str] = [],
1212
+ channels: list[str] = [],
1216
1213
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1217
1214
  secrets: Sequence[_Secret] = [],
1218
1215
  gpu: GPU_T = None,
@@ -1223,7 +1220,7 @@ class _Image(_Object, type_prefix="im"):
1223
1220
  return self
1224
1221
 
1225
1222
  def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
1226
- package_args = " ".join(shlex.quote(pkg) for pkg in pkgs)
1223
+ package_args = shlex.join(pkgs)
1227
1224
  channel_args = "".join(f" -c {channel}" for channel in channels)
1228
1225
 
1229
1226
  space = " " if package_args else ""
@@ -1251,10 +1248,10 @@ class _Image(_Object, type_prefix="im"):
1251
1248
  def _registry_setup_commands(
1252
1249
  tag: str,
1253
1250
  builder_version: ImageBuilderVersion,
1254
- setup_commands: List[str],
1251
+ setup_commands: list[str],
1255
1252
  add_python: Optional[str] = None,
1256
- ) -> List[str]:
1257
- add_python_commands: List[str] = []
1253
+ ) -> list[str]:
1254
+ add_python_commands: list[str] = []
1258
1255
  if add_python:
1259
1256
  _validate_python_version(add_python, builder_version, allow_micro_granularity=False)
1260
1257
  add_python_commands = [
@@ -1285,7 +1282,7 @@ class _Image(_Object, type_prefix="im"):
1285
1282
  tag: str,
1286
1283
  *,
1287
1284
  secret: Optional[_Secret] = None,
1288
- setup_dockerfile_commands: List[str] = [],
1285
+ setup_dockerfile_commands: list[str] = [],
1289
1286
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1290
1287
  add_python: Optional[str] = None,
1291
1288
  **kwargs,
@@ -1344,7 +1341,7 @@ class _Image(_Object, type_prefix="im"):
1344
1341
  tag: str,
1345
1342
  secret: Optional[_Secret] = None,
1346
1343
  *,
1347
- setup_dockerfile_commands: List[str] = [],
1344
+ setup_dockerfile_commands: list[str] = [],
1348
1345
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1349
1346
  add_python: Optional[str] = None,
1350
1347
  **kwargs,
@@ -1395,7 +1392,7 @@ class _Image(_Object, type_prefix="im"):
1395
1392
  tag: str,
1396
1393
  secret: Optional[_Secret] = None,
1397
1394
  *,
1398
- setup_dockerfile_commands: List[str] = [],
1395
+ setup_dockerfile_commands: list[str] = [],
1399
1396
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1400
1397
  add_python: Optional[str] = None,
1401
1398
  **kwargs,
@@ -1550,7 +1547,7 @@ class _Image(_Object, type_prefix="im"):
1550
1547
 
1551
1548
  def apt_install(
1552
1549
  self,
1553
- *packages: Union[str, List[str]], # A list of packages, e.g. ["ssh", "libpq-dev"]
1550
+ *packages: Union[str, list[str]], # A list of packages, e.g. ["ssh", "libpq-dev"]
1554
1551
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1555
1552
  secrets: Sequence[_Secret] = [],
1556
1553
  gpu: GPU_T = None,
@@ -1567,7 +1564,7 @@ class _Image(_Object, type_prefix="im"):
1567
1564
  if not pkgs:
1568
1565
  return self
1569
1566
 
1570
- package_args = " ".join(shlex.quote(pkg) for pkg in pkgs)
1567
+ package_args = shlex.join(pkgs)
1571
1568
 
1572
1569
  def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
1573
1570
  commands = [
@@ -1590,11 +1587,11 @@ class _Image(_Object, type_prefix="im"):
1590
1587
  raw_f: Callable[..., Any],
1591
1588
  secrets: Sequence[_Secret] = (), # Optional Modal Secret objects with environment variables for the container
1592
1589
  gpu: Union[
1593
- GPU_T, List[GPU_T]
1590
+ GPU_T, list[GPU_T]
1594
1591
  ] = None, # GPU request as string ("any", "T4", ...), object (`modal.GPU.A100()`, ...), or a list of either
1595
1592
  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
1593
+ volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {}, # Volume mount paths
1594
+ network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem] = {}, # NFS mount paths
1598
1595
  cpu: Optional[float] = None, # How many CPU cores to request. This is a soft limit.
1599
1596
  memory: Optional[int] = None, # How much memory to request, in MiB. This is a soft limit.
1600
1597
  timeout: Optional[int] = 60 * 60, # Maximum execution time of the function in seconds.
@@ -1602,7 +1599,7 @@ class _Image(_Object, type_prefix="im"):
1602
1599
  cloud: Optional[str] = None, # Cloud provider to run the function on. Possible values are aws, gcp, oci, auto.
1603
1600
  region: Optional[Union[str, Sequence[str]]] = None, # Region or regions to run the function on.
1604
1601
  args: Sequence[Any] = (), # Positional arguments to the function.
1605
- kwargs: Dict[str, Any] = {}, # Keyword arguments to the function.
1602
+ kwargs: dict[str, Any] = {}, # Keyword arguments to the function.
1606
1603
  ) -> "_Image":
1607
1604
  """Run user-defined function `raw_f` as an image build step. The function runs just like an ordinary Modal
1608
1605
  function, and any kwargs accepted by `@app.function` (such as `Mount`s, `NetworkFileSystem`s,
@@ -1676,7 +1673,7 @@ class _Image(_Object, type_prefix="im"):
1676
1673
  force_build=self.force_build or force_build,
1677
1674
  )
1678
1675
 
1679
- def env(self, vars: Dict[str, str]) -> "_Image":
1676
+ def env(self, vars: dict[str, str]) -> "_Image":
1680
1677
  """Sets the environment variables in an Image.
1681
1678
 
1682
1679
  **Example**