modal 1.0.6.dev58__py3-none-any.whl → 1.2.3.dev7__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.

Potentially problematic release.


This version of modal might be problematic. Click here for more details.

Files changed (147) hide show
  1. modal/__main__.py +3 -4
  2. modal/_billing.py +80 -0
  3. modal/_clustered_functions.py +7 -3
  4. modal/_clustered_functions.pyi +4 -2
  5. modal/_container_entrypoint.py +41 -49
  6. modal/_functions.py +424 -195
  7. modal/_grpc_client.py +171 -0
  8. modal/_load_context.py +105 -0
  9. modal/_object.py +68 -20
  10. modal/_output.py +58 -45
  11. modal/_partial_function.py +36 -11
  12. modal/_pty.py +7 -3
  13. modal/_resolver.py +21 -35
  14. modal/_runtime/asgi.py +4 -3
  15. modal/_runtime/container_io_manager.py +301 -186
  16. modal/_runtime/container_io_manager.pyi +70 -61
  17. modal/_runtime/execution_context.py +18 -2
  18. modal/_runtime/execution_context.pyi +4 -1
  19. modal/_runtime/gpu_memory_snapshot.py +170 -63
  20. modal/_runtime/user_code_imports.py +28 -58
  21. modal/_serialization.py +57 -1
  22. modal/_utils/async_utils.py +33 -12
  23. modal/_utils/auth_token_manager.py +2 -5
  24. modal/_utils/blob_utils.py +110 -53
  25. modal/_utils/function_utils.py +49 -42
  26. modal/_utils/grpc_utils.py +80 -50
  27. modal/_utils/mount_utils.py +26 -1
  28. modal/_utils/name_utils.py +17 -3
  29. modal/_utils/task_command_router_client.py +536 -0
  30. modal/_utils/time_utils.py +34 -6
  31. modal/app.py +219 -83
  32. modal/app.pyi +229 -56
  33. modal/billing.py +5 -0
  34. modal/{requirements → builder}/2025.06.txt +1 -0
  35. modal/{requirements → builder}/PREVIEW.txt +1 -0
  36. modal/cli/_download.py +19 -3
  37. modal/cli/_traceback.py +3 -2
  38. modal/cli/app.py +4 -4
  39. modal/cli/cluster.py +15 -7
  40. modal/cli/config.py +5 -3
  41. modal/cli/container.py +7 -6
  42. modal/cli/dict.py +22 -16
  43. modal/cli/entry_point.py +12 -5
  44. modal/cli/environment.py +5 -4
  45. modal/cli/import_refs.py +3 -3
  46. modal/cli/launch.py +102 -5
  47. modal/cli/network_file_system.py +9 -13
  48. modal/cli/profile.py +3 -2
  49. modal/cli/programs/launch_instance_ssh.py +94 -0
  50. modal/cli/programs/run_jupyter.py +1 -1
  51. modal/cli/programs/run_marimo.py +95 -0
  52. modal/cli/programs/vscode.py +1 -1
  53. modal/cli/queues.py +57 -26
  54. modal/cli/run.py +58 -16
  55. modal/cli/secret.py +48 -22
  56. modal/cli/utils.py +3 -4
  57. modal/cli/volume.py +28 -25
  58. modal/client.py +13 -116
  59. modal/client.pyi +9 -91
  60. modal/cloud_bucket_mount.py +5 -3
  61. modal/cloud_bucket_mount.pyi +5 -1
  62. modal/cls.py +130 -102
  63. modal/cls.pyi +45 -85
  64. modal/config.py +29 -10
  65. modal/container_process.py +291 -13
  66. modal/container_process.pyi +95 -32
  67. modal/dict.py +282 -63
  68. modal/dict.pyi +423 -73
  69. modal/environments.py +15 -27
  70. modal/environments.pyi +5 -15
  71. modal/exception.py +8 -0
  72. modal/experimental/__init__.py +143 -38
  73. modal/experimental/flash.py +247 -78
  74. modal/experimental/flash.pyi +137 -9
  75. modal/file_io.py +14 -28
  76. modal/file_io.pyi +2 -2
  77. modal/file_pattern_matcher.py +25 -16
  78. modal/functions.pyi +134 -61
  79. modal/image.py +255 -86
  80. modal/image.pyi +300 -62
  81. modal/io_streams.py +436 -126
  82. modal/io_streams.pyi +236 -171
  83. modal/mount.py +62 -157
  84. modal/mount.pyi +45 -172
  85. modal/network_file_system.py +30 -53
  86. modal/network_file_system.pyi +16 -76
  87. modal/object.pyi +42 -8
  88. modal/parallel_map.py +821 -113
  89. modal/parallel_map.pyi +134 -0
  90. modal/partial_function.pyi +4 -1
  91. modal/proxy.py +16 -7
  92. modal/proxy.pyi +10 -2
  93. modal/queue.py +263 -61
  94. modal/queue.pyi +409 -66
  95. modal/runner.py +112 -92
  96. modal/runner.pyi +45 -27
  97. modal/sandbox.py +451 -124
  98. modal/sandbox.pyi +513 -67
  99. modal/secret.py +291 -67
  100. modal/secret.pyi +425 -19
  101. modal/serving.py +7 -11
  102. modal/serving.pyi +7 -8
  103. modal/snapshot.py +11 -8
  104. modal/token_flow.py +4 -4
  105. modal/volume.py +344 -98
  106. modal/volume.pyi +464 -68
  107. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/METADATA +9 -8
  108. modal-1.2.3.dev7.dist-info/RECORD +195 -0
  109. modal_docs/mdmd/mdmd.py +11 -1
  110. modal_proto/api.proto +399 -67
  111. modal_proto/api_grpc.py +241 -1
  112. modal_proto/api_pb2.py +1395 -1000
  113. modal_proto/api_pb2.pyi +1239 -79
  114. modal_proto/api_pb2_grpc.py +499 -4
  115. modal_proto/api_pb2_grpc.pyi +162 -14
  116. modal_proto/modal_api_grpc.py +175 -160
  117. modal_proto/sandbox_router.proto +145 -0
  118. modal_proto/sandbox_router_grpc.py +105 -0
  119. modal_proto/sandbox_router_pb2.py +149 -0
  120. modal_proto/sandbox_router_pb2.pyi +333 -0
  121. modal_proto/sandbox_router_pb2_grpc.py +203 -0
  122. modal_proto/sandbox_router_pb2_grpc.pyi +75 -0
  123. modal_proto/task_command_router.proto +144 -0
  124. modal_proto/task_command_router_grpc.py +105 -0
  125. modal_proto/task_command_router_pb2.py +149 -0
  126. modal_proto/task_command_router_pb2.pyi +333 -0
  127. modal_proto/task_command_router_pb2_grpc.py +203 -0
  128. modal_proto/task_command_router_pb2_grpc.pyi +75 -0
  129. modal_version/__init__.py +1 -1
  130. modal-1.0.6.dev58.dist-info/RECORD +0 -183
  131. modal_proto/modal_options_grpc.py +0 -3
  132. modal_proto/options.proto +0 -19
  133. modal_proto/options_grpc.py +0 -3
  134. modal_proto/options_pb2.py +0 -35
  135. modal_proto/options_pb2.pyi +0 -20
  136. modal_proto/options_pb2_grpc.py +0 -4
  137. modal_proto/options_pb2_grpc.pyi +0 -7
  138. /modal/{requirements → builder}/2023.12.312.txt +0 -0
  139. /modal/{requirements → builder}/2023.12.txt +0 -0
  140. /modal/{requirements → builder}/2024.04.txt +0 -0
  141. /modal/{requirements → builder}/2024.10.txt +0 -0
  142. /modal/{requirements → builder}/README.md +0 -0
  143. /modal/{requirements → builder}/base-images.json +0 -0
  144. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/WHEEL +0 -0
  145. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/entry_points.txt +0 -0
  146. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/licenses/LICENSE +0 -0
  147. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/top_level.txt +0 -0
