modal 1.6.4.dev10__tar.gz → 1.6.4.dev11__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 (254) hide show
  1. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/PKG-INFO +1 -1
  2. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/__init__.py +11 -0
  3. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_function_variants.py +1 -0
  4. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_functions.py +39 -4
  5. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_image.py +21 -0
  6. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_type_manager.py +7 -0
  7. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/name_utils.py +8 -1
  8. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/profile.py +40 -29
  9. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/token.py +83 -14
  10. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/client.py +1 -0
  11. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/client.pyi +2 -2
  12. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/config.py +1 -0
  13. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/functions.pyi +13 -6
  14. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/token_flow.py +32 -2
  15. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/token_flow.pyi +2 -0
  16. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal.egg-info/PKG-INFO +1 -1
  17. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/api_grpc.py +16 -0
  18. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/api_pb2.py +1791 -1645
  19. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/api_pb2.pyi +310 -0
  20. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/api_pb2_grpc.py +33 -0
  21. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/api_pb2_grpc.pyi +10 -0
  22. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/modal_api_grpc.py +1 -0
  23. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_version/__init__.py +1 -1
  24. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/pyproject.toml +1 -0
  25. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/LICENSE +0 -0
  26. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/README.md +0 -0
  27. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/__main__.py +0 -0
  28. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_audit_logs/__init__.py +0 -0
  29. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_audit_logs/_rpcs.py +0 -0
  30. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_audit_logs/count.py +0 -0
  31. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_audit_logs/list.py +0 -0
  32. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_billing.py +0 -0
  33. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_cluster.py +0 -0
  34. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_cluster.pyi +0 -0
  35. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_container_entrypoint.py +0 -0
  36. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_environments.py +0 -0
  37. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_grpc_client.py +0 -0
  38. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_ipython.py +0 -0
  39. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_load_context.py +0 -0
  40. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_location.py +0 -0
  41. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_logs.py +0 -0
  42. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_logs_manager.py +0 -0
  43. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_logs_manager.pyi +0 -0
  44. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_metrics.py +0 -0
  45. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_metrics.pyi +0 -0
  46. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_object.py +0 -0
  47. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_outbound_policy.py +0 -0
  48. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_outbound_policy.pyi +0 -0
  49. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_output/__init__.py +0 -0
  50. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_output/manager.py +0 -0
  51. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_output/pty.py +0 -0
  52. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_output/rich.py +0 -0
  53. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_output/status.py +0 -0
  54. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_partial_function.py +0 -0
  55. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_resolver.py +0 -0
  56. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_resources.py +0 -0
  57. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/__init__.py +0 -0
  58. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/asgi.py +0 -0
  59. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/container_io_manager.py +0 -0
  60. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/container_io_manager.pyi +0 -0
  61. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/execution_context.py +0 -0
  62. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/execution_context.pyi +0 -0
  63. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  64. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/task_lifecycle_manager.py +0 -0
  65. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  66. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/telemetry.py +0 -0
  67. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/user_code_event_loop.py +0 -0
  68. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/user_code_imports.py +0 -0
  69. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_serialization.py +0 -0
  70. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_server.py +0 -0
  71. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_server.pyi +0 -0
  72. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_supports_logs.py +0 -0
  73. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_traceback.py +0 -0
  74. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_tunnel.py +0 -0
  75. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_tunnel.pyi +0 -0
  76. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/__init__.py +0 -0
  77. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/app_utils.py +0 -0
  78. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/async_utils.py +0 -0
  79. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/auth_token_manager.py +0 -0
  80. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/blob_utils.py +0 -0
  81. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/browser_utils.py +0 -0
  82. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/bytes_io_segment_payload.py +0 -0
  83. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/curl_utils.py +0 -0
  84. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/deprecation.py +0 -0
  85. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/display_utils.py +0 -0
  86. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/docker_utils.py +0 -0
  87. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/function_utils.py +0 -0
  88. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/git_utils.py +0 -0
  89. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/grpc_testing.py +0 -0
  90. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/grpc_utils.py +0 -0
  91. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/hash_utils.py +0 -0
  92. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/http_utils.py +0 -0
  93. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/idle_countdown.py +0 -0
  94. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/jwt_utils.py +0 -0
  95. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/logger.py +0 -0
  96. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/mount_utils.py +0 -0
  97. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/oauth.py +0 -0
  98. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/package_utils.py +0 -0
  99. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/pattern_utils.py +0 -0
  100. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/proxy_support.py +0 -0
  101. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/rand_pb_testing.py +0 -0
  102. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/sandbox_fs_utils.py +0 -0
  103. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/shell_utils.py +0 -0
  104. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/time_utils.py +0 -0
  105. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_vendor/__init__.py +0 -0
  106. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  107. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_vendor/cloudpickle.py +0 -0
  108. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_vendor/tblib.py +0 -0
  109. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_vendor/version.py +0 -0
  110. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_watcher.py +0 -0
  111. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_workspace.py +0 -0
  112. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/app.py +0 -0
  113. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/app.pyi +0 -0
  114. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/billing.py +0 -0
  115. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/billing.pyi +0 -0
  116. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/2023.12.312.txt +0 -0
  117. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/2023.12.txt +0 -0
  118. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/2024.04.txt +0 -0
  119. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/2024.10.txt +0 -0
  120. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/2025.06.txt +0 -0
  121. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/2026.10.txt +0 -0
  122. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/PREVIEW.txt +0 -0
  123. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/README.md +0 -0
  124. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/base-images.json +0 -0
  125. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/call_graph.py +0 -0
  126. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/__init__.py +0 -0
  127. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/_download.py +0 -0
  128. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/_help.py +0 -0
  129. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/_logs.py +0 -0
  130. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/_stats.py +0 -0
  131. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/_traceback.py +0 -0
  132. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/app.py +0 -0
  133. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/billing.py +0 -0
  134. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/changelog.py +0 -0
  135. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/config.py +0 -0
  136. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/container.py +0 -0
  137. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/curl.py +0 -0
  138. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/dashboard.py +0 -0
  139. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/dict.py +0 -0
  140. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/endpoint.py +0 -0
  141. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/entry_point.py +0 -0
  142. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/environment.py +0 -0
  143. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/function.py +0 -0
  144. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/image.py +0 -0
  145. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/import_refs.py +0 -0
  146. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/logo.py +0 -0
  147. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/network_file_system.py +0 -0
  148. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/queues.py +0 -0
  149. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/run.py +0 -0
  150. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/secret.py +0 -0
  151. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/selector.py +0 -0
  152. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/server.py +0 -0
  153. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/shell.py +0 -0
  154. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/skills.py +0 -0
  155. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/utils.py +0 -0
  156. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/volume.py +0 -0
  157. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/workspace.py +0 -0
  158. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cloud_bucket_mount.py +0 -0
  159. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cloud_bucket_mount.pyi +0 -0
  160. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cls.py +0 -0
  161. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cls.pyi +0 -0
  162. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cluster.py +0 -0
  163. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cluster.pyi +0 -0
  164. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/container_process.py +0 -0
  165. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/container_process.pyi +0 -0
  166. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/dict.py +0 -0
  167. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/dict.pyi +0 -0
  168. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/environments.py +0 -0
  169. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/environments.pyi +0 -0
  170. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/exception.py +0 -0
  171. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/experimental/__init__.py +0 -0
  172. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/experimental/_flash_container_entry.py +0 -0
  173. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/experimental/flash.py +0 -0
  174. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/experimental/flash.pyi +0 -0
  175. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/experimental/flash_server.py +0 -0
  176. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/experimental/flash_server.pyi +0 -0
  177. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/experimental/ipython.py +0 -0
  178. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/file_pattern_matcher.py +0 -0
  179. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/functions.py +0 -0
  180. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/image.py +0 -0
  181. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/image.pyi +0 -0
  182. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/io_streams.py +0 -0
  183. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/io_streams.pyi +0 -0
  184. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/mount.py +0 -0
  185. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/mount.pyi +0 -0
  186. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/network_file_system.py +0 -0
  187. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/network_file_system.pyi +0 -0
  188. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/object.py +0 -0
  189. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/object.pyi +0 -0
  190. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/output.py +0 -0
  191. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/parallel_map.py +0 -0
  192. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/parallel_map.pyi +0 -0
  193. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/partial_function.py +0 -0
  194. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/partial_function.pyi +0 -0
  195. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/proxy.py +0 -0
  196. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/proxy.pyi +0 -0
  197. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/py.typed +0 -0
  198. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/queue.py +0 -0
  199. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/queue.pyi +0 -0
  200. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/retries.py +0 -0
  201. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/runner.py +0 -0
  202. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/runner.pyi +0 -0
  203. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/running_app.py +0 -0
  204. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/__init__.py +0 -0
  205. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_common.py +0 -0
  206. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_filesystem.py +0 -0
  207. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_impl_persistent.py +0 -0
  208. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_impl_v1.py +0 -0
  209. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_impl_v2.py +0 -0
  210. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_sandbox.py +0 -0
  211. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_sidecar.py +0 -0
  212. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_snapshot.py +0 -0
  213. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_task_command_router_client.py +0 -0
  214. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/sandbox.py +0 -0
  215. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/sandbox.pyi +0 -0
  216. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/schedule.py +0 -0
  217. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/scheduler_placement.py +0 -0
  218. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/secret.py +0 -0
  219. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/secret.pyi +0 -0
  220. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/server.py +0 -0
  221. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/server.pyi +0 -0
  222. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/serving.py +0 -0
  223. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/serving.pyi +0 -0
  224. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/skills/modal/SKILL.md +0 -0
  225. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/stream_type.py +0 -0
  226. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/types.py +0 -0
  227. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/volume.py +0 -0
  228. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/volume.pyi +0 -0
  229. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/workspace.py +0 -0
  230. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/workspace.pyi +0 -0
  231. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal.egg-info/SOURCES.txt +0 -0
  232. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal.egg-info/dependency_links.txt +0 -0
  233. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal.egg-info/entry_points.txt +0 -0
  234. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal.egg-info/requires.txt +0 -0
  235. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal.egg-info/top_level.txt +0 -0
  236. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/__init__.py +0 -0
  237. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/gen_cli_docs.py +0 -0
  238. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/gen_cli_docs_main.py +0 -0
  239. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/gen_reference_docs.py +0 -0
  240. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/gen_reference_docs_main.py +0 -0
  241. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/mdmd/__init__.py +0 -0
  242. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/mdmd/mdmd.py +0 -0
  243. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/mdmd/signatures.py +0 -0
  244. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/mdmd/types.py +0 -0
  245. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/reference_intro.md +0 -0
  246. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/__init__.py +0 -0
  247. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/py.typed +0 -0
  248. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/task_command_router_grpc.py +0 -0
  249. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/task_command_router_pb2.py +0 -0
  250. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/task_command_router_pb2.pyi +0 -0
  251. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  252. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  253. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_version/__main__.py +0 -0
  254. {modal-1.6.4.dev10 → modal-1.6.4.dev11}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.6.4.dev10
