modal 0.72.5__py3-none-any.whl → 0.72.48__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 (73) hide show
  1. modal/_container_entrypoint.py +5 -10
  2. modal/_object.py +297 -0
  3. modal/_resolver.py +7 -5
  4. modal/_runtime/container_io_manager.py +0 -11
  5. modal/_runtime/user_code_imports.py +7 -7
  6. modal/_serialization.py +4 -3
  7. modal/_tunnel.py +1 -1
  8. modal/app.py +14 -61
  9. modal/app.pyi +25 -25
  10. modal/cli/app.py +3 -2
  11. modal/cli/container.py +1 -1
  12. modal/cli/import_refs.py +185 -113
  13. modal/cli/launch.py +10 -5
  14. modal/cli/programs/run_jupyter.py +2 -2
  15. modal/cli/programs/vscode.py +3 -3
  16. modal/cli/run.py +134 -68
  17. modal/client.py +1 -0
  18. modal/client.pyi +18 -14
  19. modal/cloud_bucket_mount.py +4 -0
  20. modal/cloud_bucket_mount.pyi +4 -0
  21. modal/cls.py +33 -5
  22. modal/cls.pyi +20 -5
  23. modal/container_process.pyi +8 -6
  24. modal/dict.py +1 -1
  25. modal/dict.pyi +32 -29
  26. modal/environments.py +1 -1
  27. modal/environments.pyi +2 -1
  28. modal/experimental.py +47 -11
  29. modal/experimental.pyi +29 -0
  30. modal/file_io.pyi +30 -28
  31. modal/file_pattern_matcher.py +3 -4
  32. modal/functions.py +31 -23
  33. modal/functions.pyi +57 -50
  34. modal/gpu.py +19 -26
  35. modal/image.py +47 -19
  36. modal/image.pyi +28 -21
  37. modal/io_streams.pyi +14 -12
  38. modal/mount.py +14 -5
  39. modal/mount.pyi +28 -25
  40. modal/network_file_system.py +7 -7
  41. modal/network_file_system.pyi +27 -24
  42. modal/object.py +2 -265
  43. modal/object.pyi +46 -130
  44. modal/parallel_map.py +2 -2
  45. modal/parallel_map.pyi +10 -7
  46. modal/partial_function.py +22 -3
  47. modal/partial_function.pyi +45 -27
  48. modal/proxy.py +1 -1
  49. modal/proxy.pyi +2 -1
  50. modal/queue.py +1 -1
  51. modal/queue.pyi +26 -23
  52. modal/runner.py +14 -3
  53. modal/sandbox.py +11 -7
  54. modal/sandbox.pyi +30 -27
  55. modal/secret.py +1 -1
  56. modal/secret.pyi +2 -1
  57. modal/token_flow.pyi +6 -4
  58. modal/volume.py +1 -1
  59. modal/volume.pyi +36 -33
  60. {modal-0.72.5.dist-info → modal-0.72.48.dist-info}/METADATA +2 -2
  61. {modal-0.72.5.dist-info → modal-0.72.48.dist-info}/RECORD +73 -71
  62. modal_proto/api.proto +151 -4
  63. modal_proto/api_grpc.py +113 -0
  64. modal_proto/api_pb2.py +998 -795
  65. modal_proto/api_pb2.pyi +430 -11
  66. modal_proto/api_pb2_grpc.py +233 -1
  67. modal_proto/api_pb2_grpc.pyi +75 -3
  68. modal_proto/modal_api_grpc.py +7 -0
  69. modal_version/_version_generated.py +1 -1
  70. {modal-0.72.5.dist-info → modal-0.72.48.dist-info}/LICENSE +0 -0
  71. {modal-0.72.5.dist-info → modal-0.72.48.dist-info}/WHEEL +0 -0
  72. {modal-0.72.5.dist-info → modal-0.72.48.dist-info}/entry_points.txt +0 -0
  73. {modal-0.72.5.dist-info → modal-0.72.48.dist-info}/top_level.txt +0 -0
modal/parallel_map.pyi CHANGED
@@ -1,3 +1,4 @@
1
+ import collections.abc
1
2
  import modal._utils.async_utils
2
3
  import modal.client
3
4
  import modal.functions
@@ -9,26 +10,28 @@ class _SynchronizedQueue:
9
10
  async def put(self, item): ...
10
11
  async def get(self): ...
11
12
 
13
+ SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
14
+
12
15
  class SynchronizedQueue:
13
16
  def __init__(self, /, *args, **kwargs): ...
14
17
 
15
- class __init_spec(typing_extensions.Protocol):
18
+ class __init_spec(typing_extensions.Protocol[SUPERSELF]):
16
19
  def __call__(self): ...