modal/secret.pyi CHANGED
@@ -1,9 +1,367 @@
1
+ import datetime
2
+ import google.protobuf.message
3
+ import modal._load_context
1
4
  import modal._object
2
5
  import modal.client
3
6
  import modal.object
7
+ import modal_proto.api_pb2
8
+ import synchronicity
4
9
  import typing
5
10
  import typing_extensions
6
11
 
12
+ class SecretInfo:
13
+ """Information about the Secret object."""
14
+
15
+ name: typing.Optional[str]
16
+ created_at: datetime.datetime
17
+ created_by: typing.Optional[str]
18
+
19
+ def __init__(
20
+ self, name: typing.Optional[str], created_at: datetime.datetime, created_by: typing.Optional[str]
21
+ ) -> None:
22
+ """Initialize self. See help(type(self)) for accurate signature."""
23
+ ...
24
+
25
+ def __repr__(self):
26
+ """Return repr(self)."""
27
+ ...
28
+
29
+ def __eq__(self, other):
30
+ """Return self==value."""
31
+ ...
32
+
33
+ class _SecretManager:
34
+ """Namespace with methods for managing named Secret objects."""
35
+ @staticmethod
36
+ async def create(
37
+ name: str,
38
+ env_dict: dict[str, str],
39
+ *,
40
+ allow_existing: bool = False,
41
+ environment_name: typing.Optional[str] = None,
42
+ client: typing.Optional[modal.client._Client] = None,
43
+ ) -> None:
44
+ """Create a new Secret object.
45
+
46
+ **Examples:**
47
+
48
+ ```python notest
49
+ contents = {"MY_KEY": "my-value", "MY_OTHER_KEY": "my-other-value"}
50
+ modal.Secret.objects.create("my-secret", contents)
51
+ ```
52
+
53
+ Secrets will be created in the active environment, or another one can be specified:
54
+
55
+ ```python notest
56
+ modal.Secret.objects.create("my-secret", contents, environment_name="dev")
57
+ ```
58
+
59
+ By default, an error will be raised if the Secret already exists, but passing
60
+ `allow_existing=True` will make the creation attempt a no-op in this case.
61
+ If the `env_dict` data differs from the existing Secret, it will be ignored.
62
+
63
+ ```python notest
64
+ modal.Secret.objects.create("my-secret", contents, allow_existing=True)
65
+ ```
66
+
67
+ Note that this method does not return a local instance of the Secret. You can use
68
+ `modal.Secret.from_name` to perform a lookup after creation.
69
+
70
+ Added in v1.1.2.
71
+ """
72
+ ...
73
+
74
+ @staticmethod
75
+ async def list(
76
+ *,
77
+ max_objects: typing.Optional[int] = None,
78
+ created_before: typing.Union[datetime.datetime, str, None] = None,
79
+ environment_name: str = "",
80
+ client: typing.Optional[modal.client._Client] = None,
81
+ ) -> list[_Secret]:
82
+ """Return a list of hydrated Secret objects.
83
+
84
+ **Examples:**
85
+
86
+ ```python
87
+ secrets = modal.Secret.objects.list()
88
+ print([s.name for s in secrets])
89
+ ```
90
+
91
+ Secrets will be retreived from the active environment, or another one can be specified:
92
+
93
+ ```python notest
94
+ dev_secrets = modal.Secret.objects.list(environment_name="dev")
95
+ ```
96
+
97
+ By default, all named Secrets are returned, newest to oldest. It's also possible to limit the
98
+ number of results and to filter by creation date:
99
+
100
+ ```python
101
+ secrets = modal.Secret.objects.list(max_objects=10, created_before="2025-01-01")
102
+ ```
103
+
104
+ Added in v1.1.2.
105
+ """
106
+ ...
107
+
108
+ @staticmethod
109
+ async def delete(
110
+ name: str,
111
+ *,
112
+ allow_missing: bool = False,
113
+ environment_name: typing.Optional[str] = None,
114
+ client: typing.Optional[modal.client._Client] = None,
115
+ ):
116
+ """Delete a named Secret.
117
+
118
+ Warning: Deletion is irreversible and will affect any Apps currently using the Secret.
119
+
120
+ **Examples:**
121
+
122
+ ```python notest
123
+ await modal.Secret.objects.delete("my-secret")
124
+ ```
125
+
126
+ Secrets will be deleted from the active environment, or another one can be specified:
127
+
128
+ ```python notest
129
+ await modal.Secret.objects.delete("my-secret", environment_name="dev")
130
+ ```
131
+
132
+ Added in v1.1.2.
133
+ """
134
+ ...
135
+
136
+ class SecretManager:
137
+ """Namespace with methods for managing named Secret objects."""
138
+ def __init__(self, /, *args, **kwargs):
139
+ """Initialize self. See help(type(self)) for accurate signature."""
140
+ ...
141
+
142
+ class __create_spec(typing_extensions.Protocol):
143
+ def __call__(
144
+ self,
145
+ /,
146
+ name: str,
147
+ env_dict: dict[str, str],
148
+ *,
149
+ allow_existing: bool = False,
150
+ environment_name: typing.Optional[str] = None,
151
+ client: typing.Optional[modal.client.Client] = None,
152
+ ) -> None:
153
+ """Create a new Secret object.
154
+
155
+ **Examples:**
156
+
157
+ ```python notest
158
+ contents = {"MY_KEY": "my-value", "MY_OTHER_KEY": "my-other-value"}
159
+ modal.Secret.objects.create("my-secret", contents)
160
+ ```
161
+
162
+ Secrets will be created in the active environment, or another one can be specified:
163
+
164
+ ```python notest
165
+ modal.Secret.objects.create("my-secret", contents, environment_name="dev")
166
+ ```
167
+
168
+ By default, an error will be raised if the Secret already exists, but passing
169
+ `allow_existing=True` will make the creation attempt a no-op in this case.
170
+ If the `env_dict` data differs from the existing Secret, it will be ignored.
171
+
172
+ ```python notest
173
+ modal.Secret.objects.create("my-secret", contents, allow_existing=True)
174
+ ```
175
+
176
+ Note that this method does not return a local instance of the Secret. You can use
177
+ `modal.Secret.from_name` to perform a lookup after creation.
178
+
179
+ Added in v1.1.2.
180
+ """
181
+ ...
182
+
183
+ async def aio(
184
+ self,
185
+ /,
186
+ name: str,
187
+ env_dict: dict[str, str],
188
+ *,
189
+ allow_existing: bool = False,
190
+ environment_name: typing.Optional[str] = None,
191
+ client: typing.Optional[modal.client.Client] = None,
192
+ ) -> None:
193
+ """Create a new Secret object.
194
+
195
+ **Examples:**
196
+
197
+ ```python notest
198
+ contents = {"MY_KEY": "my-value", "MY_OTHER_KEY": "my-other-value"}
199
+ modal.Secret.objects.create("my-secret", contents)
200
+ ```
201
+
202
+ Secrets will be created in the active environment, or another one can be specified:
203
+
204
+ ```python notest
205
+ modal.Secret.objects.create("my-secret", contents, environment_name="dev")
206
+ ```
207
+
208
+ By default, an error will be raised if the Secret already exists, but passing
209
+ `allow_existing=True` will make the creation attempt a no-op in this case.
210
+ If the `env_dict` data differs from the existing Secret, it will be ignored.
211
+
212
+ ```python notest
213
+ modal.Secret.objects.create("my-secret", contents, allow_existing=True)
214
+ ```
215
+
216
+ Note that this method does not return a local instance of the Secret. You can use
217
+ `modal.Secret.from_name` to perform a lookup after creation.
218
+
219
+ Added in v1.1.2.
220
+ """
221
+ ...
222
+
223
+ create: __create_spec
224
+
225
+ class __list_spec(typing_extensions.Protocol):
226
+ def __call__(
227
+ self,
228
+ /,
229
+ *,
230
+ max_objects: typing.Optional[int] = None,
231
+ created_before: typing.Union[datetime.datetime, str, None] = None,
232
+ environment_name: str = "",
233
+ client: typing.Optional[modal.client.Client] = None,
234
+ ) -> list[Secret]:
235
+ """Return a list of hydrated Secret objects.
236
+
237
+ **Examples:**
238
+
239
+ ```python
240
+ secrets = modal.Secret.objects.list()
241
+ print([s.name for s in secrets])
242
+ ```
243
+
244
+ Secrets will be retreived from the active environment, or another one can be specified:
245
+
246
+ ```python notest
247
+ dev_secrets = modal.Secret.objects.list(environment_name="dev")
248
+ ```
249
+
250
+ By default, all named Secrets are returned, newest to oldest. It's also possible to limit the
251
+ number of results and to filter by creation date:
252
+
253
+ ```python
254
+ secrets = modal.Secret.objects.list(max_objects=10, created_before="2025-01-01")
255
+ ```
256
+
257
+ Added in v1.1.2.
258
+ """
259
+ ...
260
+
261
+ async def aio(
262
+ self,
263
+ /,
264
+ *,
265
+ max_objects: typing.Optional[int] = None,
266
+ created_before: typing.Union[datetime.datetime, str, None] = None,
267
+ environment_name: str = "",
268
+ client: typing.Optional[modal.client.Client] = None,
269
+ ) -> list[Secret]:
270
+ """Return a list of hydrated Secret objects.
271
+
272
+ **Examples:**
273
+
274
+ ```python
275
+ secrets = modal.Secret.objects.list()
276
+ print([s.name for s in secrets])
277
+ ```
278
+
279
+ Secrets will be retreived from the active environment, or another one can be specified:
280
+
281
+ ```python notest
282
+ dev_secrets = modal.Secret.objects.list(environment_name="dev")
283
+ ```
284
+
285
+ By default, all named Secrets are returned, newest to oldest. It's also possible to limit the
286
+ number of results and to filter by creation date:
287
+
288
+ ```python
289
+ secrets = modal.Secret.objects.list(max_objects=10, created_before="2025-01-01")
290
+ ```
291
+
292
+ Added in v1.1.2.
293
+ """
294
+ ...
295
+
296
+ list: __list_spec
297
+
298
+ class __delete_spec(typing_extensions.Protocol):
299
+ def __call__(
300
+ self,
301
+ /,
302
+ name: str,
303
+ *,
304
+ allow_missing: bool = False,
305
+ environment_name: typing.Optional[str] = None,
306
+ client: typing.Optional[modal.client.Client] = None,
307
+ ):
308
+ """Delete a named Secret.
309
+
310
+ Warning: Deletion is irreversible and will affect any Apps currently using the Secret.
311
+
312
+ **Examples:**
313
+
314
+ ```python notest
315
+ await modal.Secret.objects.delete("my-secret")
316
+ ```
317
+
318
+ Secrets will be deleted from the active environment, or another one can be specified:
319
+
320
+ ```python notest
321
+ await modal.Secret.objects.delete("my-secret", environment_name="dev")
322
+ ```
323
+
324
+ Added in v1.1.2.
325
+ """
326
+ ...
327
+
328
+ async def aio(
329
+ self,
330
+ /,
331
+ name: str,
332
+ *,
333
+ allow_missing: bool = False,
334
+ environment_name: typing.Optional[str] = None,
335
+ client: typing.Optional[modal.client.Client] = None,
336
+ ):
337
+ """Delete a named Secret.
338
+
339
+ Warning: Deletion is irreversible and will affect any Apps currently using the Secret.
340
+
341
+ **Examples:**
342
+
343
+ ```python notest
344
+ await modal.Secret.objects.delete("my-secret")
345
+ ```
346
+
347
+ Secrets will be deleted from the active environment, or another one can be specified:
348
+
349
+ ```python notest
350
+ await modal.Secret.objects.delete("my-secret", environment_name="dev")
351
+ ```
352
+
353
+ Added in v1.1.2.
354
+ """
355
+ ...
356
+
357
+ delete: __delete_spec
358
+
359
+ async def _load_from_env_dict(
360
+ instance: _Secret, load_context: modal._load_context.LoadContext, env_dict: dict[str, str]
361
+ ):
362
+ """helper method for loaders .from_dict and .from_dotenv etc."""
363
+ ...
364
+
7
365
  class _Secret(modal._object._Object):
