modal 0.62.115__py3-none-any.whl → 0.72.13__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 (220) hide show
  1. modal/__init__.py +13 -9
  2. modal/__main__.py +41 -3
  3. modal/_clustered_functions.py +80 -0
  4. modal/_clustered_functions.pyi +22 -0
  5. modal/_container_entrypoint.py +402 -398
  6. modal/_ipython.py +3 -13
  7. modal/_location.py +17 -10
  8. modal/_output.py +243 -99
  9. modal/_pty.py +2 -2
  10. modal/_resolver.py +55 -60
  11. modal/_resources.py +26 -7
  12. modal/_runtime/__init__.py +1 -0
  13. modal/_runtime/asgi.py +519 -0
  14. modal/_runtime/container_io_manager.py +1025 -0
  15. modal/{execution_context.py → _runtime/execution_context.py} +11 -2
  16. modal/_runtime/telemetry.py +169 -0
  17. modal/_runtime/user_code_imports.py +356 -0
  18. modal/_serialization.py +123 -6
  19. modal/_traceback.py +47 -187
  20. modal/_tunnel.py +50 -14
  21. modal/_tunnel.pyi +19 -36
  22. modal/_utils/app_utils.py +3 -17
  23. modal/_utils/async_utils.py +386 -104
  24. modal/_utils/blob_utils.py +157 -186
  25. modal/_utils/bytes_io_segment_payload.py +97 -0
  26. modal/_utils/deprecation.py +89 -0
  27. modal/_utils/docker_utils.py +98 -0
  28. modal/_utils/function_utils.py +299 -98
  29. modal/_utils/grpc_testing.py +47 -34
  30. modal/_utils/grpc_utils.py +54 -21
  31. modal/_utils/hash_utils.py +51 -10
  32. modal/_utils/http_utils.py +39 -9
  33. modal/_utils/logger.py +2 -1
  34. modal/_utils/mount_utils.py +34 -16
  35. modal/_utils/name_utils.py +58 -0
  36. modal/_utils/package_utils.py +14 -1
  37. modal/_utils/pattern_utils.py +205 -0
  38. modal/_utils/rand_pb_testing.py +3 -3
  39. modal/_utils/shell_utils.py +15 -49
  40. modal/_vendor/a2wsgi_wsgi.py +62 -72
  41. modal/_vendor/cloudpickle.py +1 -1
  42. modal/_watcher.py +12 -10
  43. modal/app.py +561 -323
  44. modal/app.pyi +474 -262
  45. modal/call_graph.py +7 -6
  46. modal/cli/_download.py +22 -6
  47. modal/cli/_traceback.py +200 -0
  48. modal/cli/app.py +203 -42
  49. modal/cli/config.py +12 -5
  50. modal/cli/container.py +61 -13
  51. modal/cli/dict.py +128 -0
  52. modal/cli/entry_point.py +26 -13
  53. modal/cli/environment.py +40 -9
  54. modal/cli/import_refs.py +21 -48
  55. modal/cli/launch.py +28 -14
  56. modal/cli/network_file_system.py +57 -21
  57. modal/cli/profile.py +1 -1
  58. modal/cli/programs/run_jupyter.py +34 -9
  59. modal/cli/programs/vscode.py +58 -8
  60. modal/cli/queues.py +131 -0
  61. modal/cli/run.py +199 -96
  62. modal/cli/secret.py +5 -4
  63. modal/cli/token.py +7 -2
  64. modal/cli/utils.py +74 -8
  65. modal/cli/volume.py +97 -56
  66. modal/client.py +248 -144
  67. modal/client.pyi +156 -124
  68. modal/cloud_bucket_mount.py +43 -30
  69. modal/cloud_bucket_mount.pyi +32 -25
  70. modal/cls.py +528 -141
  71. modal/cls.pyi +189 -145
  72. modal/config.py +32 -15
  73. modal/container_process.py +177 -0
  74. modal/container_process.pyi +82 -0
  75. modal/dict.py +50 -54
  76. modal/dict.pyi +120 -164
  77. modal/environments.py +106 -5
  78. modal/environments.pyi +77 -25
  79. modal/exception.py +30 -43
  80. modal/experimental.py +62 -2
  81. modal/file_io.py +537 -0
  82. modal/file_io.pyi +235 -0
  83. modal/file_pattern_matcher.py +196 -0
  84. modal/functions.py +846 -428
  85. modal/functions.pyi +446 -387
  86. modal/gpu.py +57 -44
  87. modal/image.py +943 -417
  88. modal/image.pyi +584 -245
  89. modal/io_streams.py +434 -0
  90. modal/io_streams.pyi +122 -0
  91. modal/mount.py +223 -90
  92. modal/mount.pyi +241 -243
  93. modal/network_file_system.py +85 -86
  94. modal/network_file_system.pyi +151 -110
  95. modal/object.py +66 -36
  96. modal/object.pyi +166 -143
  97. modal/output.py +63 -0
  98. modal/parallel_map.py +73 -47
  99. modal/parallel_map.pyi +51 -63
  100. modal/partial_function.py +272 -107
  101. modal/partial_function.pyi +219 -120
  102. modal/proxy.py +15 -12
  103. modal/proxy.pyi +3 -8
  104. modal/queue.py +96 -72
  105. modal/queue.pyi +210 -135
  106. modal/requirements/2024.04.txt +2 -1
  107. modal/requirements/2024.10.txt +16 -0
  108. modal/requirements/README.md +21 -0
  109. modal/requirements/base-images.json +22 -0
  110. modal/retries.py +45 -4
  111. modal/runner.py +325 -203
  112. modal/runner.pyi +124 -110
  113. modal/running_app.py +27 -4
  114. modal/sandbox.py +509 -231
  115. modal/sandbox.pyi +396 -169
  116. modal/schedule.py +2 -2
  117. modal/scheduler_placement.py +20 -3
  118. modal/secret.py +41 -25
  119. modal/secret.pyi +62 -42
  120. modal/serving.py +39 -49
  121. modal/serving.pyi +37 -43
  122. modal/stream_type.py +15 -0
  123. modal/token_flow.py +5 -3
  124. modal/token_flow.pyi +37 -32
  125. modal/volume.py +123 -137
  126. modal/volume.pyi +228 -221
  127. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/METADATA +5 -5
  128. modal-0.72.13.dist-info/RECORD +174 -0
  129. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/top_level.txt +0 -1
  130. modal_docs/gen_reference_docs.py +3 -1
  131. modal_docs/mdmd/mdmd.py +0 -1
  132. modal_docs/mdmd/signatures.py +1 -2
  133. modal_global_objects/images/base_images.py +28 -0
  134. modal_global_objects/mounts/python_standalone.py +2 -2
  135. modal_proto/__init__.py +1 -1
  136. modal_proto/api.proto +1231 -531
  137. modal_proto/api_grpc.py +750 -430
  138. modal_proto/api_pb2.py +2102 -1176
  139. modal_proto/api_pb2.pyi +8859 -0
  140. modal_proto/api_pb2_grpc.py +1329 -675
  141. modal_proto/api_pb2_grpc.pyi +1416 -0
  142. modal_proto/modal_api_grpc.py +149 -0
  143. modal_proto/modal_options_grpc.py +3 -0
  144. modal_proto/options_pb2.pyi +20 -0
  145. modal_proto/options_pb2_grpc.pyi +7 -0
  146. modal_proto/py.typed +0 -0
  147. modal_version/__init__.py +1 -1
  148. modal_version/_version_generated.py +2 -2
  149. modal/_asgi.py +0 -370
  150. modal/_container_exec.py +0 -128
  151. modal/_container_io_manager.py +0 -646
  152. modal/_container_io_manager.pyi +0 -412
  153. modal/_sandbox_shell.py +0 -49
  154. modal/app_utils.py +0 -20
  155. modal/app_utils.pyi +0 -17
  156. modal/execution_context.pyi +0 -37
  157. modal/shared_volume.py +0 -23
  158. modal/shared_volume.pyi +0 -24
  159. modal-0.62.115.dist-info/RECORD +0 -207
  160. modal_global_objects/images/conda.py +0 -15
  161. modal_global_objects/images/debian_slim.py +0 -15
  162. modal_global_objects/images/micromamba.py +0 -15
  163. test/__init__.py +0 -1
  164. test/aio_test.py +0 -12
  165. test/async_utils_test.py +0 -279
  166. test/blob_test.py +0 -67
  167. test/cli_imports_test.py +0 -149
  168. test/cli_test.py +0 -674
  169. test/client_test.py +0 -203
  170. test/cloud_bucket_mount_test.py +0 -22
  171. test/cls_test.py +0 -636
  172. test/config_test.py +0 -149
  173. test/conftest.py +0 -1485
  174. test/container_app_test.py +0 -50
  175. test/container_test.py +0 -1405
  176. test/cpu_test.py +0 -23
  177. test/decorator_test.py +0 -85
  178. test/deprecation_test.py +0 -34
  179. test/dict_test.py +0 -51
  180. test/e2e_test.py +0 -68
  181. test/error_test.py +0 -7
  182. test/function_serialization_test.py +0 -32
  183. test/function_test.py +0 -791
  184. test/function_utils_test.py +0 -101
  185. test/gpu_test.py +0 -159
  186. test/grpc_utils_test.py +0 -82
  187. test/helpers.py +0 -47
  188. test/image_test.py +0 -814
  189. test/live_reload_test.py +0 -80
  190. test/lookup_test.py +0 -70
  191. test/mdmd_test.py +0 -329
  192. test/mount_test.py +0 -162
  193. test/mounted_files_test.py +0 -327
  194. test/network_file_system_test.py +0 -188
  195. test/notebook_test.py +0 -66
  196. test/object_test.py +0 -41
  197. test/package_utils_test.py +0 -25
  198. test/queue_test.py +0 -115
  199. test/resolver_test.py +0 -59
  200. test/retries_test.py +0 -67
  201. test/runner_test.py +0 -85
  202. test/sandbox_test.py +0 -191
  203. test/schedule_test.py +0 -15
  204. test/scheduler_placement_test.py +0 -57
  205. test/secret_test.py +0 -89
  206. test/serialization_test.py +0 -50
  207. test/stub_composition_test.py +0 -10
  208. test/stub_test.py +0 -361
  209. test/test_asgi_wrapper.py +0 -234
  210. test/token_flow_test.py +0 -18
  211. test/traceback_test.py +0 -135
  212. test/tunnel_test.py +0 -29
  213. test/utils_test.py +0 -88
  214. test/version_test.py +0 -14
  215. test/volume_test.py +0 -397
  216. test/watcher_test.py +0 -58
  217. test/webhook_test.py +0 -145
  218. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/LICENSE +0 -0
  219. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/WHEEL +0 -0
  220. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/entry_points.txt +0 -0
