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/app.pyi CHANGED
@@ -1,5 +1,6 @@
1
1
  import collections.abc
2
2
  import modal._functions
3
+ import modal._load_context
3
4
  import modal._partial_function
4
5
  import modal._utils.function_utils
5
6
  import modal.client
@@ -74,6 +75,42 @@ class _FunctionDecoratorType:
74
75
  self, func: collections.abc.Callable[P, ReturnType]
75
76
  ) -> modal.functions.Function[P, ReturnType, ReturnType]: ...
76
77
 
78
+ class _LocalAppState:
79
+ """All state for apps that's part of the local/definition state"""
80
+
81
+ functions: dict[str, modal._functions._Function]
82
+ classes: dict[str, modal.cls._Cls]
83
+ image_default: typing.Optional[modal.image._Image]
84
+ web_endpoints: list[str]
85
+ local_entrypoints: dict[str, _LocalEntrypoint]
86
+ tags: dict[str, str]
87
+ include_source_default: bool
88
+ secrets_default: collections.abc.Sequence[modal.secret._Secret]
89
+ volumes_default: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume]
90
+
91
+ def __init__(
92
+ self,
93
+ functions: dict[str, modal._functions._Function],
94
+ classes: dict[str, modal.cls._Cls],
95
+ image_default: typing.Optional[modal.image._Image],
96
+ web_endpoints: list[str],
97
+ local_entrypoints: dict[str, _LocalEntrypoint],
98
+ tags: dict[str, str],
99
+ include_source_default: bool,
100
+ secrets_default: collections.abc.Sequence[modal.secret._Secret],
101
+ volumes_default: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume],
102
+ ) -> None:
103
+ """Initialize self. See help(type(self)) for accurate signature."""
104
+ ...
105
+
106
+ def __repr__(self):
107
+ """Return repr(self)."""
108
+ ...
109
+
110
+ def __eq__(self, other):
111
+ """Return self==value."""
112
+ ...
113
+
77
114
  class _App:
78
115
  """A Modal App is a group of functions and classes that are deployed together.
79
116
 
@@ -110,26 +147,26 @@ class _App:
110
147
  _container_app: typing.ClassVar[typing.Optional[_App]]
111
148
  _name: typing.Optional[str]
112
149
  _description: typing.Optional[str]
113
- _functions: dict[str, modal._functions._Function]
114
- _classes: dict[str, modal.cls._Cls]
115
- _image: typing.Optional[modal.image._Image]
116
- _secrets: collections.abc.Sequence[modal.secret._Secret]
117
- _volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume]
118
- _web_endpoints: list[str]
119
- _local_entrypoints: dict[str, _LocalEntrypoint]
150
+ _local_state_attr: typing.Optional[_LocalAppState]
120
151
  _app_id: typing.Optional[str]
121
152
  _running_app: typing.Optional[modal.running_app.RunningApp]
122
153
  _client: typing.Optional[modal.client._Client]
123
- _include_source_default: typing.Optional[bool]
154
+ _root_load_context: modal._load_context.LoadContext
155
+
156
+ @property
157
+ def _local_state(self) -> _LocalAppState:
158
+ """For internal use only. Do not use this property directly."""
159
+ ...
124
160
 
125
161
  def __init__(
126
162
  self,
127
163
  name: typing.Optional[str] = None,
128
164
  *,
165
+ tags: typing.Optional[dict[str, str]] = None,
129
166
  image: typing.Optional[modal.image._Image] = None,
130
167
  secrets: collections.abc.Sequence[modal.secret._Secret] = [],
131
168
  volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume] = {},
132
- include_source: typing.Optional[bool] = None,
169
+ include_source: bool = True,
133
170
  ) -> None:
134
171
  """Construct a new app, optionally with default image, mounts, secrets, or volumes.
135
172
 
@@ -149,7 +186,11 @@ class _App:
149
186
 
150
187
  @property
151
188
  def is_interactive(self) -> bool:
152
- """Whether the current app for the app is running in interactive mode."""
189
+ """mdmd:hidden
190
+ Whether the current app for the app is running in interactive mode.
191
+
192
+ Note: this method will likely be deprecated in the future.
193
+ """
153
194
  ...
154
195
 
155
196
  @property