8
366
  """Secrets provide a dictionary of environment variables for images.
9
367
 
@@ -13,6 +371,15 @@ class _Secret(modal._object._Object):
13
371
 
14
372
  See [the secrets guide page](https://modal.com/docs/guide/secrets) for more information.
15
373
  """
374
+
375
+ _metadata: typing.Optional[modal_proto.api_pb2.SecretMetadata]
376
+
377
+ @synchronicity.classproperty
378
+ def objects(cls) -> _SecretManager: ...
379
+ @property
380
+ def name(self) -> typing.Optional[str]: ...
381
+ def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
382
+ def _get_metadata(self) -> modal_proto.api_pb2.SecretMetadata: ...
16
383
  @staticmethod
17
384
  def from_dict(env_dict: dict[str, typing.Optional[str]] = {}) -> _Secret:
18
385
  """Create a secret from a str-str dictionary. Values can also be `None`, which is ignored.
@@ -32,7 +399,7 @@ class _Secret(modal._object._Object):
32
399
  ...
33
400
 
34
401
  @staticmethod
35
- def from_dotenv(path=None, *, filename=".env") -> _Secret:
402
+ def from_dotenv(path=None, *, filename=".env", client: typing.Optional[modal.client._Client] = None) -> _Secret:
36
403
  """Create secrets from a .env file automatically.