modal/object.py CHANGED
@@ -1,24 +1,27 @@
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, Type, TypeVar
5
+ from typing import Callable, ClassVar, Optional, TypeVar
5
6
 
6
7
  from google.protobuf.message import Message
7
8
 
9
+ from modal._utils.async_utils import aclosing
10
+
8
11
  from ._resolver import Resolver
9
12
  from ._utils.async_utils import synchronize_api
10
13
  from .client import _Client
11
- from .config import config
14
+ from .config import config, logger
12
15
  from .exception import ExecutionError, InvalidError
13
16
 
14
17
  O = TypeVar("O", bound="_Object")
15
18
 
16
19
  _BLOCKING_O = synchronize_api(O)
17
20
 
18
- EPHEMERAL_OBJECT_HEARTBEAT_SLEEP = 300
21
+ EPHEMERAL_OBJECT_HEARTBEAT_SLEEP: int = 300
19
22
 
20
23
 
21
- def _get_environment_name(environment_name: Optional[str], resolver: Optional[Resolver] = None) -> Optional[str]:
24
+ def _get_environment_name(environment_name: Optional[str] = None, resolver: Optional[Resolver] = None) -> Optional[str]:
22
25
  if environment_name:
23
26
  return environment_name
24
27
  elif resolver and resolver.environment_name:
@@ -29,7 +32,7 @@ def _get_environment_name(environment_name: Optional[str], resolver: Optional[Re
29
32
 
30
33
  class _Object:
31
34
  _type_prefix: ClassVar[Optional[str]] = None
32
- _prefix_to_type: ClassVar[Dict[str, type]] = {}
35
+ _prefix_to_type: ClassVar[dict[str, type]] = {}
33
36
 
34
37
  # For constructors
35
38
  _load: Optional[Callable[[O, Resolver, Optional[str]], Awaitable[None]]]
@@ -37,13 +40,14 @@ class _Object:
37
40
  _rep: str
38
41
  _is_another_app: bool
39
42
  _hydrate_lazily: bool
40
- _deps: Optional[Callable[..., List["_Object"]]]
43
+ _deps: Optional[Callable[..., list["_Object"]]]
41
44
  _deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None
42
45
 
43
46
  # For hydrated objects
44
47
  _object_id: str
45
48
  _client: _Client
46
49
  _is_hydrated: bool
50
+ _is_rehydrated: bool
47
51
 
48
52
  @classmethod
49
53
  def __init_subclass__(cls, type_prefix: Optional[str] = None):
@@ -62,7 +66,7 @@ class _Object:
62
66
  is_another_app: bool = False,
63
67
  preload: Optional[Callable[[O, Resolver, Optional[str]], Awaitable[None]]] = None,
64
68
  hydrate_lazily: bool = False,
65
- deps: Optional[Callable[..., List["_Object"]]] = None,
69
+ deps: Optional[Callable[..., list["_Object"]]] = None,
66
70
  deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
67
71
  ):
68
72
  self._local_uuid = str(uuid.uuid4())
@@ -77,6 +81,7 @@ class _Object:
77
81
  self._object_id = None
78
82
  self._client = None
79
83
  self._is_hydrated = False
84
+ self._is_rehydrated = False
80
85
 
81
86
  self._initialize_from_empty()
82
87
 
@@ -91,7 +96,9 @@ class _Object:
91
96
 
92
97
  def _initialize_from_other(self, other):
93
98
  # default implementation, can be overriden in subclasses
94
- pass
99
+ self._object_id = other._object_id
100
+ self._is_hydrated = other._is_hydrated
101
+ self._client = other._client
95
102
 
96
103
  def _hydrate(self, object_id: str, client: _Client, metadata: Optional[Message]):
97
104
  assert isinstance(object_id, str)
@@ -116,17 +123,25 @@ class _Object:
116
123
  # the object_id is already provided by other means
117
124
  return
118
125
 
119
- def _init_from_other(self, other: O):
120
- # Transient use case, see Dict, Queue, and SharedVolume
121
- self._init(other._rep, other._load, other._is_another_app, other._preload)
126
+ def _validate_is_hydrated(self: O):
127
+ if not self._is_hydrated:
128
+ object_type = self.__class__.__name__.strip("_")
129
+ if hasattr(self, "_app") and getattr(self._app, "_running_app", "") is None:
130
+ # The most common cause of this error: e.g., user called a Function without using App.run()
131
+ reason = ", because the App it is defined on is not running"
132
+ else:
133
+ # Technically possible, but with an ambiguous cause.
134
+ reason = ""
135
+ raise ExecutionError(
136
+ f"{object_type} has not been hydrated with the metadata it needs to run on Modal{reason}."
137
+ )
122
138
 
123
139
  def clone(self: O) -> O:
124
140
  """mdmd:hidden Clone a given hydrated object."""
125
141
 
126
142
  # Object to clone must already be hydrated, otherwise from_loader is more suitable.
127
- assert self._is_hydrated
128
-
129
- obj = _Object.__new__(type(self))
143
+ self._validate_is_hydrated()
144
+ obj = type(self).__new__(type(self))
130
145
  obj._initialize_from_other(self)
131
146
  return obj
132
147
 
@@ -138,7 +153,7 @@ class _Object:
138
153
  is_another_app: bool = False,
139
154
  preload: Optional[Callable[[O, Resolver, Optional[str]], Awaitable[None]]] = None,
140
155
  hydrate_lazily: bool = False,
141
- deps: Optional[Callable[..., List["_Object"]]] = None,
156
+ deps: Optional[Callable[..., Sequence["_Object"]]] = None,
142
157
  deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
143
158
  ):
