modal 1.5.6.dev24__tar.gz → 1.5.6.dev25__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 (239) hide show
  1. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/PKG-INFO +1 -1
  2. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/__init__.py +2 -1
  3. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_traceback.py +14 -2
  4. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/grpc_utils.py +43 -1
  5. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/task_command_router_client.py +45 -5
  6. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/token.py +20 -2
  7. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/client.pyi +2 -2
  8. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/secret.py +69 -1
  9. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/secret.pyi +70 -0
  10. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/token_flow.py +4 -2
  11. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/token_flow.pyi +15 -3
  12. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/types.py +1 -0
  13. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal.egg-info/PKG-INFO +1 -1
  14. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_proto/api_grpc.py +16 -0
  15. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_proto/api_pb2.py +409 -389
  16. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_proto/api_pb2.pyi +34 -1
  17. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_proto/api_pb2_grpc.py +33 -0
  18. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_proto/api_pb2_grpc.pyi +10 -0
  19. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_proto/modal_api_grpc.py +1 -0
  20. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_version/__init__.py +1 -1
  21. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/LICENSE +0 -0
  22. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/README.md +0 -0
  23. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/__main__.py +0 -0
  24. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_billing.py +0 -0
  25. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_clustered_functions.py +0 -0
  26. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_clustered_functions.pyi +0 -0
  27. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_container_entrypoint.py +0 -0
  28. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_environments.py +0 -0
  29. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_function_variants.py +0 -0
  30. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_functions.py +0 -0
  31. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_grpc_client.py +0 -0
  32. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_image.py +0 -0
  33. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_ipython.py +0 -0
  34. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_load_context.py +0 -0
  35. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_location.py +0 -0
  36. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_logs.py +0 -0
  37. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_logs_manager.py +0 -0
  38. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_logs_manager.pyi +0 -0
  39. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_object.py +0 -0
  40. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_output/__init__.py +0 -0
  41. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_output/manager.py +0 -0
  42. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_output/pty.py +0 -0
  43. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_output/rich.py +0 -0
  44. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_output/status.py +0 -0
  45. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_partial_function.py +0 -0
  46. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_resolver.py +0 -0
  47. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_resources.py +0 -0
  48. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_runtime/__init__.py +0 -0
  49. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_runtime/asgi.py +0 -0
  50. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_runtime/container_io_manager.py +0 -0
  51. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_runtime/container_io_manager.pyi +0 -0
  52. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_runtime/execution_context.py +0 -0
  53. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_runtime/execution_context.pyi +0 -0
  54. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  55. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_runtime/task_lifecycle_manager.py +0 -0
  56. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  57. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_runtime/telemetry.py +0 -0
  58. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_runtime/user_code_event_loop.py +0 -0
  59. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_runtime/user_code_imports.py +0 -0
  60. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_serialization.py +0 -0
  61. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_server.py +0 -0
  62. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_supports_logs.py +0 -0
  63. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_tunnel.py +0 -0
  64. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_tunnel.pyi +0 -0
  65. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_type_manager.py +0 -0
  66. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/__init__.py +0 -0
  67. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/app_utils.py +0 -0
  68. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/async_utils.py +0 -0
  69. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/auth_token_manager.py +0 -0
  70. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/blob_utils.py +0 -0
  71. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/browser_utils.py +0 -0
  72. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/bytes_io_segment_payload.py +0 -0
  73. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/curl_utils.py +0 -0
  74. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/deprecation.py +0 -0
  75. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/display_utils.py +0 -0
  76. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/docker_utils.py +0 -0
  77. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/function_utils.py +0 -0
  78. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/git_utils.py +0 -0
  79. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/grpc_testing.py +0 -0
  80. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/hash_utils.py +0 -0
  81. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/http_utils.py +0 -0
  82. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/idle_countdown.py +0 -0
  83. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/jwt_utils.py +0 -0
  84. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/logger.py +0 -0
  85. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/mount_utils.py +0 -0
  86. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/name_utils.py +0 -0
  87. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/package_utils.py +0 -0
  88. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/pattern_utils.py +0 -0
  89. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/proxy_support.py +0 -0
  90. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/rand_pb_testing.py +0 -0
  91. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/sandbox_fs_utils.py +0 -0
  92. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/shell_utils.py +0 -0
  93. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_utils/time_utils.py +0 -0
  94. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_vendor/__init__.py +0 -0
  95. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  96. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_vendor/cloudpickle.py +0 -0
  97. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_vendor/tblib.py +0 -0
  98. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_vendor/version.py +0 -0
  99. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_watcher.py +0 -0
  100. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/_workspace.py +0 -0
  101. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/app.py +0 -0
  102. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/app.pyi +0 -0
  103. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/billing.py +0 -0
  104. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/billing.pyi +0 -0
  105. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/builder/2023.12.312.txt +0 -0
  106. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/builder/2023.12.txt +0 -0
  107. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/builder/2024.04.txt +0 -0
  108. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/builder/2024.10.txt +0 -0
  109. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/builder/2025.06.txt +0 -0
  110. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/builder/PREVIEW.txt +0 -0
  111. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/builder/README.md +0 -0
  112. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/builder/base-images.json +0 -0
  113. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/call_graph.py +0 -0
  114. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/__init__.py +0 -0
  115. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/_download.py +0 -0
  116. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/_help.py +0 -0
  117. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/_logs.py +0 -0
  118. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/_stats.py +0 -0
  119. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/_traceback.py +0 -0
  120. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/app.py +0 -0
  121. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/billing.py +0 -0
  122. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/changelog.py +0 -0
  123. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/cluster.py +0 -0
  124. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/config.py +0 -0
  125. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/container.py +0 -0
  126. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/curl.py +0 -0
  127. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/dashboard.py +0 -0
  128. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/dict.py +0 -0
  129. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/endpoint.py +0 -0
  130. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/entry_point.py +0 -0
  131. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/environment.py +0 -0
  132. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/function.py +0 -0
  133. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/image.py +0 -0
  134. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/import_refs.py +0 -0
  135. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/logo.py +0 -0
  136. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/network_file_system.py +0 -0
  137. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/profile.py +0 -0
  138. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/queues.py +0 -0
  139. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/run.py +0 -0
  140. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/secret.py +0 -0
  141. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/selector.py +0 -0
  142. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/server.py +0 -0
  143. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/shell.py +0 -0
  144. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/skills.py +0 -0
  145. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/utils.py +0 -0
  146. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/volume.py +0 -0
  147. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cli/workspace.py +0 -0
  148. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/client.py +0 -0
  149. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cloud_bucket_mount.py +0 -0
  150. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cloud_bucket_mount.pyi +0 -0
  151. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cls.py +0 -0
  152. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/cls.pyi +0 -0
  153. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/config.py +0 -0
  154. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/container_process.py +0 -0
  155. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/container_process.pyi +0 -0
  156. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/dict.py +0 -0
  157. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/dict.pyi +0 -0
  158. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/environments.py +0 -0
  159. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/environments.pyi +0 -0
  160. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/exception.py +0 -0
  161. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/experimental/__init__.py +0 -0
  162. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/experimental/_flash_container_entry.py +0 -0
  163. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/experimental/flash.py +0 -0
  164. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/experimental/flash.pyi +0 -0
  165. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/experimental/flash_server.py +0 -0
  166. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/experimental/flash_server.pyi +0 -0
  167. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/experimental/ipython.py +0 -0
  168. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/file_pattern_matcher.py +0 -0
  169. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/functions.py +0 -0
  170. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/functions.pyi +0 -0
  171. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/image.py +0 -0
  172. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/image.pyi +0 -0
  173. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/io_streams.py +0 -0
  174. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/io_streams.pyi +0 -0
  175. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/mount.py +0 -0
  176. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/mount.pyi +0 -0
  177. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/network_file_system.py +0 -0
  178. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/network_file_system.pyi +0 -0
  179. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/object.py +0 -0
  180. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/object.pyi +0 -0
  181. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/outbound_policy.py +0 -0
  182. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/outbound_policy.pyi +0 -0
  183. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/output.py +0 -0
  184. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/parallel_map.py +0 -0
  185. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/parallel_map.pyi +0 -0
  186. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/partial_function.py +0 -0
  187. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/partial_function.pyi +0 -0
  188. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/proxy.py +0 -0
  189. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/proxy.pyi +0 -0
  190. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/py.typed +0 -0
  191. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/queue.py +0 -0
  192. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/queue.pyi +0 -0
  193. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/retries.py +0 -0
  194. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/runner.py +0 -0
  195. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/runner.pyi +0 -0
  196. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/running_app.py +0 -0
  197. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/sandbox.py +0 -0
  198. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/sandbox.pyi +0 -0
  199. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/sandbox_fs.py +0 -0
  200. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/sandbox_fs.pyi +0 -0
  201. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/schedule.py +0 -0
  202. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/scheduler_placement.py +0 -0
  203. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/server.py +0 -0
  204. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/server.pyi +0 -0
  205. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/serving.py +0 -0
  206. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/serving.pyi +0 -0
  207. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/skills/modal/SKILL.md +0 -0
  208. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/snapshot.py +0 -0
  209. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/snapshot.pyi +0 -0
  210. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/stream_type.py +0 -0
  211. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/volume.py +0 -0
  212. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/volume.pyi +0 -0
  213. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/workspace.py +0 -0
  214. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal/workspace.pyi +0 -0
  215. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal.egg-info/SOURCES.txt +0 -0
  216. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal.egg-info/dependency_links.txt +0 -0
  217. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal.egg-info/entry_points.txt +0 -0
  218. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal.egg-info/requires.txt +0 -0
  219. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal.egg-info/top_level.txt +0 -0
  220. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_docs/__init__.py +0 -0
  221. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_docs/gen_cli_docs.py +0 -0
  222. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_docs/gen_cli_docs_main.py +0 -0
  223. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_docs/gen_reference_docs.py +0 -0
  224. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_docs/gen_reference_docs_main.py +0 -0
  225. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_docs/mdmd/__init__.py +0 -0
  226. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_docs/mdmd/mdmd.py +0 -0
  227. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_docs/mdmd/signatures.py +0 -0
  228. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_docs/mdmd/types.py +0 -0
  229. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_docs/reference_intro.md +0 -0
  230. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_proto/__init__.py +0 -0
  231. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_proto/py.typed +0 -0
  232. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_proto/task_command_router_grpc.py +0 -0
  233. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_proto/task_command_router_pb2.py +0 -0
  234. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_proto/task_command_router_pb2.pyi +0 -0
  235. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  236. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  237. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/modal_version/__main__.py +0 -0
  238. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/pyproject.toml +0 -0
  239. {modal-1.5.6.dev24 → modal-1.5.6.dev25}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.6.dev24