@@ -183,12 +224,30 @@ class _App:
183
224
  """
184
225
  ...
185
226
 
186
- def set_description(self, description: str): ...
227
+ def set_description(self, description: str):
228
+ """mdmd:hidden
229
+ Set the description of the App before it starts running.
230
+
231
+ Note: we don't recommend using the method and may deprecate it in the future.
232
+ """
233
+ ...
234
+
187
235
  def _validate_blueprint_value(self, key: str, value: typing.Any): ...
188
236
  @property
189
- def image(self) -> modal.image._Image: ...
237
+ def image(self) -> modal.image._Image:
238
+ """mdmd:hidden
239
+ Retrieve the Image that will be used as the default for any Functions registered to the App.
240
+
241
+ Note: This property is only relevant in the build phase and won't be populated on a deployed
242
+ App that is retrieved via `modal.App.lookup`. It is likely to be deprecated in the future.
243
+ """
244
+ ...
245
+
190
246
  @image.setter
191
- def image(self, value): ...
247
+ def image(self, value):
248
+ """mdmd:hidden"""
249
+ ...
250
+
192
251
  def _uncreate_all_objects(self): ...
193
252
  def _set_local_app(
194
253
  self, client: modal.client._Client, running_app: modal.running_app.RunningApp
@@ -299,37 +358,48 @@ class _App:
299
358
  def _init_container(self, client: modal.client._Client, running_app: modal.running_app.RunningApp): ...
300
359
  @property
301
360
  def registered_functions(self) -> dict[str, modal._functions._Function]:
302
- """All modal.Function objects registered on the app.
361
+ """mdmd:hidden
362
+ All modal.Function objects registered on the app.
303
363
 
304
364
  Note: this property is populated only during the build phase, and it is not
305
365
  expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
366
+ This method is likely to be deprecated in the future in favor of a different
367
+ approach for retrieving the layout of a deployed App.
306
368
  """
307
369
  ...
308
370
 
309
371
  @property
310
372
  def registered_classes(self) -> dict[str, modal.cls._Cls]:
311
- """All modal.Cls objects registered on the app.
373
+ """mdmd:hidden
374
+ All modal.Cls objects registered on the app.
312
375
 
313
376
  Note: this property is populated only during the build phase, and it is not
314
377
  expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
378
+ This method is likely to be deprecated in the future in favor of a different
379
+ approach for retrieving the layout of a deployed App.
315
380
  """
316
381
  ...
317
382
 
318
383
  @property
319
384
  def registered_entrypoints(self) -> dict[str, _LocalEntrypoint]:
320
- """All local CLI entrypoints registered on the app.
385
+ """mdmd:hidden
386
+ All local CLI entrypoints registered on the app.
321
387
 
322
388
  Note: this property is populated only during the build phase, and it is not
323
389
  expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
390
+ This method is likely to be deprecated in the future.
324
391
  """
325
392
  ...
326
393
 
327
394
  @property
328
395
  def registered_web_endpoints(self) -> list[str]:
329
- """Names of web endpoint (ie. webhook) functions registered on the app.
396
+ """mdmd:hidden
397
+ Names of web endpoint (ie. webhook) functions registered on the app.
330
398
 
331
399
  Note: this property is populated only during the build phase, and it is not
332
400
  expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
