modal 1.1.5.dev66__py3-none-any.whl → 1.3.1.dev8__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 (143) hide show
  1. modal/__init__.py +4 -4
  2. modal/__main__.py +4 -29
  3. modal/_billing.py +84 -0
  4. modal/_clustered_functions.py +1 -3
  5. modal/_container_entrypoint.py +33 -208
  6. modal/_functions.py +171 -138
  7. modal/_grpc_client.py +191 -0
  8. modal/_ipython.py +16 -6
  9. modal/_load_context.py +106 -0
  10. modal/_object.py +72 -21
  11. modal/_output.py +12 -14
  12. modal/_partial_function.py +31 -4
  13. modal/_resolver.py +44 -57
  14. modal/_runtime/container_io_manager.py +30 -28
  15. modal/_runtime/container_io_manager.pyi +42 -44
  16. modal/_runtime/gpu_memory_snapshot.py +9 -7
  17. modal/_runtime/user_code_event_loop.py +80 -0
  18. modal/_runtime/user_code_imports.py +236 -10
  19. modal/_serialization.py +2 -1
  20. modal/_traceback.py +4 -13
  21. modal/_tunnel.py +16 -11
  22. modal/_tunnel.pyi +25 -3
  23. modal/_utils/async_utils.py +337 -10
  24. modal/_utils/auth_token_manager.py +1 -4
  25. modal/_utils/blob_utils.py +29 -22
  26. modal/_utils/function_utils.py +20 -21
  27. modal/_utils/grpc_testing.py +6 -3
  28. modal/_utils/grpc_utils.py +223 -64
  29. modal/_utils/mount_utils.py +26 -1
  30. modal/_utils/name_utils.py +2 -3
  31. modal/_utils/package_utils.py +0 -1
  32. modal/_utils/rand_pb_testing.py +8 -1
  33. modal/_utils/task_command_router_client.py +524 -0
  34. modal/_vendor/cloudpickle.py +144 -48
  35. modal/app.py +285 -105
  36. modal/app.pyi +216 -53
  37. modal/billing.py +5 -0
  38. modal/builder/2025.06.txt +6 -3
  39. modal/builder/PREVIEW.txt +2 -1
  40. modal/builder/base-images.json +4 -2
  41. modal/cli/_download.py +19 -3
  42. modal/cli/cluster.py +4 -2
  43. modal/cli/config.py +3 -1
  44. modal/cli/container.py +5 -4
  45. modal/cli/dict.py +5 -2
  46. modal/cli/entry_point.py +26 -2
  47. modal/cli/environment.py +2 -16
  48. modal/cli/launch.py +1 -76
  49. modal/cli/network_file_system.py +5 -20
  50. modal/cli/programs/run_jupyter.py +1 -1
  51. modal/cli/programs/vscode.py +1 -1
  52. modal/cli/queues.py +5 -4
  53. modal/cli/run.py +24 -204
  54. modal/cli/secret.py +1 -2
  55. modal/cli/shell.py +375 -0
  56. modal/cli/utils.py +1 -13
  57. modal/cli/volume.py +11 -17
  58. modal/client.py +16 -125
  59. modal/client.pyi +94 -144
  60. modal/cloud_bucket_mount.py +3 -1
  61. modal/cloud_bucket_mount.pyi +4 -0
  62. modal/cls.py +101 -64
  63. modal/cls.pyi +9 -8
  64. modal/config.py +21 -1
  65. modal/container_process.py +288 -12
  66. modal/container_process.pyi +99 -38
  67. modal/dict.py +72 -33
  68. modal/dict.pyi +88 -57
  69. modal/environments.py +16 -8
  70. modal/environments.pyi +6 -2
  71. modal/exception.py +154 -16
  72. modal/experimental/__init__.py +24 -53
  73. modal/experimental/flash.py +161 -74
  74. modal/experimental/flash.pyi +97 -49
  75. modal/file_io.py +50 -92
  76. modal/file_io.pyi +117 -89
  77. modal/functions.pyi +70 -87
  78. modal/image.py +82 -47
  79. modal/image.pyi +51 -30
  80. modal/io_streams.py +500 -149
  81. modal/io_streams.pyi +279 -189
  82. modal/mount.py +60 -46
  83. modal/mount.pyi +41 -17
  84. modal/network_file_system.py +19 -11
  85. modal/network_file_system.pyi +72 -39
  86. modal/object.pyi +114 -22
  87. modal/parallel_map.py +42 -44
  88. modal/parallel_map.pyi +9 -17
  89. modal/partial_function.pyi +4 -2
  90. modal/proxy.py +14 -6
  91. modal/proxy.pyi +10 -2
  92. modal/queue.py +45 -38
  93. modal/queue.pyi +88 -52
  94. modal/runner.py +96 -96
  95. modal/runner.pyi +44 -27
  96. modal/sandbox.py +225 -107
  97. modal/sandbox.pyi +226 -60
  98. modal/secret.py +58 -56
  99. modal/secret.pyi +28 -13
  100. modal/serving.py +7 -11
  101. modal/serving.pyi +7 -8
  102. modal/snapshot.py +29 -15
  103. modal/snapshot.pyi +18 -10
  104. modal/token_flow.py +1 -1
  105. modal/token_flow.pyi +4 -6
  106. modal/volume.py +102 -55
  107. modal/volume.pyi +125 -66
  108. {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/METADATA +10 -9
  109. modal-1.3.1.dev8.dist-info/RECORD +189 -0
  110. modal_proto/api.proto +141 -70
  111. modal_proto/api_grpc.py +42 -26
  112. modal_proto/api_pb2.py +1123 -1103
  113. modal_proto/api_pb2.pyi +331 -83
  114. modal_proto/api_pb2_grpc.py +80 -48
  115. modal_proto/api_pb2_grpc.pyi +26 -18
  116. modal_proto/modal_api_grpc.py +175 -174
  117. modal_proto/task_command_router.proto +164 -0
  118. modal_proto/task_command_router_grpc.py +138 -0
  119. modal_proto/task_command_router_pb2.py +180 -0
  120. modal_proto/{sandbox_router_pb2.pyi → task_command_router_pb2.pyi} +148 -57
  121. modal_proto/task_command_router_pb2_grpc.py +272 -0
  122. modal_proto/task_command_router_pb2_grpc.pyi +100 -0
  123. modal_version/__init__.py +1 -1
  124. modal_version/__main__.py +1 -1
  125. modal/cli/programs/launch_instance_ssh.py +0 -94
  126. modal/cli/programs/run_marimo.py +0 -95
  127. modal-1.1.5.dev66.dist-info/RECORD +0 -191
  128. modal_proto/modal_options_grpc.py +0 -3
  129. modal_proto/options.proto +0 -19
  130. modal_proto/options_grpc.py +0 -3
  131. modal_proto/options_pb2.py +0 -35
  132. modal_proto/options_pb2.pyi +0 -20
  133. modal_proto/options_pb2_grpc.py +0 -4
  134. modal_proto/options_pb2_grpc.pyi +0 -7
  135. modal_proto/sandbox_router.proto +0 -125
  136. modal_proto/sandbox_router_grpc.py +0 -89
  137. modal_proto/sandbox_router_pb2.py +0 -128
  138. modal_proto/sandbox_router_pb2_grpc.py +0 -169
  139. modal_proto/sandbox_router_pb2_grpc.pyi +0 -63
  140. {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/WHEEL +0 -0
  141. {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/entry_points.txt +0 -0
  142. {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/licenses/LICENSE +0 -0
  143. {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.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,18 +147,16 @@ class _App:
110
147
  _container_app: typing.ClassVar[typing.Optional[_App]]
111
148
  _name: typing.Optional[str]
112
149
  _description: typing.Optional[str]
113
- _tags: dict[str, str]
114
- _functions: dict[str, modal._functions._Function]
115
- _classes: dict[str, modal.cls._Cls]
116
- _image: typing.Optional[modal.image._Image]
117
- _secrets: collections.abc.Sequence[modal.secret._Secret]
118
- _volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume]
119
- _web_endpoints: list[str]
120
- _local_entrypoints: dict[str, _LocalEntrypoint]
150
+ _local_state_attr: typing.Optional[_LocalAppState]
121
151
  _app_id: typing.Optional[str]
122
152
  _running_app: typing.Optional[modal.running_app.RunningApp]
123
153
  _client: typing.Optional[modal.client._Client]
124
- _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
+ ...
125
160
 
126
161
  def __init__(
127
162
  self,
@@ -151,7 +186,11 @@ class _App:
151
186
 
152
187
  @property
153
188
  def is_interactive(self) -> bool:
154
- """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
+ """
155
194
  ...
156
195
 
157
196
  @property
@@ -185,12 +224,30 @@ class _App:
185
224
  """
186
225
  ...
187
226
 
188
- 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
+
189
235
  def _validate_blueprint_value(self, key: str, value: typing.Any): ...
190
236
  @property
191
- 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
+
192
246
  @image.setter
193
- def image(self, value): ...
247
+ def image(self, value):
248
+ """mdmd:hidden"""
249
+ ...
250
+
194
251
  def _uncreate_all_objects(self): ...
195
252
  def _set_local_app(
196
253
  self, client: modal.client._Client, running_app: modal.running_app.RunningApp
@@ -301,37 +358,48 @@ class _App:
301
358
  def _init_container(self, client: modal.client._Client, running_app: modal.running_app.RunningApp): ...
302
359
  @property
303
360
  def registered_functions(self) -> dict[str, modal._functions._Function]:
304
- """All modal.Function objects registered on the app.
361
+ """mdmd:hidden
362
+ All modal.Function objects registered on the app.
305
363
 
306
364
  Note: this property is populated only during the build phase, and it is not
307
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.
308
368
  """
309
369
  ...
310
370
 
311
371
  @property
312
372
  def registered_classes(self) -> dict[str, modal.cls._Cls]:
313
- """All modal.Cls objects registered on the app.
373
+ """mdmd:hidden
374
+ All modal.Cls objects registered on the app.
314
375
 
315
376
  Note: this property is populated only during the build phase, and it is not
316
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.
317
380
  """
318
381
  ...
319
382
 
320
383
  @property
321
384
  def registered_entrypoints(self) -> dict[str, _LocalEntrypoint]:
322
- """All local CLI entrypoints registered on the app.
385
+ """mdmd:hidden
386
+ All local CLI entrypoints registered on the app.
323
387
 
324
388
  Note: this property is populated only during the build phase, and it is not
325
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.
326
391
  """
327
392
  ...
328
393
 
329
394
  @property
330
395
  def registered_web_endpoints(self) -> list[str]:
331
- """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.
332
398
 
333
399
  Note: this property is populated only during the build phase, and it is not
334
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.
335
403
  """
336
404
  ...
337
405
 
@@ -419,14 +487,14 @@ class _App:
419
487
  is_generator: typing.Optional[bool] = None,
420
488
  cloud: typing.Optional[str] = None,
421
489
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
490
+ nonpreemptible: bool = False,
422
491
  enable_memory_snapshot: bool = False,
423
492
  block_network: bool = False,
424
493
  restrict_modal_access: bool = False,
425
- max_inputs: typing.Optional[int] = None,
494
+ single_use_containers: bool = False,
426
495
  i6pn: typing.Optional[bool] = None,
427
496
  include_source: typing.Optional[bool] = None,
428
497
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
429
- _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
430
498
  _experimental_proxy_ip: typing.Optional[str] = None,
431
499
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
432
500
  _experimental_restrict_output: bool = False,
@@ -434,8 +502,9 @@ class _App:
434
502
  concurrency_limit: typing.Optional[int] = None,
435
503
  container_idle_timeout: typing.Optional[int] = None,
436
504
  allow_concurrent_inputs: typing.Optional[int] = None,
437
- allow_cross_region_volumes: typing.Optional[bool] = None,
505
+ max_inputs: typing.Optional[int] = None,
438
506
  _experimental_buffer_containers: typing.Optional[int] = None,
507
+ _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
439
508
  ) -> _FunctionDecoratorType:
440
509
  """Decorator to register a new Modal Function with this App."""
441
510
  ...
@@ -473,14 +542,14 @@ class _App:
473
542
  startup_timeout: typing.Optional[int] = None,
474
543
  cloud: typing.Optional[str] = None,
475
544
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
545
+ nonpreemptible: bool = False,
476
546
  enable_memory_snapshot: bool = False,
477
547
  block_network: bool = False,
478
548
  restrict_modal_access: bool = False,
479
- max_inputs: typing.Optional[int] = None,
549
+ single_use_containers: bool = False,
480
550
  i6pn: typing.Optional[bool] = None,
481
551
  include_source: typing.Optional[bool] = None,
482
552
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
483
- _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
484
553
  _experimental_proxy_ip: typing.Optional[str] = None,
485
554
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
486
555
  _experimental_restrict_output: bool = False,
@@ -488,8 +557,9 @@ class _App:
488
557
  concurrency_limit: typing.Optional[int] = None,
489
558
  container_idle_timeout: typing.Optional[int] = None,
490
559
  allow_concurrent_inputs: typing.Optional[int] = None,
560
+ max_inputs: typing.Optional[int] = None,
491
561
  _experimental_buffer_containers: typing.Optional[int] = None,
492
- allow_cross_region_volumes: typing.Optional[bool] = None,
562
+ _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
493
563
  ) -> collections.abc.Callable[[typing.Union[CLS_T, modal._partial_function._PartialFunction]], CLS_T]:
494
564
  """Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App."""
495
565
  ...
@@ -523,6 +593,24 @@ class _App:
523
593
  """
524
594
  ...
525
595
 
596
+ async def set_tags(
597
+ self, tags: collections.abc.Mapping[str, str], *, client: typing.Optional[modal.client._Client] = None
598
+ ) -> None:
599
+ """Attach key-value metadata to the App.
600
+
601
+ Tag metadata can be used to add organization-specific context to the App and can be
602
+ included in billing reports and other informational APIs. Tags can also be set in
603
+ the App constructor.
604
+
605
+ Any tags set on the App before calling this method will be removed if they are not
606
+ included in the argument (i.e., this method does not have `.update()` semantics).
607
+ """
608
+ ...
609
+
610
+ async def get_tags(self, *, client: typing.Optional[modal.client._Client] = None) -> dict[str, str]:
611
+ """Get the tags that are currently attached to the App."""
612
+ ...
613
+
526
614
  def _logs(self, client: typing.Optional[modal.client._Client] = None) -> collections.abc.AsyncGenerator[str, None]:
527
615
  """Stream logs from the app.
528
616
 
@@ -581,18 +669,11 @@ class App:
581
669
  _container_app: typing.ClassVar[typing.Optional[App]]
582
670
  _name: typing.Optional[str]
583
671
  _description: typing.Optional[str]
584
- _tags: dict[str, str]
585
- _functions: dict[str, modal.functions.Function]
586
- _classes: dict[str, modal.cls.Cls]
587
- _image: typing.Optional[modal.image.Image]
588
- _secrets: collections.abc.Sequence[modal.secret.Secret]
589
- _volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume.Volume]
590
- _web_endpoints: list[str]
591
- _local_entrypoints: dict[str, LocalEntrypoint]
672
+ _local_state_attr: typing.Optional[_LocalAppState]
592
673
  _app_id: typing.Optional[str]
593
674
  _running_app: typing.Optional[modal.running_app.RunningApp]
594
675
  _client: typing.Optional[modal.client.Client]
595
- _include_source_default: typing.Optional[bool]
676
+ _root_load_context: modal._load_context.LoadContext
596
677
 
597
678
  def __init__(
598
679
  self,
@@ -615,6 +696,11 @@ class App:
615
696
  """
616
697
  ...
617
698
 
699
+ @property
700
+ def _local_state(self) -> _LocalAppState:
701
+ """For internal use only. Do not use this property directly."""
702
+ ...
703
+
618
704
  @property
619
705
  def name(self) -> typing.Optional[str]:
620
706
  """The user-provided name of the App."""
@@ -622,7 +708,11 @@ class App:
622
708
 
623
709
  @property
624
710
  def is_interactive(self) -> bool:
625
- """Whether the current app for the app is running in interactive mode."""
711
+ """mdmd:hidden
712
+ Whether the current app for the app is running in interactive mode.
713
+
714
+ Note: this method will likely be deprecated in the future.
715
+ """
626
716
  ...
627
717
 
628
718
  @property
@@ -680,17 +770,35 @@ class App:
680
770
  """
681
771
  ...
682
772
 
683
- lookup: __lookup_spec
773
+ lookup: typing.ClassVar[__lookup_spec]
774
+
775
+ def set_description(self, description: str):
776
+ """mdmd:hidden
777
+ Set the description of the App before it starts running.
778
+
779
+ Note: we don't recommend using the method and may deprecate it in the future.
780
+ """
781
+ ...
684
782
 
685
- def set_description(self, description: str): ...
686
783
  def _validate_blueprint_value(self, key: str, value: typing.Any): ...
687
784
  @property
688
- def image(self) -> modal.image.Image: ...
785
+ def image(self) -> modal.image.Image:
786
+ """mdmd:hidden
787
+ Retrieve the Image that will be used as the default for any Functions registered to the App.
788
+
789
+ Note: This property is only relevant in the build phase and won't be populated on a deployed
790
+ App that is retrieved via `modal.App.lookup`. It is likely to be deprecated in the future.
791
+ """
792
+ ...
793
+
689
794
  @image.setter
690
- def image(self, value): ...
795
+ def image(self, value):
796
+ """mdmd:hidden"""
797
+ ...
798
+
691
799
  def _uncreate_all_objects(self): ...
692
800
 
693
- class ___set_local_app_spec(typing_extensions.Protocol[SUPERSELF]):
801
+ class ___set_local_app_spec(typing_extensions.Protocol):
694
802
  def __call__(
695
803
  self, /, client: modal.client.Client, running_app: modal.running_app.RunningApp
696
804
  ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[None]: ...
@@ -698,9 +806,9 @@ class App:
698
806
  self, /, client: modal.client.Client, running_app: modal.running_app.RunningApp
699
807
  ) -> typing.AsyncContextManager[None]: ...
700
808
 
701
- _set_local_app: ___set_local_app_spec[typing_extensions.Self]
809
+ _set_local_app: ___set_local_app_spec
702
810
 
703
- class __run_spec(typing_extensions.Protocol[SUPERSELF]):
811
+ class __run_spec(typing_extensions.Protocol):
704
812
  def __call__(
705
813
  self,
706
814
  /,
@@ -799,7 +907,7 @@ class App:
799
907
  """
800
908
  ...
801
909
 
802
- run: __run_spec[typing_extensions.Self]
910
+ run: __run_spec
803
911
 
804
912
  class __deploy_spec(typing_extensions.Protocol[SUPERSELF]):
805
913
  def __call__(
@@ -915,37 +1023,48 @@ class App:
915
1023
  def _init_container(self, client: modal.client.Client, running_app: modal.running_app.RunningApp): ...
916
1024
  @property
917
1025
  def registered_functions(self) -> dict[str, modal.functions.Function]:
918
- """All modal.Function objects registered on the app.
1026
+ """mdmd:hidden
1027
+ All modal.Function objects registered on the app.
919
1028
 
920
1029
  Note: this property is populated only during the build phase, and it is not
921
1030
  expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
1031
+ This method is likely to be deprecated in the future in favor of a different
1032
+ approach for retrieving the layout of a deployed App.
922
1033
  """
923
1034
  ...
924
1035
 
925
1036
  @property
926
1037
  def registered_classes(self) -> dict[str, modal.cls.Cls]:
927
- """All modal.Cls objects registered on the app.
1038
+ """mdmd:hidden
1039
+ All modal.Cls objects registered on the app.
928
1040
 
929
1041
  Note: this property is populated only during the build phase, and it is not
930
1042
  expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
1043
+ This method is likely to be deprecated in the future in favor of a different
1044
+ approach for retrieving the layout of a deployed App.
931
1045
  """
932
1046
  ...
933
1047
 
934
1048
  @property
935
1049
  def registered_entrypoints(self) -> dict[str, LocalEntrypoint]:
936
- """All local CLI entrypoints registered on the app.
1050
+ """mdmd:hidden
1051
+ All local CLI entrypoints registered on the app.
937
1052
 
938
1053
  Note: this property is populated only during the build phase, and it is not
939
1054
  expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
1055
+ This method is likely to be deprecated in the future.
940
1056
  """
941
1057
  ...
942
1058
 
943
1059
  @property
944
1060
  def registered_web_endpoints(self) -> list[str]:
945
- """Names of web endpoint (ie. webhook) functions registered on the app.
1061
+ """mdmd:hidden
1062
+ Names of web endpoint (ie. webhook) functions registered on the app.
946
1063
 
947
1064
  Note: this property is populated only during the build phase, and it is not
948
1065
  expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
1066
+ This method is likely to be deprecated in the future in favor of a different
1067
+ approach for retrieving the layout of a deployed App.
949
1068
  """
950
1069
  ...
951
1070
 
@@ -1033,14 +1152,14 @@ class App:
1033
1152
  is_generator: typing.Optional[bool] = None,
1034
1153
  cloud: typing.Optional[str] = None,
1035
1154
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
1155
+ nonpreemptible: bool = False,
1036
1156
  enable_memory_snapshot: bool = False,
1037
1157
  block_network: bool = False,
1038
1158
  restrict_modal_access: bool = False,
1039
- max_inputs: typing.Optional[int] = None,
1159
+ single_use_containers: bool = False,
1040
1160
  i6pn: typing.Optional[bool] = None,
1041
1161
  include_source: typing.Optional[bool] = None,
1042
1162
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
1043
- _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
1044
1163
  _experimental_proxy_ip: typing.Optional[str] = None,
1045
1164
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
1046
1165
  _experimental_restrict_output: bool = False,
@@ -1048,8 +1167,9 @@ class App:
1048
1167
  concurrency_limit: typing.Optional[int] = None,
1049
1168
  container_idle_timeout: typing.Optional[int] = None,
1050
1169
  allow_concurrent_inputs: typing.Optional[int] = None,
1051
- allow_cross_region_volumes: typing.Optional[bool] = None,
1170
+ max_inputs: typing.Optional[int] = None,
1052
1171
  _experimental_buffer_containers: typing.Optional[int] = None,
1172
+ _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
1053
1173
  ) -> _FunctionDecoratorType:
1054
1174
  """Decorator to register a new Modal Function with this App."""
1055
1175
  ...
@@ -1087,14 +1207,14 @@ class App:
1087
1207
  startup_timeout: typing.Optional[int] = None,
1088
1208
  cloud: typing.Optional[str] = None,
1089
1209
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
1210
+ nonpreemptible: bool = False,
1090
1211
  enable_memory_snapshot: bool = False,
1091
1212
  block_network: bool = False,
1092
1213
  restrict_modal_access: bool = False,
1093
- max_inputs: typing.Optional[int] = None,
1214
+ single_use_containers: bool = False,
1094
1215
  i6pn: typing.Optional[bool] = None,
1095
1216
  include_source: typing.Optional[bool] = None,
1096
1217
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
1097
- _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
1098
1218
  _experimental_proxy_ip: typing.Optional[str] = None,
1099
1219
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
1100
1220
  _experimental_restrict_output: bool = False,
@@ -1102,8 +1222,9 @@ class App:
1102
1222
  concurrency_limit: typing.Optional[int] = None,
1103
1223
  container_idle_timeout: typing.Optional[int] = None,
1104
1224
  allow_concurrent_inputs: typing.Optional[int] = None,
1225
+ max_inputs: typing.Optional[int] = None,
1105
1226
  _experimental_buffer_containers: typing.Optional[int] = None,
1106
- allow_cross_region_volumes: typing.Optional[bool] = None,
1227
+ _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
1107
1228
  ) -> collections.abc.Callable[[typing.Union[CLS_T, modal.partial_function.PartialFunction]], CLS_T]:
1108
1229
  """Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App."""
1109
1230
  ...
@@ -1137,7 +1258,49 @@ class App:
1137
1258
  """
1138
1259
  ...
1139
1260
 
1140
- class ___logs_spec(typing_extensions.Protocol[SUPERSELF]):
1261
+ class __set_tags_spec(typing_extensions.Protocol):
1262
+ def __call__(
1263
+ self, /, tags: collections.abc.Mapping[str, str], *, client: typing.Optional[modal.client.Client] = None
1264
+ ) -> None:
1265
+ """Attach key-value metadata to the App.
1266
+
1267
+ Tag metadata can be used to add organization-specific context to the App and can be
1268
+ included in billing reports and other informational APIs. Tags can also be set in
1269
+ the App constructor.
1270
+
1271
+ Any tags set on the App before calling this method will be removed if they are not
1272
+ included in the argument (i.e., this method does not have `.update()` semantics).
1273
+ """
1274
+ ...
1275
+
1276
+ async def aio(
1277
+ self, /, tags: collections.abc.Mapping[str, str], *, client: typing.Optional[modal.client.Client] = None
1278
+ ) -> None:
1279
+ """Attach key-value metadata to the App.
1280
+
1281
+ Tag metadata can be used to add organization-specific context to the App and can be
1282
+ included in billing reports and other informational APIs. Tags can also be set in
1283
+ the App constructor.
1284
+
1285
+ Any tags set on the App before calling this method will be removed if they are not
1286
+ included in the argument (i.e., this method does not have `.update()` semantics).
1287
+ """
1288
+ ...
1289
+
1290
+ set_tags: __set_tags_spec
1291
+
1292
+ class __get_tags_spec(typing_extensions.Protocol):
1293
+ def __call__(self, /, *, client: typing.Optional[modal.client.Client] = None) -> dict[str, str]:
1294
+ """Get the tags that are currently attached to the App."""
1295
+ ...
1296
+
1297
+ async def aio(self, /, *, client: typing.Optional[modal.client.Client] = None) -> dict[str, str]:
1298
+ """Get the tags that are currently attached to the App."""
1299
+ ...
1300
+
1301
+ get_tags: __get_tags_spec
1302
+
1303
+ class ___logs_spec(typing_extensions.Protocol):
1141
1304
  def __call__(self, /, client: typing.Optional[modal.client.Client] = None) -> typing.Generator[str, None, None]:
1142
1305
  """Stream logs from the app.
1143
1306
 
@@ -1154,7 +1317,7 @@ class App:
1154
1317
  """
1155
1318
  ...
1156
1319
 
1157
- _logs: ___logs_spec[typing_extensions.Self]
1320
+ _logs: ___logs_spec
1158
1321
 
1159
1322
  @classmethod
1160
1323
  def _get_container_app(cls) -> typing.Optional[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)
modal/builder/2025.06.txt CHANGED
@@ -1,12 +1,15 @@
1
1
  aiohappyeyeballs==2.6.1
2
- aiohttp==3.12.7
3
- aiosignal==1.3.2
2
+ aiohttp==3.12.7 ; python_version < "3.14"
3
+ aiohttp==3.13.2 ; python_version >= "3.14"
4
+ aiosignal==1.3.2 ; python_version < "3.14"
5
+ aiosignal==1.4.0 ; python_version >= "3.14"
4
6
  async-timeout==5.0.1 ; python_version < "3.11"
5
7
  attrs==25.3.0
6
8
  cbor2==5.7.0
7
9
  certifi==2025.4.26
8
10
  frozenlist==1.6.0
9
- grpclib==0.4.8
11
+ grpclib==0.4.8 ; python_version < "3.14"
12
+ grpclib==0.4.9 ; python_version >= "3.14"
10
13
  h2==4.2.0
11
14
  hpack==4.1.0
12
15
  hyperframe==6.1.0
modal/builder/PREVIEW.txt CHANGED
@@ -6,7 +6,8 @@ attrs==25.3.0
6
6
  cbor2==5.7.0
7
7
  certifi==2025.4.26
8
8
  frozenlist==1.6.0
9
- grpclib==0.4.8
9
+ grpclib==0.4.8 ; python_version < "3.14"
10
+ grpclib==0.4.9 ; python_version >= "3.14"
10
11
  h2==4.2.0
11
12
  hpack==4.1.0
12
13
  hyperframe==6.1.0
@@ -12,14 +12,16 @@
12
12
  "3.10.17",
13
13
  "3.11.12",
14
14
  "3.12.10",
15
- "3.13.3"
15
+ "3.13.3",
16
+ "3.14.2"
16
17
  ],
17
18
  "2025.06": [
18
19
  "3.9.22",
19
20
  "3.10.17",
20
21
  "3.11.12",
21
22
  "3.12.10",
22
- "3.13.3"
23
+ "3.13.3",
24
+ "3.14.2"
23
25
  ],
24
26
  "2024.10": [
25
27
  "3.9.20",
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/cluster.py CHANGED
@@ -83,7 +83,9 @@ async def shell(
83
83
  )
84
84
  exec_res: api_pb2.ContainerExecResponse = await client.stub.ContainerExec(req)
85
85
  if pty:
86
- await _ContainerProcess(exec_res.exec_id, client).attach()
86
+ await _ContainerProcess(exec_res.exec_id, task_id, client).attach()
87
87
  else:
88
88
  # TODO: redirect stderr to its own stream?
89
- await _ContainerProcess(exec_res.exec_id, client, stdout=StreamType.STDOUT, stderr=StreamType.STDOUT).wait()
89
+ await _ContainerProcess(
90
+ exec_res.exec_id, task_id, client, stdout=StreamType.STDOUT, stderr=StreamType.STDOUT
91
+ ).wait()
modal/cli/config.py CHANGED
@@ -1,4 +1,6 @@
1
1
  # Copyright Modal Labs 2022
2
+ import json
3
+
2
4
  import typer
3
5
 
4
6
  from modal._output import make_console
@@ -25,7 +27,7 @@ def show(redact: bool = typer.Option(True, help="Redact the `token_secret` value
25
27
  config_dict["token_secret"] = "***"
26
28
 
27
29
  console = make_console()
28
- console.print(config_dict)
30
+ console.print_json(json.dumps(config_dict))
29
31
 
30
32
 
31
33
  SET_DEFAULT_ENV_HELP = """Set the default Modal environment for the active profile