modal 1.2.1.dev7__tar.gz → 1.2.1.dev9__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.

Potentially problematic release.


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

Files changed (197) hide show
  1. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/PKG-INFO +1 -1
  2. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/app.py +89 -53
  3. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/app.pyi +48 -18
  4. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/client.pyi +2 -2
  5. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cls.py +5 -12
  6. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/runner.py +24 -33
  7. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/runner.pyi +40 -24
  8. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal.egg-info/PKG-INFO +1 -1
  9. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_version/__init__.py +1 -1
  10. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/LICENSE +0 -0
  11. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/README.md +0 -0
  12. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/__init__.py +0 -0
  13. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/__main__.py +0 -0
  14. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_billing.py +0 -0
  15. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_clustered_functions.py +0 -0
  16. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_clustered_functions.pyi +0 -0
  17. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_container_entrypoint.py +0 -0
  18. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_functions.py +0 -0
  19. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_ipython.py +0 -0
  20. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_location.py +0 -0
  21. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_object.py +0 -0
  22. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_output.py +0 -0
  23. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_partial_function.py +0 -0
  24. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_pty.py +0 -0
  25. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_resolver.py +0 -0
  26. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_resources.py +0 -0
  27. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_runtime/__init__.py +0 -0
  28. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_runtime/asgi.py +0 -0
  29. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_runtime/container_io_manager.py +0 -0
  30. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_runtime/container_io_manager.pyi +0 -0
  31. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_runtime/execution_context.py +0 -0
  32. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_runtime/execution_context.pyi +0 -0
  33. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  34. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_runtime/telemetry.py +0 -0
  35. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_runtime/user_code_imports.py +0 -0
  36. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_serialization.py +0 -0
  37. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_traceback.py +0 -0
  38. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_tunnel.py +0 -0
  39. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_tunnel.pyi +0 -0
  40. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_type_manager.py +0 -0
  41. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/__init__.py +0 -0
  42. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/app_utils.py +0 -0
  43. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/async_utils.py +0 -0
  44. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/auth_token_manager.py +0 -0
  45. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/blob_utils.py +0 -0
  46. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/bytes_io_segment_payload.py +0 -0
  47. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/deprecation.py +0 -0
  48. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/docker_utils.py +0 -0
  49. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/function_utils.py +0 -0
  50. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/git_utils.py +0 -0
  51. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/grpc_testing.py +0 -0
  52. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/grpc_utils.py +0 -0
  53. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/hash_utils.py +0 -0
  54. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/http_utils.py +0 -0
  55. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/jwt_utils.py +0 -0
  56. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/logger.py +0 -0
  57. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/mount_utils.py +0 -0
  58. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/name_utils.py +0 -0
  59. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/package_utils.py +0 -0
  60. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/pattern_utils.py +0 -0
  61. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/rand_pb_testing.py +0 -0
  62. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/shell_utils.py +0 -0
  63. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_utils/time_utils.py +0 -0
  64. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_vendor/__init__.py +0 -0
  65. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  66. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_vendor/cloudpickle.py +0 -0
  67. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_vendor/tblib.py +0 -0
  68. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/_watcher.py +0 -0
  69. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/billing.py +0 -0
  70. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/builder/2023.12.312.txt +0 -0
  71. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/builder/2023.12.txt +0 -0
  72. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/builder/2024.04.txt +0 -0
  73. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/builder/2024.10.txt +0 -0
  74. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/builder/2025.06.txt +0 -0
  75. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/builder/PREVIEW.txt +0 -0
  76. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/builder/README.md +0 -0
  77. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/builder/base-images.json +0 -0
  78. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/call_graph.py +0 -0
  79. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/__init__.py +0 -0
  80. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/_download.py +0 -0
  81. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/_traceback.py +0 -0
  82. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/app.py +0 -0
  83. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/cluster.py +0 -0
  84. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/config.py +0 -0
  85. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/container.py +0 -0
  86. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/dict.py +0 -0
  87. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/entry_point.py +0 -0
  88. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/environment.py +0 -0
  89. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/import_refs.py +0 -0
  90. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/launch.py +0 -0
  91. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/network_file_system.py +0 -0
  92. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/profile.py +0 -0
  93. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/programs/__init__.py +0 -0
  94. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/programs/launch_instance_ssh.py +0 -0
  95. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/programs/run_jupyter.py +0 -0
  96. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/programs/run_marimo.py +0 -0
  97. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/programs/vscode.py +0 -0
  98. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/queues.py +0 -0
  99. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/run.py +0 -0
  100. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/secret.py +0 -0
  101. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/token.py +0 -0
  102. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/utils.py +0 -0
  103. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cli/volume.py +0 -0
  104. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/client.py +0 -0
  105. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cloud_bucket_mount.py +0 -0
  106. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cloud_bucket_mount.pyi +0 -0
  107. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/cls.pyi +0 -0
  108. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/config.py +0 -0
  109. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/container_process.py +0 -0
  110. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/container_process.pyi +0 -0
  111. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/dict.py +0 -0
  112. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/dict.pyi +0 -0
  113. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/environments.py +0 -0
  114. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/environments.pyi +0 -0
  115. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/exception.py +0 -0
  116. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/experimental/__init__.py +0 -0
  117. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/experimental/flash.py +0 -0
  118. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/experimental/flash.pyi +0 -0
  119. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/experimental/ipython.py +0 -0
  120. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/file_io.py +0 -0
  121. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/file_io.pyi +0 -0
  122. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/file_pattern_matcher.py +0 -0
  123. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/functions.py +0 -0
  124. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/functions.pyi +0 -0
  125. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/gpu.py +0 -0
  126. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/image.py +0 -0
  127. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/image.pyi +0 -0
  128. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/io_streams.py +0 -0
  129. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/io_streams.pyi +0 -0
  130. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/mount.py +0 -0
  131. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/mount.pyi +0 -0
  132. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/network_file_system.py +0 -0
  133. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/network_file_system.pyi +0 -0
  134. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/object.py +0 -0
  135. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/object.pyi +0 -0
  136. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/output.py +0 -0
  137. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/parallel_map.py +0 -0
  138. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/parallel_map.pyi +0 -0
  139. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/partial_function.py +0 -0
  140. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/partial_function.pyi +0 -0
  141. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/proxy.py +0 -0
  142. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/proxy.pyi +0 -0
  143. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/py.typed +0 -0
  144. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/queue.py +0 -0
  145. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/queue.pyi +0 -0
  146. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/retries.py +0 -0
  147. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/running_app.py +0 -0
  148. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/sandbox.py +0 -0
  149. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/sandbox.pyi +0 -0
  150. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/schedule.py +0 -0
  151. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/scheduler_placement.py +0 -0
  152. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/secret.py +0 -0
  153. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/secret.pyi +0 -0
  154. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/serving.py +0 -0
  155. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/serving.pyi +0 -0
  156. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/snapshot.py +0 -0
  157. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/snapshot.pyi +0 -0
  158. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/stream_type.py +0 -0
  159. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/token_flow.py +0 -0
  160. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/token_flow.pyi +0 -0
  161. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/volume.py +0 -0
  162. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal/volume.pyi +0 -0
  163. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal.egg-info/SOURCES.txt +0 -0
  164. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal.egg-info/dependency_links.txt +0 -0
  165. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal.egg-info/entry_points.txt +0 -0
  166. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal.egg-info/requires.txt +0 -0
  167. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal.egg-info/top_level.txt +0 -0
  168. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_docs/__init__.py +0 -0
  169. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_docs/gen_cli_docs.py +0 -0
  170. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_docs/gen_reference_docs.py +0 -0
  171. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_docs/mdmd/__init__.py +0 -0
  172. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_docs/mdmd/mdmd.py +0 -0
  173. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_docs/mdmd/signatures.py +0 -0
  174. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/__init__.py +0 -0
  175. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/api.proto +0 -0
  176. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/api_grpc.py +0 -0
  177. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/api_pb2.py +0 -0
  178. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/api_pb2.pyi +0 -0
  179. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/api_pb2_grpc.py +0 -0
  180. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/api_pb2_grpc.pyi +0 -0
  181. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/modal_api_grpc.py +0 -0
  182. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/py.typed +0 -0
  183. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/sandbox_router.proto +0 -0
  184. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/sandbox_router_grpc.py +0 -0
  185. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/sandbox_router_pb2.py +0 -0
  186. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/sandbox_router_pb2.pyi +0 -0
  187. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
  188. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
  189. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/task_command_router.proto +0 -0
  190. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/task_command_router_grpc.py +0 -0
  191. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/task_command_router_pb2.py +0 -0
  192. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/task_command_router_pb2.pyi +0 -0
  193. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  194. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  195. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/modal_version/__main__.py +0 -0
  196. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/pyproject.toml +0 -0
  197. {modal-1.2.1.dev7 → modal-1.2.1.dev9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.1.dev7
3
+ Version: 1.2.1.dev9
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -2,6 +2,7 @@
2
2
  import inspect
3
3
  import typing
4
4
  from collections.abc import AsyncGenerator, Collection, Coroutine, Mapping, Sequence
5
+ from dataclasses import dataclass
5
6
  from pathlib import PurePosixPath
6
7
  from textwrap import dedent
7
8
  from typing import (
@@ -114,6 +115,22 @@ class _FunctionDecoratorType:
114
115
  def __call__(self, func): ...
115
116
 
116
117
 
118
+ @dataclass()
119
+ class _LocalAppState:
120
+ """All state for apps that's part of the local/definition state"""
121
+
122
+ functions: dict[str, _Function]
123
+ classes: dict[str, _Cls]
124
+ image_default: Optional[_Image]
125
+ web_endpoints: list[str] # Used by the CLI
126
+ local_entrypoints: dict[str, _LocalEntrypoint]
127
+ tags: dict[str, str]
128
+
129
+ include_source_default: bool
130
+ secrets_default: Sequence[_Secret]
131
+ volumes_default: dict[Union[str, PurePosixPath], _Volume]
132
+
133
+
117
134
  class _App:
118
135
  """A Modal App is a group of functions and classes that are deployed together.
119
136
 
@@ -151,23 +168,21 @@ class _App:
151
168
 
152
169
  _name: Optional[str]
153
170
  _description: Optional[str]
154
- _tags: dict[str, str]
155
-
156
- _functions: dict[str, _Function]
157
- _classes: dict[str, _Cls]
158
171
 
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]
172
+ _local_state_attr: Optional[_LocalAppState] = None
164
173
 
165
174
  # Running apps only (container apps or running local)
166
175
  _app_id: Optional[str] # Kept after app finishes
167
176
  _running_app: Optional[RunningApp] # Various app info
168
177
  _client: Optional[_Client]
169
178
 
170
- _include_source_default: Optional[bool] = None
179
+ @property
180
+ def _local_state(self) -> _LocalAppState:
181
+ """For internal use only. Do not use this property directly."""
182
+
183
+ if self._local_state_attr is None:
184
+ raise AttributeError("Local state is not initialized - app is not locally available")
185
+ return self._local_state_attr
171
186
 
172
187
  def __init__(
173
188
  self,
@@ -196,8 +211,6 @@ class _App:
196
211
 
197
212
  self._name = name
198
213
  self._description = name
199
- self._tags = tags or {}
200
- self._include_source_default = include_source
201
214
 
202
215
  check_sequence(secrets, _Secret, "`secrets=` has to be a list or tuple of `modal.Secret` objects")
203
216
  validate_volumes(volumes)
@@ -205,16 +218,24 @@ class _App:
205
218
  if image is not None and not isinstance(image, _Image):
206
219
  raise InvalidError("`image=` has to be a `modal.Image` object")
207
220
 
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 = []
221
+ self._local_state_attr = _LocalAppState(
222
+ functions={},
223
+ classes={},
224
+ image_default=image,
225
+ secrets_default=secrets,
226
+ volumes_default=volumes,
227
+ include_source_default=include_source,
228
+ web_endpoints=[],
229
+ local_entrypoints={},
230
+ tags=tags or {},
231
+ )
215
232
 
233
+ # Running apps only
216
234
  self._app_id = None
217
235
  self._running_app = None # Set inside container, OR during the time an app is running locally
236
+
237
+ # Client is special - needed to be set just before the app is "hydrated" or running at the latest
238
+ # Guaranteed to be set for running apps, but also needed to actually *hydrate* the app and make it running
218
239
  self._client = None
219
240
 
220
241
  # Register this app. This is used to look up the app in the container, when we can't get it from the function
@@ -283,7 +304,8 @@ class _App:
283
304
 
284
305
  response = await retry_transient_errors(client.stub.AppGetOrCreate, request)
285
306
 
286
- app = _App(name)
307
+ app = _App(name) # TODO: this should probably be a distinct constructor, possibly even a distinct type
308
+ app._local_state_attr = None # this is not a locally defined App, so no local state
287
309
  app._app_id = response.app_id
288
310
  app._client = client
289
311
  app._running_app = RunningApp(response.app_id, interactive=False)
@@ -310,18 +332,19 @@ class _App:
310
332
  App that is retrieved via `modal.App.lookup`. It is likely to be deprecated in the future.
311
333
 
312
334
  """
313
- return self._image
335
+ return self._local_state.image_default
314
336
 
315
337
  @image.setter
316
338
  def image(self, value):
317
339
  """mdmd:hidden"""
318
- self._image = value
340
+ self._local_state.image_default = value
319
341
 
320
342
  def _uncreate_all_objects(self):
321
343
  # TODO(erikbern): this doesn't unhydrate objects that aren't tagged
322
- for obj in self._functions.values():
344
+ local_state = self._local_state
345
+ for obj in local_state.functions.values():
323
346
  obj._unhydrate()
324
- for obj in self._classes.values():
347
+ for obj in local_state.classes.values():
325
348
  obj._unhydrate()
326
349
 
327
350
  @asynccontextmanager
@@ -457,8 +480,9 @@ class _App:
457
480
  return self
458
481
 
459
482
  def _get_default_image(self):
460
- if self._image:
461
- return self._image
483
+ local_state = self._local_state
484
+ if local_state.image_default:
485
+ return local_state.image_default
462
486
  else:
463
487
  return _default_image
464
488
 
@@ -473,7 +497,8 @@ class _App:
473
497
  return [m for m in all_mounts if m.is_local()]
474
498
 
475
499
  def _add_function(self, function: _Function, is_web_endpoint: bool):
476
- if old_function := self._functions.get(function.tag, None):
500
+ local_state = self._local_state
501
+ if old_function := local_state.functions.get(function.tag, None):
477
502
  if old_function is function:
478
503
  return # already added the same exact instance, ignore
479
504
 
@@ -484,7 +509,7 @@ class _App:
484
509
  f"[{old_function._info.module_name}].{old_function._info.function_name}"
485
510
  f" with new function [{function._info.module_name}].{function._info.function_name}"
486
511
  )
487
- if function.tag in self._classes:
512
+ if function.tag in local_state.classes:
488
513
  logger.warning(f"Warning: tag {function.tag} exists but is overridden by function")
489
514
 
490
515
  if self._running_app:
@@ -495,9 +520,9 @@ class _App:
495
520
  metadata: Message = self._running_app.object_handle_metadata[object_id]
496
521
  function._hydrate(object_id, self._client, metadata)
497
522
 
498
- self._functions[function.tag] = function
523
+ local_state.functions[function.tag] = function
499
524
  if is_web_endpoint:
500
- self._web_endpoints.append(function.tag)
525
+ local_state.web_endpoints.append(function.tag)
501
526
 
502
527
  def _add_class(self, tag: str, cls: _Cls):
503
528
  if self._running_app:
@@ -508,7 +533,7 @@ class _App:
508
533
  metadata: Message = self._running_app.object_handle_metadata[object_id]
509
534
  cls._hydrate(object_id, self._client, metadata)
510
535
 
511
- self._classes[tag] = cls
536
+ self._local_state.classes[tag] = cls
512
537
 
513
538
  def _init_container(self, client: _Client, running_app: RunningApp):
514
539
  self._app_id = running_app.app_id
@@ -516,18 +541,18 @@ class _App:
516
541
  self._client = client
517
542
 
518
543
  _App._container_app = self
519
-
544
+ local_state = self._local_state
520
545
  # Hydrate function objects
521
546
  for tag, object_id in running_app.function_ids.items():
522
- if tag in self._functions:
523
- obj = self._functions[tag]
547
+ if tag in local_state.functions:
548
+ obj = local_state.functions[tag]
524
549
  handle_metadata = running_app.object_handle_metadata[object_id]
525
550
  obj._hydrate(object_id, client, handle_metadata)
526
551
 
527
552
  # Hydrate class objects
528
553
  for tag, object_id in running_app.class_ids.items():
529
- if tag in self._classes:
530
- obj = self._classes[tag]
554
+ if tag in local_state.classes:
555
+ obj = local_state.classes[tag]
531
556
  handle_metadata = running_app.object_handle_metadata[object_id]
532
557
  obj._hydrate(object_id, client, handle_metadata)
533
558
 
@@ -541,7 +566,7 @@ class _App:
541
566
  This method is likely to be deprecated in the future in favor of a different
542
567
  approach for retrieving the layout of a deployed App.
543
568
  """
544
- return self._functions
569
+ return self._local_state.functions
545
570
 
546
571
  @property
547
572
  def registered_classes(self) -> dict[str, _Cls]:
@@ -553,7 +578,7 @@ class _App:
553
578
  This method is likely to be deprecated in the future in favor of a different
554
579
  approach for retrieving the layout of a deployed App.
555
580
  """
556
- return self._classes
581
+ return self._local_state.classes
557
582
 
558
583
  @property
559
584
  def registered_entrypoints(self) -> dict[str, _LocalEntrypoint]:
@@ -564,7 +589,7 @@ class _App:
564
589
  expected to work when a deplyoed App has been retrieved via `modal.App.lookup`.
565
590
  This method is likely to be deprecated in the future.
566
591
  """
567
- return self._local_entrypoints
592
+ return self._local_state.local_entrypoints
568
593
 
569
594
  @property
570
595
  def registered_web_endpoints(self) -> list[str]:
@@ -576,7 +601,7 @@ class _App:
576
601
  This method is likely to be deprecated in the future in favor of a different
577
602
  approach for retrieving the layout of a deployed App.
578
603
  """
579
- return self._web_endpoints
604
+ return self._local_state.web_endpoints
580
605
 
581
606
  def local_entrypoint(
582
607
  self, _warn_parentheses_missing: Any = None, *, name: Optional[str] = None
@@ -637,10 +662,11 @@ class _App:
637
662
  def wrapped(raw_f: Callable[..., Any]) -> _LocalEntrypoint:
638
663
  info = FunctionInfo(raw_f)
639
664
  tag = name if name is not None else raw_f.__qualname__
640
- if tag in self._local_entrypoints:
665
+ local_state = self._local_state
666
+ if tag in local_state.local_entrypoints:
641
667
  # TODO: get rid of this limitation.
642
668
  raise InvalidError(f"Duplicate local entrypoint name: {tag}. Local entrypoint names must be unique.")
643
- entrypoint = self._local_entrypoints[tag] = _LocalEntrypoint(info, self)
669
+ entrypoint = local_state.local_entrypoints[tag] = _LocalEntrypoint(info, self)
644
670
  return entrypoint
645
671
 
646
672
  return wrapped
@@ -732,7 +758,8 @@ class _App:
732
758
  secrets = secrets or []
733
759
  if env:
734
760
  secrets = [*secrets, _Secret.from_dict(env)]
735
- secrets = [*self._secrets, *secrets]
761
+ local_state = self._local_state
762
+ secrets = [*local_state.secrets_default, *secrets]
736
763
 
737
764
  def wrapped(
738
765
  f: Union[_PartialFunction, Callable[..., Any], None],
@@ -840,7 +867,7 @@ class _App:
840
867
  is_generator=is_generator,
841
868
  gpu=gpu,
842
869
  network_file_systems=network_file_systems,
843
- volumes={**self._volumes, **volumes},
870
+ volumes={**local_state.volumes_default, **volumes},
844
871
  cpu=cpu,
845
872
  memory=memory,
846
873
  ephemeral_disk=ephemeral_disk,
@@ -866,7 +893,7 @@ class _App:
866
893
  i6pn_enabled=i6pn_enabled,
867
894
  cluster_size=cluster_size, # Experimental: Clustered functions
868
895
  rdma=rdma,
869
- include_source=include_source if include_source is not None else self._include_source_default,
896
+ include_source=include_source if include_source is not None else local_state.include_source_default,
870
897
  experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
871
898
  _experimental_proxy_ip=_experimental_proxy_ip,
872
899
  restrict_output=_experimental_restrict_output,
@@ -963,6 +990,7 @@ class _App:
963
990
  secrets = [*secrets, _Secret.from_dict(env)]
964
991
 
965
992
  def wrapper(wrapped_cls: Union[CLS_T, _PartialFunction]) -> CLS_T:
993
+ local_state = self._local_state
966
994
  # Check if the decorated object is a class
967
995
  if isinstance(wrapped_cls, _PartialFunction):
968
996
  wrapped_cls.registered = True
@@ -1029,10 +1057,10 @@ class _App:
1029
1057
  info,
1030
1058
  app=self,
1031
1059
  image=image or self._get_default_image(),
1032
- secrets=[*self._secrets, *secrets],
1060
+ secrets=[*local_state.secrets_default, *secrets],
1033
1061
  gpu=gpu,
1034
1062
  network_file_systems=network_file_systems,
1035
- volumes={**self._volumes, **volumes},
1063
+ volumes={**local_state.volumes_default, **volumes},
1036
1064
  cpu=cpu,
1037
1065
  memory=memory,
1038
1066
  ephemeral_disk=ephemeral_disk,
@@ -1057,7 +1085,7 @@ class _App:
1057
1085
  i6pn_enabled=i6pn_enabled,
1058
1086
  cluster_size=cluster_size,
1059
1087
  rdma=rdma,
1060
- include_source=include_source if include_source is not None else self._include_source_default,
1088
+ include_source=include_source if include_source is not None else local_state.include_source_default,
1061
1089
  experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
1062
1090
  _experimental_proxy_ip=_experimental_proxy_ip,
1063
1091
  _experimental_custom_scaling_factor=_experimental_custom_scaling_factor,
@@ -1067,6 +1095,11 @@ class _App:
1067
1095
  self._add_function(cls_func, is_web_endpoint=False)
1068
1096
 
1069
1097
  cls: _Cls = _Cls.from_local(user_cls, self, cls_func)
1098
+ for method_name, partial_function in cls._method_partials.items():
1099
+ if partial_function.params.webhook_config is not None:
1100
+ full_name = f"{user_cls.__name__}.{method_name}"
1101
+ local_state.web_endpoints.append(full_name)
1102
+ partial_function.registered = True
1070
1103
 
1071
1104
  tag: str = user_cls.__name__
1072
1105
  self._add_class(tag, cls)
@@ -1102,11 +1135,14 @@ class _App:
1102
1135
  (with this App's tags taking precedence in the case of conflicts).
1103
1136
 
1104
1137
  """
1105
- for tag, function in other_app._functions.items():
1138
+ other_app_local_state = other_app._local_state
1139
+ this_local_state = self._local_state
1140
+
1141
+ for tag, function in other_app_local_state.functions.items():
1106
1142
  self._add_function(function, False) # TODO(erikbern): webhook config?
1107
1143
 
1108
- for tag, cls in other_app._classes.items():
1109
- existing_cls = self._classes.get(tag)
1144
+ for tag, cls in other_app_local_state.classes.items():
1145
+ existing_cls = this_local_state.classes.get(tag)
1110
1146
  if existing_cls and existing_cls != cls:
1111
1147
  logger.warning(
1112
1148
  f"Named app class {tag} with existing value {existing_cls} is being "
@@ -1116,7 +1152,7 @@ class _App:
1116
1152
  self._add_class(tag, cls)
1117
1153
 
1118
1154
  if inherit_tags:
1119
- self._tags = {**other_app._tags, **self._tags}
1155
+ this_local_state.tags = {**other_app_local_state.tags, **this_local_state.tags}
1120
1156
 
1121
1157
  return self
1122
1158
 
@@ -1132,7 +1168,7 @@ class _App:
1132
1168
 
1133
1169
  """
1134
1170
  # Note that we are requiring the App to be "running" before we set the tags.
1135
- # Alternatively, we could hold onto the tags (i.e. in `self._tags`) and then pass
1171
+ # Alternatively, we could hold onto the tags (i.e. in `self._local_state.tags`) and then pass
1136
1172
  # then up when AppPublish gets called. I'm not certain we want to support it, though.
1137
1173
  # It might not be obvious to users that `.set_tags()` is eager and has immediate effect
1138
1174
  # when the App is running, but lazy (and potentially ignored) otherwise. There would be
@@ -74,6 +74,42 @@ 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
+
77
113
  class _App:
78
114
  """A Modal App is a group of functions and classes that are deployed together.
79
115
 
@@ -110,18 +146,15 @@ class _App:
110
146
  _container_app: typing.ClassVar[typing.Optional[_App]]
111
147
  _name: typing.Optional[str]
112
148
  _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]
149
+ _local_state_attr: typing.Optional[_LocalAppState]
121
150
  _app_id: typing.Optional[str]
122
151
  _running_app: typing.Optional[modal.running_app.RunningApp]
123
152
  _client: typing.Optional[modal.client._Client]
124
- _include_source_default: typing.Optional[bool]
153
+
154
+ @property
155
+ def _local_state(self) -> _LocalAppState:
156
+ """For internal use only. Do not use this property directly."""
157
+ ...
125
158
 
126
159
  def __init__(
127
160
  self,
@@ -630,18 +663,10 @@ class App:
630
663
  _container_app: typing.ClassVar[typing.Optional[App]]
631
664
  _name: typing.Optional[str]
632
665
  _description: typing.Optional[str]
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]
666
+ _local_state_attr: typing.Optional[_LocalAppState]
641
667
  _app_id: typing.Optional[str]
642
668
  _running_app: typing.Optional[modal.running_app.RunningApp]
643
669
  _client: typing.Optional[modal.client.Client]
644
- _include_source_default: typing.Optional[bool]
645
670
 
646
671
  def __init__(
647
672
  self,
@@ -664,6 +689,11 @@ class App:
664
689
  """
665
690
  ...
666
691
 
692
+ @property
693
+ def _local_state(self) -> _LocalAppState:
694
+ """For internal use only. Do not use this property directly."""
695
+ ...
696
+
667
697
  @property
668
698
  def name(self) -> typing.Optional[str]:
669
699
  """The user-provided name of the App."""
@@ -33,7 +33,7 @@ class _Client:
33
33
  server_url: str,
34
34
  client_type: int,
35
35
  credentials: typing.Optional[tuple[str, str]],
36
- version: str = "1.2.1.dev7",
36
+ version: str = "1.2.1.dev9",
37
37
  ):
38
38
  """mdmd:hidden
39
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -164,7 +164,7 @@ class Client:
164
164
  server_url: str,
165
165
  client_type: int,
166
166
  credentials: typing.Optional[tuple[str, str]],
167
- version: str = "1.2.1.dev7",
167
+ version: str = "1.2.1.dev9",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
@@ -577,22 +577,15 @@ More information on class parameterization can be found here: https://modal.com/
577
577
  # validate signature
578
578
  _Cls.validate_construction_mechanism(user_cls)
579
579
 
580
- method_partials: dict[str, _PartialFunction] = _find_partial_methods_for_user_cls(
581
- user_cls, _PartialFunctionFlags.interface_flags()
582
- )
583
-
584
- for method_name, partial_function in method_partials.items():
585
- if partial_function.params.webhook_config is not None:
586
- full_name = f"{user_cls.__name__}.{method_name}"
587
- app._web_endpoints.append(full_name)
588
- partial_function.registered = True
589
-
590
580
  # Disable the warning that lifecycle methods are not wrapped
591
- for partial_function in _find_partial_methods_for_user_cls(
581
+ lifecycle_method_partials = _find_partial_methods_for_user_cls(
592
582
  user_cls, ~_PartialFunctionFlags.interface_flags()
593
- ).values():
583
+ )
584
+ for partial_function in lifecycle_method_partials.values():
594
585
  partial_function.registered = True
595
586
 
587
+ method_partials = _find_partial_methods_for_user_cls(user_cls, _PartialFunctionFlags.interface_flags())
588
+
596
589
  # Get all callables
597
590
  callables: dict[str, Callable] = {
598
591
  k: pf.raw_f