401
+ This method is likely to be deprecated in the future in favor of a different
402
+ approach for retrieving the layout of a deployed App.
333
403
  """
334
404
  ...
335
405
 
@@ -387,11 +457,12 @@ class _App:
387
457
 
388
458
  def function(
389
459
  self,
390
- _warn_parentheses_missing: typing.Any = None,
460
+ _warn_parentheses_missing=None,
391
461
  *,
392
462
  image: typing.Optional[modal.image._Image] = None,
393
463
  schedule: typing.Optional[modal.schedule.Schedule] = None,
394
- secrets: collections.abc.Sequence[modal.secret._Secret] = (),
464
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
465
+ secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
395
466
  gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
396
467
  serialized: bool = False,
397
468
  network_file_systems: dict[
@@ -410,7 +481,8 @@ class _App:
410
481
  scaledown_window: typing.Optional[int] = None,
411
482
  proxy: typing.Optional[modal.proxy._Proxy] = None,
412
483
  retries: typing.Union[int, modal.retries.Retries, None] = None,
413
- timeout: typing.Optional[int] = None,
484
+ timeout: int = 300,
485
+ startup_timeout: typing.Optional[int] = None,
414
486
  name: typing.Optional[str] = None,
415
487
  is_generator: typing.Optional[bool] = None,
416
488
  cloud: typing.Optional[str] = None,
@@ -425,13 +497,12 @@ class _App:
425
497
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
426
498
  _experimental_proxy_ip: typing.Optional[str] = None,
427
499
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
428
- _experimental_enable_gpu_snapshot: bool = False,
500
+ _experimental_restrict_output: bool = False,
429
501
  keep_warm: typing.Optional[int] = None,
430
502
  concurrency_limit: typing.Optional[int] = None,
431
503
  container_idle_timeout: typing.Optional[int] = None,
432
504
  allow_concurrent_inputs: typing.Optional[int] = None,
433
505
  _experimental_buffer_containers: typing.Optional[int] = None,
434
- allow_cross_region_volumes: typing.Optional[bool] = None,
435
506
  ) -> _FunctionDecoratorType:
436
507
  """Decorator to register a new Modal Function with this App."""
437
508
  ...
@@ -442,10 +513,11 @@ class _App:
442
513
  )
443
514
  def cls(
444
515
  self,
445
- _warn_parentheses_missing: typing.Optional[bool] = None,
516
+ _warn_parentheses_missing=None,
446
517
  *,
447
518
  image: typing.Optional[modal.image._Image] = None,
448
- secrets: collections.abc.Sequence[modal.secret._Secret] = (),
519
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
520
+ secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
449
521
  gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
450
522
  serialized: bool = False,
451
523
  network_file_systems: dict[
@@ -464,7 +536,8 @@ class _App:
464
536
  scaledown_window: typing.Optional[int] = None,
465
537
  proxy: typing.Optional[modal.proxy._Proxy] = None,
466
538
  retries: typing.Union[int, modal.retries.Retries, None] = None,
467
- timeout: typing.Optional[int] = None,
539
+ timeout: int = 300,
540
+ startup_timeout: typing.Optional[int] = None,
468
541
  cloud: typing.Optional[str] = None,
469
542
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
470
543
  enable_memory_snapshot: bool = False,
@@ -477,18 +550,17 @@ class _App:
477
550
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
478
551
  _experimental_proxy_ip: typing.Optional[str] = None,
479
552
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
480
- _experimental_enable_gpu_snapshot: bool = False,
553
+ _experimental_restrict_output: bool = False,
481
554
  keep_warm: typing.Optional[int] = None,
482
555
  concurrency_limit: typing.Optional[int] = None,
483
556
  container_idle_timeout: typing.Optional[int] = None,
484
557
  allow_concurrent_inputs: typing.Optional[int] = None,
485
558
  _experimental_buffer_containers: typing.Optional[int] = None,
486
- allow_cross_region_volumes: typing.Optional[bool] = None,
487
559
  ) -> collections.abc.Callable[[typing.Union[CLS_T, modal._partial_function._PartialFunction]], CLS_T]:
488
560
  """Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App."""
489
561
  ...
490
562
 
491
- def include(self, /, other_app: _App) -> typing_extensions.Self:
563
+ def include(self, /, other_app: _App, inherit_tags: bool = True) -> typing_extensions.Self:
492
564
  """Include another App's objects in this one.
493
565
 
494
566
  Useful for splitting up Modal Apps across different self-contained files.
@@ -511,9 +583,30 @@ class _App:
511
583
  # use function declared on the included app
512
584
  bar.remote()