144
159
  # TODO(erikbern): flip the order of the two first arguments
@@ -146,26 +161,34 @@ class _Object:
146
161
  obj._init(rep, load, is_another_app, preload, hydrate_lazily, deps, deduplication_key)
147
162
  return obj
148
163
 
164
+ @classmethod
165
+ def _get_type_from_id(cls: type[O], object_id: str) -> type[O]:
166
+ parts = object_id.split("-")
167
+ if len(parts) != 2:
168
+ raise InvalidError(f"Object id {object_id} has no dash in it")
169
+ prefix = parts[0]
170
+ if prefix not in cls._prefix_to_type:
171
+ raise InvalidError(f"Object prefix {prefix} does not correspond to a type")
172
+ return cls._prefix_to_type[prefix]
173
+
174
+ @classmethod
175
+ def _is_id_type(cls: type[O], object_id) -> bool:
176
+ return cls._get_type_from_id(object_id) == cls
177
+
149
178
  @classmethod
150
179
  def _new_hydrated(
151
- cls: Type[O], object_id: str, client: _Client, handle_metadata: Optional[Message], is_another_app: bool = False
180
+ cls: type[O], object_id: str, client: _Client, handle_metadata: Optional[Message], is_another_app: bool = False
152
181
  ) -> O:
153
182
  if cls._type_prefix is not None:
154
183
  # This is called directly on a subclass, e.g. Secret.from_id
155
184
  if not object_id.startswith(cls._type_prefix + "-"):
156
185
  raise InvalidError(f"Object {object_id} does not start with {cls._type_prefix}")
157
- prefix = cls._type_prefix
186
+ obj_cls = cls
158
187
  else:
159
188
  # This is called on the base class, e.g. Handle.from_id
160
- parts = object_id.split("-")
161
- if len(parts) != 2:
162
- raise InvalidError(f"Object id {object_id} has no dash in it")
163
- prefix = parts[0]
164
- if prefix not in cls._prefix_to_type:
165
- raise InvalidError(f"Object prefix {prefix} does not correspond to a type")
189
+ obj_cls = cls._get_type_from_id(object_id)
166
190
 
167
191
  # Instantiate provider
168
- obj_cls = cls._prefix_to_type[prefix]
169
192
  obj = _Object.__new__(obj_cls)
170
193
  rep = f"Object({object_id})" # TODO(erikbern): dumb
171
194
  obj._init(rep, is_another_app=is_another_app)
@@ -185,7 +208,7 @@ class _Object:
185
208
  return self._local_uuid
186
209
 
187
210
  @property
188
- def object_id(self):
211
+ def object_id(self) -> str:
189
212
  """mdmd:hidden"""
190
213
  return self._object_id
191
214
 
@@ -195,24 +218,30 @@ class _Object:
195
218
  return self._is_hydrated
196
219
 
197
220
  @property
198
- def deps(self) -> Callable[..., List["_Object"]]:
221
+ def deps(self) -> Callable[..., list["_Object"]]:
199
222
  """mdmd:hidden"""
200
223
  return self._deps if self._deps is not None else lambda: []
201
224
 
202
- async def resolve(self):
225
+ async def resolve(self, client: Optional[_Client] = None):
203
226
  """mdmd:hidden"""
204
227
  if self._is_hydrated:
228
+ # memory snapshots capture references which must be rehydrated
229
+ # on restore to handle staleness.
230
+ if self._client._snapshotted and not self._is_rehydrated:
231
+ logger.debug(f"rehydrating {self} after snapshot")
232
+ self._is_hydrated = False # un-hydrate and re-resolve
233
+ c = client if client is not None else await _Client.from_env()
234
+ resolver = Resolver(c)
235
+ await resolver.load(self)
236
+ self._is_rehydrated = True
237
+ logger.debug(f"rehydrated {self} with client {id(c)}")
205
238
  return
