modAL 1.2.1__tar.gz → 1.2.1.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. {modal-1.2.1 → modal-1.2.1.dev0}/PKG-INFO +5 -5
  2. {modal-1.2.1 → modal-1.2.1.dev0}/README.md +4 -4
  3. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_container_entrypoint.py +1 -4
  4. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_partial_function.py +3 -28
  5. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/function_utils.py +0 -4
  6. {modal-1.2.1 → modal-1.2.1.dev0}/modal/app.py +54 -93
  7. {modal-1.2.1 → modal-1.2.1.dev0}/modal/app.pyi +18 -48
  8. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/_download.py +3 -19
  9. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/cluster.py +2 -4
  10. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/container.py +2 -4
  11. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/entry_point.py +0 -1
  12. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/launch.py +2 -1
  13. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/run.py +0 -6
  14. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/volume.py +1 -7
  15. {modal-1.2.1 → modal-1.2.1.dev0}/modal/client.pyi +10 -2
  16. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cls.py +12 -5
  17. {modal-1.2.1 → modal-1.2.1.dev0}/modal/config.py +0 -14
  18. modal-1.2.1.dev0/modal/container_process.py +196 -0
  19. {modal-1.2.1 → modal-1.2.1.dev0}/modal/container_process.pyi +32 -95
  20. {modal-1.2.1 → modal-1.2.1.dev0}/modal/exception.py +0 -4
  21. {modal-1.2.1 → modal-1.2.1.dev0}/modal/experimental/flash.py +47 -21
  22. {modal-1.2.1 → modal-1.2.1.dev0}/modal/experimental/flash.pyi +20 -6
  23. {modal-1.2.1 → modal-1.2.1.dev0}/modal/functions.pyi +6 -6
  24. modal-1.2.1.dev0/modal/io_streams.py +471 -0
  25. {modal-1.2.1 → modal-1.2.1.dev0}/modal/io_streams.pyi +95 -220
  26. {modal-1.2.1 → modal-1.2.1.dev0}/modal/partial_function.pyi +1 -4
  27. {modal-1.2.1 → modal-1.2.1.dev0}/modal/runner.py +36 -39
  28. {modal-1.2.1 → modal-1.2.1.dev0}/modal/runner.pyi +24 -40
  29. {modal-1.2.1 → modal-1.2.1.dev0}/modal/sandbox.py +11 -130
  30. {modal-1.2.1 → modal-1.2.1.dev0}/modal/sandbox.pyi +9 -145
  31. {modal-1.2.1 → modal-1.2.1.dev0}/modal/volume.py +3 -23
  32. {modal-1.2.1 → modal-1.2.1.dev0}/modal/volume.pyi +0 -30
  33. {modal-1.2.1 → modal-1.2.1.dev0}/modal.egg-info/PKG-INFO +5 -5
  34. {modal-1.2.1 → modal-1.2.1.dev0}/modal.egg-info/SOURCES.txt +0 -1
  35. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/api.proto +26 -2
  36. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/api_grpc.py +32 -0
  37. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/api_pb2.py +367 -327
  38. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/api_pb2.pyi +69 -6
  39. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/api_pb2_grpc.py +67 -0
  40. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/api_pb2_grpc.pyi +22 -0
  41. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/modal_api_grpc.py +2 -0
  42. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/sandbox_router.proto +4 -0
  43. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/sandbox_router_pb2.pyi +4 -0
  44. {modal-1.2.1 → modal-1.2.1.dev0}/modal_version/__init__.py +1 -1
  45. modal-1.2.1/modal/_utils/task_command_router_client.py +0 -537
  46. modal-1.2.1/modal/container_process.py +0 -476
  47. modal-1.2.1/modal/io_streams.py +0 -804
  48. {modal-1.2.1 → modal-1.2.1.dev0}/LICENSE +0 -0
  49. {modal-1.2.1 → modal-1.2.1.dev0}/modal/__init__.py +0 -0
  50. {modal-1.2.1 → modal-1.2.1.dev0}/modal/__main__.py +0 -0
  51. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_billing.py +0 -0
  52. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_clustered_functions.py +0 -0
  53. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_clustered_functions.pyi +0 -0
  54. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_functions.py +0 -0
  55. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_ipython.py +0 -0
  56. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_location.py +0 -0
  57. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_object.py +0 -0
  58. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_output.py +0 -0
  59. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_pty.py +0 -0
  60. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_resolver.py +0 -0
  61. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_resources.py +0 -0
  62. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/__init__.py +0 -0
  63. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/asgi.py +0 -0
  64. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/container_io_manager.py +0 -0
  65. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
  66. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/execution_context.py +0 -0
  67. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/execution_context.pyi +0 -0
  68. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  69. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/telemetry.py +0 -0
  70. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_runtime/user_code_imports.py +0 -0
  71. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_serialization.py +0 -0
  72. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_traceback.py +0 -0
  73. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_tunnel.py +0 -0
  74. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_tunnel.pyi +0 -0
  75. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_type_manager.py +0 -0
  76. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/__init__.py +0 -0
  77. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/app_utils.py +0 -0
  78. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/async_utils.py +0 -0
  79. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/auth_token_manager.py +0 -0
  80. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/blob_utils.py +0 -0
  81. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
  82. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/deprecation.py +0 -0
  83. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/docker_utils.py +0 -0
  84. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/git_utils.py +0 -0
  85. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/grpc_testing.py +0 -0
  86. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/grpc_utils.py +0 -0
  87. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/hash_utils.py +0 -0
  88. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/http_utils.py +0 -0
  89. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/jwt_utils.py +0 -0
  90. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/logger.py +0 -0
  91. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/mount_utils.py +0 -0
  92. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/name_utils.py +0 -0
  93. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/package_utils.py +0 -0
  94. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/pattern_utils.py +0 -0
  95. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/rand_pb_testing.py +0 -0
  96. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/shell_utils.py +0 -0
  97. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_utils/time_utils.py +0 -0
  98. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_vendor/__init__.py +0 -0
  99. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  100. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_vendor/cloudpickle.py +0 -0
  101. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_vendor/tblib.py +0 -0
  102. {modal-1.2.1 → modal-1.2.1.dev0}/modal/_watcher.py +0 -0
  103. {modal-1.2.1 → modal-1.2.1.dev0}/modal/billing.py +0 -0
  104. {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/2023.12.312.txt +0 -0
  105. {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/2023.12.txt +0 -0
  106. {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/2024.04.txt +0 -0
  107. {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/2024.10.txt +0 -0
  108. {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/2025.06.txt +0 -0
  109. {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/PREVIEW.txt +0 -0
  110. {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/README.md +0 -0
  111. {modal-1.2.1 → modal-1.2.1.dev0}/modal/builder/base-images.json +0 -0
  112. {modal-1.2.1 → modal-1.2.1.dev0}/modal/call_graph.py +0 -0
  113. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/__init__.py +0 -0
  114. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/_traceback.py +0 -0
  115. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/app.py +0 -0
  116. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/config.py +0 -0
  117. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/dict.py +0 -0
  118. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/environment.py +0 -0
  119. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/import_refs.py +0 -0
  120. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/network_file_system.py +0 -0
  121. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/profile.py +0 -0
  122. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/programs/__init__.py +0 -0
  123. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/programs/launch_instance_ssh.py +0 -0
  124. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/programs/run_jupyter.py +0 -0
  125. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/programs/run_marimo.py +0 -0
  126. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/programs/vscode.py +0 -0
  127. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/queues.py +0 -0
  128. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/secret.py +0 -0
  129. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/token.py +0 -0
  130. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cli/utils.py +0 -0
  131. {modal-1.2.1 → modal-1.2.1.dev0}/modal/client.py +0 -0
  132. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cloud_bucket_mount.py +0 -0
  133. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cloud_bucket_mount.pyi +0 -0
  134. {modal-1.2.1 → modal-1.2.1.dev0}/modal/cls.pyi +0 -0
  135. {modal-1.2.1 → modal-1.2.1.dev0}/modal/dict.py +0 -0
  136. {modal-1.2.1 → modal-1.2.1.dev0}/modal/dict.pyi +0 -0
  137. {modal-1.2.1 → modal-1.2.1.dev0}/modal/environments.py +0 -0
  138. {modal-1.2.1 → modal-1.2.1.dev0}/modal/environments.pyi +0 -0
  139. {modal-1.2.1 → modal-1.2.1.dev0}/modal/experimental/__init__.py +0 -0
  140. {modal-1.2.1 → modal-1.2.1.dev0}/modal/experimental/ipython.py +0 -0
  141. {modal-1.2.1 → modal-1.2.1.dev0}/modal/file_io.py +0 -0
  142. {modal-1.2.1 → modal-1.2.1.dev0}/modal/file_io.pyi +0 -0
  143. {modal-1.2.1 → modal-1.2.1.dev0}/modal/file_pattern_matcher.py +0 -0
  144. {modal-1.2.1 → modal-1.2.1.dev0}/modal/functions.py +0 -0
  145. {modal-1.2.1 → modal-1.2.1.dev0}/modal/gpu.py +0 -0
  146. {modal-1.2.1 → modal-1.2.1.dev0}/modal/image.py +0 -0
  147. {modal-1.2.1 → modal-1.2.1.dev0}/modal/image.pyi +0 -0
  148. {modal-1.2.1 → modal-1.2.1.dev0}/modal/mount.py +0 -0
  149. {modal-1.2.1 → modal-1.2.1.dev0}/modal/mount.pyi +0 -0
  150. {modal-1.2.1 → modal-1.2.1.dev0}/modal/network_file_system.py +0 -0
  151. {modal-1.2.1 → modal-1.2.1.dev0}/modal/network_file_system.pyi +0 -0
  152. {modal-1.2.1 → modal-1.2.1.dev0}/modal/object.py +0 -0
  153. {modal-1.2.1 → modal-1.2.1.dev0}/modal/object.pyi +0 -0
  154. {modal-1.2.1 → modal-1.2.1.dev0}/modal/output.py +0 -0
  155. {modal-1.2.1 → modal-1.2.1.dev0}/modal/parallel_map.py +0 -0
  156. {modal-1.2.1 → modal-1.2.1.dev0}/modal/parallel_map.pyi +0 -0
  157. {modal-1.2.1 → modal-1.2.1.dev0}/modal/partial_function.py +0 -0
  158. {modal-1.2.1 → modal-1.2.1.dev0}/modal/proxy.py +0 -0
  159. {modal-1.2.1 → modal-1.2.1.dev0}/modal/proxy.pyi +0 -0
  160. {modal-1.2.1 → modal-1.2.1.dev0}/modal/py.typed +0 -0
  161. {modal-1.2.1 → modal-1.2.1.dev0}/modal/queue.py +0 -0
  162. {modal-1.2.1 → modal-1.2.1.dev0}/modal/queue.pyi +0 -0
  163. {modal-1.2.1 → modal-1.2.1.dev0}/modal/retries.py +0 -0
  164. {modal-1.2.1 → modal-1.2.1.dev0}/modal/running_app.py +0 -0
  165. {modal-1.2.1 → modal-1.2.1.dev0}/modal/schedule.py +0 -0
  166. {modal-1.2.1 → modal-1.2.1.dev0}/modal/scheduler_placement.py +0 -0
  167. {modal-1.2.1 → modal-1.2.1.dev0}/modal/secret.py +0 -0
  168. {modal-1.2.1 → modal-1.2.1.dev0}/modal/secret.pyi +0 -0
  169. {modal-1.2.1 → modal-1.2.1.dev0}/modal/serving.py +0 -0
  170. {modal-1.2.1 → modal-1.2.1.dev0}/modal/serving.pyi +0 -0
  171. {modal-1.2.1 → modal-1.2.1.dev0}/modal/snapshot.py +0 -0
  172. {modal-1.2.1 → modal-1.2.1.dev0}/modal/snapshot.pyi +0 -0
  173. {modal-1.2.1 → modal-1.2.1.dev0}/modal/stream_type.py +0 -0
  174. {modal-1.2.1 → modal-1.2.1.dev0}/modal/token_flow.py +0 -0
  175. {modal-1.2.1 → modal-1.2.1.dev0}/modal/token_flow.pyi +0 -0
  176. {modal-1.2.1 → modal-1.2.1.dev0}/modal.egg-info/dependency_links.txt +0 -0
  177. {modal-1.2.1 → modal-1.2.1.dev0}/modal.egg-info/entry_points.txt +0 -0
  178. {modal-1.2.1 → modal-1.2.1.dev0}/modal.egg-info/requires.txt +0 -0
  179. {modal-1.2.1 → modal-1.2.1.dev0}/modal.egg-info/top_level.txt +0 -0
  180. {modal-1.2.1 → modal-1.2.1.dev0}/modal_docs/__init__.py +0 -0
  181. {modal-1.2.1 → modal-1.2.1.dev0}/modal_docs/gen_cli_docs.py +0 -0
  182. {modal-1.2.1 → modal-1.2.1.dev0}/modal_docs/gen_reference_docs.py +0 -0
  183. {modal-1.2.1 → modal-1.2.1.dev0}/modal_docs/mdmd/__init__.py +0 -0
  184. {modal-1.2.1 → modal-1.2.1.dev0}/modal_docs/mdmd/mdmd.py +0 -0
  185. {modal-1.2.1 → modal-1.2.1.dev0}/modal_docs/mdmd/signatures.py +0 -0
  186. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/__init__.py +0 -0
  187. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/py.typed +0 -0
  188. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/sandbox_router_grpc.py +0 -0
  189. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/sandbox_router_pb2.py +0 -0
  190. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
  191. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
  192. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/task_command_router.proto +0 -0
  193. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/task_command_router_grpc.py +0 -0
  194. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/task_command_router_pb2.py +0 -0
  195. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
  196. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  197. {modal-1.2.1 → modal-1.2.1.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  198. {modal-1.2.1 → modal-1.2.1.dev0}/modal_version/__main__.py +0 -0
  199. {modal-1.2.1 → modal-1.2.1.dev0}/pyproject.toml +0 -0
  200. {modal-1.2.1 → modal-1.2.1.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.1
3
+ Version: 1.2.1.dev0
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -32,14 +32,14 @@ Requires-Dist: watchfiles
32
32
  Requires-Dist: typing_extensions~=4.6
33
33
  Dynamic: license-file
34
34
 
35
- # Modal Python SDK
35
+ # Modal Python Library
36
36
 
37
37
  [![PyPI Version](https://img.shields.io/pypi/v/modal.svg)](https://pypi.org/project/modal/)
38
38
  [![License](https://img.shields.io/badge/license-apache_2.0-darkviolet.svg)](https://github.com/modal-labs/modal-client/blob/master/LICENSE)
39
39
  [![Tests](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml)
40
40
  [![Slack](https://img.shields.io/badge/slack-join-blue.svg?logo=slack)](https://modal.com/slack)
41
41
 
42
- The [Modal](https://modal.com/) Python SDK provides convenient, on-demand
42
+ The [Modal](https://modal.com/) Python library provides convenient, on-demand
43
43
  access to serverless cloud compute from Python scripts on your local computer.
44
44
 
45
45
  ## Documentation
@@ -53,10 +53,10 @@ a [user guide](https://modal.com/docs/guide), and the detailed
53
53
 
54
54
  **This library requires Python 3.9 – 3.13.**
55
55
 
56
- Install the package with `uv` or `pip`:
56
+ Install the package with `pip`:
57
57
 
58
58
  ```bash
59
- uv pip install modal
59
+ pip install modal
60
60
  ```
61
61
 
62
62
  You can create a Modal account (or link your existing one) directly on the
@@ -1,11 +1,11 @@
1
- # Modal Python SDK
1
+ # Modal Python Library
2
2
 
3
3
  [![PyPI Version](https://img.shields.io/pypi/v/modal.svg)](https://pypi.org/project/modal/)
4
4
  [![License](https://img.shields.io/badge/license-apache_2.0-darkviolet.svg)](https://github.com/modal-labs/modal-client/blob/master/LICENSE)
5
5
  [![Tests](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml)
6
6
  [![Slack](https://img.shields.io/badge/slack-join-blue.svg?logo=slack)](https://modal.com/slack)
7
7
 
8
- The [Modal](https://modal.com/) Python SDK provides convenient, on-demand
8
+ The [Modal](https://modal.com/) Python library provides convenient, on-demand
9
9
  access to serverless cloud compute from Python scripts on your local computer.
10
10
 
11
11
  ## Documentation
@@ -19,10 +19,10 @@ a [user guide](https://modal.com/docs/guide), and the detailed
19
19
 
20
20
  **This library requires Python 3.9 – 3.13.**
21
21
 
22
- Install the package with `uv` or `pip`:
22
+ Install the package with `pip`:
23
23
 
24
24
  ```bash
25
- uv pip install modal
25
+ pip install modal
26
26
  ```
27
27
 
28
28
  You can create a Modal account (or link your existing one) directly on the
@@ -450,10 +450,7 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
450
450
  f"Function has {len(service.service_deps)} dependencies"
451
451
  f" but container got {len(dep_object_ids)} object ids.\n"
452
452
  f"Code deps: {service.service_deps}\n"
453
- f"Object ids: {dep_object_ids}\n"
454
- "\n"
455
- "This can happen if you are defining Modal objects under a conditional statement "
456
- "that evaluates differently in the local and remote environments."
453
+ f"Object ids: {dep_object_ids}"
457
454
  )
458
455
  for object_id, obj in zip(dep_object_ids, service.service_deps):
459
456
  metadata: Message = container_app.object_handle_metadata[object_id]
@@ -19,7 +19,7 @@ from ._functions import _Function
19
19
  from ._utils.async_utils import synchronizer
20
20
  from ._utils.deprecation import deprecation_warning
21
21
  from ._utils.function_utils import callable_has_non_self_params
22
- from .config import config, logger
22
+ from .config import logger
23
23
  from .exception import InvalidError
24
24
 
25
25
  MAX_MAX_BATCH_SIZE = 1000
@@ -93,26 +93,6 @@ NullaryFuncOrMethod = Union[Callable[[], Any], Callable[[Any], Any]]
93
93
  NullaryMethod = Callable[[Any], Any]
94
94
 
95
95
 
96
- def verify_concurrent_params(params: _PartialFunctionParams, is_flash: bool = False) -> None:
97
- def _verify_concurrent_params_with_flash_settings(params: _PartialFunctionParams) -> None:
98
- if params.max_concurrent_inputs is not None:
99
- raise TypeError(
100
- "@modal.concurrent(max_inputs=...) is not yet supported for Flash functions. "
101
- "Use `@modal.concurrent(target_inputs=...)` instead."
102
- )
103
- if params.target_concurrent_inputs is None:
104
- raise TypeError("`@modal.concurrent()` missing required argument: `target_inputs`.")
105
-
106
- def _verify_concurrent_params(params: _PartialFunctionParams) -> None:
107
- if params.max_concurrent_inputs is None:
108
- raise TypeError("`@modal.concurrent()` missing required argument: `max_inputs`.")
109
-
110
- if is_flash:
111
- _verify_concurrent_params_with_flash_settings(params)
112
- else:
113
- _verify_concurrent_params(params)
114
-
115
-
116
96
  class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
117
97
  """Object produced by a decorator in the `modal` namespace
118
98
 
@@ -398,7 +378,6 @@ def _fastapi_endpoint(
398
378
  method=method,
399
379
  web_endpoint_docs=docs,
400
380
  requested_suffix=label or "",
401
- ephemeral_suffix=config.get("dev_suffix"),
402
381
  async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
403
382
  custom_domains=_parse_custom_domains(custom_domains),
404
383
  requires_proxy_auth=requires_proxy_auth,
@@ -467,7 +446,6 @@ def _web_endpoint(
467
446
  method=method,
468
447
  web_endpoint_docs=docs,
469
448
  requested_suffix=label or "",
470
- ephemeral_suffix=config.get("dev_suffix"),
471
449
  async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
472
450
  custom_domains=_parse_custom_domains(custom_domains),
473
451
  requires_proxy_auth=requires_proxy_auth,
@@ -527,7 +505,6 @@ def _asgi_app(
527
505
  webhook_config = api_pb2.WebhookConfig(
528
506
  type=api_pb2.WEBHOOK_TYPE_ASGI_APP,
529
507
  requested_suffix=label or "",
530
- ephemeral_suffix=config.get("dev_suffix"),
531
508
  async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
532
509
  custom_domains=_parse_custom_domains(custom_domains),
533
510
  requires_proxy_auth=requires_proxy_auth,
@@ -585,7 +562,6 @@ def _wsgi_app(
585
562
  webhook_config = api_pb2.WebhookConfig(
586
563
  type=api_pb2.WEBHOOK_TYPE_WSGI_APP,
587
564
  requested_suffix=label or "",
588
- ephemeral_suffix=config.get("dev_suffix"),
589
565
  async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
590
566
  custom_domains=_parse_custom_domains(custom_domains),
591
567
  requires_proxy_auth=requires_proxy_auth,
@@ -647,7 +623,6 @@ def _web_server(
647
623
  webhook_config = api_pb2.WebhookConfig(
648
624
  type=api_pb2.WEBHOOK_TYPE_WEB_SERVER,
649
625
  requested_suffix=label or "",
650
- ephemeral_suffix=config.get("dev_suffix"),
651
626
  async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
652
627
  custom_domains=_parse_custom_domains(custom_domains),
653
628
  web_server_port=port,
@@ -785,7 +760,7 @@ def _batched(
785
760
  def _concurrent(
786
761
  _warn_parentheses_missing=None, # mdmd:line-hidden
787
762
  *,
788
- max_inputs: Optional[int] = None, # Hard limit on each container's input concurrency
763
+ max_inputs: int, # Hard limit on each container's input concurrency
789
764
  target_inputs: Optional[int] = None, # Input concurrency that Modal's autoscaler should target
790
765
  ) -> Callable[
791
766
  [Union[Callable[P, ReturnType], _PartialFunction[P, ReturnType, ReturnType]]],
@@ -837,7 +812,7 @@ def _concurrent(
837
812
  "Positional arguments are not allowed. Did you forget parentheses? Suggestion: `@modal.concurrent()`."
838
813
  )
839
814
 
840
- if max_inputs is not None and target_inputs is not None and target_inputs > max_inputs:
815
+ if target_inputs and target_inputs > max_inputs:
841
816
  raise InvalidError("`target_inputs` parameter cannot be greater than `max_inputs`.")
842
817
 
843
818
  flags = _PartialFunctionFlags.CONCURRENT
@@ -75,10 +75,6 @@ def is_global_object(object_qual_name: str):
75
75
  return "<locals>" not in object_qual_name.split(".")
76
76
 
77
77
 
78
- def is_flash_object(experimental_options: Optional[dict[str, Any]]) -> bool:
79
- return experimental_options.get("flash", False) if experimental_options else False
80
-
81
-
82
78
  def is_method_fn(object_qual_name: str):
83
79
  # methods have names like Cls.foo.
84
80
  if "<locals>" in object_qual_name:
@@ -2,7 +2,6 @@
2
2
  import inspect
3
3
  import typing
4
4
  from collections.abc import AsyncGenerator, Collection, Coroutine, Mapping, Sequence
5
- from dataclasses import dataclass
6
5
  from pathlib import PurePosixPath
7
6
  from textwrap import dedent
8
7
  from typing import (
@@ -27,14 +26,13 @@ from ._partial_function import (
27
26
  _find_partial_methods_for_user_cls,
28
27
  _PartialFunction,
29
28
  _PartialFunctionFlags,
30
- verify_concurrent_params,
31
29
  )
32
30
  from ._utils.async_utils import synchronize_api
33
31
  from ._utils.deprecation import (
34
32
  deprecation_warning,
35
33
  warn_on_renamed_autoscaler_settings,
36
34
  )
37
- from ._utils.function_utils import FunctionInfo, is_flash_object, is_global_object, is_method_fn
35
+ from ._utils.function_utils import FunctionInfo, is_global_object, is_method_fn
38
36
  from ._utils.grpc_utils import retry_transient_errors
39
37
  from ._utils.mount_utils import validate_volumes
40
38
  from ._utils.name_utils import check_object_name, check_tag_dict
@@ -116,22 +114,6 @@ class _FunctionDecoratorType:
116
114
  def __call__(self, func): ...
117
115
 
118
116
 
119
- @dataclass()
120
- class _LocalAppState:
121
- """All state for apps that's part of the local/definition state"""
122
-
123
- functions: dict[str, _Function]
124
- classes: dict[str, _Cls]
125
- image_default: Optional[_Image]
126
- web_endpoints: list[str] # Used by the CLI
127
- local_entrypoints: dict[str, _LocalEntrypoint]
128
- tags: dict[str, str]
129
-
130
- include_source_default: bool
131
- secrets_default: Sequence[_Secret]
132
- volumes_default: dict[Union[str, PurePosixPath], _Volume]
133
-
134
-
135
117
  class _App:
136
118
  """A Modal App is a group of functions and classes that are deployed together.
137
119
 
@@ -169,21 +151,23 @@ class _App:
169
151
 
170
152
  _name: Optional[str]
171
153
  _description: Optional[str]
154
+ _tags: dict[str, str]
155
+
156
+ _functions: dict[str, _Function]
157
+ _classes: dict[str, _Cls]
172
158
 
173
- _local_state_attr: Optional[_LocalAppState] = None
159
+ _image: Optional[_Image]
160
+ _secrets: Sequence[_Secret]
161
+ _volumes: dict[Union[str, PurePosixPath], _Volume]
162
+ _web_endpoints: list[str] # Used by the CLI
163
+ _local_entrypoints: dict[str, _LocalEntrypoint]
174
164
 
175
165
  # Running apps only (container apps or running local)
176
166
  _app_id: Optional[str] # Kept after app finishes
177
167
  _running_app: Optional[RunningApp] # Various app info
178
168
  _client: Optional[_Client]
179
169
 
180
- @property
181
- def _local_state(self) -> _LocalAppState:
182
- """For internal use only. Do not use this property directly."""
183
-
184
- if self._local_state_attr is None:
185
- raise AttributeError("Local state is not initialized - app is not locally available")
186
- return self._local_state_attr
170
+ _include_source_default: Optional[bool] = None
187
171
 
188
172
  def __init__(
189
173
  self,
@@ -212,6 +196,8 @@ class _App:
212
196
 
213
197
  self._name = name
214
198
  self._description = name
199
+ self._tags = tags or {}
200
+ self._include_source_default = include_source
215
201
 
216
202
  check_sequence(secrets, _Secret, "`secrets=` has to be a list or tuple of `modal.Secret` objects")
217
203
  validate_volumes(volumes)
@@ -219,24 +205,16 @@ class _App:
219
205
  if image is not None and not isinstance(image, _Image):
220
206
  raise InvalidError("`image=` has to be a `modal.Image` object")
221
207
 
222
- self._local_state_attr = _LocalAppState(
223
- functions={},
224
- classes={},
225
- image_default=image,
226
- secrets_default=secrets,
227
- volumes_default=volumes,
228
- include_source_default=include_source,
229
- web_endpoints=[],
230
- local_entrypoints={},
231
- tags=tags or {},
232
- )
208
+ self._functions = {}
209
+ self._classes = {}
210
+ self._image = image
211
+ self._secrets = secrets
212
+ self._volumes = volumes
213
+ self._local_entrypoints = {}
214
+ self._web_endpoints = []
233
215
 
234
- # Running apps only
235
216
  self._app_id = None
236
217
  self._running_app = None # Set inside container, OR during the time an app is running locally
237
-
238
- # Client is special - needed to be set just before the app is "hydrated" or running at the latest
239
- # Guaranteed to be set for running apps, but also needed to actually *hydrate* the app and make it running
240
218
  self._client = None
241
219
 
242
220
  # Register this app. This is used to look up the app in the container, when we can't get it from the function
@@ -305,8 +283,7 @@ class _App:
305
283
 
306
284
  response = await retry_transient_errors(client.stub.AppGetOrCreate, request)
307
285
 
308
- app = _App(name) # TODO: this should probably be a distinct constructor, possibly even a distinct type
309
- app._local_state_attr = None # this is not a locally defined App, so no local state
286
+ app = _App(name)
310
287
  app._app_id = response.app_id
311
288
  app._client = client
312
289
  app._running_app = RunningApp(response.app_id, interactive=False)
@@ -333,19 +310,18 @@ class _App:
333
310
  App that is retrieved via `modal.App.lookup`. It is likely to be deprecated in the future.
334
311
 
335
312
  """
336
- return self._local_state.image_default
313
+ return self._image
337
314
 
338
315
  @image.setter
339
316
  def image(self, value):
340
317
  """mdmd:hidden"""
341
- self._local_state.image_default = value
318
+ self._image = value
342
319
 
343
320
  def _uncreate_all_objects(self):
344
321
  # TODO(erikbern): this doesn't unhydrate objects that aren't tagged
345
- local_state = self._local_state
346
- for obj in local_state.functions.values():
322
+ for obj in self._functions.values():
347
323
  obj._unhydrate()
348
- for obj in local_state.classes.values():
324
+ for obj in self._classes.values():
349
325
  obj._unhydrate()
350
326
 
351
327
  @asynccontextmanager
@@ -481,9 +457,8 @@ class _App:
481
457
  return self
482
458
 
483
459
  def _get_default_image(self):
484
- local_state = self._local_state
485
- if local_state.image_default:
486
- return local_state.image_default
460
+ if self._image:
461
+ return self._image
487
462
  else:
488
463
  return _default_image
489
464
 
@@ -498,8 +473,7 @@ class _App:
498
473
  return [m for m in all_mounts if m.is_local()]
499
474
 
500
475
  def _add_function(self, function: _Function, is_web_endpoint: bool):
501
- local_state = self._local_state
502
- if old_function := local_state.functions.get(function.tag, None):
476
+ if old_function := self._functions.get(function.tag, None):
503
477
  if old_function is function:
504
478
  return # already added the same exact instance, ignore
505
479
 
@@ -510,7 +484,7 @@ class _App:
510
484
  f"[{old_function._info.module_name}].{old_function._info.function_name}"
511
485
  f" with new function [{function._info.module_name}].{function._info.function_name}"
512
486
  )
513
- if function.tag in local_state.classes:
487
+ if function.tag in self._classes:
514
488
  logger.warning(f"Warning: tag {function.tag} exists but is overridden by function")
515
489
 
516
490
  if self._running_app:
@@ -521,9 +495,9 @@ class _App:
521
495
  metadata: Message = self._running_app.object_handle_metadata[object_id]
522
496
  function._hydrate(object_id, self._client, metadata)
523
497
 
524
- local_state.functions[function.tag] = function
498
+ self._functions[function.tag] = function
525
499
  if is_web_endpoint:
526
- local_state.web_endpoints.append(function.tag)
500
+ self._web_endpoints.append(function.tag)
527
501
 
528
502
  def _add_class(self, tag: str, cls: _Cls):
529
503
  if self._running_app:
@@ -534,7 +508,7 @@ class _App:
534
508
  metadata: Message = self._running_app.object_handle_metadata[object_id]
535
509
  cls._hydrate(object_id, self._client, metadata)
536
510
 
537
- self._local_state.classes[tag] = cls
511
+ self._classes[tag] = cls
538
512
 
539
513
  def _init_container(self, client: _Client, running_app: RunningApp):
540
514
  self._app_id = running_app.app_id
@@ -542,18 +516,18 @@ class _App:
542
516
  self._client = client
543
517
 
544
518
  _App._container_app = self
545
- local_state = self._local_state
519
+
546
520
  # Hydrate function objects
547
521
  for tag, object_id in running_app.function_ids.items():
548
- if tag in local_state.functions:
549
- obj = local_state.functions[tag]
522
+ if tag in self._functions:
523
+ obj = self._functions[tag]
550
524
  handle_metadata = running_app.object_handle_metadata[object_id]
551
525
  obj._hydrate(object_id, client, handle_metadata)
552
526
 
553
527
  # Hydrate class objects
554
528
  for tag, object_id in running_app.class_ids.items():
555
- if tag in local_state.classes:
556
- obj = local_state.classes[tag]
529
+ if tag in self._classes:
530
+ obj = self._classes[tag]
557
531
  handle_metadata = running_app.object_handle_metadata[object_id]
558
532
  obj._hydrate(object_id, client, handle_metadata)
559
533
 
@@ -567,7 +541,7 @@ class _App:
567
541
  This method is likely to be deprecated in the future in favor of a different
568
542
  approach for retrieving the layout of a deployed App.
569
543
  """
570
- return self._local_state.functions
544
+ return self._functions
571
545
 
572
546
  @property
573
547
  def registered_classes(self) -> dict[str, _Cls]:
@@ -579,7 +553,7 @@ class _App:
579
553
  This method is likely to be deprecated in the future in favor of a different
580
554
  approach for retrieving the layout of a deployed App.
581
555
  """
582
- return self._local_state.classes
556
+ return self._classes
583
557
 
584
558
  @property
585
559
  def registered_entrypoints(self) -> dict[str, _LocalEntrypoint]:
@@ -590,7 +564,7 @@ class _App:
590
564
  expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
591
565
  This method is likely to be deprecated in the future.
592
566
  """
593
- return self._local_state.local_entrypoints
567
+ return self._local_entrypoints
594
568
 
595
569
  @property
596
570
  def registered_web_endpoints(self) -> list[str]:
@@ -602,7 +576,7 @@ class _App:
602
576
  This method is likely to be deprecated in the future in favor of a different
603
577
  approach for retrieving the layout of a deployed App.
604
578
  """
605
- return self._local_state.web_endpoints
579
+ return self._web_endpoints
606
580
 
607
581
  def local_entrypoint(
608
582
  self, _warn_parentheses_missing: Any = None, *, name: Optional[str] = None
@@ -663,11 +637,10 @@ class _App:
663
637
  def wrapped(raw_f: Callable[..., Any]) -> _LocalEntrypoint:
664
638
  info = FunctionInfo(raw_f)
665
639
  tag = name if name is not None else raw_f.__qualname__
666
- local_state = self._local_state
667
- if tag in local_state.local_entrypoints:
640
+ if tag in self._local_entrypoints:
668
641
  # TODO: get rid of this limitation.
669
642
  raise InvalidError(f"Duplicate local entrypoint name: {tag}. Local entrypoint names must be unique.")
670
- entrypoint = local_state.local_entrypoints[tag] = _LocalEntrypoint(info, self)
643
+ entrypoint = self._local_entrypoints[tag] = _LocalEntrypoint(info, self)
671
644
  return entrypoint
672
645
 
673
646
  return wrapped
@@ -759,8 +732,7 @@ class _App:
759
732
  secrets = secrets or []
760
733
  if env:
761
734
  secrets = [*secrets, _Secret.from_dict(env)]
762
- local_state = self._local_state
763
- secrets = [*local_state.secrets_default, *secrets]
735
+ secrets = [*self._secrets, *secrets]
764
736
 
765
737
  def wrapped(
766
738
  f: Union[_PartialFunction, Callable[..., Any], None],
@@ -803,7 +775,6 @@ class _App:
803
775
  batch_max_size = f.params.batch_max_size
804
776
  batch_wait_ms = f.params.batch_wait_ms
805
777
  if f.flags & _PartialFunctionFlags.CONCURRENT:
806
- verify_concurrent_params(params=f.params, is_flash=is_flash_object(experimental_options))
807
778
  max_concurrent_inputs = f.params.max_concurrent_inputs
808
779
  target_concurrent_inputs = f.params.target_concurrent_inputs
809
780
  else:
@@ -869,7 +840,7 @@ class _App:
869
840
  is_generator=is_generator,
870
841
  gpu=gpu,
871
842
  network_file_systems=network_file_systems,
872
- volumes={**local_state.volumes_default, **volumes},
843
+ volumes={**self._volumes, **volumes},
873
844
  cpu=cpu,
874
845
  memory=memory,
875
846
  ephemeral_disk=ephemeral_disk,
@@ -895,7 +866,7 @@ class _App:
895
866
  i6pn_enabled=i6pn_enabled,
896
867
  cluster_size=cluster_size, # Experimental: Clustered functions
897
868
  rdma=rdma,
898
- include_source=include_source if include_source is not None else local_state.include_source_default,
869
+ include_source=include_source if include_source is not None else self._include_source_default,
899
870
  experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
900
871
  _experimental_proxy_ip=_experimental_proxy_ip,
901
872
  restrict_output=_experimental_restrict_output,
@@ -992,13 +963,11 @@ class _App:
992
963
  secrets = [*secrets, _Secret.from_dict(env)]
993
964
 
994
965
  def wrapper(wrapped_cls: Union[CLS_T, _PartialFunction]) -> CLS_T:
995
- local_state = self._local_state
996
966
  # Check if the decorated object is a class
997
967
  if isinstance(wrapped_cls, _PartialFunction):
998
968
  wrapped_cls.registered = True
999
969
  user_cls = wrapped_cls.user_cls
1000
970
  if wrapped_cls.flags & _PartialFunctionFlags.CONCURRENT:
1001
- verify_concurrent_params(params=wrapped_cls.params, is_flash=is_flash_object(experimental_options))
1002
971
  max_concurrent_inputs = wrapped_cls.params.max_concurrent_inputs
1003
972
  target_concurrent_inputs = wrapped_cls.params.target_concurrent_inputs
1004
973
  else:
@@ -1060,10 +1029,10 @@ class _App:
1060
1029
  info,
1061
1030
  app=self,
1062
1031
  image=image or self._get_default_image(),
1063
- secrets=[*local_state.secrets_default, *secrets],
1032
+ secrets=[*self._secrets, *secrets],
1064
1033
  gpu=gpu,
1065
1034
  network_file_systems=network_file_systems,
1066
- volumes={**local_state.volumes_default, **volumes},
1035
+ volumes={**self._volumes, **volumes},
1067
1036
  cpu=cpu,
1068
1037
  memory=memory,
1069
1038
  ephemeral_disk=ephemeral_disk,
@@ -1088,7 +1057,7 @@ class _App:
1088
1057
  i6pn_enabled=i6pn_enabled,
1089
1058
  cluster_size=cluster_size,
1090
1059
  rdma=rdma,
1091
- include_source=include_source if include_source is not None else local_state.include_source_default,
1060
+ include_source=include_source if include_source is not None else self._include_source_default,
1092
1061
  experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
1093
1062
  _experimental_proxy_ip=_experimental_proxy_ip,
1094
1063
  _experimental_custom_scaling_factor=_experimental_custom_scaling_factor,
@@ -1098,11 +1067,6 @@ class _App:
1098
1067
  self._add_function(cls_func, is_web_endpoint=False)
1099
1068
 
1100
1069
  cls: _Cls = _Cls.from_local(user_cls, self, cls_func)
1101
- for method_name, partial_function in cls._method_partials.items():
1102
- if partial_function.params.webhook_config is not None:
1103
- full_name = f"{user_cls.__name__}.{method_name}"
1104
- local_state.web_endpoints.append(full_name)
1105
- partial_function.registered = True
1106
1070
 
1107
1071
  tag: str = user_cls.__name__
1108
1072
  self._add_class(tag, cls)
@@ -1138,14 +1102,11 @@ class _App:
1138
1102
  (with this App's tags taking precedence in the case of conflicts).
1139
1103
 
1140
1104
  """
1141
- other_app_local_state = other_app._local_state
1142
- this_local_state = self._local_state
1143
-
1144
- for tag, function in other_app_local_state.functions.items():
1105
+ for tag, function in other_app._functions.items():
1145
1106
  self._add_function(function, False) # TODO(erikbern): webhook config?
1146
1107
 
1147
- for tag, cls in other_app_local_state.classes.items():
1148
- existing_cls = this_local_state.classes.get(tag)
1108
+ for tag, cls in other_app._classes.items():
1109
+ existing_cls = self._classes.get(tag)
1149
1110
  if existing_cls and existing_cls != cls:
1150
1111
  logger.warning(
1151
1112
  f"Named app class {tag} with existing value {existing_cls} is being "
@@ -1155,7 +1116,7 @@ class _App:
1155
1116
  self._add_class(tag, cls)
1156
1117
 
1157
1118
  if inherit_tags:
1158
- this_local_state.tags = {**other_app_local_state.tags, **this_local_state.tags}
1119
+ self._tags = {**other_app._tags, **self._tags}
1159
1120
 
1160
1121
  return self
1161
1122
 
@@ -1171,7 +1132,7 @@ class _App:
1171
1132
 
1172
1133
  """
1173
1134
  # Note that we are requiring the App to be "running" before we set the tags.
1174
- # Alternatively, we could hold onto the tags (i.e. in `self._local_state.tags`) and then pass
1135
+ # Alternatively, we could hold onto the tags (i.e. in `self._tags`) and then pass
1175
1136
  # then up when AppPublish gets called. I'm not certain we want to support it, though.
1176
1137
  # It might not be obvious to users that `.set_tags()` is eager and has immediate effect
1177
1138
  # when the App is running, but lazy (and potentially ignored) otherwise. There would be
@@ -74,42 +74,6 @@ class _FunctionDecoratorType:
74
74
  self, func: collections.abc.Callable[P, ReturnType]
75
75
  ) -> modal.functions.Function[P, ReturnType, ReturnType]: ...
76
76
 
77
- class _LocalAppState:
78
- """All state for apps that's part of the local/definition state"""
79
-
80
- functions: dict[str, modal._functions._Function]
81
- classes: dict[str, modal.cls._Cls]
82
- image_default: typing.Optional[modal.image._Image]
83
- web_endpoints: list[str]
84
- local_entrypoints: dict[str, _LocalEntrypoint]
85
- tags: dict[str, str]
86
- include_source_default: bool
87
- secrets_default: collections.abc.Sequence[modal.secret._Secret]
88
- volumes_default: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume]
89
-
90
- def __init__(
91
- self,
92
- functions: dict[str, modal._functions._Function],
93
- classes: dict[str, modal.cls._Cls],
94
- image_default: typing.Optional[modal.image._Image],
95
- web_endpoints: list[str],
96
- local_entrypoints: dict[str, _LocalEntrypoint],
97
- tags: dict[str, str],
98
- include_source_default: bool,
99
- secrets_default: collections.abc.Sequence[modal.secret._Secret],
100
- volumes_default: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume],
101
- ) -> None:
102
- """Initialize self. See help(type(self)) for accurate signature."""
103
- ...
104
-
105
- def __repr__(self):
106
- """Return repr(self)."""
107
- ...
108
-
109
- def __eq__(self, other):
110
- """Return self==value."""
111
- ...
112
-
113
77
  class _App:
114
78
  """A Modal App is a group of functions and classes that are deployed together.
115
79
 
@@ -146,15 +110,18 @@ class _App:
146
110
  _container_app: typing.ClassVar[typing.Optional[_App]]
147
111
  _name: typing.Optional[str]
148
112
  _description: typing.Optional[str]
149
- _local_state_attr: typing.Optional[_LocalAppState]
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
121
  _app_id: typing.Optional[str]
151
122
  _running_app: typing.Optional[modal.running_app.RunningApp]
152
123
  _client: typing.Optional[modal.client._Client]
153
-
154
- @property
155
- def _local_state(self) -> _LocalAppState:
156
- """For internal use only. Do not use this property directly."""
157
- ...
124
+ _include_source_default: typing.Optional[bool]
158
125
 
159
126
  def __init__(
160
127
  self,
@@ -663,10 +630,18 @@ class App:
663
630
  _container_app: typing.ClassVar[typing.Optional[App]]
664
631
  _name: typing.Optional[str]
665
632
  _description: typing.Optional[str]
666
- _local_state_attr: typing.Optional[_LocalAppState]
633
+ _tags: dict[str, str]
634
+ _functions: dict[str, modal.functions.Function]
635
+ _classes: dict[str, modal.cls.Cls]
636
+ _image: typing.Optional[modal.image.Image]
637
+ _secrets: collections.abc.Sequence[modal.secret.Secret]
638
+ _volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume.Volume]
639
+ _web_endpoints: list[str]
640
+ _local_entrypoints: dict[str, LocalEntrypoint]
667
641
  _app_id: typing.Optional[str]
668
642
  _running_app: typing.Optional[modal.running_app.RunningApp]
669
643
  _client: typing.Optional[modal.client.Client]
644
+ _include_source_default: typing.Optional[bool]
670
645
 
671
646
  def __init__(
672
647
  self,
@@ -689,11 +664,6 @@ class App:
689
664
  """
690
665
  ...
691
666
 
692
- @property
693
- def _local_state(self) -> _LocalAppState:
694
- """For internal use only. Do not use this property directly."""
695
- ...
696
-
697
667
  @property
698
668
  def name(self) -> typing.Optional[str]:
699
669
  """The user-provided name of the App."""