37
404
 
38
405
  If no argument is provided, it will use the current working directory as the starting
@@ -63,7 +430,12 @@ class _Secret(modal._object._Object):
63
430
 
64
431
  @staticmethod
65
432
  def from_name(
66
- name: str, *, namespace=None, environment_name: typing.Optional[str] = None, required_keys: list[str] = []
433
+ name: str,
434
+ *,
435
+ namespace=None,
436
+ environment_name: typing.Optional[str] = None,
437
+ required_keys: list[str] = [],
438
+ client: typing.Optional[modal.client._Client] = None,
67
439
  ) -> _Secret:
68
440
  """Reference a Secret by its name.
69
441
 
@@ -82,18 +454,19 @@ class _Secret(modal._object._Object):
82
454
  ...
83
455
 
84
456
  @staticmethod
85
- async def lookup(
86
- name: str,
457
+ async def create_deployed(
458
+ deployment_name: str,
459
+ env_dict: dict[str, str],
87
460
  namespace=None,
88
461
  client: typing.Optional[modal.client._Client] = None,
89
462
  environment_name: typing.Optional[str] = None,
90
- required_keys: list[str] = [],
91
- ) -> _Secret:
463
+ overwrite: bool = False,
464
+ ) -> str:
92
465
  """mdmd:hidden"""
93
466
  ...
94
467
 
95
468
  @staticmethod
96
- async def create_deployed(
469
+ async def _create_deployed(
97
470
  deployment_name: str,
98
471
  env_dict: dict[str, str],
99
472
  namespace=None,
@@ -104,6 +477,12 @@ class _Secret(modal._object._Object):
104
477
  """mdmd:hidden"""
105
478
  ...
106
479
 
480
+ async def info(self) -> SecretInfo:
481
+ """Return information about the Secret object."""
482
+ ...
483
+
484
+ SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
485
+
107
486
  class Secret(modal.object.Object):
108
487
  """Secrets provide a dictionary of environment variables for images.