206
239
  elif not self._hydrate_lazily:
207
- raise ExecutionError(
208
- "Object has not been hydrated and doesn't support lazy hydration."
209
- " This might happen if an object is defined on a different stub,"
210
- " or if it's on the same stub but it didn't get created because it"
211
- " wasn't defined in global scope."
212
- )
240
+ self._validate_is_hydrated()
213
241
  else:
214
242
  # TODO: this client and/or resolver can't be changed by a caller to X.from_name()
215
- resolver = Resolver(await _Client.from_env())
243
+ c = client if client is not None else await _Client.from_env()
244
+ resolver = Resolver(c)
216
245
  await resolver.load(self)
217
246
 
218
247
 
@@ -232,7 +261,8 @@ def live_method_gen(method):
232
261
  @wraps(method)
233
262
  async def wrapped(self, *args, **kwargs):
234
263
  await self.resolve()
235
- async for item in method(self, *args, **kwargs):
236
- yield item
264
+ async with aclosing(method(self, *args, **kwargs)) as stream:
265
+ async for item in stream:
266
+ yield item
237
267
 
238
268
  return wrapped
modal/object.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.client
@@ -8,189 +9,211 @@ O = typing.TypeVar("O", bound="_Object")
8
9
 
9
10
  _BLOCKING_O = typing.TypeVar("_BLOCKING_O", bound="Object")
10
11
 
11
- def _get_environment_name(environment_name: typing.Union[str, None], resolver: typing.Union[modal._resolver.Resolver, None] = None) -> typing.Union[str, None]:
12
- ...
13
-
12
+ def _get_environment_name(
13
+ environment_name: typing.Optional[str] = None, resolver: typing.Optional[modal._resolver.Resolver] = None
14
+ ) -> typing.Optional[str]: ...
14
15
 
15
16
  class _Object:
16
- _type_prefix: typing.ClassVar[typing.Union[str, None]]
17
- _prefix_to_type: typing.ClassVar[typing.Dict[str, type]]
18
- _load: typing.Union[typing.Callable[[O, modal._resolver.Resolver, typing.Union[str, None]], typing.Awaitable[None]], None]
19
- _preload: typing.Union[typing.Callable[[O, modal._resolver.Resolver, typing.Union[str, None]], typing.Awaitable[None]], None]
17
+ _type_prefix: typing.ClassVar[typing.Optional[str]]
18
+ _prefix_to_type: typing.ClassVar[dict[str, type]]
19
+ _load: typing.Optional[
20
+ typing.Callable[[O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]]
21
+ ]
22
+ _preload: typing.Optional[
23
+ typing.Callable[[O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]]
24
+ ]
20
25
  _rep: str
21
26
  _is_another_app: bool
22
27
  _hydrate_lazily: bool
23
- _deps: typing.Union[typing.Callable[..., typing.List[_Object]], None]
24
- _deduplication_key: typing.Union[typing.Callable[[], typing.Awaitable[typing.Hashable]], None]
28
+ _deps: typing.Optional[typing.Callable[..., list[_Object]]]
29
+ _deduplication_key: typing.Optional[typing.Callable[[], collections.abc.Awaitable[collections.abc.Hashable]]]
25
30
  _object_id: str
26
31
  _client: modal.client._Client
27
32
  _is_hydrated: bool
33
+ _is_rehydrated: bool
28
34
 
29
35
  @classmethod
30
- def __init_subclass__(cls, type_prefix: typing.Union[str, None] = None):
31
- ...
32
-
33
- def __init__(self, *args, **kwargs):
34
- ...
35
-
36
- def _init(self, rep: str, load: typing.Union[typing.Callable[[O, modal._resolver.Resolver, typing.Union[str, None]], typing.Awaitable[None]], None] = None, is_another_app: bool = False, preload: typing.Union[typing.Callable[[O, modal._resolver.Resolver, typing.Union[str, None]], typing.Awaitable[None]], None] = None, hydrate_lazily: bool = False, deps: typing.Union[typing.Callable[..., typing.List[_Object]], None] = None, deduplication_key: typing.Union[typing.Callable[[], typing.Awaitable[typing.Hashable]], None] = None):
37
- ...
38
-
39
- def _unhydrate(self):
40
- ...
41
-
42
- def _initialize_from_empty(self):
43
- ...
44
-
45
- def _initialize_from_other(self, other):
46
- ...
47
-
48
- def _hydrate(self, object_id: str, client: modal.client._Client, metadata: typing.Union[google.protobuf.message.Message, None]):
49
- ...
50
-
51
- def _hydrate_metadata(self, metadata: typing.Union[google.protobuf.message.Message, None]):
52
- ...
53
-
54
- def _get_metadata(self) -> typing.Union[google.protobuf.message.Message, None]:
55
- ...
56
-
57
- def _init_from_other(self, other: O):
58
- ...
59
-
60
- def clone(self: O) -> O:
61
- ...
62
-
36
+ def __init_subclass__(cls, type_prefix: typing.Optional[str] = None): ...
37
+ def __init__(self, *args, **kwargs): ...
38
+ def _init(
39
+ self,
40
+ rep: str,
41
+ load: typing.Optional[
42
+ typing.Callable[[O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]]
43
+ ] = None,
44
+ is_another_app: bool = False,
45
+ preload: typing.Optional[
46
+ typing.Callable[[O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]]
47
+ ] = None,
48
+ hydrate_lazily: bool = False,
49
+ deps: typing.Optional[typing.Callable[..., list[_Object]]] = None,
50
+ deduplication_key: typing.Optional[
51
+ typing.Callable[[], collections.abc.Awaitable[collections.abc.Hashable]]
52
+ ] = None,
53
+ ): ...
54
+ def _unhydrate(self): ...
55
+ def _initialize_from_empty(self): ...
56
+ def _initialize_from_other(self, other): ...
57
+ def _hydrate(
58
+ self, object_id: str, client: modal.client._Client, metadata: typing.Optional[google.protobuf.message.Message]
59
+ ): ...
60
+ def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
61
+ def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
62
+ def _validate_is_hydrated(self: O): ...
63
+ def clone(self: O) -> O: ...
63
64
  @classmethod
