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/mount.py CHANGED
@@ -9,8 +9,9 @@ import sys
9
9
  import sysconfig
10
10
  import time
11
11
  import typing
12
+ from collections.abc import AsyncGenerator
12
13
  from pathlib import Path, PurePosixPath
13
- from typing import AsyncGenerator, Callable, List, Optional, Tuple, Type, Union
14
+ from typing import Callable, Optional, Union
14
15
 
15
16
  from google.protobuf.message import Message
16
17
 
@@ -36,7 +37,7 @@ MOUNT_PUT_FILE_CLIENT_TIMEOUT = 10 * 60 # 10 min max for transferring files
36
37
  #
37
38
  # These can be updated safely, but changes will trigger a rebuild for all images
38
39
  # that rely on `add_python()` in their constructor.
39
- PYTHON_STANDALONE_VERSIONS: typing.Dict[str, typing.Tuple[str, str]] = {
40
+ PYTHON_STANDALONE_VERSIONS: dict[str, tuple[str, str]] = {
40
41
  "3.9": ("20230826", "3.9.18"),
41
42
  "3.10": ("20230826", "3.10.13"),
42
43
  "3.11": ("20230826", "3.11.5"),
@@ -73,21 +74,21 @@ class _MountEntry(metaclass=abc.ABCMeta):
73
74
  ...
74
75
 
75
76
  @abc.abstractmethod
76
- def get_files_to_upload(self) -> typing.Iterator[Tuple[Path, str]]:
77
+ def get_files_to_upload(self) -> typing.Iterator[tuple[Path, str]]:
77
78
  ...
78
79
 
79
80
  @abc.abstractmethod
80
- def watch_entry(self) -> Tuple[Path, Path]:
81
+ def watch_entry(self) -> tuple[Path, Path]:
81
82
  ...
82
83
 
83
84
  @abc.abstractmethod
84
- def top_level_paths(self) -> List[Tuple[Path, PurePosixPath]]:
85
+ def top_level_paths(self) -> list[tuple[Path, PurePosixPath]]:
85
86
  ...
86
87
 
87
88
 
88
- def _select_files(entries: List[_MountEntry]) -> List[Tuple[Path, PurePosixPath]]:
89
+ def _select_files(entries: list[_MountEntry]) -> list[tuple[Path, PurePosixPath]]:
89
90
  # TODO: make this async
90
- all_files: typing.Set[Tuple[Path, PurePosixPath]] = set()
91
+ all_files: set[tuple[Path, PurePosixPath]] = set()
91
92
  for entry in entries:
92
93
  all_files |= set(entry.get_files_to_upload())
93
94
  return list(all_files)
@@ -113,7 +114,7 @@ class _MountFile(_MountEntry):
113
114
  safe_path = self.local_file.expanduser().absolute()
114
115
  return safe_path.parent, safe_path
115
116
 
116
- def top_level_paths(self) -> List[Tuple[Path, PurePosixPath]]:
117
+ def top_level_paths(self) -> list[tuple[Path, PurePosixPath]]:
117
118
  return [(self.local_file, self.remote_path)]
118
119
 
119
120
 
@@ -150,7 +151,7 @@ class _MountDir(_MountEntry):
150
151
  def watch_entry(self):
151
152
  return self.local_dir.resolve().expanduser(), None
152
153
 
153
- def top_level_paths(self) -> List[Tuple[Path, PurePosixPath]]:
154
+ def top_level_paths(self) -> list[tuple[Path, PurePosixPath]]:
154
155
  return [(self.local_dir, self.remote_path)]
155
156
 
156
157
 
@@ -194,7 +195,7 @@ class _MountedPythonModule(_MountEntry):
194
195
  def description(self) -> str:
195
196
  return f"PythonPackage:{self.module_name}"
196
197
 
197
- def _proxy_entries(self) -> List[_MountEntry]:
198
+ def _proxy_entries(self) -> list[_MountEntry]:
198
199
  mount_infos = get_module_mount_info(self.module_name)
199
200
  entries = []
200
201
  for mount_info in mount_infos:
@@ -220,16 +221,16 @@ class _MountedPythonModule(_MountEntry):
220
221
  )
221
222
  return entries
222
223
 
223
- def get_files_to_upload(self) -> typing.Iterator[Tuple[Path, str]]:
224
+ def get_files_to_upload(self) -> typing.Iterator[tuple[Path, str]]:
224
225
  for entry in self._proxy_entries():
225
226
  yield from entry.get_files_to_upload()
226
227
 
227
- def watch_entry(self) -> Tuple[Path, Path]:
228
+ def watch_entry(self) -> tuple[Path, Path]:
228
229
  for entry in self._proxy_entries():
229
230
  # TODO: fix watch for mounts of multi-path packages
230
231
  return entry.watch_entry()
231
232
 
232
- def top_level_paths(self) -> List[Tuple[Path, PurePosixPath]]:
233
+ def top_level_paths(self) -> list[tuple[Path, PurePosixPath]]:
233
234
  paths = []
234
235
  for sub in self._proxy_entries():
235
236
  paths.extend(sub.top_level_paths())
@@ -262,14 +263,14 @@ class _Mount(_Object, type_prefix="mo"):
262
263
  the file's contents to skip uploading files that have been uploaded before.
263
264
  """
264
265
 
265
- _entries: Optional[List[_MountEntry]] = None
266
+ _entries: Optional[list[_MountEntry]] = None
266
267
  _deployment_name: Optional[str] = None
267
268
  _namespace: Optional[int] = None
268
269
  _environment_name: Optional[str] = None
269
270
  _content_checksum_sha256_hex: Optional[str] = None
270
271
 
271
272
  @staticmethod
272
- def _new(entries: List[_MountEntry] = []) -> "_Mount":
273
+ def _new(entries: list[_MountEntry] = []) -> "_Mount":
273
274
  rep = f"Mount({entries})"
274
275
 
275
276
  async def mount_content_deduplication_key():
@@ -298,10 +299,10 @@ class _Mount(_Object, type_prefix="mo"):
298
299
  assert isinstance(handle_metadata, api_pb2.MountHandleMetadata)
299
300
  self._content_checksum_sha256_hex = handle_metadata.content_checksum_sha256_hex
300
301
 
301
- def _top_level_paths(self) -> List[Tuple[Path, PurePosixPath]]:
302
+ def _top_level_paths(self) -> list[tuple[Path, PurePosixPath]]:
302
303
  # Returns [(local_absolute_path, remote_path), ...] for all top level entries in the Mount
303
304
  # Used to determine if a package mount is installed in a sys directory or not
304
- res: List[Tuple[Path, PurePosixPath]] = []
305
+ res: list[tuple[Path, PurePosixPath]] = []
305
306
  for entry in self.entries:
306
307
  res.extend(entry.top_level_paths())
307
308
  return res
@@ -413,12 +414,12 @@ class _Mount(_Object, type_prefix="mo"):
413
414
  return _Mount._new().add_local_file(local_path, remote_path=remote_path)
414
415
 
415
416
  @staticmethod
416
- def _description(entries: List[_MountEntry]) -> str:
417
+ def _description(entries: list[_MountEntry]) -> str:
417
418
  local_contents = [e.description() for e in entries]
418
419
  return ", ".join(local_contents)
419
420
 
420
421
  @staticmethod
421
- async def _get_files(entries: List[_MountEntry]) -> AsyncGenerator[FileUploadSpec, None]:
422
+ async def _get_files(entries: list[_MountEntry]) -> AsyncGenerator[FileUploadSpec, None]:
422
423
  loop = asyncio.get_event_loop()
423
424
  with concurrent.futures.ThreadPoolExecutor() as exe:
424
425
  all_files = await loop.run_in_executor(exe, _select_files, entries)
@@ -502,7 +503,7 @@ class _Mount(_Object, type_prefix="mo"):
502
503
 
503
504
  # Upload files, or check if they already exist.
504
505
  n_concurrent_uploads = 512
505
- files: List[api_pb2.MountFile] = []
506
+ files: list[api_pb2.MountFile] = []
506
507
  async with aclosing(
507
508
  async_map(_Mount._get_files(self._entries), _put_file, concurrency=n_concurrent_uploads)
508
509
  ) as stream:
@@ -602,7 +603,7 @@ class _Mount(_Object, type_prefix="mo"):
602
603
 
603
604
  @classmethod
604
605
  async def lookup(
605
- cls: Type["_Mount"],
606
+ cls: type["_Mount"],
606
607
  label: str,
607
608
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
608
609
  client: Optional[_Client] = None,
@@ -715,7 +716,7 @@ def _is_modal_path(remote_path: PurePosixPath):
715
716
  return False
716
717
 
717
718
 
718
- def get_auto_mounts() -> typing.List[_Mount]:
719
+ def get_auto_mounts() -> list[_Mount]:
719
720
  """mdmd:hidden
720
721
 
721
722
  Auto-mount local modules that have been imported in global scope.
modal/mount.pyi CHANGED
@@ -1,3 +1,4 @@
1
+ import collections.abc
1
2
  import google.protobuf.message
2
3
  import modal._resolver
3
4
  import modal._utils.blob_utils
@@ -13,13 +14,11 @@ def python_standalone_mount_name(version: str) -> str: ...
13
14
 
14
15
  class _MountEntry:
15
16
  def description(self) -> str: ...
16
- def get_files_to_upload(self) -> typing.Iterator[typing.Tuple[pathlib.Path, str]]: ...
17
- def watch_entry(self) -> typing.Tuple[pathlib.Path, pathlib.Path]: ...
18
- def top_level_paths(self) -> typing.List[typing.Tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
17
+ def get_files_to_upload(self) -> typing.Iterator[tuple[pathlib.Path, str]]: ...
18
+ def watch_entry(self) -> tuple[pathlib.Path, pathlib.Path]: ...
19
+ def top_level_paths(self) -> list[tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
19
20
 
20
- def _select_files(
21
- entries: typing.List[_MountEntry],
22
- ) -> typing.List[typing.Tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
21
+ def _select_files(entries: list[_MountEntry]) -> list[tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
23
22
 
24
23
  class _MountFile(_MountEntry):
25
24
  local_file: pathlib.Path
@@ -28,7 +27,7 @@ class _MountFile(_MountEntry):
28
27
  def description(self) -> str: ...
29
28
  def get_files_to_upload(self): ...
30
29
  def watch_entry(self): ...
31
- def top_level_paths(self) -> typing.List[typing.Tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
30
+ def top_level_paths(self) -> list[tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
32
31
  def __init__(self, local_file: pathlib.Path, remote_path: pathlib.PurePosixPath) -> None: ...
33
32
  def __repr__(self): ...
34
33
  def __eq__(self, other): ...
@@ -42,7 +41,7 @@ class _MountDir(_MountEntry):
42
41
  def description(self): ...
43
42
  def get_files_to_upload(self): ...
44
43
  def watch_entry(self): ...
45
- def top_level_paths(self) -> typing.List[typing.Tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
44
+ def top_level_paths(self) -> list[tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
46
45
  def __init__(
47
46
  self,
48
47
  local_dir: pathlib.Path,
@@ -61,10 +60,10 @@ class _MountedPythonModule(_MountEntry):
61
60
  condition: typing.Optional[typing.Callable[[str], bool]]
62
61
 
63
62
  def description(self) -> str: ...
64
- def _proxy_entries(self) -> typing.List[_MountEntry]: ...
65
- def get_files_to_upload(self) -> typing.Iterator[typing.Tuple[pathlib.Path, str]]: ...
66
- def watch_entry(self) -> typing.Tuple[pathlib.Path, pathlib.Path]: ...
67
- def top_level_paths(self) -> typing.List[typing.Tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
63
+ def _proxy_entries(self) -> list[_MountEntry]: ...
64
+ def get_files_to_upload(self) -> typing.Iterator[tuple[pathlib.Path, str]]: ...
65
+ def watch_entry(self) -> tuple[pathlib.Path, pathlib.Path]: ...
66
+ def top_level_paths(self) -> list[tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
68
67
  def __init__(
69
68
  self,
70
69
  module_name: str,
@@ -77,19 +76,19 @@ class _MountedPythonModule(_MountEntry):
77
76
  class NonLocalMountError(Exception): ...
78
77
 
79
78
  class _Mount(modal.object._Object):
80
- _entries: typing.Optional[typing.List[_MountEntry]]
79
+ _entries: typing.Optional[list[_MountEntry]]
81
80
  _deployment_name: typing.Optional[str]
82
81
  _namespace: typing.Optional[int]
83
82
  _environment_name: typing.Optional[str]
84
83
  _content_checksum_sha256_hex: typing.Optional[str]
85
84
 
86
85
  @staticmethod
87
- def _new(entries: typing.List[_MountEntry] = []) -> _Mount: ...
86
+ def _new(entries: list[_MountEntry] = []) -> _Mount: ...
88
87
  def _extend(self, entry: _MountEntry) -> _Mount: ...
89
88
  @property
90
89
  def entries(self): ...
91
90
  def _hydrate_metadata(self, handle_metadata: typing.Optional[google.protobuf.message.Message]): ...
92
- def _top_level_paths(self) -> typing.List[typing.Tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
91
+ def _top_level_paths(self) -> list[tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
93
92
  def is_local(self) -> bool: ...
94
93
  def add_local_dir(
95
94
  self,
@@ -117,11 +116,11 @@ class _Mount(modal.object._Object):
117
116
  local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None
118
117
  ) -> _Mount: ...
119
118
  @staticmethod
120
- def _description(entries: typing.List[_MountEntry]) -> str: ...
119
+ def _description(entries: list[_MountEntry]) -> str: ...
121
120
  @staticmethod
122
121
  def _get_files(
123
- entries: typing.List[_MountEntry],
124
- ) -> typing.AsyncGenerator[modal._utils.blob_utils.FileUploadSpec, None]: ...
122
+ entries: list[_MountEntry],
123
+ ) -> collections.abc.AsyncGenerator[modal._utils.blob_utils.FileUploadSpec, None]: ...
125
124
  async def _load_mount(
126
125
  self: _Mount, resolver: modal._resolver.Resolver, existing_object_id: typing.Optional[str]
127
126
  ): ...
@@ -135,7 +134,7 @@ class _Mount(modal.object._Object):
135
134
  def from_name(label: str, namespace=1, environment_name: typing.Optional[str] = None) -> _Mount: ...
136
135
  @classmethod
137
136
  async def lookup(
138
- cls: typing.Type[_Mount],
137
+ cls: type[_Mount],
139
138
  label: str,
140
139
  namespace=1,
141
140
  client: typing.Optional[modal.client._Client] = None,
@@ -151,7 +150,7 @@ class _Mount(modal.object._Object):
151
150
  def _get_metadata(self) -> modal_proto.api_pb2.MountHandleMetadata: ...
152
151
 
153
152
  class Mount(modal.object.Object):
154
- _entries: typing.Optional[typing.List[_MountEntry]]
153
+ _entries: typing.Optional[list[_MountEntry]]
155
154
  _deployment_name: typing.Optional[str]
156
155
  _namespace: typing.Optional[int]
157
156
  _environment_name: typing.Optional[str]
@@ -159,12 +158,12 @@ class Mount(modal.object.Object):
159
158
 
160
159
  def __init__(self, *args, **kwargs): ...
161
160
  @staticmethod
162
- def _new(entries: typing.List[_MountEntry] = []) -> Mount: ...
161
+ def _new(entries: list[_MountEntry] = []) -> Mount: ...
163
162
  def _extend(self, entry: _MountEntry) -> Mount: ...
164
163
  @property
165
164
  def entries(self): ...
166
165
  def _hydrate_metadata(self, handle_metadata: typing.Optional[google.protobuf.message.Message]): ...
167
- def _top_level_paths(self) -> typing.List[typing.Tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
166
+ def _top_level_paths(self) -> list[tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
168
167
  def is_local(self) -> bool: ...
169
168
  def add_local_dir(
170
169
  self,
@@ -192,15 +191,15 @@ class Mount(modal.object.Object):
192
191
  local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None
193
192
  ) -> Mount: ...
194
193
  @staticmethod
195
- def _description(entries: typing.List[_MountEntry]) -> str: ...
194
+ def _description(entries: list[_MountEntry]) -> str: ...
196
195
 
197
196
  class ___get_files_spec(typing_extensions.Protocol):
198
197
  def __call__(
199
- self, entries: typing.List[_MountEntry]
198
+ self, entries: list[_MountEntry]
200
199
  ) -> typing.Generator[modal._utils.blob_utils.FileUploadSpec, None, None]: ...
201
200
  def aio(
202
- self, entries: typing.List[_MountEntry]
203
- ) -> typing.AsyncGenerator[modal._utils.blob_utils.FileUploadSpec, None]: ...
201
+ self, entries: list[_MountEntry]
202
+ ) -> collections.abc.AsyncGenerator[modal._utils.blob_utils.FileUploadSpec, None]: ...
204
203
 
205
204
  _get_files: ___get_files_spec
206
205
 
@@ -220,7 +219,7 @@ class Mount(modal.object.Object):
220
219
  def from_name(label: str, namespace=1, environment_name: typing.Optional[str] = None) -> Mount: ...
221
220
  @classmethod
222
221
  def lookup(
223
- cls: typing.Type[Mount],
222
+ cls: type[Mount],
224
223
  label: str,
225
224
  namespace=1,
226
225
  client: typing.Optional[modal.client.Client] = None,
@@ -251,8 +250,8 @@ def _create_client_mount(): ...
251
250
  def create_client_mount(): ...
252
251
  def _get_client_mount(): ...
253
252
  def _is_modal_path(remote_path: pathlib.PurePosixPath): ...
254
- def get_auto_mounts() -> typing.List[_Mount]: ...
253
+ def get_auto_mounts() -> list[_Mount]: ...
255
254
 
256
255
  ROOT_DIR: pathlib.PurePosixPath
257
256
 
258
- PYTHON_STANDALONE_VERSIONS: typing.Dict[str, typing.Tuple[str, str]]
257
+ PYTHON_STANDALONE_VERSIONS: dict[str, tuple[str, str]]
@@ -2,8 +2,9 @@
2
2
  import functools
3
3
  import os
4
4
  import time
5
+ from collections.abc import AsyncIterator
5
6
  from pathlib import Path, PurePosixPath
6
- from typing import Any, AsyncIterator, BinaryIO, Callable, List, Optional, Tuple, Type, Union
7
+ from typing import Any, BinaryIO, Callable, Optional, Union
7
8
 
8
9
  from grpclib import GRPCError, Status
9
10
  from synchronicity.async_wrap import asynccontextmanager
@@ -34,9 +35,9 @@ NETWORK_FILE_SYSTEM_PUT_FILE_CLIENT_TIMEOUT = (
34
35
 
35
36
 
36
37
  def network_file_system_mount_protos(
37
- validated_network_file_systems: List[Tuple[str, "_NetworkFileSystem"]],
38
+ validated_network_file_systems: list[tuple[str, "_NetworkFileSystem"]],
38
39
  allow_cross_region_volumes: bool,
39
- ) -> List[api_pb2.SharedVolumeMount]:
40
+ ) -> list[api_pb2.SharedVolumeMount]:
40
41
  network_file_system_mounts = []
41
42
  # Relies on dicts being ordered (true as of Python 3.6).
42
43
  for path, volume in validated_network_file_systems:
@@ -143,7 +144,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
143
144
  @classmethod
144
145
  @asynccontextmanager
145
146
  async def ephemeral(
146
- cls: Type["_NetworkFileSystem"],
147
+ cls: type["_NetworkFileSystem"],
147
148
  client: Optional[_Client] = None,
148
149
  environment_name: Optional[str] = None,
149
150
  _heartbeat_sleep: float = EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
@@ -329,7 +330,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
329
330
  relpath_str = subpath.relative_to(_local_path).as_posix()
330
331
  yield subpath, PurePosixPath(remote_path, relpath_str)
331
332
 
332
- async def _add_local_file(paths: Tuple[Path, PurePosixPath]) -> int:
333
+ async def _add_local_file(paths: tuple[Path, PurePosixPath]) -> int:
333
334
  return await self.add_local_file(paths[0], paths[1], progress_cb)
334
335
 
335
336
  async with aclosing(async_map(sync_or_async_iter(gen_transfers()), _add_local_file, concurrency=20)) as stream:
@@ -337,7 +338,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
337
338
  pass
338
339
 
339
340
  @live_method
340
- async def listdir(self, path: str) -> List[FileEntry]:
341
+ async def listdir(self, path: str) -> list[FileEntry]:
341
342
  """List all files in a directory in the network file system.
342
343
 
343
344
  * Passing a directory path lists all files in the directory (names are relative to the directory)
@@ -1,3 +1,4 @@
1
+ import collections.abc
1
2
  import modal.client
2
3
  import modal.object
3
4
  import modal.volume
@@ -8,8 +9,8 @@ import typing
8
9
  import typing_extensions
9
10
 
10
11
  def network_file_system_mount_protos(
11
- validated_network_file_systems: typing.List[typing.Tuple[str, _NetworkFileSystem]], allow_cross_region_volumes: bool
12
- ) -> typing.List[modal_proto.api_pb2.SharedVolumeMount]: ...
12
+ validated_network_file_systems: list[tuple[str, _NetworkFileSystem]], allow_cross_region_volumes: bool
13
+ ) -> list[modal_proto.api_pb2.SharedVolumeMount]: ...
13
14
 
14
15
  class _NetworkFileSystem(modal.object._Object):
15
16
  @staticmethod
@@ -20,7 +21,7 @@ class _NetworkFileSystem(modal.object._Object):
20
21
  ) -> _NetworkFileSystem: ...
21
22
  @classmethod
22
23
  def ephemeral(
23
- cls: typing.Type[_NetworkFileSystem],
24
+ cls: type[_NetworkFileSystem],
24
25
  client: typing.Optional[modal.client._Client] = None,
25
26
  environment_name: typing.Optional[str] = None,
26
27
  _heartbeat_sleep: float = 300,
@@ -46,8 +47,8 @@ class _NetworkFileSystem(modal.object._Object):
46
47
  fp: typing.BinaryIO,
47
48
  progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
48
49
  ) -> int: ...
49
- def read_file(self, path: str) -> typing.AsyncIterator[bytes]: ...
50
- def iterdir(self, path: str) -> typing.AsyncIterator[modal.volume.FileEntry]: ...
50
+ def read_file(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
51
+ def iterdir(self, path: str) -> collections.abc.AsyncIterator[modal.volume.FileEntry]: ...
51
52
  async def add_local_file(
52
53
  self,
53
54
  local_path: typing.Union[pathlib.Path, str],
@@ -60,7 +61,7 @@ class _NetworkFileSystem(modal.object._Object):
60
61
  remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
61
62
  progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
62
63
  ): ...
63
- async def listdir(self, path: str) -> typing.List[modal.volume.FileEntry]: ...
64
+ async def listdir(self, path: str) -> list[modal.volume.FileEntry]: ...
64
65
  async def remove_file(self, path: str, recursive=False): ...
65
66
 
66
67
  class NetworkFileSystem(modal.object.Object):
@@ -73,7 +74,7 @@ class NetworkFileSystem(modal.object.Object):
73
74
  ) -> NetworkFileSystem: ...
74
75
  @classmethod
75
76
  def ephemeral(
76
- cls: typing.Type[NetworkFileSystem],
77
+ cls: type[NetworkFileSystem],
77
78
  client: typing.Optional[modal.client.Client] = None,
78
79
  environment_name: typing.Optional[str] = None,
79
80
  _heartbeat_sleep: float = 300,
@@ -135,13 +136,13 @@ class NetworkFileSystem(modal.object.Object):
135
136
 
136
137
  class __read_file_spec(typing_extensions.Protocol):
137
138
  def __call__(self, path: str) -> typing.Iterator[bytes]: ...
138
- def aio(self, path: str) -> typing.AsyncIterator[bytes]: ...
139
+ def aio(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
139
140
 
140
141
  read_file: __read_file_spec
141
142
 
142
143
  class __iterdir_spec(typing_extensions.Protocol):
143
144
  def __call__(self, path: str) -> typing.Iterator[modal.volume.FileEntry]: ...
144
- def aio(self, path: str) -> typing.AsyncIterator[modal.volume.FileEntry]: ...
145
+ def aio(self, path: str) -> collections.abc.AsyncIterator[modal.volume.FileEntry]: ...
145
146
 
146
147
  iterdir: __iterdir_spec
147
148
 
@@ -178,8 +179,8 @@ class NetworkFileSystem(modal.object.Object):
178
179
  add_local_dir: __add_local_dir_spec
179
180
 
180
181
  class __listdir_spec(typing_extensions.Protocol):
181
- def __call__(self, path: str) -> typing.List[modal.volume.FileEntry]: ...
182
- async def aio(self, path: str) -> typing.List[modal.volume.FileEntry]: ...
182
+ def __call__(self, path: str) -> list[modal.volume.FileEntry]: ...
183
+ async def aio(self, path: str) -> list[modal.volume.FileEntry]: ...
183
184
 
184
185
  listdir: __listdir_spec
185
186
 
modal/object.py CHANGED
@@ -1,7 +1,8 @@
1
1
  # Copyright Modal Labs 2022
2
2
  import uuid
3
+ from collections.abc import Awaitable, Hashable, Sequence
3
4
  from functools import wraps
4
- from typing import Awaitable, Callable, ClassVar, Dict, Hashable, List, Optional, Sequence, Type, TypeVar
5
+ from typing import Callable, ClassVar, Optional, TypeVar
5
6
 
6
7
  from google.protobuf.message import Message
7
8
 
@@ -31,7 +32,7 @@ def _get_environment_name(environment_name: Optional[str] = None, resolver: Opti
31
32
 
32
33
  class _Object:
33
34
  _type_prefix: ClassVar[Optional[str]] = None
34
- _prefix_to_type: ClassVar[Dict[str, type]] = {}
35
+ _prefix_to_type: ClassVar[dict[str, type]] = {}
35
36
 
36
37
  # For constructors
37
38
  _load: Optional[Callable[[O, Resolver, Optional[str]], Awaitable[None]]]
@@ -39,7 +40,7 @@ class _Object:
39
40
  _rep: str
40
41
  _is_another_app: bool
41
42
  _hydrate_lazily: bool
42
- _deps: Optional[Callable[..., List["_Object"]]]
43
+ _deps: Optional[Callable[..., list["_Object"]]]
43
44
  _deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None
44
45
 
45
46
  # For hydrated objects
@@ -65,7 +66,7 @@ class _Object:
65
66
  is_another_app: bool = False,
66
67
  preload: Optional[Callable[[O, Resolver, Optional[str]], Awaitable[None]]] = None,
67
68
  hydrate_lazily: bool = False,
68
- deps: Optional[Callable[..., List["_Object"]]] = None,
69
+ deps: Optional[Callable[..., list["_Object"]]] = None,
69
70
  deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
70
71
  ):
71
72
  self._local_uuid = str(uuid.uuid4())
@@ -159,7 +160,7 @@ class _Object:
159
160
  return obj
160
161
 
161
162
  @classmethod
162
- def _get_type_from_id(cls: Type[O], object_id: str) -> Type[O]:
163
+ def _get_type_from_id(cls: type[O], object_id: str) -> type[O]:
163
164
  parts = object_id.split("-")
164
165
  if len(parts) != 2:
165
166
  raise InvalidError(f"Object id {object_id} has no dash in it")
@@ -169,12 +170,12 @@ class _Object:
169
170
  return cls._prefix_to_type[prefix]
170
171
 
171
172
  @classmethod
172
- def _is_id_type(cls: Type[O], object_id) -> bool:
173
+ def _is_id_type(cls: type[O], object_id) -> bool:
173
174
  return cls._get_type_from_id(object_id) == cls
174
175
 
175
176
  @classmethod
176
177
  def _new_hydrated(
177
- cls: Type[O], object_id: str, client: _Client, handle_metadata: Optional[Message], is_another_app: bool = False
178
+ cls: type[O], object_id: str, client: _Client, handle_metadata: Optional[Message], is_another_app: bool = False
178
179
  ) -> O:
179
180
  if cls._type_prefix is not None:
180
181
  # This is called directly on a subclass, e.g. Secret.from_id
@@ -215,7 +216,7 @@ class _Object:
215
216
  return self._is_hydrated
216
217
 
217
218
  @property
218
- def deps(self) -> Callable[..., List["_Object"]]:
219
+ def deps(self) -> Callable[..., list["_Object"]]:
219
220
  """mdmd:hidden"""
220
221
  return self._deps if self._deps is not None else lambda: []
221
222