17
20
  async def aio(self): ...
18
21
 
19
- init: __init_spec
22
+ init: __init_spec[typing_extensions.Self]
20
23
 
21
- class __put_spec(typing_extensions.Protocol):
24
+ class __put_spec(typing_extensions.Protocol[SUPERSELF]):
22
25
  def __call__(self, item): ...
23
26
  async def aio(self, item): ...
24
27
 
25
- put: __put_spec
28
+ put: __put_spec[typing_extensions.Self]
26
29
 
27
- class __get_spec(typing_extensions.Protocol):
30
+ class __get_spec(typing_extensions.Protocol[SUPERSELF]):
28
31
  def __call__(self): ...
29
32
  async def aio(self): ...
30
33
 
31
- get: __get_spec
34
+ get: __get_spec[typing_extensions.Self]
32
35
 
33
36
  class _OutputValue:
34
37
  value: typing.Any
@@ -43,7 +46,7 @@ def _map_invocation(
43
46
  client: modal.client._Client,
44
47
  order_outputs: bool,
45
48
  return_exceptions: bool,
46
- count_update_callback: typing.Optional[typing.Callable[[int, int], None]],
49
+ count_update_callback: typing.Optional[collections.abc.Callable[[int, int], None]],
47
50
  ): ...
48
51
  def _map_sync(
49
52
  self, *input_iterators, kwargs={}, order_outputs: bool = True, return_exceptions: bool = False
modal/partial_function.py CHANGED
@@ -89,6 +89,14 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
89
89
  self.force_build = force_build
90
90
  self.build_timeout = build_timeout
91
91
 
92
+ def _get_raw_f(self) -> Callable[P, ReturnType]:
93
+ return self.raw_f
94
+
95
+ def _is_web_endpoint(self) -> bool:
96
+ if self.webhook_config is None:
97
+ return False
98
+ return self.webhook_config.type != api_pb2.WEBHOOK_TYPE_UNSPECIFIED
99
+
92
100
  def __get__(self, obj, objtype=None) -> _Function[P, ReturnType, OriginalReturnType]:
93
101
  k = self.raw_f.__name__
94
102
  if obj: # accessing the method on an instance of a class, e.g. `MyClass().fun``
@@ -530,10 +538,12 @@ def _build(
530
538
  _warn_parentheses_missing=None, *, force: bool = False, timeout: int = 86400
531
539
  ) -> Callable[[Union[Callable[[Any], Any], _PartialFunction]], _PartialFunction]:
532
540
  """
533
- Decorator for methods that should execute at _build time_ to create a new layer
534
- in a `modal.Image`.
541
+ Decorator for methods that execute at _build time_ to create a new Image layer.
535
542
 
536
- See the [lifeycle function guide](https://modal.com/docs/guide/lifecycle-functions#build) for more information.
543
+ **Deprecated**: This function is deprecated. We recommend using `modal.Volume`
544
+ to store large assets (such as model weights) instead of writing them to the
545
+ Image during the build process. For other use cases, you can replace this
546
+ decorator with the `Image.run_function` method.
537
547
 
538
548
  **Usage**
539
549
 
@@ -552,6 +562,15 @@ def _build(
552
562
  if _warn_parentheses_missing is not None:
553
563
  raise InvalidError("Positional arguments are not allowed. Did you forget parentheses? Suggestion: `@build()`.")
554
564
 
565
+ deprecation_warning(
566
+ (2025, 1, 15),
567
+ "The `@modal.build` decorator is deprecated and will be removed in a future release."
568
+ "\n\nWe now recommend storing large assets (such as model weights) using a `modal.Volume`"
569
+ " instead of writing them directly into the `modal.Image` filesystem."
570
+ " For other use cases we recommend using `Image.run_function` instead."
571
+ "\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
572
+ )
573
+
555
574
  def wrapper(f: Union[Callable[[Any], Any], _PartialFunction]) -> _PartialFunction:
556
575
  if isinstance(f, _PartialFunction):
557
576
  _disallow_wrapping_method(f, "build")
@@ -25,7 +25,7 @@ ReturnType = typing.TypeVar("ReturnType", covariant=True)
25
25
  OriginalReturnType = typing.TypeVar("OriginalReturnType", covariant=True)
26
26
 
27
27
  class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
28
- raw_f: typing.Callable[P, ReturnType]
28
+ raw_f: collections.abc.Callable[P, ReturnType]
29
29
  flags: _PartialFunctionFlags
30
30
  webhook_config: typing.Optional[modal_proto.api_pb2.WebhookConfig]
31
31
  is_generator: bool
@@ -38,7 +38,7 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
38
38
 
39
39
  def __init__(
40
40
  self,
41
- raw_f: typing.Callable[P, ReturnType],
41
+ raw_f: collections.abc.Callable[P, ReturnType],
42
42
  flags: _PartialFunctionFlags,
43
43
  webhook_config: typing.Optional[modal_proto.api_pb2.WebhookConfig] = None,
44
44
  is_generator: typing.Optional[bool] = None,
@@ -49,12 +49,14 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
49
49
  force_build: bool = False,
50
50
  build_timeout: typing.Optional[int] = None,
51
51
  ): ...
52
+ def _get_raw_f(self) -> collections.abc.Callable[P, ReturnType]: ...
53
+ def _is_web_endpoint(self) -> bool: ...
52
54
  def __get__(self, obj, objtype=None) -> modal.functions._Function[P, ReturnType, OriginalReturnType]: ...
53
55
  def __del__(self): ...
54
56
  def add_flags(self, flags) -> _PartialFunction: ...
55
57
 
56
58
  class PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
57
- raw_f: typing.Callable[P, ReturnType]
59
+ raw_f: collections.abc.Callable[P, ReturnType]
58
60
  flags: _PartialFunctionFlags
59
61
  webhook_config: typing.Optional[modal_proto.api_pb2.WebhookConfig]
60
62
  is_generator: bool
@@ -67,7 +69,7 @@ class PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
67
69
 
68
70
  def __init__(
69
71
  self,
70
- raw_f: typing.Callable[P, ReturnType],
72
+ raw_f: collections.abc.Callable[P, ReturnType],
71
73
  flags: _PartialFunctionFlags,
72
74
  webhook_config: typing.Optional[modal_proto.api_pb2.WebhookConfig] = None,
73
75
  is_generator: typing.Optional[bool] = None,
@@ -78,12 +80,16 @@ class PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
78
80
  force_build: bool = False,
79
81
  build_timeout: typing.Optional[int] = None,
80
82
  ): ...
83
+ def _get_raw_f(self) -> collections.abc.Callable[P, ReturnType]: ...
84
+ def _is_web_endpoint(self) -> bool: ...
81
85
  def __get__(self, obj, objtype=None) -> modal.functions.Function[P, ReturnType, OriginalReturnType]: ...
82
86
  def __del__(self): ...
83
87
  def add_flags(self, flags) -> PartialFunction: ...
84
88
 
85
89
  def _find_partial_methods_for_user_cls(user_cls: type[typing.Any], flags: int) -> dict[str, _PartialFunction]: ...
86
- def _find_callables_for_obj(user_obj: typing.Any, flags: int) -> dict[str, typing.Callable[..., typing.Any]]: ...
90
+ def _find_callables_for_obj(
91
+ user_obj: typing.Any, flags: int
92
+ ) -> dict[str, collections.abc.Callable[..., typing.Any]]: ...
87
93
 
88
94
  class _MethodDecoratorType:
89
95
  @typing.overload
@@ -93,13 +99,13 @@ class _MethodDecoratorType:
93
99
  @typing.overload
94
100
  def __call__(
95
101
  self,
96
- func: typing.Callable[
102
+ func: collections.abc.Callable[
97
103
  typing_extensions.Concatenate[typing.Any, P], collections.abc.Coroutine[typing.Any, typing.Any, ReturnType]
98
104
  ],
99
105
  ) -> PartialFunction[P, ReturnType, collections.abc.Coroutine[typing.Any, typing.Any, ReturnType]]: ...
100
106
  @typing.overload
101
107
  def __call__(
102
- self, func: typing.Callable[typing_extensions.Concatenate[typing.Any, P], ReturnType]
108
+ self, func: collections.abc.Callable[typing_extensions.Concatenate[typing.Any, P], ReturnType]
103
109
  ) -> PartialFunction[P, ReturnType, ReturnType]: ...
104
110
 
105
111
  def _method(
@@ -120,7 +126,9 @@ def _web_endpoint(
120
126
  custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
121
127
  requires_proxy_auth: bool = False,
122
128
  wait_for_response: bool = True,
123
- ) -> typing.Callable[[typing.Callable[P, ReturnType]], _PartialFunction[P, ReturnType, ReturnType]]: ...
129
+ ) -> collections.abc.Callable[
130
+ [collections.abc.Callable[P, ReturnType]], _PartialFunction[P, ReturnType, ReturnType]
131
+ ]: ...
124
132
  def _asgi_app(
125
133
  _warn_parentheses_missing=None,
126
134
  *,
@@ -128,7 +136,7 @@ def _asgi_app(
128
136
  custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
129
137
  requires_proxy_auth: bool = False,
130
138
  wait_for_response: bool = True,
131
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], _PartialFunction]: ...
139
+ ) -> collections.abc.Callable[[collections.abc.Callable[..., typing.Any]], _PartialFunction]: ...
132
140
  def _wsgi_app(
133
141
  _warn_parentheses_missing=None,
134
142
  *,
@@ -136,7 +144,7 @@ def _wsgi_app(
136
144
  custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
137
145
  requires_proxy_auth: bool = False,
138
146
  wait_for_response: bool = True,
139
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], _PartialFunction]: ...
147
+ ) -> collections.abc.Callable[[collections.abc.Callable[..., typing.Any]], _PartialFunction]: ...
140
148
  def _web_server(
141
149
  port: int,
142
150
  *,
@@ -144,31 +152,35 @@ def _web_server(
144
152
  label: typing.Optional[str] = None,
145
153
  custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
146
154
  requires_proxy_auth: bool = False,
147
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], _PartialFunction]: ...
155
+ ) -> collections.abc.Callable[[collections.abc.Callable[..., typing.Any]], _PartialFunction]: ...
148
156
  def _disallow_wrapping_method(f: _PartialFunction, wrapper: str) -> None: ...
149
157
  def _build(
150
158
  _warn_parentheses_missing=None, *, force: bool = False, timeout: int = 86400
151
- ) -> typing.Callable[[typing.Union[typing.Callable[[typing.Any], typing.Any], _PartialFunction]], _PartialFunction]: ...
159
+ ) -> collections.abc.Callable[
160
+ [typing.Union[collections.abc.Callable[[typing.Any], typing.Any], _PartialFunction]], _PartialFunction
161
+ ]: ...
152
162
  def _enter(
153
163
  _warn_parentheses_missing=None, *, snap: bool = False
154
- ) -> typing.Callable[[typing.Union[typing.Callable[[typing.Any], typing.Any], _PartialFunction]], _PartialFunction]: ...
164
+ ) -> collections.abc.Callable[
165
+ [typing.Union[collections.abc.Callable[[typing.Any], typing.Any], _PartialFunction]], _PartialFunction
166
+ ]: ...
155
167
  def _exit(
156
168
  _warn_parentheses_missing=None,
157
- ) -> typing.Callable[
169
+ ) -> collections.abc.Callable[
158
170
  [
159
171
  typing.Union[
160
- typing.Callable[
172
+ collections.abc.Callable[
161
173
  [typing.Any, typing.Optional[type[BaseException]], typing.Optional[BaseException], typing.Any],
162
174
  typing.Any,
163
175
  ],
164
- typing.Callable[[typing.Any], typing.Any],
176
+ collections.abc.Callable[[typing.Any], typing.Any],
165
177
  ]
166
178
  ],
167
179
  _PartialFunction,
168
180
  ]: ...
169
181
  def _batched(
170
182
  _warn_parentheses_missing=None, *, max_batch_size: int, wait_ms: int
171
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], _PartialFunction]: ...
183
+ ) -> collections.abc.Callable[[collections.abc.Callable[..., typing.Any]], _PartialFunction]: ...
172
184
  def method(
173
185
  _warn_parentheses_missing=None,
174
186
  *,
@@ -184,7 +196,9 @@ def web_endpoint(
184
196
  custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
185
197
  requires_proxy_auth: bool = False,
186
198
  wait_for_response: bool = True,
187
- ) -> typing.Callable[[typing.Callable[P, ReturnType]], PartialFunction[P, ReturnType, ReturnType]]: ...
199
+ ) -> collections.abc.Callable[
200
+ [collections.abc.Callable[P, ReturnType]], PartialFunction[P, ReturnType, ReturnType]
201
+ ]: ...
188
202
  def asgi_app(
189
203
  _warn_parentheses_missing=None,
190
204
  *,
@@ -192,7 +206,7 @@ def asgi_app(
192
206
  custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
193
207
  requires_proxy_auth: bool = False,
194
208
  wait_for_response: bool = True,
195
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], PartialFunction]: ...
209
+ ) -> collections.abc.Callable[[collections.abc.Callable[..., typing.Any]], PartialFunction]: ...
196
210
  def wsgi_app(
197
211
  _warn_parentheses_missing=None,
198
212
  *,
@@ -200,7 +214,7 @@ def wsgi_app(
200
214
  custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
201
215
  requires_proxy_auth: bool = False,
202
216
  wait_for_response: bool = True,
203
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], PartialFunction]: ...
217
+ ) -> collections.abc.Callable[[collections.abc.Callable[..., typing.Any]], PartialFunction]: ...
204
218
  def web_server(
205
219
  port: int,
206
220
  *,
@@ -208,27 +222,31 @@ def web_server(
208
222
  label: typing.Optional[str] = None,
209
223
  custom_domains: typing.Optional[collections.abc.Iterable[str]] = None,
210
224
  requires_proxy_auth: bool = False,
211
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], PartialFunction]: ...
225
+ ) -> collections.abc.Callable[[collections.abc.Callable[..., typing.Any]], PartialFunction]: ...
212
226
  def build(
213
227
  _warn_parentheses_missing=None, *, force: bool = False, timeout: int = 86400
214
- ) -> typing.Callable[[typing.Union[typing.Callable[[typing.Any], typing.Any], PartialFunction]], PartialFunction]: ...
228
+ ) -> collections.abc.Callable[
229
+ [typing.Union[collections.abc.Callable[[typing.Any], typing.Any], PartialFunction]], PartialFunction
230
+ ]: ...
215
231
  def enter(
216
232
  _warn_parentheses_missing=None, *, snap: bool = False
217
- ) -> typing.Callable[[typing.Union[typing.Callable[[typing.Any], typing.Any], PartialFunction]], PartialFunction]: ...
233
+ ) -> collections.abc.Callable[
234
+ [typing.Union[collections.abc.Callable[[typing.Any], typing.Any], PartialFunction]], PartialFunction
235
+ ]: ...
218
236
  def exit(
219
237
  _warn_parentheses_missing=None,
220
- ) -> typing.Callable[
238
+ ) -> collections.abc.Callable[
221
239
  [
222
240
  typing.Union[
223
- typing.Callable[
241
+ collections.abc.Callable[
224
242
  [typing.Any, typing.Optional[type[BaseException]], typing.Optional[BaseException], typing.Any],
225
243
  typing.Any,
226
244
  ],
227
- typing.Callable[[typing.Any], typing.Any],
245
+ collections.abc.Callable[[typing.Any], typing.Any],
228
246
  ]
229
247
  ],
230
248
  PartialFunction,
231
249
  ]: ...
232
250
  def batched(
233
251
  _warn_parentheses_missing=None, *, max_batch_size: int, wait_ms: int
234
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], PartialFunction]: ...
252
+ ) -> collections.abc.Callable[[collections.abc.Callable[..., typing.Any]], PartialFunction]: ...
modal/proxy.py CHANGED
@@ -3,9 +3,9 @@ from typing import Optional
3
3
 
4
4
  from modal_proto import api_pb2
5
5
 
6
+ from ._object import _get_environment_name, _Object
6
7
  from ._resolver import Resolver
7
8
  from ._utils.async_utils import synchronize_api
8
- from .object import _get_environment_name, _Object
9
9
 
10
10
 
11
11
  class _Proxy(_Object, type_prefix="pr"):
modal/proxy.pyi CHANGED
@@ -1,7 +1,8 @@
1
+ import modal._object
1
2
  import modal.object
2
3
  import typing
3
4
 
4
- class _Proxy(modal.object._Object):
5
+ class _Proxy(modal._object._Object):
5
6
  @staticmethod
6
7
  def from_name(name: str, environment_name: typing.Optional[str] = None) -> _Proxy: ...
7
8
 
modal/queue.py CHANGED
@@ -10,6 +10,7 @@ from synchronicity.async_wrap import asynccontextmanager
10
10
 
11
11
  from modal_proto import api_pb2
12
12
 
13
+ from ._object import EPHEMERAL_OBJECT_HEARTBEAT_SLEEP, _get_environment_name, _Object, live_method, live_method_gen
13
14
  from ._resolver import Resolver
14
15
  from ._serialization import deserialize, serialize
15
16
  from ._utils.async_utils import TaskContext, synchronize_api, warn_if_generator_is_not_consumed
@@ -18,7 +19,6 @@ from ._utils.grpc_utils import retry_transient_errors
18
19
  from ._utils.name_utils import check_object_name
19
20
  from .client import _Client
20
21
  from .exception import InvalidError, RequestSizeError
21
- from .object import EPHEMERAL_OBJECT_HEARTBEAT_SLEEP, _get_environment_name, _Object, live_method, live_method_gen
22
22
 
23
23
 
24
24
  class _Queue(_Object, type_prefix="qu"):
modal/queue.pyi CHANGED
@@ -1,11 +1,12 @@
1
1
  import collections.abc
2
+ import modal._object
2
3
  import modal.client
3
4
  import modal.object
4
5
  import synchronicity.combined_types
5
6
  import typing
6
7
  import typing_extensions
7
8
 
8
- class _Queue(modal.object._Object):
9
+ class _Queue(modal._object._Object):
9
10
  def __init__(self): ...
10
11
  @staticmethod
11
12
  def validate_partition_key(partition: typing.Optional[str]) -> bytes: ...
@@ -84,6 +85,8 @@ class _Queue(modal.object._Object):
84
85
  self, *, partition: typing.Optional[str] = None, item_poll_timeout: float = 0.0
85
86
  ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
86
87
 
88
+ SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
89
+
87
90
  class Queue(modal.object.Object):
88
91
  def __init__(self): ...
89
92
  @staticmethod
@@ -138,13 +141,13 @@ class Queue(modal.object.Object):
138
141
 
139
142
  delete: __delete_spec
140
143
 
141
- class ___get_nonblocking_spec(typing_extensions.Protocol):
144
+ class ___get_nonblocking_spec(typing_extensions.Protocol[SUPERSELF]):
142
145
  def __call__(self, partition: typing.Optional[str], n_values: int) -> list[typing.Any]: ...
143
146
  async def aio(self, partition: typing.Optional[str], n_values: int) -> list[typing.Any]: ...
144
147
 
145
- _get_nonblocking: ___get_nonblocking_spec
148
+ _get_nonblocking: ___get_nonblocking_spec[typing_extensions.Self]
146
149
 
147
- class ___get_blocking_spec(typing_extensions.Protocol):
150
+ class ___get_blocking_spec(typing_extensions.Protocol[SUPERSELF]):
148
151
  def __call__(
149
152
  self, partition: typing.Optional[str], timeout: typing.Optional[float], n_values: int
150
153
  ) -> list[typing.Any]: ...
@@ -152,15 +155,15 @@ class Queue(modal.object.Object):
152
155
  self, partition: typing.Optional[str], timeout: typing.Optional[float], n_values: int
153
156
  ) -> list[typing.Any]: ...
154
157
 
155
- _get_blocking: ___get_blocking_spec
158
+ _get_blocking: ___get_blocking_spec[typing_extensions.Self]
156
159
 
157
- class __clear_spec(typing_extensions.Protocol):
160
+ class __clear_spec(typing_extensions.Protocol[SUPERSELF]):
158
161
  def __call__(self, *, partition: typing.Optional[str] = None, all: bool = False) -> None: ...
159
162
  async def aio(self, *, partition: typing.Optional[str] = None, all: bool = False) -> None: ...
160
163
 
161
- clear: __clear_spec
164
+ clear: __clear_spec[typing_extensions.Self]
162
165
 
163
- class __get_spec(typing_extensions.Protocol):
166
+ class __get_spec(typing_extensions.Protocol[SUPERSELF]):
164
167
  def __call__(
165
168
  self, block: bool = True, timeout: typing.Optional[float] = None, *, partition: typing.Optional[str] = None
166
169
  ) -> typing.Optional[typing.Any]: ...
@@ -168,9 +171,9 @@ class Queue(modal.object.Object):
168
171
  self, block: bool = True, timeout: typing.Optional[float] = None, *, partition: typing.Optional[str] = None
169
172
  ) -> typing.Optional[typing.Any]: ...
170
173
 
171
- get: __get_spec
174
+ get: __get_spec[typing_extensions.Self]
172
175
 
173
- class __get_many_spec(typing_extensions.Protocol):
176
+ class __get_many_spec(typing_extensions.Protocol[SUPERSELF]):
174
177
  def __call__(
175
178
  self,
176
179
  n_values: int,
@@ -188,9 +191,9 @@ class Queue(modal.object.Object):
188
191
  partition: typing.Optional[str] = None,
189
192
  ) -> list[typing.Any]: ...
190
193
 
191
- get_many: __get_many_spec
194
+ get_many: __get_many_spec[typing_extensions.Self]
192
195
 
193
- class __put_spec(typing_extensions.Protocol):
196
+ class __put_spec(typing_extensions.Protocol[SUPERSELF]):
194
197
  def __call__(
195
198
  self,
196
199
  v: typing.Any,
@@ -210,9 +213,9 @@ class Queue(modal.object.Object):
210
213
  partition_ttl: int = 86400,
211
214
  ) -> None: ...
212
215
 
213
- put: __put_spec
216
+ put: __put_spec[typing_extensions.Self]
214
217
 
215
- class __put_many_spec(typing_extensions.Protocol):
218
+ class __put_many_spec(typing_extensions.Protocol[SUPERSELF]):
216
219
  def __call__(
217
220
  self,
218
221
  vs: list[typing.Any],
@@ -232,9 +235,9 @@ class Queue(modal.object.Object):
232
235
  partition_ttl: int = 86400,
233
236
  ) -> None: ...
234
237
 
235
- put_many: __put_many_spec
238
+ put_many: __put_many_spec[typing_extensions.Self]
236
239
 
237
- class ___put_many_blocking_spec(typing_extensions.Protocol):
240
+ class ___put_many_blocking_spec(typing_extensions.Protocol[SUPERSELF]):
238
241
  def __call__(
239
242
  self,
240
243
  partition: typing.Optional[str],
@@ -250,21 +253,21 @@ class Queue(modal.object.Object):
250
253
  timeout: typing.Optional[float] = None,
251
254
  ): ...
252
255
 
253
- _put_many_blocking: ___put_many_blocking_spec
256
+ _put_many_blocking: ___put_many_blocking_spec[typing_extensions.Self]
254
257
 
255
- class ___put_many_nonblocking_spec(typing_extensions.Protocol):
258
+ class ___put_many_nonblocking_spec(typing_extensions.Protocol[SUPERSELF]):
256
259
  def __call__(self, partition: typing.Optional[str], partition_ttl: int, vs: list[typing.Any]): ...
257
260
  async def aio(self, partition: typing.Optional[str], partition_ttl: int, vs: list[typing.Any]): ...
258
261
 
259
- _put_many_nonblocking: ___put_many_nonblocking_spec
262
+ _put_many_nonblocking: ___put_many_nonblocking_spec[typing_extensions.Self]
260
263
 
261
- class __len_spec(typing_extensions.Protocol):
264
+ class __len_spec(typing_extensions.Protocol[SUPERSELF]):
262
265
  def __call__(self, *, partition: typing.Optional[str] = None, total: bool = False) -> int: ...
263
266
  async def aio(self, *, partition: typing.Optional[str] = None, total: bool = False) -> int: ...
264
267
 
265
- len: __len_spec
268
+ len: __len_spec[typing_extensions.Self]
266
269
 
267
- class __iterate_spec(typing_extensions.Protocol):
270
+ class __iterate_spec(typing_extensions.Protocol[SUPERSELF]):
268
271
  def __call__(
269
272
  self, *, partition: typing.Optional[str] = None, item_poll_timeout: float = 0.0
270
273
  ) -> typing.Generator[typing.Any, None, None]: ...
@@ -272,4 +275,4 @@ class Queue(modal.object.Object):
272
275
  self, *, partition: typing.Optional[str] = None, item_poll_timeout: float = 0.0
273
276
  ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
274
277
 
275
- iterate: __iterate_spec
278
+ iterate: __iterate_spec[typing_extensions.Self]
modal/runner.py CHANGED
@@ -4,6 +4,7 @@ import dataclasses
4
4
  import os
5
5
  import time
6
6
  import typing
7
+ import warnings
7
8
  from collections.abc import AsyncGenerator
8
9
  from multiprocessing.synchronize import Event
9
10
  from typing import TYPE_CHECKING, Any, Optional, TypeVar
@@ -14,6 +15,7 @@ from synchronicity.async_wrap import asynccontextmanager
14
15
  import modal_proto.api_pb2
15
16
  from modal_proto import api_pb2
16
17
 
18
+ from ._object import _get_environment_name, _Object
17
19
  from ._pty import get_pty_info
18
20
  from ._resolver import Resolver
19
21
  from ._runtime.execution_context import is_local
@@ -28,7 +30,6 @@ from .config import config, logger
28
30
  from .environments import _get_environment_cached
29
31
  from .exception import InteractiveTimeoutError, InvalidError, RemoteError, _CliUserExecutionError
30
32
  from .functions import _Function
31
- from .object import _get_environment_name, _Object
32
33
  from .output import _get_output_manager, enable_output
33
34
  from .running_app import RunningApp, running_app_from_layout
34
35
  from .sandbox import _Sandbox
@@ -155,7 +156,7 @@ async def _create_all_objects(
155
156
  # this is to ensure that directly referenced functions from the global scope has
156
157
  # ids associated with them when they are serialized into other functions
157
158
  await resolver.preload(obj, existing_object_id)
158
- if obj.object_id is not None:
159
+ if obj.is_hydrated:
159
160
  tag_to_object_id[tag] = obj.object_id
160
161
 
161
162
  await TaskContext.gather(*(_preload(tag, obj) for tag, obj in indexed_objects.items()))
@@ -287,6 +288,16 @@ async def _run_app(
287
288
  client = await _Client.from_env()
288
289
 
289
290
  app_state = api_pb2.APP_STATE_DETACHED if detach else api_pb2.APP_STATE_EPHEMERAL
291
+
292
+ output_mgr = _get_output_manager()
293
+ if interactive and output_mgr is None:
294
+ warnings.warn(
295
+ "Interactive mode is disabled because no output manager is active. "
296
+ "Use 'with modal.enable_output():' to enable interactive mode and see logs.",
297
+ stacklevel=2,
298
+ )
299
+ interactive = False
300
+
290
301
  running_app: RunningApp = await _init_local_app_new(
291
302
  client,
292
303
  app.description or "",
@@ -306,7 +317,7 @@ async def _run_app(
306
317
  tc.infinite_loop(heartbeat, sleep=HEARTBEAT_INTERVAL, log_exception=not detach)
307
318
  logs_loop: Optional[asyncio.Task] = None
308
319
 
309
- if output_mgr := _get_output_manager():
320
+ if output_mgr is not None:
310
321
  # Defer import so this module is rich-safe
311
322
  # TODO(michael): The get_app_logs_loop function is itself rich-safe aside from accepting an OutputManager
312
323
  # as an argument, so with some refactoring we could avoid the need for this deferred import.
modal/sandbox.py CHANGED
@@ -16,9 +16,10 @@ from modal.volume import _Volume
16
16
  from modal_proto import api_pb2
17
17
 
18
18
  from ._location import parse_cloud_provider
19
+ from ._object import _get_environment_name, _Object
19
20
  from ._resolver import Resolver
20
21
  from ._resources import convert_fn_config_to_resources_config
21
- from ._utils.async_utils import synchronize_api
22
+ from ._utils.async_utils import TaskContext, synchronize_api
22
23
  from ._utils.deprecation import deprecation_error
23
24
  from ._utils.grpc_utils import retry_transient_errors
24
25
  from ._utils.mount_utils import validate_network_file_systems, validate_volumes
@@ -32,7 +33,6 @@ from .image import _Image
32
33
  from .io_streams import StreamReader, StreamWriter, _StreamReader, _StreamWriter
33
34
  from .mount import _Mount
34
35
  from .network_file_system import _NetworkFileSystem, network_file_system_mount_protos
35
- from .object import _get_environment_name, _Object
36
36
  from .proxy import _Proxy
37
37
  from .scheduler_placement import SchedulerPlacement
38
38
  from .secret import _Secret
@@ -165,7 +165,8 @@ class _Sandbox(_Object, type_prefix="sb"):
165
165
  resources=convert_fn_config_to_resources_config(
166
166
  cpu=cpu, memory=memory, gpu=gpu, ephemeral_disk=ephemeral_disk
167
167
  ),
168
- cloud_provider=parse_cloud_provider(cloud) if cloud else None,
168
+ cloud_provider=parse_cloud_provider(cloud) if cloud else None, # Deprecated at some point
169
+ cloud_provider_str=cloud.upper() if cloud else None, # Supersedes cloud_provider
169
170
  nfs_mounts=network_file_system_mount_protos(validated_network_file_systems, False),
170
171
  runtime_debug=config.get("function_runtime_debug"),
171
172
  cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
@@ -350,6 +351,7 @@ class _Sandbox(_Object, type_prefix="sb"):
350
351
  Returns an [`Image`](https://modal.com/docs/reference/modal.Image) object which
351
352
  can be used to spawn a new Sandbox with the same filesystem.
352
353
  """
354
+ await self._get_task_id() # Ensure the sandbox has started
353
355
  req = api_pb2.SandboxSnapshotFsRequest(sandbox_id=self.object_id, timeout=timeout)
354
356
  resp = await retry_transient_errors(self._client.stub.SandboxSnapshotFs, req)
355
357
 
@@ -360,10 +362,12 @@ class _Sandbox(_Object, type_prefix="sb"):
360
362
  metadata = resp.image_metadata
361
363
 
362
364
  async def _load(self: _Image, resolver: Resolver, existing_object_id: Optional[str]):
363
- self._hydrate(image_id, resolver.client, metadata)
365
+ # no need to hydrate again since we do it eagerly below
366
+ pass
364
367
 
365
368
  rep = "Image()"
366
- image = _Image._from_loader(_load, rep)
369
+ image = _Image._from_loader(_load, rep, hydrate_lazily=True)
370
+ image._hydrate(image_id, self._client, metadata) # hydrating eagerly since we have all of the data
367
371
 
368
372
  return image
369
373
 
@@ -513,8 +517,8 @@ class _Sandbox(_Object, type_prefix="sb"):
513
517
  raise InvalidError(f"workdir must be an absolute path, got: {workdir}")
514
518
 
515
519
  # Force secret resolution so we can pass the secret IDs to the backend.
516
- for secret in secrets:
517
- await secret.resolve(client=self._client)
520
+ secret_coros = [secret.hydrate(client=self._client) for secret in secrets]
521
+ await TaskContext.gather(*secret_coros)
518
522
 
519
523
  task_id = await self._get_task_id()
520
524
  req = api_pb2.ContainerExecRequest(