64
- def _from_loader(cls, load: typing.Callable[[O, modal._resolver.Resolver, typing.Union[str, None]], typing.Awaitable[None]], rep: str, is_another_app: bool = False, preload: typing.Union[typing.Callable[[O, modal._resolver.Resolver, typing.Union[str, None]], typing.Awaitable[None]], None] = None, hydrate_lazily: bool = False, deps: typing.Union[typing.Callable[..., typing.List[_Object]], None] = None, deduplication_key: typing.Union[typing.Callable[[], typing.Awaitable[typing.Hashable]], None] = None):
65
- ...
66
-
65
+ def _from_loader(
66
+ cls,
67
+ load: typing.Callable[[O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]],
68
+ rep: str,
69
+ is_another_app: bool = False,
70
+ preload: typing.Optional[
71
+ typing.Callable[[O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]]
72
+ ] = None,
73
+ hydrate_lazily: bool = False,
74
+ deps: typing.Optional[typing.Callable[..., collections.abc.Sequence[_Object]]] = None,
75
+ deduplication_key: typing.Optional[
76
+ typing.Callable[[], collections.abc.Awaitable[collections.abc.Hashable]]
77
+ ] = None,
78
+ ): ...
67
79
  @classmethod
68
- def _new_hydrated(cls: typing.Type[O], object_id: str, client: modal.client._Client, handle_metadata: typing.Union[google.protobuf.message.Message, None], is_another_app: bool = False) -> O:
69
- ...
70
-
71
- def _hydrate_from_other(self, other: O):
72
- ...
73
-
74
- def __repr__(self):
75
- ...
76
-
80
+ def _get_type_from_id(cls: type[O], object_id: str) -> type[O]: ...
81
+ @classmethod
82
+ def _is_id_type(cls: type[O], object_id) -> bool: ...
83
+ @classmethod
84
+ def _new_hydrated(
85
+ cls: type[O],
86
+ object_id: str,
87
+ client: modal.client._Client,
88
+ handle_metadata: typing.Optional[google.protobuf.message.Message],
89
+ is_another_app: bool = False,
90
+ ) -> O: ...
91
+ def _hydrate_from_other(self, other: O): ...
92
+ def __repr__(self): ...
77
93
  @property
78
- def local_uuid(self):
79
- ...
80
-
94
+ def local_uuid(self): ...
81
95
  @property
82
- def object_id(self):
83
- ...
84
-
96
+ def object_id(self) -> str: ...
85
97
  @property
86
- def is_hydrated(self) -> bool:
87
- ...
88
-
98
+ def is_hydrated(self) -> bool: ...
89
99
  @property
90
- def deps(self) -> typing.Callable[..., typing.List[_Object]]:
91
- ...
92
-
93
- async def resolve(self):
94
- ...
95
-
100
+ def deps(self) -> typing.Callable[..., list[_Object]]: ...
101
+ async def resolve(self, client: typing.Optional[modal.client._Client] = None): ...
96
102
 
97
103
  class Object:
98
- _type_prefix: typing.ClassVar[typing.Union[str, None]]
99
- _prefix_to_type: typing.ClassVar[typing.Dict[str, type]]
100
- _load: typing.Union[typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Union[str, None]], None], None]
101
- _preload: typing.Union[typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Union[str, None]], None], None]
104
+ _type_prefix: typing.ClassVar[typing.Optional[str]]
105
+ _prefix_to_type: typing.ClassVar[dict[str, type]]
106
+ _load: typing.Optional[
107
+ typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]]
108
+ ]
109
+ _preload: typing.Optional[
110
+ typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]]
111
+ ]
102
112
  _rep: str
103
113
  _is_another_app: bool
104
114
  _hydrate_lazily: bool