109
488
 
@@ -113,10 +492,19 @@ class Secret(modal.object.Object):
113
492
 
114
493
  See [the secrets guide page](https://modal.com/docs/guide/secrets) for more information.
115
494
  """
495
+
496
+ _metadata: typing.Optional[modal_proto.api_pb2.SecretMetadata]
497
+
116
498
  def __init__(self, *args, **kwargs):
117
499
  """mdmd:hidden"""
118
500
  ...
119
501
 
502
+ @synchronicity.classproperty
503
+ def objects(cls) -> SecretManager: ...
504
+ @property
505
+ def name(self) -> typing.Optional[str]: ...
506
+ def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
507
+ def _get_metadata(self) -> modal_proto.api_pb2.SecretMetadata: ...
120
508
  @staticmethod
121
509
  def from_dict(env_dict: dict[str, typing.Optional[str]] = {}) -> Secret:
122
510
  """Create a secret from a str-str dictionary. Values can also be `None`, which is ignored.
@@ -136,7 +524,7 @@ class Secret(modal.object.Object):
136
524
  ...
137
525
 
138
526
  @staticmethod
139
- def from_dotenv(path=None, *, filename=".env") -> Secret:
527
+ def from_dotenv(path=None, *, filename=".env", client: typing.Optional[modal.client.Client] = None) -> Secret:
140
528
  """Create secrets from a .env file automatically.
141
529
 
142
530
  If no argument is provided, it will use the current working directory as the starting
@@ -167,7 +555,12 @@ class Secret(modal.object.Object):
167
555
 
168
556
  @staticmethod
169
557
  def from_name(
170
- name: str, *, namespace=None, environment_name: typing.Optional[str] = None, required_keys: list[str] = []
558
+ name: str,
559
+ *,
560
+ namespace=None,
561
+ environment_name: typing.Optional[str] = None,
562
+ required_keys: list[str] = [],
563
+ client: typing.Optional[modal.client.Client] = None,
171
564
  ) -> Secret:
172
565
  """Reference a Secret by its name.
173
566
 
@@ -185,34 +578,36 @@ class Secret(modal.object.Object):
185
578
  """
186
579
  ...
187
580
 
188
- class __lookup_spec(typing_extensions.Protocol):
581
+ class __create_deployed_spec(typing_extensions.Protocol):
189
582
  def __call__(
190
583
  self,
191
584
  /,
192
- name: str,
585
+ deployment_name: str,
586
+ env_dict: dict[str, str],
193
587
  namespace=None,
194
588
  client: typing.Optional[modal.client.Client] = None,
195
589
  environment_name: typing.Optional[str] = None,
196
- required_keys: list[str] = [],
197
- ) -> Secret:
590
+ overwrite: bool = False,
591
+ ) -> str:
198
592
  """mdmd:hidden"""
199
593
  ...
200
594
 
201
595
  async def aio(
202
596
  self,
203
597
  /,
204
- name: str,
598
+ deployment_name: str,
599
+ env_dict: dict[str, str],
205
600
  namespace=None,
206
601
  client: typing.Optional[modal.client.Client] = None,
207
602
  environment_name: typing.Optional[str] = None,
208
- required_keys: list[str] = [],
209
- ) -> Secret:
603
+ overwrite: bool = False,
604
+ ) -> str:
210
605
  """mdmd:hidden"""
211
606
  ...
212
607
 
213
- lookup: __lookup_spec
608
+ create_deployed: __create_deployed_spec
214
609
 
215
- class __create_deployed_spec(typing_extensions.Protocol):
610
+ class ___create_deployed_spec(typing_extensions.Protocol):
216
611
  def __call__(
217
612
  self,
218
613
  /,
@@ -239,4 +634,15 @@ class Secret(modal.object.Object):
239
634
  """mdmd:hidden"""
240
635
  ...
241
636
 
242
- create_deployed: __create_deployed_spec
637
+ _create_deployed: ___create_deployed_spec
638
+
639
+ class __info_spec(typing_extensions.Protocol[SUPERSELF]):
640
+ def __call__(self, /) -> SecretInfo:
641
+ """Return information about the Secret object."""
642
+ ...
643
+
644
+ async def aio(self, /) -> SecretInfo:
645
+ """Return information about the Secret object."""
646
+ ...
647
+
648
+ info: __info_spec[typing_extensions.Self]
modal/serving.py CHANGED
@@ -4,13 +4,13 @@ import platform
4
4
  from collections.abc import AsyncGenerator
5
5
  from multiprocessing.context import SpawnProcess
6
6
  from multiprocessing.synchronize import Event
7
- from typing import TYPE_CHECKING, Optional, TypeVar
7
+ from typing import TYPE_CHECKING, Optional
8
8
 
9
9
  from synchronicity.async_wrap import asynccontextmanager
10
10
 
11
11
  from modal._output import OutputManager
12
12
 
13
- from ._utils.async_utils import TaskContext, asyncify, synchronize_api, synchronizer
13
+ from ._utils.async_utils import TaskContext, asyncify, synchronize_api
14
14
  from ._utils.logger import logger
15
15
  from ._watcher import watch
16
16
  from .cli.import_refs import ImportRef, import_app_from_ref
@@ -20,20 +20,16 @@ from .output import _get_output_manager, enable_output
20
20
  from .runner import _run_app, serve_update
21
21
 
22
22
  if TYPE_CHECKING:
23
- from .app import _App
24
- else:
25
- _App = TypeVar("_App")
23
+ import modal.app
26
24
 
27
25
 
28
26
  def _run_serve(
29
27
  import_ref: ImportRef, existing_app_id: str, is_ready: Event, environment_name: str, show_progress: bool
30
28
  ):
31
- # subprocess entrypoint
32
- _app = import_app_from_ref(import_ref, base_cmd="modal serve")
33
- blocking_app = synchronizer._translate_out(_app)
29
+ app = import_app_from_ref(import_ref, base_cmd="modal serve")
34
30
 
35
31
  with enable_output(show_progress=show_progress):
36
- serve_update(blocking_app, existing_app_id, is_ready, environment_name)
32
+ serve_update(app, existing_app_id, is_ready, environment_name)
37
33
 
38
34
 
39
35
  async def _restart_serve(
@@ -97,12 +93,12 @@ async def _run_watch_loop(
97
93
 
98
94
  @asynccontextmanager
99
95
  async def _serve_app(
100
- app: "_App",
96
+ app: "modal.app._App",
101
97
  import_ref: ImportRef,
102
98
  *,
103
99
  _watcher: Optional[AsyncGenerator[set[str], None]] = None, # for testing
104
100
  environment_name: Optional[str] = None,
105
- ) -> AsyncGenerator["_App", None]:
101
+ ) -> AsyncGenerator["modal.app._App", None]:
106
102
  if environment_name is None:
107
103
  environment_name = config.get("environment")
108
104
 
modal/serving.pyi CHANGED
@@ -1,4 +1,5 @@
1
1
  import collections.abc
2
+ import modal.app
2
3
  import modal.cli.import_refs
3
4
  import multiprocessing.context
4
5
  import multiprocessing.synchronize
@@ -6,8 +7,6 @@ import synchronicity.combined_types
6
7
  import typing
7
8
  import typing_extensions
8
9
 
9
- _App = typing.TypeVar("_App")
10
-
11
10
  def _run_serve(
12
11
  import_ref: modal.cli.import_refs.ImportRef,
13
12
  existing_app_id: str,
@@ -27,31 +26,31 @@ async def _run_watch_loop(
27
26
  environment_name: str,
28
27
  ): ...
29
28
  def _serve_app(
30
- app: _App,
29
+ app: modal.app._App,
31
30
  import_ref: modal.cli.import_refs.ImportRef,
32
31
  *,
33
32
  _watcher: typing.Optional[collections.abc.AsyncGenerator[set[str], None]] = None,
34
33
  environment_name: typing.Optional[str] = None,
35
- ) -> typing.AsyncContextManager[_App]: ...
34
+ ) -> typing.AsyncContextManager[modal.app._App]: ...
36
35
 
37
36
  class __serve_app_spec(typing_extensions.Protocol):
38
37
  def __call__(
39
38
  self,
40
39
  /,
41
- app: _App,
40
+ app: modal.app.App,
42
41
  import_ref: modal.cli.import_refs.ImportRef,
43
42
  *,
44
43
  _watcher: typing.Optional[typing.Generator[set[str], None, None]] = None,
45
44
  environment_name: typing.Optional[str] = None,
46
- ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[_App]: ...
45
+ ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[modal.app.App]: ...
47
46
  def aio(
48
47
  self,
49
48
  /,
50
- app: _App,
49
+ app: modal.app.App,
51
50
  import_ref: modal.cli.import_refs.ImportRef,
52
51
  *,
53
52
  _watcher: typing.Optional[collections.abc.AsyncGenerator[set[str], None]] = None,
54
53
  environment_name: typing.Optional[str] = None,
55
- ) -> typing.AsyncContextManager[_App]: ...
54
+ ) -> typing.AsyncContextManager[modal.app.App]: ...
56
55
 
57
56
  serve_app: __serve_app_spec
modal/snapshot.py CHANGED
@@ -3,10 +3,10 @@ from typing import Optional
3
3
 
4
4
  from modal_proto import api_pb2
5
5
 
6
+ from ._load_context import LoadContext
6
7
  from ._object import _Object
7
8
  from ._resolver import Resolver
8
9
  from ._utils.async_utils import synchronize_api
9
- from ._utils.grpc_utils import retry_transient_errors
10
10
  from .client import _Client
11
11
 
12
12
 
@@ -24,16 +24,19 @@ class _SandboxSnapshot(_Object, type_prefix="sn"):
24
24
  """
25
25
  Construct a `SandboxSnapshot` object from a sandbox snapshot ID.
26
26
  """
27
- if client is None:
28
- client = await _Client.from_env()
29
-
30
- async def _load(self: _SandboxSnapshot, resolver: Resolver, existing_object_id: Optional[str]):
31
- await retry_transient_errors(
32
- client.stub.SandboxSnapshotGet, api_pb2.SandboxSnapshotGetRequest(snapshot_id=sandbox_snapshot_id)
27
+ # TODO: remove this - from_id constructor should not do io:
28
+ client = client or await _Client.from_env()
29
+
30
+ async def _load(
31
+ self: _SandboxSnapshot, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
32
+ ):
33
+ await load_context.client.stub.SandboxSnapshotGet(
34
+ api_pb2.SandboxSnapshotGetRequest(snapshot_id=sandbox_snapshot_id)
33
35
  )
34
36
 
35
37
  rep = "SandboxSnapshot()"
36
- obj = _SandboxSnapshot._from_loader(_load, rep)
38
+ obj = _SandboxSnapshot._from_loader(_load, rep, load_context_overrides=LoadContext(client=client))
39
+ # TODO: should this be a _Object._new_hydrated instead?
37
40
  obj._hydrate(sandbox_snapshot_id, client, None)
38
41
 
39
42
  return obj