3
+ Version: 1.6.4.dev11
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -1,4 +1,5 @@
1
1
  # Copyright Modal Labs 2022
2
+ import os
2
3
  import sys
3
4
 
4
5
  if sys.version_info[:2] < (3, 10):
@@ -59,6 +60,16 @@ except Exception:
59
60
  print()
60
61
  raise
61
62
 
63
+ if sys.version_info[:2] == (3, 10) and os.environ.get("MODAL_IS_REMOTE") != "1":
64
+ from ._utils.deprecation import deprecation_warning
65
+
66
+ deprecation_warning(
67
+ (2026, 11, 4),
68
+ "Python 3.10 is EOL and Modal will not support it in version 1.7.0. "
69
+ "Please upgrade your local Python to 3.11 or later.",
70
+ show_source=False,
71
+ )
72
+
62
73
 
63
74
  __all__ = [
64
75
  "__version__",
@@ -408,6 +408,7 @@ def _make_function_variant(
408
408
  if not base_function._is_hydrated:
409
409
  await base_function.hydrate(load_context.client)
410
410
 
411
+ base_function._check_not_observability_only()
411
412
  assert base_function._client and base_function._client._stub
412
413
 
413
414
  if parameter_schema is None:
@@ -119,6 +119,15 @@ if TYPE_CHECKING:
119
119
  import modal.functions
120
120
 
121
121
  MAX_INTERNAL_FAILURE_COUNT = 8
122
+ _OBSERVABILITY_ONLY_APIS = (
123
+ ".logs",
124
+ ".metrics",
125
+ ".info()",
126
+ ".stats()",
127
+ ".get_current_stats()",
128
+ ".get_web_url()",
129
+ ".autoscaler.settings()",
130
+ )
122
131
  TERMINAL_STATUSES = (
123
132
  api_pb2.GenericResult.GENERIC_STATUS_SUCCESS,
124
133
  api_pb2.GenericResult.GENERIC_STATUS_TERMINATED,
@@ -651,6 +660,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
651
660
  _base_function: "_Function | None" = None
652
661
  _app_id: str | None = None
653
662
  _function_info: FunctionInfo | None = None
663
+ _is_observability_only: bool = False
654
664
 
655
665
  async def _get_log_query_data(self) -> _LogQueryData:
656
666
  await self.hydrate()
@@ -1398,6 +1408,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1398
1408
 
1399
1409
  """
1400
1410
 
1411
+ self._check_not_observability_only()
1401
1412
  deprecation_warning(
1402
1413
  (2026, 10, 16),
1403
1414
  "`Function.update_autoscaler()` is deprecated and will be removed in a future version "
@@ -1485,16 +1496,15 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1485
1496
  # refine the error message
1486
1497
  msg = f"Lookup failed for {called_from} '{function_id}': {exc}."
1487
1498
  raise NotFoundError(msg) from None
1488
- if called_from == "Function" and (response.function.is_server or response.function.is_class):
1489
- if response.function.is_server:
1490
- raise InvalidError(f"{function_id} is a Server. Use \n ``modal.Server.from_id('{function_id}')``")
1491
- raise InvalidError(f"{function_id} is a Cls and cannot be loaded with Function.from_id().")
1499
+ if called_from == "Function" and response.function.is_server:
1500
+ raise InvalidError(f"{function_id} is a Server. Use \n ``modal.Server.from_id('{function_id}')``")
1492
1501
  if called_from == "Server" and not response.function.is_server:
1493
1502
  if response.function.is_class:
1494
1503
  raise InvalidError(f"{function_id} is a Cls and cannot be loaded with Server.from_id().")
1495
1504
  raise InvalidError(f"{function_id} is a Function. Use \n ``modal.Function.from_id('{function_id}')``")
1496
1505
  self._hydrate(function_id, load_context.client, response.handle_metadata)
1497
1506
  self._function_info = FunctionInfo._from_function_proto(response.function)
1507
+ self._is_observability_only = called_from == "Function" and response.function.is_class
1498
1508
 
1499
1509
  rep = f"modal.Function.from_id('{function_id}')"
1500
1510
  return cls._from_loader(
@@ -1514,6 +1524,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1514
1524
  object with metadata from Modal servers until the first
1515
1525
  time it is actually used.
1516
1526
 
1527
+ If the ID belongs to a Cls, the Function only supports the following APIs:
1528
+ `.logs`, `.metrics`, `.info()`, `.stats()`, `.get_current_stats()`,
1529
+ `.get_web_url()` and `.autoscaler.settings()`. Use `modal.Cls.from_name(...)`
1530
+ to invoke all other methods.
1531
+
1517
1532
  Args:
1518
1533
  function_id: ID of the function.
1519
1534
  client: Modal client to use; defaults to `Client.from_env()` when omitted.
@@ -1744,6 +1759,18 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1744
1759
  + f"or call it locally: {self._function_name}.local()"
1745
1760
  )
1746
1761
 
1762
+ def _check_not_observability_only(self):
1763
+ if self._is_observability_only:
1764
+ assert self._function_name
1765
+ class_name = self._function_name.removesuffix(".*")
1766
+ apis = [f"`{name}`" for name in _OBSERVABILITY_ONLY_APIS]
1767
+ allowed_apis = ", ".join(apis[:-1]) + " and " + apis[-1]
1768
+ raise InvalidError(
1769
+ f"`{self.object_id}` belongs to the Cls `{class_name}`. A Function loaded with "
1770
+ f"`Function.from_id` only supports {allowed_apis}. "
1771
+ "Use `modal.Cls.from_name(...)` to invoke all other methods."
1772
+ )
1773
+
1747
1774
  @live_method
1748
1775
  async def get_web_url(self) -> str | None:
1749
1776
  """URL for addressing a Web Function via HTTP.
@@ -1779,6 +1806,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1779
1806
  config_method_name: str,
1780
1807
  ) -> "_Function[P, ReturnType, OriginalReturnType]":
1781
1808
  base_function = self._base_function or self
1809
+ base_function._check_not_observability_only()
1782
1810
  combined_options = self._options.merge_options(new_options)
1783
1811
 
1784
1812
  # For "True Functions" (i.e. not the Function inside a Cls), we will use
@@ -1941,6 +1969,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1941
1969
  _SynchronizedQueue is used instead of asyncio.Queue so that the main thread can put
1942
1970
  items in the queue safely.
1943
1971
  """
1972
+ self._check_not_observability_only()
1944
1973
  self._check_no_web_url("map")
1945
1974
  if self._is_generator:
1946
1975
  raise InvalidError("A generator function cannot be called with `.map(...)`.")
@@ -1978,6 +2007,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1978
2007
 
1979
2008
  @live_method
1980
2009
  async def _spawn_map(self, input_queue: _SynchronizedQueue) -> "_FunctionCall[ReturnType]":
2010
+ self._check_not_observability_only()
1981
2011
  self._check_no_web_url("spawn_map")
1982
2012
  if self._is_generator:
1983
2013
  raise InvalidError("A generator function cannot be called with `.spawn_map(...)`.")
@@ -1995,6 +2025,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1995
2025
  return fc
1996
2026
 
1997
2027
  async def _call_function(self, args, kwargs) -> ReturnType:
2028
+ self._check_not_observability_only()
1998
2029
  invocation: _Invocation | _InputPlaneInvocation
1999
2030
  if self._input_plane_url:
2000
2031
  invocation = await _InputPlaneInvocation.create(
@@ -2024,6 +2055,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
2024
2055
  function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType",
2025
2056
  from_spawn_map: bool = False,
2026
2057
  ) -> _Invocation:
2058
+ self._check_not_observability_only()
2027
2059
  return await _Invocation.create(
2028
2060
  self,
2029
2061
  args,
@@ -2037,6 +2069,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
2037
2069
  @live_method_gen
2038
2070
  @synchronizer.no_input_translation
2039
2071
  async def _call_generator(self, args, kwargs):
2072
+ self._check_not_observability_only()
2040
2073
  invocation: _Invocation | _InputPlaneInvocation
2041
2074
  if self._input_plane_url:
2042
2075
  invocation = await _InputPlaneInvocation.create(
@@ -2093,6 +2126,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
2093
2126
  Values produced by the remote generator.
2094
2127
  """
2095
2128
  # TODO: Generics/TypeVars
2129
+ self._check_not_observability_only()
2096
2130
  self._check_no_web_url("remote_gen")
2097
2131
 
2098
2132
  if not self._is_generator:
@@ -2669,6 +2703,7 @@ class _FunctionAutoscalerManager:
2669
2703
  if not self._function._is_hydrated:
2670
2704
  await self._function.hydrate()
2671
2705
 
2706
+ self._function._check_not_observability_only()
2672
2707
  settings: api_pb2.AutoscalerSettings = api_pb2.AutoscalerSettings()
2673
2708
 
2674
2709
  if min_containers != UNSET_PARAMETER:
@@ -37,6 +37,7 @@ from ._serialization import get_preferred_payload_format, serialize
37
37
  from ._supports_logs import _ImageLogQueryData
38
38
  from ._utils.async_utils import TaskContext, deprecate_aio_usage, synchronizer
39
39
  from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
40
+ from ._utils.deprecation import deprecation_warning
40
41
  from ._utils.docker_utils import (
41
42
  extract_copy_command_patterns,
42
43
  find_dockerignore_file,
@@ -87,6 +88,23 @@ SUPPORTED_PYTHON_SERIES: dict[ImageBuilderVersion, list[str]] = {
87
88
  "2023.12": ["3.10", "3.11", "3.12"],
88
89
  }
89
90
 
91
+
92
+ def _uses_python_310(metadata: api_pb2.ImageMetadata | None) -> bool:
93
+ return metadata is not None and bool(re.match(r"Python 3\.10\.", metadata.python_version_info))
94
+
95
+
96
+ def _warn_if_python_310(image_id: str, metadata: api_pb2.ImageMetadata | None) -> None:
97
+ if _uses_python_310(metadata):
98
+ deprecation_warning(
99
+ (2026, 11, 4),
100
+ f"Image {image_id} uses Python 3.10, which is EOL. Modal 1.7.0 and later will not run or deploy apps "
101
+ "that use Images with Python 3.10; apps already deployed with older clients keep working. "
102
+ "Please update the Image to use Python 3.11 or later "
103
+ "(e.g. via `python_version=` or `add_python=`).",
104
+ show_source=False,
105
+ )
106
+
107
+
90
108
  LOCAL_REQUIREMENTS_DIR = Path(__file__).parent / "builder"
91
109
  CONTAINER_REQUIREMENTS_PATH = "/modal_requirements.txt"
92
110
 
@@ -877,6 +895,8 @@ class _Image(_Object, type_prefix="im"):
877
895
  raise ImageBuildError("Unknown status %s!" % result.status, image_id)
878
896
 
879
897
  self._hydrate(image_id, load_context.client, metadata)
898
+ if not any(_uses_python_310(base._metadata) for base in base_images.values()):
899
+ _warn_if_python_310(image_id, metadata)
880
900
  local_mounts: set[_Mount] = set()
881
901
  for base in base_images.values():
882
902
  local_mounts |= base._serve_mounts
@@ -1098,6 +1118,7 @@ class _Image(_Object, type_prefix="im"):
1098
1118
  async def _load(self: _Image, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None):
1099
1119
  resp = await load_context.client._stub.ImageFromId(api_pb2.ImageFromIdRequest(image_id=image_id))
1100
1120
  self._hydrate(resp.image_id, load_context.client, resp.metadata)
1121
+ _warn_if_python_310(resp.image_id, resp.metadata)
1101
1122
 
1102
1123
  rep = f"Image.from_id({image_id!r})"
1103
1124
 
@@ -1,4 +1,5 @@
1
1
  # Copyright Modal Labs 2025
2
+ import types
2
3
  import typing
3
4
  from typing import Any
4
5
 
@@ -186,6 +187,12 @@ class SchemaRegistry:
186
187
 
187
188
  def get_proto_generic_type(self, declared_type: type):
188
189
  if origin := typing_extensions.get_origin(declared_type):
190
+ if origin in (typing.Union, types.UnionType):
191
+ args = [arg for arg in typing_extensions.get_args(declared_type) if arg is not type(None)]
192
+ if len(args) == 1:
193
+ return self.get_proto_generic_type(args[0])
194
+ return unknown_type_schema(declared_type)
195
+
189
196
  base_type = origin
190
197
  else:
191
198
  base_type = declared_type
@@ -16,6 +16,13 @@ def replace_invalid_subdomain_chars(label: str) -> str:
16
16
  return re.sub("[^a-z0-9-]", "-", label.lower())
17
17
 
18
18
 
19
+ app_id_regex = re.compile("^ap-[a-zA-Z0-9]{22}$")
20
+
21
+
22
+ def is_app_id(identifier: str) -> bool:
23
+ return app_id_regex.match(identifier) is not None
24
+
25
+
19
26
  def is_valid_object_name(name: str) -> bool:
20
27
  return (
21
28
  # Limit object name length
@@ -23,7 +30,7 @@ def is_valid_object_name(name: str) -> bool:
23
30
  # Limit character set
24
31
  and re.match("^[a-zA-Z0-9-_.]+$", name) is not None
25
32
  # Avoid collisions with App IDs
26
- and re.match("^ap-[a-zA-Z0-9]{22}$", name) is None
33
+ and not is_app_id(name)
27
34
  )
28
35
 
29
36
 
@@ -5,11 +5,12 @@ import os
5
5
  from contextlib import suppress
6
6
 
7
7
  import click
8
- from rich.json import JSON
9
- from rich.table import Table
8
+ from rich.box import SIMPLE_HEAD
9
+ from rich.text import Text
10
10
 
11
11
  from modal import config as config_module
12
12
  from modal._utils.async_utils import synchronizer
13
+ from modal.cli.utils import display_table
13
14
  from modal.config import (
14
15
  Config,
15
16
  _lookup_workspace,
@@ -28,7 +29,7 @@ profile_cli = ModalGroup(name="profile", help="Switch between Modal profiles.")
28
29
  @profile_cli.command("list", help="Show all Modal profiles and highlight the active one.")
29
30
  @click.option("--json", is_flag=True, default=False)
30
31
  @synchronizer.create_blocking
31
- async def list_(json: bool | None = False):
32
+ async def list_(json: bool = False):
32
33
  config = Config()
33
34
  profiles = config_profiles()
34
35
  lookup_coros = [
@@ -44,22 +45,6 @@ async def list_(json: bool | None = False):
44
45
  ]
45
46
  responses = await asyncio.gather(*lookup_coros, return_exceptions=True)
46
47
 
47
- rows = []
48
- for profile, resp in zip(profiles, responses):
49
- active = profile == config_module._profile
50
- if isinstance(resp, AuthError):
51
- workspace = "Unknown (authentication failure)"
52
- elif isinstance(resp, TimeoutError):
53
- workspace = "Unknown (timed out)"
54
- elif isinstance(resp, Exception):
55
- # Catch-all for other exceptions, like incorrect server url
56
- workspace = "Unknown (profile misconfigured)"
57
- else:
58
- assert not isinstance(resp, BaseException)
59
- workspace = resp.username
60
- content = ["•" if active else "", profile, workspace]
61
- rows.append((active, content))
62
-
63
48
  env_based_workspace: str | None = None
64
49
  credential_env_vars = (
65
50
  "MODAL_TOKEN_ID",
@@ -87,18 +72,44 @@ async def list_(json: bool | None = False):
87
72
 
88
73
  output = OutputManager.get()
89
74
  highlight = "bold green" if env_based_workspace is None else "yellow"
90
- if json:
91
- json_data = []
92
- for active, content in rows:
93
- json_data.append({"name": content[1], "workspace": content[2], "active": active})
94
- output.print(JSON.from_data(json_data))
75
+
76
+ if not json:
77
+ table_columns = [" Profile", "Workspace"]
95
78
  else:
96
- table = Table(" ", "Profile", "Workspace")
97
- for active, content in rows:
98
- table.add_row(*content, style=highlight if active else "dim")
99
- output.print(table)
79
+ table_columns = ["name", "workspace", "active"]
80
+
81
+ table_rows: list[list[str | Text | bool]] = []
82
+ for profile, resp in zip(profiles, responses):
83
+ active = profile == config_module._profile
84
+
85
+ if isinstance(resp, AuthError):
86
+ workspace = "Unknown (authentication failure)"
87
+ elif isinstance(resp, TimeoutError):
88
+ workspace = "Unknown (timed out)"
89
+ elif isinstance(resp, Exception):
90
+ # Catch-all for other exceptions, like incorrect server url
91
+ workspace = "Unknown (profile misconfigured)"
92
+ else:
93
+ assert not isinstance(resp, BaseException)
94
+ workspace = resp.username
95
+
96
+ if json:
97
+ table_rows.append([profile, workspace, active])
98
+ continue
99
+
100
+ rendered_profile = f"• {profile}" if active else f" {profile}"
101
+ style = highlight if active else "dim"
102
+
103
+ table_rows.append(
104
+ [
105
+ Text(rendered_profile, style=style),
106
+ Text(workspace, style=style),
107
+ ]
108
+ )
109
+
110
+ display_table(table_columns, table_rows, json=json, table_box=SIMPLE_HEAD)
100
111
 
101
- if env_based_workspace is not None:
112
+ if not json and env_based_workspace is not None:
102
113
  output.print(
103
114
  f"[yellow]Using [bold]{env_based_workspace}[/bold] workspace based on environment variables[/yellow]"
104
115
  )
@@ -70,18 +70,7 @@ async def info():
70
70
  output.print(f"[bold]Expires at:[/bold] [white]{expires_dt.strftime('%Y-%m-%d %H:%M:%S %Z')}[/white]")
71
71
 
72
72
 
73
- @token_cli.command("new")
74
- @click.option("--activate/--no-activate", default=True, help=_ACTIVATE_HELP)
75
- @click.option("--verify/--no-verify", default=True, help=_VERIFY_HELP)
76
- @click.option(
77
- "--expires-in",
78
- default=None,
79
- help="Lifetime of the new token, e.g. 12h, 7d, or 90d. Defaults to the workspace's maximum allowed lifetime.",
80
- )
81
- @click.option("--source", default=None, hidden=True)
82
- @synchronizer.create_blocking
83
- async def new(activate: bool, verify: bool, expires_in: str | None, source: str | None):
84
- """Create a new token by using an authenticated web session."""
73
+ def _validate_expires_in(expires_in: str | None) -> int | None:
85
74
  expires_in_seconds: int | None = None
86
75
  if expires_in is not None:
87
76
  try:
@@ -94,7 +83,74 @@ async def new(activate: bool, verify: bool, expires_in: str | None, source: str
94
83
  raise click.BadParameter("Duration must be positive.", param_hint="--expires-in")
95
84
  if expires_in_seconds > 2**32 - 1:
96
85
  raise click.BadParameter("Duration is too large.", param_hint="--expires-in")
97
- await _new_token(activate=activate, verify=verify, source=source, expires_in_seconds=expires_in_seconds)
86
+
87
+ return expires_in_seconds
88
+
89
+
90
+ @token_cli.command("new")
91
+ @click.option("--activate/--no-activate", default=True, help=_ACTIVATE_HELP)
92
+ @click.option("--verify/--no-verify", default=True, help=_VERIFY_HELP)
93
+ @click.option(
94
+ "--expires-in",
95
+ default=None,
96
+ help="Lifetime of the new token, e.g. 12h, 7d, or 90d. Defaults to the workspace's maximum allowed lifetime.",
97
+ )
98
+ @click.option(
99
+ "--delete-existing",
100
+ default=False,
101
+ is_flag=True,
102
+ help="In addition to creating a new token, delete the existing token in the active profile",
103
+ )
104
+ @click.option("--source", default=None, hidden=True)
105
+ @synchronizer.create_blocking
106
+ async def new(
107
+ activate: bool,
108
+ verify: bool,
109
+ expires_in: str | None,
110
+ delete_existing: bool,
111
+ source: str | None,
112
+ ):
113
+ """Create a new token by using an authenticated web session."""
114
+
115
+ await _new_token(
116
+ activate=activate,
117
+ verify=verify,
118
+ source=source,
119
+ expires_in_seconds=_validate_expires_in(expires_in),
120
+ delete_existing=delete_existing,
121
+ )
122
+
123
+
124
+ @token_cli.command("rotate")
125
+ @click.option(
126
+ "--expires-in",
127
+ default=None,
128
+ help="Lifetime of the new token, e.g. 12h, 7d, or 90d. Defaults to the workspace's maximum allowed lifetime.",
129
+ )
130
+ @synchronizer.create_blocking
131
+ async def rotate(expires_in: str | None):
132
+ """Create a new token in a web session, then delete the current token.
133
+
134
+ Note that this is equivalent to `modal token new --delete-existing --no-activate`. You may also
135
+ optionally provide a custom expiration window with `--expires-in`. If not provided, the token's
136
+ TTL will fall back to the workspace default.
137
+
138
+ Examples:
139
+
140
+ ```bash
141
+ modal token rotate
142
+
143
+ # Set a custom TTL of 1 week
144
+ modal token rotate --expires-in 7d
145
+ ```
146
+ """
147
+
148
+ await _new_token(
149
+ activate=False,
150
+ verify=True,
151
+ expires_in_seconds=_validate_expires_in(expires_in),
152
+ delete_existing=True,
153
+ )
98
154
 
99
155
 
100
156
  @token_cli.command("set")
@@ -102,12 +158,19 @@ async def new(activate: bool, verify: bool, expires_in: str | None, source: str
102
158
  @click.option("--token-secret", default=None, help="Account token secret.")
103
159
  @click.option("--activate/--no-activate", default=True, help=_ACTIVATE_HELP)
104
160
  @click.option("--verify/--no-verify", default=True, help=_VERIFY_HELP)
161
+ @click.option(
162
+ "--delete-existing",
163
+ default=False,
164
+ is_flag=True,
165
+ help="In addition to setting a new token, delete the existing token in the active profile",
166
+ )
105
167
  @synchronizer.create_blocking
106
168
  async def set(
107
169
  token_id: str | None,
108
170
  token_secret: str | None,
109
171
  activate: bool,
110
172
  verify: bool,
173
+ delete_existing: bool,
111
174
  ):
112
175
  """Set account credentials for connecting to Modal.
113
176
 
@@ -117,4 +180,10 @@ async def set(
117
180
  token_id = getpass.getpass("Token ID:")
118
181
  if token_secret is None:
119
182
  token_secret = getpass.getpass("Token secret:")
120
- await _set_token(token_id, token_secret, activate=activate, verify=verify)
183
+ await _set_token(
184
+ token_id,
185
+ token_secret,
186
+ activate=activate,
187
+ verify=verify,
188
+ delete_existing=delete_existing,
189
+ )
@@ -360,6 +360,7 @@ class _Client:
360
360
  "Run `modal profile list` to see available profiles, or "
361
361
  f"`modal token new --profile {profile}` to configure it."
362
362
  )
363
+
363
364
  raise AuthError(
364
365
  "Token missing. Could not authenticate client."
365
366
  " If you have token credentials, see modal.com/docs/sdk/py/latest/config for setup help."
@@ -87,7 +87,7 @@ class _Client:
87
87
  server_url: str,
88
88
  client_type: int,
89
89
  credentials: typing.Optional[tuple[str, str]],
90
- version: str = "1.6.4.dev10",
90
+ version: str = "1.6.4.dev11",
91
91
  *,
92
92
  oauth_credentials: typing.Optional[_OAuthCredentials] = None,
93
93
  ):
@@ -304,7 +304,7 @@ class Client:
304
304
  server_url: str,
305
305
  client_type: int,
306
306
  credentials: typing.Optional[tuple[str, str]],
307
- version: str = "1.6.4.dev10",
307
+ version: str = "1.6.4.dev11",
308
308
  *,
309
309
  oauth_credentials: typing.Optional[_OAuthCredentials] = None,
310
310
  ):
@@ -521,6 +521,7 @@ def _store_user_config(
521
521
  """Internal method, used by the CLI to set tokens."""
522
522
  if profile is None:
523
523
  profile = _profile
524
+
524
525
  user_config = _read_user_config()
525
526
  user_config.setdefault(profile, {}).update(**new_settings)
526
527
  if active_profile is not None:
@@ -65,6 +65,7 @@ class Function(
65
65
  _base_function: typing.Optional[Function]
66
66
  _app_id: typing.Optional[str]
67
67
  _function_info: typing.Optional[modal.types.FunctionInfo]
68
+ _is_observability_only: bool
68
69
 
69
70
  def __init__(self, *args, **kwargs):
70
71
  """mdmd:hidden"""
@@ -319,6 +320,11 @@ class Function(
319
320
  object with metadata from Modal servers until the first
320
321
  time it is actually used.
321
322
 
323
+ If the ID belongs to a Cls, the Function only supports the following APIs:
324
+ `.logs`, `.metrics`, `.info()`, `.stats()`, `.get_current_stats()`,
325
+ `.get_web_url()` and `.autoscaler.settings()`. Use `modal.Cls.from_name(...)`
326
+ to invoke all other methods.
327
+
322
328
  Args:
323
329
  function_id: ID of the function.
324
330
  client: Modal client to use; defaults to `Client.from_env()` when omitted.
@@ -428,6 +434,7 @@ class Function(
428
434
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
429
435
  def _get_metadata(self): ...
430
436
  def _check_no_web_url(self, fn_name: str): ...
437
+ def _check_not_observability_only(self): ...
431
438
 
432
439
  class __get_web_url_spec(typing_extensions.Protocol):
433
440
  def __call__(self, /) -> typing.Optional[str]:
@@ -619,7 +626,7 @@ class Function(
619
626
 
620
627
  _call_generator: ___call_generator_spec
621
628
 
622
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
629
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
623
630
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
624
631
  """Calls the function remotely, executing it with the given arguments and returning the execution's result.
625
632
 
@@ -644,7 +651,7 @@ class Function(
644
651
  """
645
652
  ...
646
653
 
647
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
654
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
648
655
 
649
656
  class __remote_gen_spec(typing_extensions.Protocol):
650
657
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -694,7 +701,7 @@ class Function(
694
701
  """
695
702
  ...
696
703
 
697
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
704
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
698
705
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
699
706
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
700
707
 
@@ -727,7 +734,7 @@ class Function(
727
734
  """
728
735
  ...
729
736
 
730
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
737
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
731
738
 
732
739
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
733
740
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -735,7 +742,7 @@ class Function(
735
742
 
736
743
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
737
744
 
738
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
745
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
739
746
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
740
747
  """Calls the function with the given arguments, without waiting for the results.
741
748
 
@@ -768,7 +775,7 @@ class Function(
768
775
  """
769
776
  ...
770
777
 
771
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
778
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
772
779
 
773
780
  @property
774
781
  def _raw_f_(self) -> collections.abc.Callable[..., typing.Any]: ...