105
- _deps: typing.Union[typing.Callable[..., typing.List[Object]], None]
106
- _deduplication_key: typing.Union[typing.Callable[[], typing.Hashable], None]
115
+ _deps: typing.Optional[typing.Callable[..., list[Object]]]
116
+ _deduplication_key: typing.Optional[typing.Callable[[], collections.abc.Awaitable[collections.abc.Hashable]]]
107
117
  _object_id: str
108
118
  _client: modal.client.Client
109
119
  _is_hydrated: bool
120
+ _is_rehydrated: bool
110
121
 
111
- def __init__(self, *args, **kwargs):
112
- ...
113
-
122
+ def __init__(self, *args, **kwargs): ...
114
123
  @classmethod
115
- def __init_subclass__(cls, type_prefix: typing.Union[str, None] = None):
116
- ...
124
+ def __init_subclass__(cls, type_prefix: typing.Optional[str] = None): ...
117
125
 
118
126
  class ___init_spec(typing_extensions.Protocol):
119
- def __call__(self, rep: str, load: typing.Union[typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Union[str, None]], None], None] = None, is_another_app: bool = False, preload: typing.Union[typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Union[str, None]], None], None] = None, hydrate_lazily: bool = False, deps: typing.Union[typing.Callable[..., typing.List[Object]], None] = None, deduplication_key: typing.Union[typing.Callable[[], typing.Hashable], None] = None):
120
- ...
121
-
122
- def aio(self, rep: str, load: typing.Union[typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Union[str, None]], typing.Awaitable[None]], None] = None, is_another_app: bool = False, preload: typing.Union[typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Union[str, None]], typing.Awaitable[None]], None] = None, hydrate_lazily: bool = False, deps: typing.Union[typing.Callable[..., typing.List[Object]], None] = None, deduplication_key: typing.Union[typing.Callable[[], typing.Awaitable[typing.Hashable]], None] = None):
123
- ...
127
+ def __call__(
128
+ self,
129
+ rep: str,
130
+ load: typing.Optional[
131
+ typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], None]
132
+ ] = None,
133
+ is_another_app: bool = False,
134
+ preload: typing.Optional[
135
+ typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], None]
136
+ ] = None,
137
+ hydrate_lazily: bool = False,
138
+ deps: typing.Optional[typing.Callable[..., list[Object]]] = None,
139
+ deduplication_key: typing.Optional[typing.Callable[[], collections.abc.Hashable]] = None,
140
+ ): ...
141
+ def aio(
142
+ self,
143
+ rep: str,
144
+ load: typing.Optional[
145
+ typing.Callable[
146
+ [_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]
147
+ ]
148
+ ] = None,
149
+ is_another_app: bool = False,
150
+ preload: typing.Optional[
151
+ typing.Callable[
152
+ [_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]
153
+ ]
154
+ ] = None,
155
+ hydrate_lazily: bool = False,
156
+ deps: typing.Optional[typing.Callable[..., list[Object]]] = None,
157
+ deduplication_key: typing.Optional[
158
+ typing.Callable[[], collections.abc.Awaitable[collections.abc.Hashable]]
159
+ ] = None,
160
+ ): ...
124
161
 
125
162
  _init: ___init_spec
126
163
 
127
- def _unhydrate(self):
128
- ...
129
-
130
- def _initialize_from_empty(self):
131
- ...
132
-
133
- def _initialize_from_other(self, other):
134
- ...
135
-
136
- def _hydrate(self, object_id: str, client: modal.client.Client, metadata: typing.Union[google.protobuf.message.Message, None]):
137
- ...
138
-
139
- def _hydrate_metadata(self, metadata: typing.Union[google.protobuf.message.Message, None]):
140
- ...
141
-
142
- def _get_metadata(self) -> typing.Union[google.protobuf.message.Message, None]:
143
- ...
144
-
145
- def _init_from_other(self, other: _BLOCKING_O):
146
- ...
147
-
148
- def clone(self: _BLOCKING_O) -> _BLOCKING_O:
149
- ...
150
-
164
+ def _unhydrate(self): ...
165
+ def _initialize_from_empty(self): ...
166
+ def _initialize_from_other(self, other): ...
167
+ def _hydrate(
168
+ self, object_id: str, client: modal.client.Client, metadata: typing.Optional[google.protobuf.message.Message]
169
+ ): ...
170
+ def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
171
+ def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
172
+ def _validate_is_hydrated(self: _BLOCKING_O): ...
173
+ def clone(self: _BLOCKING_O) -> _BLOCKING_O: ...
151
174
  @classmethod
152
- def _from_loader(cls, load: typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Union[str, None]], None], rep: str, is_another_app: bool = False, preload: typing.Union[typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Union[str, None]], None], None] = None, hydrate_lazily: bool = False, deps: typing.Union[typing.Callable[..., typing.List[Object]], None] = None, deduplication_key: typing.Union[typing.Callable[[], typing.Hashable], None] = None):
153
- ...
154
-
175
+ def _from_loader(
176
+ cls,
177
+ load: typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], None],
178
+ rep: str,
179
+ is_another_app: bool = False,
180
+ preload: typing.Optional[
181
+ typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], None]
182
+ ] = None,
183
+ hydrate_lazily: bool = False,
184
+ deps: typing.Optional[typing.Callable[..., collections.abc.Sequence[Object]]] = None,
185
+ deduplication_key: typing.Optional[typing.Callable[[], collections.abc.Hashable]] = None,
186
+ ): ...
155
187
  @classmethod