513
585
  ```
586
+
587
+ When `inherit_tags=True` any tags set on the other App will be inherited by this App
588
+ (with this App's tags taking precedence in the case of conflicts).
514
589
  """
515
590
  ...
516
591
 
592
+ async def set_tags(
593
+ self, tags: collections.abc.Mapping[str, str], *, client: typing.Optional[modal.client._Client] = None
594
+ ) -> None:
595
+ """Attach key-value metadata to the App.
596
+
597
+ Tag metadata can be used to add organization-specific context to the App and can be
598
+ included in billing reports and other informational APIs. Tags can also be set in
599
+ the App constructor.
600
+
601
+ Any tags set on the App before calling this method will be removed if they are not
602
+ included in the argument (i.e., this method does not have `.update()` semantics).
603
+ """
604
+ ...
605
+
606
+ async def get_tags(self, *, client: typing.Optional[modal.client._Client] = None) -> dict[str, str]:
607
+ """Get the tags that are currently attached to the App."""
608
+ ...
609
+
517
610
  def _logs(self, client: typing.Optional[modal.client._Client] = None) -> collections.abc.AsyncGenerator[str, None]:
518
611
  """Stream logs from the app.
519
612
 
@@ -572,26 +665,21 @@ class App:
572
665
  _container_app: typing.ClassVar[typing.Optional[App]]
573
666
  _name: typing.Optional[str]
574
667
  _description: typing.Optional[str]
575
- _functions: dict[str, modal.functions.Function]
576
- _classes: dict[str, modal.cls.Cls]
577
- _image: typing.Optional[modal.image.Image]
578
- _secrets: collections.abc.Sequence[modal.secret.Secret]
579
- _volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume.Volume]
580
- _web_endpoints: list[str]
581
- _local_entrypoints: dict[str, LocalEntrypoint]
668
+ _local_state_attr: typing.Optional[_LocalAppState]
582
669
  _app_id: typing.Optional[str]
583
670
  _running_app: typing.Optional[modal.running_app.RunningApp]
584
671
  _client: typing.Optional[modal.client.Client]
585
- _include_source_default: typing.Optional[bool]
672
+ _root_load_context: modal._load_context.LoadContext
586
673
 
587
674
  def __init__(
588
675
  self,
589
676
  name: typing.Optional[str] = None,
590
677
  *,
678
+ tags: typing.Optional[dict[str, str]] = None,
591
679
  image: typing.Optional[modal.image.Image] = None,
592
680
  secrets: collections.abc.Sequence[modal.secret.Secret] = [],
593
681
  volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume.Volume] = {},
594
- include_source: typing.Optional[bool] = None,
682
+ include_source: bool = True,
595
683
  ) -> None:
596
684
  """Construct a new app, optionally with default image, mounts, secrets, or volumes.
597
685
 
@@ -604,6 +692,11 @@ class App:
604
692
  """
605
693
  ...
606
694
 
695
+ @property
696
+ def _local_state(self) -> _LocalAppState:
697
+ """For internal use only. Do not use this property directly."""
698
+ ...
699
+
607
700
  @property
608
701
  def name(self) -> typing.Optional[str]:
609
702
  """The user-provided name of the App."""
@@ -611,7 +704,11 @@ class App:
611
704
 
612
705
  @property
613
706
  def is_interactive(self) -> bool:
614
- """Whether the current app for the app is running in interactive mode."""
707
+ """mdmd:hidden
708
+ Whether the current app for the app is running in interactive mode.
709
+
710
+ Note: this method will likely be deprecated in the future.
711
+ """
615
712
  ...
616
713
 
617
714
  @property
@@ -671,12 +768,30 @@ class App:
671
768
 
672
769
  lookup: __lookup_spec
673
770
 
674
- def set_description(self, description: str): ...
771
+ def set_description(self, description: str):
772
+ """mdmd:hidden
773
+ Set the description of the App before it starts running.
774
+
775
+ Note: we don't recommend using the method and may deprecate it in the future.
776
+ """
777
+ ...
778
+
675
779
  def _validate_blueprint_value(self, key: str, value: typing.Any): ...
676
780
  @property
677
- def image(self) -> modal.image.Image: ...
781
+ def image(self) -> modal.image.Image:
782
+ """mdmd:hidden
783
+ Retrieve the Image that will be used as the default for any Functions registered to the App.
784
+
785
+ Note: This property is only relevant in the build phase and won't be populated on a deployed
786
+ App that is retrieved via `modal.App.lookup`. It is likely to be deprecated in the future.
787
+ """
788
+ ...
789
+
678
790
  @image.setter
679
- def image(self, value): ...
791
+ def image(self, value):
792
+ """mdmd:hidden"""
793
+ ...
794
+
680
795
  def _uncreate_all_objects(self): ...
681
796
 
682
797
  class ___set_local_app_spec(typing_extensions.Protocol[SUPERSELF]):
@@ -904,37 +1019,48 @@ class App:
904
1019
  def _init_container(self, client: modal.client.Client, running_app: modal.running_app.RunningApp): ...
905
1020
  @property
906
1021
  def registered_functions(self) -> dict[str, modal.functions.Function]:
907
- """All modal.Function objects registered on the app.
1022
+ """mdmd:hidden
1023
+ All modal.Function objects registered on the app.
908
1024
 
909
1025
  Note: this property is populated only during the build phase, and it is not
910
1026
  expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
1027
+ This method is likely to be deprecated in the future in favor of a different
1028
+ approach for retrieving the layout of a deployed App.
911
1029
  """
912
1030
  ...
913
1031
 
914
1032
  @property
915
1033
  def registered_classes(self) -> dict[str, modal.cls.Cls]:
916
- """All modal.Cls objects registered on the app.
1034
+ """mdmd:hidden
1035
+ All modal.Cls objects registered on the app.
917
1036
 
918
1037
  Note: this property is populated only during the build phase, and it is not
919
1038
  expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
1039
+ This method is likely to be deprecated in the future in favor of a different
1040
+ approach for retrieving the layout of a deployed App.
920
1041
  """
921
1042
  ...
922
1043
 
923
1044
  @property
924
1045
  def registered_entrypoints(self) -> dict[str, LocalEntrypoint]:
925
- """All local CLI entrypoints registered on the app.
1046
+ """mdmd:hidden
1047
+ All local CLI entrypoints registered on the app.
926
1048
 
927
1049
  Note: this property is populated only during the build phase, and it is not
928
1050
  expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
1051
+ This method is likely to be deprecated in the future.
929
1052
  """
930
1053
  ...
931
1054
 
932
1055
  @property
933
1056
  def registered_web_endpoints(self) -> list[str]:
934
- """Names of web endpoint (ie. webhook) functions registered on the app.
1057
+ """mdmd:hidden
1058
+ Names of web endpoint (ie. webhook) functions registered on the app.
935
1059
 
936
1060
  Note: this property is populated only during the build phase, and it is not
937
1061
  expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
1062
+ This method is likely to be deprecated in the future in favor of a different
1063
+ approach for retrieving the layout of a deployed App.
938
1064
  """
939
1065
  ...
940
1066
 
@@ -992,11 +1118,12 @@ class App:
992
1118
 
993
1119
  def function(
994
1120
  self,
995
- _warn_parentheses_missing: typing.Any = None,
1121
+ _warn_parentheses_missing=None,
996
1122
  *,
997
1123
  image: typing.Optional[modal.image.Image] = None,
998
1124
  schedule: typing.Optional[modal.schedule.Schedule] = None,
999
- secrets: collections.abc.Sequence[modal.secret.Secret] = (),
1125
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
1126
+ secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
1000
1127
  gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
1001
1128
  serialized: bool = False,
1002
1129
  network_file_systems: dict[
@@ -1015,7 +1142,8 @@ class App:
1015
1142
  scaledown_window: typing.Optional[int] = None,
1016
1143
  proxy: typing.Optional[modal.proxy.Proxy] = None,
1017
1144
  retries: typing.Union[int, modal.retries.Retries, None] = None,
1018
- timeout: typing.Optional[int] = None,
1145
+ timeout: int = 300,
1146
+ startup_timeout: typing.Optional[int] = None,
1019
1147
  name: typing.Optional[str] = None,
1020
1148
  is_generator: typing.Optional[bool] = None,
1021
1149
  cloud: typing.Optional[str] = None,
@@ -1030,13 +1158,12 @@ class App:
1030
1158
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
1031
1159
  _experimental_proxy_ip: typing.Optional[str] = None,
1032
1160
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
1033
- _experimental_enable_gpu_snapshot: bool = False,
1161
+ _experimental_restrict_output: bool = False,
1034
1162
  keep_warm: typing.Optional[int] = None,
1035
1163
  concurrency_limit: typing.Optional[int] = None,
1036
1164
  container_idle_timeout: typing.Optional[int] = None,
1037
1165
  allow_concurrent_inputs: typing.Optional[int] = None,
1038
1166
  _experimental_buffer_containers: typing.Optional[int] = None,
1039
- allow_cross_region_volumes: typing.Optional[bool] = None,
1040
1167
  ) -> _FunctionDecoratorType:
1041
1168
  """Decorator to register a new Modal Function with this App."""
1042
1169
  ...
@@ -1047,10 +1174,11 @@ class App:
1047
1174
  )
1048
1175
  def cls(
1049
1176
  self,
1050
- _warn_parentheses_missing: typing.Optional[bool] = None,
1177
+ _warn_parentheses_missing=None,
1051
1178
  *,
1052
1179
  image: typing.Optional[modal.image.Image] = None,
1053
- secrets: collections.abc.Sequence[modal.secret.Secret] = (),
1180
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
1181
+ secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
1054
1182
  gpu: typing.Union[None, str, modal.gpu._GPUConfig, list[typing.Union[None, str, modal.gpu._GPUConfig]]] = None,
1055
1183
  serialized: bool = False,
1056
1184
  network_file_systems: dict[
@@ -1069,7 +1197,8 @@ class App:
1069
1197
  scaledown_window: typing.Optional[int] = None,
1070
1198
  proxy: typing.Optional[modal.proxy.Proxy] = None,
1071
1199
  retries: typing.Union[int, modal.retries.Retries, None] = None,
1072
- timeout: typing.Optional[int] = None,
1200
+ timeout: int = 300,
1201
+ startup_timeout: typing.Optional[int] = None,
1073
1202
  cloud: typing.Optional[str] = None,
1074
1203
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
1075
1204
  enable_memory_snapshot: bool = False,
@@ -1082,18 +1211,17 @@ class App:
1082
1211
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
1083
1212
  _experimental_proxy_ip: typing.Optional[str] = None,
1084
1213
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
1085
- _experimental_enable_gpu_snapshot: bool = False,
1214
+ _experimental_restrict_output: bool = False,
1086
1215
  keep_warm: typing.Optional[int] = None,
1087
1216
  concurrency_limit: typing.Optional[int] = None,
1088
1217
  container_idle_timeout: typing.Optional[int] = None,
1089
1218
  allow_concurrent_inputs: typing.Optional[int] = None,
1090
1219
  _experimental_buffer_containers: typing.Optional[int] = None,
1091
- allow_cross_region_volumes: typing.Optional[bool] = None,
1092
1220
  ) -> collections.abc.Callable[[typing.Union[CLS_T, modal.partial_function.PartialFunction]], CLS_T]:
1093
1221
  """Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App."""
1094
1222
  ...
1095
1223
 
1096
- def include(self, /, other_app: App) -> typing_extensions.Self:
1224
+ def include(self, /, other_app: App, inherit_tags: bool = True) -> typing_extensions.Self:
1097
1225
  """Include another App's objects in this one.
1098
1226
 
1099
1227
  Useful for splitting up Modal Apps across different self-contained files.
@@ -1116,9 +1244,54 @@ class App:
1116
1244
  # use function declared on the included app
1117
1245
  bar.remote()
1118
1246
  ```