3
+ Version: 1.5.6.dev25
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -9,7 +9,7 @@ if sys.version_info[:2] >= (3, 15):
9
9
  from modal_version import __version__
10
10
 
11
11
  try:
12
- from . import billing, types
12
+ from . import billing, experimental, types
13
13
  from ._runtime.execution_context import current_function_call_id, current_input_id, interact, is_local
14
14
  from ._tunnel import Tunnel, forward
15
15
  from .app import App
@@ -96,6 +96,7 @@ __all__ = [
96
96
  "enable_output",
97
97
  "enter",
98
98
  "exit",
99
+ "experimental",
99
100
  "fastapi_endpoint",
100
101
  "forward",
101
102
  "is_local",
@@ -123,13 +123,25 @@ def print_exception(exc: type[BaseException] | None, value: BaseException | None
123
123
  print(*notes, sep="\n", file=sys.stderr) # noqa: T201
124
124
 
125
125
 
126
- def print_server_warnings(server_warnings: Iterable[api_pb2.Warning]):
126
+ _server_warning_registry: dict[Any, Any] = {}
127
+ SERVER_WARNING_REGISTRY_LIMIT = 2048
128
+
129
+
130
+ def print_server_warning(message: str):
127
131
  """Issue a warning originating from the server with empty metadata about local origin.
128
132
 
129
133
  When using the Modal CLI, these warnings should get caught and coerced into Rich panels.
130
134
  """
135
+ if len(_server_warning_registry) > SERVER_WARNING_REGISTRY_LIMIT:
136
+ # Start over instead of going quiet: overflow costs a repeat, never a hidden warning.
137
+ _server_warning_registry.clear()
138
+ warnings.warn_explicit(message, ServerWarning, "<modal-server>", 0, registry=_server_warning_registry)
139
+
140
+
141
+ def print_server_warnings(server_warnings: Iterable[api_pb2.Warning]):
142
+ """Issue warnings that the server attached to a response message."""
131
143
  for warning in server_warnings:
132
- warnings.warn_explicit(warning.message, ServerWarning, "<modal-server>", 0)
144
+ print_server_warning(warning.message)
133
145
 
134
146
 
135
147
  # for some reason, the traceback cleanup here can't be moved into a context manager :(
@@ -24,12 +24,13 @@ from grpclib.config import Configuration
24
24
  from grpclib.encoding.base import CodecBase, StatusDetailsCodecBase
25
25
  from grpclib.exceptions import StreamTerminatedError
26
26
  from grpclib.protocol import H2Protocol
27
+ from h2.exceptions import ProtocolError as H2ProtocolError
27
28
 
28
29
  from modal.exception import ClientClosed, ConnectionError, NotFoundError
29
30
  from modal_proto import api_pb2
30
31
  from modal_version import __version__
31
32
 
32
- from .._traceback import suppress_tb_frame
33
+ from .._traceback import print_server_warning, suppress_tb_frame
33
34
  from ..config import config
34
35
  from .async_utils import TaskContext, retry
35
36
  from .logger import logger
@@ -130,6 +131,24 @@ class ModalChannel(grpclib.client.Channel):
130
131
  return await super().__connect__()
131
132
 
132
133
  async def _create_connection(self) -> H2Protocol:
134
+ target = self._path if self.__use_unix_socket else f"{self.__target_host}:{self.__target_port}"
135
+ tls = self.__ssl_context is not None
136
+ started_at = time.monotonic()
137
+ # Logged before awaiting: a reconnect is invisible until it resolves, which
138
+ # can be long after the RPC waiting on it appears to hang.
139
+ logger.debug(f"Starting connection attempt to {target} (TLS={tls})")
140
+ try:
141
+ protocol = await self._open_connection()
142
+ except (asyncio.CancelledError, Exception) as exc:
143
+ outcome = "cancelled" if isinstance(exc, asyncio.CancelledError) else "failed"
144
+ elapsed = time.monotonic() - started_at
145
+ logger.debug(f"Connection attempt to {target} {outcome} after {elapsed:.3f}s: {exc!r}")
146
+ raise
147
+ elapsed = time.monotonic() - started_at
148
+ logger.debug(f"Connection to {target} established after {elapsed:.3f}s")
149
+ return protocol
150
+
151
+ async def _open_connection(self) -> H2Protocol:
133
152
  if self.__use_unix_socket:
134
153
  return await super()._create_connection()
135
154
 
@@ -308,6 +327,20 @@ def create_channel_config(*, sustained_keepalive: bool = False) -> grpclib.confi
308
327
  )
309
328
 
310
329
 
330
+ # Header the server uses to attach a non-fatal message to any response, one entry per
331
+ # percent-encoded warning. It rides in the trailing metadata, which grpclib also
332
+ # surfaces for trailers-only responses, i.e. failed requests.
333
+ SERVER_WARNING_HEADER = "x-modal-warning"
334
+
335
+
336
+ def _issue_server_warnings(metadata: Any) -> None:
337
+ """Issue the warnings in a response's metadata, which `print_server_warning` deduplicates."""
338
+ for key, encoded_message in metadata.items():
339
+ if key != SERVER_WARNING_HEADER:
340
+ continue
341
+ print_server_warning(urllib.parse.unquote(encoded_message))
342
+
343
+
311
344
  def create_channel(
312
345
  server_url: str,
313
346
  metadata: dict[str, str] = {},
@@ -356,6 +389,12 @@ def create_channel(
356
389
 
357
390
  grpclib.events.listen(channel, grpclib.events.SendRequest, send_request)
358
391
 
392
+ # Surface warnings the server attached to any response on this channel.
393
+ async def recv_trailing_metadata(event: grpclib.events.RecvTrailingMetadata) -> None:
394
+ _issue_server_warnings(event.metadata)
395
+
396
+ grpclib.events.listen(channel, grpclib.events.RecvTrailingMetadata, recv_trailing_metadata)
397
+
359
398
  return channel
360
399
 
361
400
 
@@ -496,6 +535,8 @@ def process_exception_before_retry(
496
535
  raise ConnectionError(str(exc))
497
536
  elif isinstance(exc, asyncio.TimeoutError):
498
537
  raise ConnectionError(str(exc))
538
+ elif isinstance(exc, H2ProtocolError):
539
+ raise ConnectionError(str(exc))
499
540
  else:
500
541
  raise exc
501
542
 
@@ -583,6 +624,7 @@ async def _retry_transient_errors(
583
624
  OSError,
584
625
  asyncio.TimeoutError,
585
626
  AttributeError,
627
+ H2ProtocolError,
586
628
  ) as exc:
587
629
  # Note that we only catch AttributeError to handle a specific case that works around a bug
588
630
  # in grpclib<=0.4.7. See above (search for `write_appdata`).
@@ -89,6 +89,11 @@ def _parse_jwt_expiration(jwt_token: str) -> float | None:
89
89
  return None
90
90
 
91
91
 
92
+ # Enough consecutive failures to distinguish a connection that keeps breaking from
93
+ # an isolated blip, without leaving a stuck call silent for long.
94
+ RETRY_WARNING_THRESHOLD = 3
95
+
96
+
92
97
  async def call_with_retries_on_transient_errors(
93
98
  func,
94
99
  *,
@@ -97,6 +102,9 @@ async def call_with_retries_on_transient_errors(
97
102
  max_retries: int | None = 10,
98
103
  exclude_status_codes: list[Status] | None = None,
99
104
  timeout_deadline: float | None = None,
105
+ rpc_name: str = "RPC",
106
+ exec_id: str | None = None,
107
+ warning_message: str | None = None,
100
108
  ):
101
109
  """Call func() with transient error retries and exponential backoff.
102
110
 
@@ -113,10 +121,18 @@ async def call_with_retries_on_transient_errors(
113
121
  exception (e.g., into a TimeoutError) based on the deadline check.
114
122
  The caller is also responsible for propagating the remaining budget
115
123
  into `func()` (typically as the per-call gRPC timeout).
124
+ rpc_name: RPC name to include in retry diagnostics.
125
+ exec_id: Execution ID to include in retry diagnostics, for correlating
126
+ the retries of concurrent executions.
127
+ warning_message: Optional warning to emit once after
128
+ RETRY_WARNING_THRESHOLD consecutive non-timeout failures.
116
129
  """
117
130
  delay_secs = base_delay_secs
118
131
  num_retries = 0
132
+ consecutive_failures = 0
133
+ warned = False
119
134
  exclude_status_codes = exclude_status_codes or []
135
+ called_for = f" for exec {exec_id}" if exec_id is not None else ""
120
136
 
121
137
  def is_retryable_status(status: Status) -> bool:
122
138
  return status in RETRYABLE_GRPC_STATUS_CODES and status not in exclude_status_codes
@@ -128,25 +144,41 @@ async def call_with_retries_on_transient_errors(
128
144
  return False
129
145
  return True
130
146
 
131
- async def sleep_and_advance(e: Exception):
132
- nonlocal delay_secs, num_retries
147
+ async def sleep_and_advance(e: Exception, *, count_failure: bool = True):
148
+ nonlocal delay_secs, num_retries, consecutive_failures, warned
133
149
  # Clamp the backoff sleep to the remaining deadline so we don't sleep
134
150
  # past it just to fail on the next iteration's deadline check.
135
151
  sleep_for = delay_secs
136
152
  if timeout_deadline is not None:
137
153
  sleep_for = min(sleep_for, max(0.0, timeout_deadline - time.monotonic()))
138
- logger.debug(f"Retrying RPC with delay {sleep_for}s due to error: {e}")
154
+ elapsed = time.monotonic() - attempt_started_at
155
+ logger.debug(
156
+ f"{rpc_name}{called_for} attempt {num_retries + 1} failed after {elapsed:.3f}s, "
157
+ f"retrying in {sleep_for}s: {e!r}"
158
+ )
159
+ if count_failure:
160
+ consecutive_failures += 1
161
+ if warning_message is not None and consecutive_failures >= RETRY_WARNING_THRESHOLD and not warned:
162
+ logger.warning(
163
+ f"{warning_message} Retrying after {consecutive_failures} consecutive failures. "
164
+ "Set MODAL_LOGLEVEL=DEBUG for details."
165
+ )
166
+ warned = True
167
+ else:
168
+ consecutive_failures = 0
139
169
  await asyncio.sleep(sleep_for)
140
170
  delay_secs *= delay_factor
141
171
  num_retries += 1
142
172
 
143
173
  while True:
174
+ attempt_started_at = time.monotonic()
144
175
  try:
145
176
  return await func()
146
177
  except GRPCError as e:
147
178
  if not is_retryable_status(e.status) or not can_retry():
148
179
  raise
149
- await sleep_and_advance(e)
180
+ # Long-poll deadlines can expire while the process is still running on a healthy connection.
181
+ await sleep_and_advance(e, count_failure=e.status != Status.DEADLINE_EXCEEDED)
150
182
  except AttributeError as e:
151
183
  # StreamTerminatedError are not properly raised in grpclib<=0.4.7
152
184
  # fixed in https://github.com/vmagamedov/grpclib/issues/185
@@ -165,7 +197,9 @@ async def call_with_retries_on_transient_errors(
165
197
  # `timeout_deadline` can further translate this based on
166
198
  # whether the deadline has elapsed.
167
199
  raise ConnectionError(str(e))
168
- await sleep_and_advance(e)
200
+ # A client-side timeout says nothing about the connection's health, so it
201
+ # doesn't count toward the warning.
202
+ await sleep_and_advance(e, count_failure=not isinstance(e, asyncio.TimeoutError))
169
203
 
170
204
 
171
205
  _StdioReq = TypeVar("_StdioReq")
@@ -828,6 +862,12 @@ class TaskCommandRouterClient:
828
862
  # infinitely retry. For callers without an exec deadline, this
829
863
  # could hang indefinitely.
830
864
  lambda: self._call_with_auth_retry(self._stub.TaskExecWait, request, timeout=60),
865
+ rpc_name="TaskExecWait",
866
+ exec_id=exec_id,
867
+ warning_message=(
868
+ f"Cannot retrieve the exit status for exec {exec_id} due to connection or service errors. "
869
+ "The command may have already exited."
870
+ ),
831
871
  base_delay_secs=1, # Retry after 1s since total time is expected to be long.
832
872
  delay_factor=1, # Fixed delay.
833
873
  max_retries=None, # Retry forever.
@@ -6,6 +6,7 @@ from datetime import datetime
6
6
  import click
7
7
 
8
8
  from modal._utils.async_utils import synchronizer
9
+ from modal._utils.time_utils import parse_duration
9
10
  from modal.client import _Client
10
11
  from modal.output import OutputManager
11
12
  from modal.token_flow import _new_token, _set_token
@@ -45,11 +46,28 @@ async def set(
45
46
  @token_cli.command("new")
46
47
  @click.option("--activate/--no-activate", default=True, help=_ACTIVATE_HELP)
47
48
  @click.option("--verify/--no-verify", default=True, help=_VERIFY_HELP)
49
+ @click.option(
50
+ "--expires-in",
51
+ default=None,
52
+ help="Lifetime of the new token, e.g. 12h, 7d, or 90d. Defaults to the workspace's maximum allowed lifetime.",
53
+ )
48
54
  @click.option("--source", default=None, hidden=True)
49
55
  @synchronizer.create_blocking
50
- async def new(activate: bool, verify: bool, source: str | None):
56
+ async def new(activate: bool, verify: bool, expires_in: str | None, source: str | None):
51
57
  """Create a new token by using an authenticated web session."""
52
- await _new_token(activate=activate, verify=verify, source=source)
58
+ expires_in_seconds: int | None = None
59
+ if expires_in is not None:
60
+ try:
61
+ expires_in_seconds = int(parse_duration(expires_in).total_seconds())
62
+ except (OverflowError, ValueError):
63
+ raise click.BadParameter(
64
+ "Expected a duration such as 30m, 12h, or 7d.", param_hint="--expires-in"
65
+ ) from None
66
+ if expires_in_seconds <= 0:
67
+ raise click.BadParameter("Duration must be positive.", param_hint="--expires-in")
68
+ if expires_in_seconds > 2**32 - 1:
69
+ raise click.BadParameter("Duration is too large.", param_hint="--expires-in")
70
+ await _new_token(activate=activate, verify=verify, source=source, expires_in_seconds=expires_in_seconds)
53
71
 
54
72
 
55
73
  @token_cli.command("info")
@@ -77,7 +77,7 @@ class _Client:
77
77
  server_url: str,
78
78
  client_type: int,
79
79
  credentials: typing.Optional[tuple[str, str]],
80
- version: str = "1.5.6.dev24",
80
+ version: str = "1.5.6.dev25",
81
81
  *,
82
82
  oauth_credentials: typing.Optional[_OAuthCredentials] = None,
83
83
  ):
@@ -277,7 +277,7 @@ class Client:
277
277
  server_url: str,
278
278
  client_type: int,
279
279
  credentials: typing.Optional[tuple[str, str]],
280
- version: str = "1.5.6.dev24",
280
+ version: str = "1.5.6.dev25",
281
281
  *,
282
282
  oauth_credentials: typing.Optional[_OAuthCredentials] = None,
283
283
  ):
@@ -242,6 +242,7 @@ class _Secret(_Object, type_prefix="st"):
242
242
  """
243
243
 
244
244
  _metadata: api_pb2.SecretMetadata | None = None
245
+ _keys: set[str] | None = None
245
246
  _load_env_dict: Callable[[], dict[str, str]] | None = None
246
247
 
247
248
  @classproperty
@@ -253,16 +254,35 @@ class _Secret(_Object, type_prefix="st"):
253
254
  def name(self) -> str | None:
254
255
  return self._name
255
256
 
257
+ async def _refresh_metadata(self):
258
+ # Not using @live_method because if we are dehydrated, the act of hydration will refresh
259
+ # metadata anyway, so we don't need to do the SecretGetInfo rpc
260
+ if not self._is_hydrated:
261
+ await self.hydrate()
262
+ return
263
+
264
+ req = api_pb2.SecretGetInfoRequest(secret_id=self.object_id)
265
+ response = await self.client.stub.SecretGetInfo(req)
266
+ self._hydrate(self.object_id, self.client, response.metadata)
267
+
256
268
  def _hydrate_metadata(self, metadata: Message | None):
257
269
  if metadata:
258
270
  assert isinstance(metadata, api_pb2.SecretMetadata)
259
271
  self._metadata = metadata
260
272
  self._name = metadata.name
273
+ self._keys = set(metadata.keys)
261
274
 
262
275
  def _get_metadata(self) -> api_pb2.SecretMetadata:
263
276
  assert self._metadata
264
277
  return self._metadata
265
278
 
279
+ async def _get_keys(self, *, refresh: bool = False) -> set[str]:
280
+ if refresh or self._keys is None:
281
+ await self._refresh_metadata()
282
+
283
+ assert self._keys is not None
284
+ return set(self._keys)
285
+
266
286
  @property
267
287
  def _is_ephemeral(self) -> bool:
268
288
  """Whether this Secret is backed by a locally-resolvable env dict rather than a named deployment.
@@ -307,9 +327,12 @@ class _Secret(_Object, type_prefix="st"):
307
327
 
308
328
  rep = f"Secret.from_dict([{', '.join(env_dict.keys())}])"
309
329
  # TODO: scoping - these should probably not be lazily hydrated without having an app and/or sandbox association
310
- return _Secret._from_load_env_dict(
330
+ obj = _Secret._from_load_env_dict(
311
331
  _load_env_dict, rep, hydrate_lazily=True, load_context_overrides=LoadContext.empty()
312
332
  )
333
+ obj._keys = set(env_dict_filtered.keys())
334
+
335
+ return obj
313
336
 
314
337
  @staticmethod
315
338
  def from_local_environ(
@@ -458,6 +481,45 @@ class _Secret(_Object, type_prefix="st"):
458
481
  skip_reload=True,
459
482
  )
460
483
 
484
+ @staticmethod
485
+ def _from_id(secret_id: str, *, client: _Client | None = None):
486
+ """mdmd:hidden
487
+
488
+ Reference a Secret by ID.
489
+
490
+ Hydration is lazy until the Secret is used.
491
+
492
+ Args:
493
+ secret_id: ID of the Secret.
494
+ client: Modal client to use for loading; defaults to `Client.from_env()` when omitted.
495
+
496
+ Returns:
497
+ A `Secret` handle (possibly not yet hydrated).
498
+
499
+ Examples:
500
+ ```python
501
+ secret = modal.Secret._from_id("st-1234")
502
+
503
+ @app.function(secrets=[secret])
504
+ def run():
505
+ ...
506
+ ```
507
+ """
508
+
509
+ async def _load(self: _Secret, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None):
510
+ req = api_pb2.SecretGetInfoRequest(secret_id=secret_id)
511
+ response = await load_context.client.stub.SecretGetInfo(req)
512
+ self._hydrate(secret_id, load_context.client, response.metadata)
513
+
514
+ rep = f"modal.Secret.from_id({secret_id!r})"
515
+ return _Secret._from_loader(
516
+ _load,
517
+ rep,
518
+ hydrate_lazily=True,
519
+ load_context_overrides=LoadContext(client=client),
520
+ skip_reload=True,
521
+ )
522
+
461
523
  @staticmethod
462
524
  async def _create_deployed(
463
525
  deployment_name: str,
@@ -493,6 +555,7 @@ class _Secret(_Object, type_prefix="st"):
493
555
  name=metadata.name or None,
494
556
  created_at=timestamp_to_localized_dt(creation_info.created_at),
495
557
  created_by=creation_info.created_by or None,
558
+ environment_name=metadata.environment_name,
496
559
  )
497
560
 
498
561
  @live_method
@@ -511,8 +574,13 @@ class _Secret(_Object, type_prefix="st"):
511
574
  raise InvalidError(err)
512
575
  updates = [api_pb2.SecretUpdateRequest.Update(key=k, value=v) for k, v in env_dict.items()]
513
576
  req = api_pb2.SecretUpdateRequest(secret_id=self.object_id, updates=updates)
577
+
514
578
  await self.client.stub.SecretUpdate(req)
515
579
 
580
+ # Since we are hydrated at this point, this will never be None
581
+ assert self._keys is not None
582
+ self._keys = self._keys | set(env_dict.keys())
583
+
516
584
 
517
585
  def _split_env_dict_and_resolvable_secrets(secrets: list[_Secret]) -> tuple[dict[str, str], list[_Secret]]:
518
586
  """Split secrets into secrets that can be resolved locally and secrets that are remote.
@@ -423,6 +423,7 @@ class _Secret(modal._object._Object):
423
423
  """
424
424
 
425
425
  _metadata: typing.Optional[modal_proto.api_pb2.SecretMetadata]
426
+ _keys: typing.Optional[set[str]]
426
427
  _load_env_dict: typing.Optional[collections.abc.Callable[[], dict[str, str]]]
427
428
 
428
429
  @synchronicity.classproperty
@@ -430,8 +431,10 @@ class _Secret(modal._object._Object):
430
431
  def objects(cls) -> _SecretManager: ...
431
432
  @property
432
433
  def name(self) -> typing.Optional[str]: ...
434
+ async def _refresh_metadata(self): ...
433
435
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
434
436
  def _get_metadata(self) -> modal_proto.api_pb2.SecretMetadata: ...
437
+ async def _get_keys(self, *, refresh: bool = False) -> set[str]: ...
435
438
  @property
436
439
  def _is_ephemeral(self) -> bool:
437
440
  """Whether this Secret is backed by a locally-resolvable env dict rather than a named deployment.
@@ -542,6 +545,32 @@ class _Secret(modal._object._Object):
542
545
  """
543
546
  ...
544
547
 
548
+ @staticmethod
549
+ def _from_id(secret_id: str, *, client: typing.Optional[modal.client._Client] = None):
550
+ """mdmd:hidden
551
+
552
+ Reference a Secret by ID.
553
+
554
+ Hydration is lazy until the Secret is used.
555
+
556
+ Args:
557
+ secret_id: ID of the Secret.
558
+ client: Modal client to use for loading; defaults to `Client.from_env()` when omitted.
559
+
560
+ Returns:
561
+ A `Secret` handle (possibly not yet hydrated).
562
+
563
+ Examples:
564
+ ```python
565
+ secret = modal.Secret._from_id("st-1234")
566
+
567
+ @app.function(secrets=[secret])
568
+ def run():
569
+ ...
570
+ ```
571
+ """
572
+ ...
573
+
545
574
  @staticmethod
546
575
  async def _create_deployed(
547
576
  deployment_name: str,
@@ -584,6 +613,7 @@ class Secret(modal.object.Object):
584
613
  """
585
614
 
586
615
  _metadata: typing.Optional[modal_proto.api_pb2.SecretMetadata]
616
+ _keys: typing.Optional[set[str]]
587
617
  _load_env_dict: typing.Optional[collections.abc.Callable[[], dict[str, str]]]
588
618
 
589
619
  def __init__(self, *args, **kwargs):
@@ -595,8 +625,22 @@ class Secret(modal.object.Object):
595
625
  def objects(cls) -> SecretManager: ...
596
626
  @property
597
627
  def name(self) -> typing.Optional[str]: ...
628
+
629
+ class ___refresh_metadata_spec(typing_extensions.Protocol):
630
+ def __call__(self, /): ...
631
+ async def aio(self, /): ...
632
+
633
+ _refresh_metadata: ___refresh_metadata_spec
634
+
598
635
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
599
636
  def _get_metadata(self) -> modal_proto.api_pb2.SecretMetadata: ...
637
+
638
+ class ___get_keys_spec(typing_extensions.Protocol):
639
+ def __call__(self, /, *, refresh: bool = False) -> set[str]: ...
640
+ async def aio(self, /, *, refresh: bool = False) -> set[str]: ...
641
+
642
+ _get_keys: ___get_keys_spec
643
+
600
644
  @property
601
645
  def _is_ephemeral(self) -> bool:
602
646
  """Whether this Secret is backed by a locally-resolvable env dict rather than a named deployment.
@@ -707,6 +751,32 @@ class Secret(modal.object.Object):
707
751
  """
708
752
  ...
709
753
 
754
+ @staticmethod
755
+ def _from_id(secret_id: str, *, client: typing.Optional[modal.client.Client] = None):
756
+ """mdmd:hidden
757
+
758
+ Reference a Secret by ID.
759
+
760
+ Hydration is lazy until the Secret is used.
761
+
762
+ Args:
763
+ secret_id: ID of the Secret.
764
+ client: Modal client to use for loading; defaults to `Client.from_env()` when omitted.
765
+
766
+ Returns:
767
+ A `Secret` handle (possibly not yet hydrated).
768
+
769
+ Examples:
770
+ ```python
771
+ secret = modal.Secret._from_id("st-1234")
772
+
773
+ @app.function(secrets=[secret])
774
+ def run():
775
+ ...
776
+ ```
777
+ """
778
+ ...
779
+
710
780
  class ___create_deployed_spec(typing_extensions.Protocol):
711
781
  def __call__(
712
782
  self,
@@ -23,7 +23,7 @@ class _TokenFlow:
23
23
 
24
24
  @asynccontextmanager
25
25
  async def start(
26
- self, utm_source: str | None = None, next_url: str | None = None
26
+ self, utm_source: str | None = None, next_url: str | None = None, expires_in_seconds: int | None = None
27
27
  ) -> AsyncGenerator[tuple[str, str, str], None]:
28
28
  """mdmd:hidden"""
29
29
  # Run a temporary http server returning the token id on /
@@ -41,6 +41,7 @@ class _TokenFlow:
41
41
  utm_source=utm_source or "",
42
42
  next_url=next_url or "",
43
43
  localhost_port=int(url.split(":")[-1]),
44
+ expires_in_seconds=expires_in_seconds or 0,
44
45
  )
45
46
  resp = await self.stub.TokenFlowCreate(req)
46
47
  self.token_flow_id = resp.token_flow_id
@@ -72,6 +73,7 @@ async def _new_token(
72
73
  verify: bool = True,
73
74
  source: str | None = None,
74
75
  next_url: str | None = None,
76
+ expires_in_seconds: int | None = None,
75
77
  ):
76
78
  server_url = config.get("server_url", profile=profile)
77
79
 
@@ -80,7 +82,7 @@ async def _new_token(
80
82
  async with _Client.anonymous(server_url) as client:
81
83
  token_flow = _TokenFlow(client)
82
84
 
83
- async with token_flow.start(source, next_url) as (_, web_url, code):
85
+ async with token_flow.start(source, next_url, expires_in_seconds) as (_, web_url, code):
84
86
  with output.status("Waiting for authentication in the web browser"):
85
87
  # Open the web url in the browser
86
88
  if open_url(web_url):
@@ -10,7 +10,10 @@ class _TokenFlow:
10
10
  ...
11
11
 
12
12
  def start(
13
- self, utm_source: typing.Optional[str] = None, next_url: typing.Optional[str] = None
13
+ self,
14
+ utm_source: typing.Optional[str] = None,
15
+ next_url: typing.Optional[str] = None,
16
+ expires_in_seconds: typing.Optional[int] = None,
14
17
  ) -> typing.AsyncContextManager[tuple[str, str, str]]:
15
18
  """mdmd:hidden"""
16
19
  ...
@@ -26,13 +29,21 @@ class TokenFlow:
26
29
 
27
30
  class __start_spec(typing_extensions.Protocol):
28
31
  def __call__(
29
- self, /, utm_source: typing.Optional[str] = None, next_url: typing.Optional[str] = None
32
+ self,
33
+ /,
34
+ utm_source: typing.Optional[str] = None,
35
+ next_url: typing.Optional[str] = None,
36
+ expires_in_seconds: typing.Optional[int] = None,
30
37
  ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[tuple[str, str, str]]:
31
38
  """mdmd:hidden"""
32
39
  ...
33
40
 
34
41
  def aio(
35
- self, /, utm_source: typing.Optional[str] = None, next_url: typing.Optional[str] = None
42
+ self,
43
+ /,
44
+ utm_source: typing.Optional[str] = None,
45
+ next_url: typing.Optional[str] = None,
46
+ expires_in_seconds: typing.Optional[int] = None,
36
47
  ) -> typing.AsyncContextManager[tuple[str, str, str]]:
37
48
  """mdmd:hidden"""
38
49
  ...
@@ -61,6 +72,7 @@ async def _new_token(
61
72
  verify: bool = True,
62
73
  source: typing.Optional[str] = None,
63
74
  next_url: typing.Optional[str] = None,
75
+ expires_in_seconds: typing.Optional[int] = None,
64
76
  ): ...
65
77
  async def _set_token(
66
78
  token_id: str,
@@ -108,6 +108,7 @@ class SecretInfo:
108
108
  # since it is transmitted from the server when the object is hydrated and could be stale when accessed.
109
109
 
110
110
  name: str | None
111
+ environment_name: str
111
112
  created_at: datetime
112
113
  created_by: str | None
113
114
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.6.dev24
3
+ Version: 1.5.6.dev25
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0