156
- def _new_hydrated(cls: typing.Type[_BLOCKING_O], object_id: str, client: modal.client.Client, handle_metadata: typing.Union[google.protobuf.message.Message, None], is_another_app: bool = False) -> _BLOCKING_O:
157
- ...
158
-
159
- def _hydrate_from_other(self, other: _BLOCKING_O):
160
- ...
161
-
162
- def __repr__(self):
163
- ...
164
-
188
+ def _get_type_from_id(cls: type[_BLOCKING_O], object_id: str) -> type[_BLOCKING_O]: ...
189
+ @classmethod
190
+ def _is_id_type(cls: type[_BLOCKING_O], object_id) -> bool: ...
191
+ @classmethod
192
+ def _new_hydrated(
193
+ cls: type[_BLOCKING_O],
194
+ object_id: str,
195
+ client: modal.client.Client,
196
+ handle_metadata: typing.Optional[google.protobuf.message.Message],
197
+ is_another_app: bool = False,
198
+ ) -> _BLOCKING_O: ...
199
+ def _hydrate_from_other(self, other: _BLOCKING_O): ...
200
+ def __repr__(self): ...
165
201
  @property
166
- def local_uuid(self):
167
- ...
168
-
202
+ def local_uuid(self): ...
169
203
  @property
170
- def object_id(self):
171
- ...
172
-
204
+ def object_id(self) -> str: ...
173
205
  @property
174
- def is_hydrated(self) -> bool:
175
- ...
176
-
206
+ def is_hydrated(self) -> bool: ...
177
207
  @property
178
- def deps(self) -> typing.Callable[..., typing.List[Object]]:
179
- ...
208
+ def deps(self) -> typing.Callable[..., list[Object]]: ...
180
209
 
181
210
  class __resolve_spec(typing_extensions.Protocol):
182
- def __call__(self):
183
- ...
184
-
185
- async def aio(self, *args, **kwargs):
186
- ...
211
+ def __call__(self, client: typing.Optional[modal.client.Client] = None): ...
212
+ async def aio(self, client: typing.Optional[modal.client.Client] = None): ...
187
213
 
188
214
  resolve: __resolve_spec
189
215
 
216
+ def live_method(method): ...
217
+ def live_method_gen(method): ...
190
218
 
191
- def live_method(method):
192
- ...
193
-
194
-
195
- def live_method_gen(method):
196
- ...
219
+ EPHEMERAL_OBJECT_HEARTBEAT_SLEEP: int
modal/output.py ADDED
@@ -0,0 +1,63 @@
1
+ # Copyright Modal Labs 2024
2
+ """Interface to Modal's OutputManager functionality.
3
+
4
+ These functions live here so that Modal library code can import them without
5
+ transitively importing Rich, as we do in global scope in _output.py. This allows
6
+ us to avoid importing Rich for client code that runs in the container environment.
7
+
8
+ """
9
+ import contextlib
10
+ from collections.abc import Generator
11
+ from typing import TYPE_CHECKING, Optional
12
+
13
+ if TYPE_CHECKING:
14
+ from ._output import OutputManager
15
+
16
+
17
+ OUTPUT_ENABLED = False
18
+
19
+
20
+ @contextlib.contextmanager
21
+ def enable_output(show_progress: bool = True) -> Generator[None, None, None]:
22
+ """Context manager that enable output when using the Python SDK.
23
+
24
+ This will print to stdout and stderr things such as
25
+ 1. Logs from running functions
26
+ 2. Status of creating objects
27
+ 3. Map progress
28
+
29
+ Example:
30
+ ```python
31
+ app = modal.App()
32
+ with modal.enable_output():
33
+ with app.run():
34
+ ...
35
+ ```
36
+ """
37
+ from ._output import OutputManager
38
+
39
+ # Toggle the output flag from within this function so that we can
40
+ # call _get_output_manager from within the library and only import
41
+ # the _output module if output is explicitly enabled. That prevents
42
+ # us from trying to import rich inside a container environment where
43
+ # it might not be installed. This is sort of hacky and I would prefer
44
+ # a more thorough refactor where the OutputManager is fully useable
45
+ # without rich installed, but that's a larger project.
46
+ global OUTPUT_ENABLED
47
+
48
+ try:
49
+ with OutputManager.enable_output(show_progress):
50
+ OUTPUT_ENABLED = True
51
+ yield
52
+ finally:
53
+ OUTPUT_ENABLED = False
54
+
55
+
56
+ def _get_output_manager() -> Optional["OutputManager"]:
57
+ """Interface to the OutputManager that returns None when output is not enabled."""
58
+ if OUTPUT_ENABLED:
59
+ from ._output import OutputManager
60
+
61
+ return OutputManager.get()
62
+ else:
63
+ return None