1247
+
1248
+ When `inherit_tags=True` any tags set on the other App will be inherited by this App
1249
+ (with this App's tags taking precedence in the case of conflicts).
1119
1250
  """
1120
1251
  ...
1121
1252
 
1253
+ class __set_tags_spec(typing_extensions.Protocol[SUPERSELF]):
1254
+ def __call__(
1255
+ self, /, tags: collections.abc.Mapping[str, str], *, client: typing.Optional[modal.client.Client] = None
1256
+ ) -> None:
1257
+ """Attach key-value metadata to the App.
1258
+
1259
+ Tag metadata can be used to add organization-specific context to the App and can be
1260
+ included in billing reports and other informational APIs. Tags can also be set in
1261
+ the App constructor.
1262
+
1263
+ Any tags set on the App before calling this method will be removed if they are not
1264
+ included in the argument (i.e., this method does not have `.update()` semantics).
1265
+ """
1266
+ ...
1267
+
1268
+ async def aio(
1269
+ self, /, tags: collections.abc.Mapping[str, str], *, client: typing.Optional[modal.client.Client] = None
1270
+ ) -> None:
1271
+ """Attach key-value metadata to the App.
1272
+
1273
+ Tag metadata can be used to add organization-specific context to the App and can be
1274
+ included in billing reports and other informational APIs. Tags can also be set in
1275
+ the App constructor.
1276
+
1277
+ Any tags set on the App before calling this method will be removed if they are not
1278
+ included in the argument (i.e., this method does not have `.update()` semantics).
1279
+ """
1280
+ ...
1281
+
1282
+ set_tags: __set_tags_spec[typing_extensions.Self]
1283
+
1284
+ class __get_tags_spec(typing_extensions.Protocol[SUPERSELF]):
1285
+ def __call__(self, /, *, client: typing.Optional[modal.client.Client] = None) -> dict[str, str]:
1286
+ """Get the tags that are currently attached to the App."""
1287
+ ...
1288
+
1289
+ async def aio(self, /, *, client: typing.Optional[modal.client.Client] = None) -> dict[str, str]:
1290
+ """Get the tags that are currently attached to the App."""
1291
+ ...
1292
+
1293
+ get_tags: __get_tags_spec[typing_extensions.Self]
1294
+
1122
1295
  class ___logs_spec(typing_extensions.Protocol[SUPERSELF]):
1123
1296
  def __call__(self, /, client: typing.Optional[modal.client.Client] = None) -> typing.Generator[str, None, None]:
1124
1297
  """Stream logs from the app.
modal/billing.py ADDED
@@ -0,0 +1,5 @@
1
+ # Copyright Modal Labs 2025
2
+ from ._billing import _workspace_billing_report
3
+ from ._utils.async_utils import synchronize_api
4
+
5
+ workspace_billing_report = synchronize_api(_workspace_billing_report)
@@ -3,6 +3,7 @@ aiohttp==3.12.7
3
3
  aiosignal==1.3.2
4
4
  async-timeout==5.0.1 ; python_version < "3.11"
5
5
  attrs==25.3.0
6
+ cbor2==5.7.0
6
7
  certifi==2025.4.26
7
8
  frozenlist==1.6.0
8
9
  grpclib==0.4.8
@@ -3,6 +3,7 @@ aiohttp==3.12.7
3
3
  aiosignal==1.3.2
4
4
  async-timeout==5.0.1 ; python_version < "3.11"
5
5
  attrs==25.3.0
6
+ cbor2==5.7.0
6
7
  certifi==2025.4.26
7
8
  frozenlist==1.6.0
8
9
  grpclib==0.4.8
modal/cli/_download.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # Copyright Modal Labs 2023
2
2
  import asyncio
3
3
  import functools
4
+ import multiprocessing
4
5
  import os
5
6
  import shutil
6
7
  import sys
@@ -23,12 +24,22 @@ async def _volume_download(
23
24
  remote_path: str,
24
25
  local_destination: Path,
25
26
  overwrite: bool,
26
- progress_cb: Callable,
27
+ concurrency: Optional[int] = None,
28
+ progress_cb: Optional[Callable] = None,
27
29
  ):
30
+ if progress_cb is None:
31
+
32
+ def progress_cb(*_, **__):
33
+ pass
34
+
35
+ if concurrency is None:
36
+ concurrency = max(128, 2 * multiprocessing.cpu_count())
37
+
28
38
  is_pipe = local_destination == PIPE_PATH
29
39
 
30
40
  q: asyncio.Queue[tuple[Optional[Path], Optional[FileEntry]]] = asyncio.Queue()
31
- num_consumers = 1 if is_pipe else 10 # concurrency limit for downloading files
41
+ num_consumers = 1 if is_pipe else concurrency # concurrency limit for downloading files
42
+ download_semaphore = asyncio.Semaphore(concurrency)
32
43
 
33
44
  async def producer():
34
45
  iterator: AsyncIterator[FileEntry]
@@ -86,7 +97,12 @@ async def _volume_download(
86
97
 
87
98
  with output_path.open("wb") as fp:
88
99
  if isinstance(volume, _Volume):
89
- b = await volume.read_file_into_fileobj(entry.path, fp, file_progress_cb)
100
+ b = await volume._read_file_into_fileobj(
101
+ path=entry.path,
102
+ fileobj=fp,
103
+ download_semaphore=download_semaphore,
104
+ progress_cb=file_progress_cb,
105
+ )
90
106
  else:
91
107
  b = 0
92
108
  async for chunk in volume.read_file(entry.path):
modal/cli/_traceback.py CHANGED
@@ -6,12 +6,13 @@ import re
6
6
  import warnings
7
7
  from typing import Optional
8
8
 
9
- from rich.console import Console, RenderResult, group
9
+ from rich.console import RenderResult, group
10
10
  from rich.panel import Panel
11
11
  from rich.syntax import Syntax
12
12
  from rich.text import Text
13
13
  from rich.traceback import PathHighlighter, Stack, Traceback, install
14
14
 
15
+ from .._output import make_console
15
16
  from ..exception import DeprecationError, PendingDeprecationError, ServerWarning
16
17
 
17
18
 
@@ -193,7 +194,7 @@ def highlight_modal_warnings() -> None:
193
194
  title=title,
194
195
  title_align="left",
195
196
  )
196
- Console().print(panel)
197
+ make_console().print(panel)
197
198
  else:
198
199
  base_showwarning(warning, category, filename, lineno, file=None, line=None)
199
200