modal 1.5.6.dev22__tar.gz → 1.5.6.dev23__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.dev22 → modal-1.5.6.dev23}/PKG-INFO +1 -1
  2. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_function_variants.py +157 -0
  3. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/function.py +211 -2
  4. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/utils.py +13 -1
  5. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/volume.py +1 -13
  6. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/client.pyi +2 -2
  7. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/functions.pyi +6 -6
  8. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal.egg-info/PKG-INFO +1 -1
  9. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_version/__init__.py +1 -1
  10. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/LICENSE +0 -0
  11. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/README.md +0 -0
  12. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/__init__.py +0 -0
  13. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/__main__.py +0 -0
  14. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_billing.py +0 -0
  15. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_clustered_functions.py +0 -0
  16. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_clustered_functions.pyi +0 -0
  17. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_container_entrypoint.py +0 -0
  18. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_environments.py +0 -0
  19. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_functions.py +0 -0
  20. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_grpc_client.py +0 -0
  21. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_image.py +0 -0
  22. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_ipython.py +0 -0
  23. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_load_context.py +0 -0
  24. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_location.py +0 -0
  25. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_logs.py +0 -0
  26. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_logs_manager.py +0 -0
  27. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_logs_manager.pyi +0 -0
  28. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_object.py +0 -0
  29. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_output/__init__.py +0 -0
  30. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_output/manager.py +0 -0
  31. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_output/pty.py +0 -0
  32. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_output/rich.py +0 -0
  33. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_output/status.py +0 -0
  34. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_partial_function.py +0 -0
  35. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_resolver.py +0 -0
  36. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_resources.py +0 -0
  37. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_runtime/__init__.py +0 -0
  38. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_runtime/asgi.py +0 -0
  39. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_runtime/container_io_manager.py +0 -0
  40. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_runtime/container_io_manager.pyi +0 -0
  41. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_runtime/execution_context.py +0 -0
  42. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_runtime/execution_context.pyi +0 -0
  43. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  44. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_runtime/task_lifecycle_manager.py +0 -0
  45. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  46. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_runtime/telemetry.py +0 -0
  47. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_runtime/user_code_event_loop.py +0 -0
  48. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_runtime/user_code_imports.py +0 -0
  49. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_serialization.py +0 -0
  50. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_server.py +0 -0
  51. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_supports_logs.py +0 -0
  52. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_traceback.py +0 -0
  53. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_tunnel.py +0 -0
  54. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_tunnel.pyi +0 -0
  55. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_type_manager.py +0 -0
  56. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/__init__.py +0 -0
  57. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/app_utils.py +0 -0
  58. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/async_utils.py +0 -0
  59. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/auth_token_manager.py +0 -0
  60. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/blob_utils.py +0 -0
  61. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/browser_utils.py +0 -0
  62. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/bytes_io_segment_payload.py +0 -0
  63. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/curl_utils.py +0 -0
  64. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/deprecation.py +0 -0
  65. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/display_utils.py +0 -0
  66. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/docker_utils.py +0 -0
  67. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/function_utils.py +0 -0
  68. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/git_utils.py +0 -0
  69. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/grpc_testing.py +0 -0
  70. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/grpc_utils.py +0 -0
  71. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/hash_utils.py +0 -0
  72. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/http_utils.py +0 -0
  73. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/idle_countdown.py +0 -0
  74. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/jwt_utils.py +0 -0
  75. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/logger.py +0 -0
  76. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/mount_utils.py +0 -0
  77. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/name_utils.py +0 -0
  78. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/package_utils.py +0 -0
  79. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/pattern_utils.py +0 -0
  80. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/proxy_support.py +0 -0
  81. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/rand_pb_testing.py +0 -0
  82. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/sandbox_fs_utils.py +0 -0
  83. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/shell_utils.py +0 -0
  84. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/task_command_router_client.py +0 -0
  85. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_utils/time_utils.py +0 -0
  86. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_vendor/__init__.py +0 -0
  87. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  88. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_vendor/cloudpickle.py +0 -0
  89. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_vendor/tblib.py +0 -0
  90. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_vendor/version.py +0 -0
  91. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_watcher.py +0 -0
  92. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/_workspace.py +0 -0
  93. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/app.py +0 -0
  94. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/app.pyi +0 -0
  95. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/billing.py +0 -0
  96. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/billing.pyi +0 -0
  97. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/builder/2023.12.312.txt +0 -0
  98. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/builder/2023.12.txt +0 -0
  99. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/builder/2024.04.txt +0 -0
  100. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/builder/2024.10.txt +0 -0
  101. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/builder/2025.06.txt +0 -0
  102. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/builder/PREVIEW.txt +0 -0
  103. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/builder/README.md +0 -0
  104. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/builder/base-images.json +0 -0
  105. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/call_graph.py +0 -0
  106. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/__init__.py +0 -0
  107. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/_download.py +0 -0
  108. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/_help.py +0 -0
  109. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/_logs.py +0 -0
  110. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/_stats.py +0 -0
  111. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/_traceback.py +0 -0
  112. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/app.py +0 -0
  113. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/billing.py +0 -0
  114. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/changelog.py +0 -0
  115. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/cluster.py +0 -0
  116. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/config.py +0 -0
  117. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/container.py +0 -0
  118. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/curl.py +0 -0
  119. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/dashboard.py +0 -0
  120. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/dict.py +0 -0
  121. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/endpoint.py +0 -0
  122. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/entry_point.py +0 -0
  123. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/environment.py +0 -0
  124. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/image.py +0 -0
  125. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/import_refs.py +0 -0
  126. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/logo.py +0 -0
  127. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/network_file_system.py +0 -0
  128. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/profile.py +0 -0
  129. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/queues.py +0 -0
  130. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/run.py +0 -0
  131. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/secret.py +0 -0
  132. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/selector.py +0 -0
  133. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/server.py +0 -0
  134. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/shell.py +0 -0
  135. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/skills.py +0 -0
  136. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/token.py +0 -0
  137. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cli/workspace.py +0 -0
  138. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/client.py +0 -0
  139. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cloud_bucket_mount.py +0 -0
  140. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cloud_bucket_mount.pyi +0 -0
  141. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cls.py +0 -0
  142. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/cls.pyi +0 -0
  143. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/config.py +0 -0
  144. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/container_process.py +0 -0
  145. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/container_process.pyi +0 -0
  146. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/dict.py +0 -0
  147. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/dict.pyi +0 -0
  148. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/environments.py +0 -0
  149. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/environments.pyi +0 -0
  150. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/exception.py +0 -0
  151. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/experimental/__init__.py +0 -0
  152. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/experimental/_flash_container_entry.py +0 -0
  153. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/experimental/flash.py +0 -0
  154. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/experimental/flash.pyi +0 -0
  155. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/experimental/flash_server.py +0 -0
  156. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/experimental/flash_server.pyi +0 -0
  157. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/experimental/ipython.py +0 -0
  158. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/file_pattern_matcher.py +0 -0
  159. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/functions.py +0 -0
  160. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/image.py +0 -0
  161. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/image.pyi +0 -0
  162. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/io_streams.py +0 -0
  163. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/io_streams.pyi +0 -0
  164. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/mount.py +0 -0
  165. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/mount.pyi +0 -0
  166. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/network_file_system.py +0 -0
  167. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/network_file_system.pyi +0 -0
  168. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/object.py +0 -0
  169. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/object.pyi +0 -0
  170. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/outbound_policy.py +0 -0
  171. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/outbound_policy.pyi +0 -0
  172. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/output.py +0 -0
  173. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/parallel_map.py +0 -0
  174. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/parallel_map.pyi +0 -0
  175. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/partial_function.py +0 -0
  176. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/partial_function.pyi +0 -0
  177. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/proxy.py +0 -0
  178. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/proxy.pyi +0 -0
  179. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/py.typed +0 -0
  180. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/queue.py +0 -0
  181. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/queue.pyi +0 -0
  182. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/retries.py +0 -0
  183. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/runner.py +0 -0
  184. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/runner.pyi +0 -0
  185. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/running_app.py +0 -0
  186. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/sandbox.py +0 -0
  187. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/sandbox.pyi +0 -0
  188. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/sandbox_fs.py +0 -0
  189. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/sandbox_fs.pyi +0 -0
  190. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/schedule.py +0 -0
  191. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/scheduler_placement.py +0 -0
  192. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/secret.py +0 -0
  193. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/secret.pyi +0 -0
  194. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/server.py +0 -0
  195. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/server.pyi +0 -0
  196. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/serving.py +0 -0
  197. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/serving.pyi +0 -0
  198. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/skills/modal/SKILL.md +0 -0
  199. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/snapshot.py +0 -0
  200. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/snapshot.pyi +0 -0
  201. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/stream_type.py +0 -0
  202. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/token_flow.py +0 -0
  203. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/token_flow.pyi +0 -0
  204. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/types.py +0 -0
  205. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/volume.py +0 -0
  206. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/volume.pyi +0 -0
  207. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/workspace.py +0 -0
  208. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal/workspace.pyi +0 -0
  209. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal.egg-info/SOURCES.txt +0 -0
  210. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal.egg-info/dependency_links.txt +0 -0
  211. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal.egg-info/entry_points.txt +0 -0
  212. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal.egg-info/requires.txt +0 -0
  213. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal.egg-info/top_level.txt +0 -0
  214. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_docs/__init__.py +0 -0
  215. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_docs/gen_cli_docs.py +0 -0
  216. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_docs/gen_cli_docs_main.py +0 -0
  217. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_docs/gen_reference_docs.py +0 -0
  218. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_docs/gen_reference_docs_main.py +0 -0
  219. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_docs/mdmd/__init__.py +0 -0
  220. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_docs/mdmd/mdmd.py +0 -0
  221. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_docs/mdmd/signatures.py +0 -0
  222. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_docs/mdmd/types.py +0 -0
  223. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_docs/reference_intro.md +0 -0
  224. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_proto/__init__.py +0 -0
  225. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_proto/api_grpc.py +0 -0
  226. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_proto/api_pb2.py +0 -0
  227. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_proto/api_pb2.pyi +0 -0
  228. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_proto/api_pb2_grpc.py +0 -0
  229. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_proto/api_pb2_grpc.pyi +0 -0
  230. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_proto/modal_api_grpc.py +0 -0
  231. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_proto/py.typed +0 -0
  232. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_proto/task_command_router_grpc.py +0 -0
  233. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_proto/task_command_router_pb2.py +0 -0
  234. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_proto/task_command_router_pb2.pyi +0 -0
  235. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  236. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  237. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/modal_version/__main__.py +0 -0
  238. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/pyproject.toml +0 -0
  239. {modal-1.5.6.dev22 → modal-1.5.6.dev23}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.6.dev22
3
+ Version: 1.5.6.dev23
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -4,12 +4,15 @@ from collections.abc import Collection, Sequence, Sized
4
4
  from pathlib import PurePosixPath
5
5
  from typing import TYPE_CHECKING, Any
6
6
 
7
+ import google.protobuf.message
8
+
7
9
  from modal.types import CloudBucketMountInfo, FunctionInfo, VolumeMountInfo
8
10
  from modal_proto import api_pb2
9
11
 
10
12
  from ._resources import convert_fn_config_to_resources_config
11
13
  from ._serialization import (
12
14
  apply_defaults,
15
+ deserialize_proto_params,
13
16
  serialize,
14
17
  serialize_proto_params,
15
18
  validate_parameter_values,
@@ -17,11 +20,14 @@ from ._serialization import (
17
20
  from ._utils.function_utils import _parse_retries
18
21
  from ._utils.mount_utils import validate_volumes, validate_volumes_by_object_id
19
22
  from .cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
23
+ from .exception import InvalidError
20
24
  from .retries import Retries
21
25
  from .secret import _Secret
22
26
  from .volume import _Volume, _volume_to_mount_proto
23
27
 
24
28
  if TYPE_CHECKING:
29
+ from modal.client import _Client
30
+
25
31
  from ._functions import _Function
26
32
  from ._load_context import LoadContext
27
33
  from ._object import _Object
@@ -190,6 +196,157 @@ class _FunctionOptions:
190
196
  )
191
197
 
192
198
 
199
+ @dataclasses.dataclass(frozen=True)
200
+ class _FunctionOptionsInfo:
201
+ """Configuration overrides applied to a Function variant.
202
+
203
+ Fields are named after the parameters of the builder methods that set them: `.with_options()`,
204
+ `.with_concurrency()` (`max_inputs`, `target_inputs`), and `.with_batching()` (`max_batch_size`,
205
+ `wait_ms`). Fields are None when the variant does not override that setting.
206
+ """
207
+
208
+ cpu: float | tuple[float, float] | None = None
209
+ memory: int | tuple[int, int] | None = None
210
+ gpu: str | None = None
211
+ secrets: list[str] | None = None
212
+ volumes: dict[str, VolumeMountInfo] | None = None # mount path -> volume and its mount options
213
+ cloud_bucket_mounts: dict[str, CloudBucketMountInfo] | None = None # mount path -> bucket mount
214
+ retries: Retries | None = None
215
+ max_containers: int | None = None
216
+ buffer_containers: int | None = None
217
+ scaledown_window: int | None = None
218
+ timeout: int | None = None
219
+ region: str | list[str] | None = None
220
+ cloud: str | None = None
221
+ routing_region: str | None = None
222
+ max_inputs: int | None = None
223
+ target_inputs: int | None = None
224
+ max_batch_size: int | None = None
225
+ wait_ms: int | None = None
226
+
227
+ @classmethod
228
+ def _from_proto(cls, proto: api_pb2.FunctionOptions) -> "_FunctionOptionsInfo":
229
+ cpu: float | tuple[float, float] | None = None
230
+ memory: int | tuple[int, int] | None = None
231
+ gpu: str | None = None
232
+ if proto.HasField("resources"):
233
+ resources = proto.resources
234
+ if resources.milli_cpu_max > 0:
235
+ cpu = (resources.milli_cpu / 1000, resources.milli_cpu_max / 1000)
236
+ elif resources.milli_cpu > 0:
237
+ cpu = resources.milli_cpu / 1000
238
+
239
+ if resources.memory_mb_max > 0:
240
+ memory = (resources.memory_mb, resources.memory_mb_max)
241
+ elif resources.memory_mb > 0:
242
+ memory = resources.memory_mb
243
+
244
+ if resources.gpu_config.count > 0:
245
+ gpu_config = resources.gpu_config
246
+ gpu = gpu_config.gpu_type if gpu_config.count == 1 else f"{gpu_config.gpu_type}:{gpu_config.count}"
247
+
248
+ retries: Retries | None = None
249
+ if proto.HasField("retry_policy"):
250
+ retry_policy = proto.retry_policy
251
+ retries = Retries(
252
+ max_retries=retry_policy.retries,
253
+ backoff_coefficient=retry_policy.backoff_coefficient or 2.0,
254
+ initial_delay=retry_policy.initial_delay_ms / 1000,
255
+ max_delay=retry_policy.max_delay_ms / 1000 or 60.0,
256
+ )
257
+
258
+ region: str | list[str] | None = None
259
+ if proto.HasField("scheduler_placement") and proto.scheduler_placement.regions:
260
+ region = list(proto.scheduler_placement.regions)
261
+
262
+ return cls(
263
+ cpu=cpu,
264
+ memory=memory,
265
+ gpu=gpu,
266
+ secrets=list(proto.secret_ids) or None,
267
+ volumes={
268
+ vm.mount_path: VolumeMountInfo(
269
+ name=None, # The mount records which Volume, not what it was called.
270
+ volume_id=vm.volume_id,
271
+ read_only=vm.read_only,
272
+ sub_path=vm.sub_path if vm.HasField("sub_path") else None,
273
+ )
274
+ for vm in proto.volume_mounts
275
+ }
276
+ or None,
277
+ cloud_bucket_mounts={
278
+ cbm.mount_path: CloudBucketMountInfo._from_proto(cbm) for cbm in proto.cloud_bucket_mounts
279
+ }
280
+ or None,
281
+ retries=retries,
282
+ max_containers=proto.concurrency_limit if proto.HasField("concurrency_limit") else None,
283
+ buffer_containers=proto.buffer_containers if proto.HasField("buffer_containers") else None,
284
+ scaledown_window=proto.task_idle_timeout_secs if proto.HasField("task_idle_timeout_secs") else None,
285
+ timeout=proto.timeout_secs if proto.HasField("timeout_secs") else None,
286
+ region=region,
287
+ cloud=proto.cloud_provider_str if proto.HasField("cloud_provider_str") else None,
288
+ routing_region=proto.routing_region if proto.HasField("routing_region") else None,
289
+ max_inputs=proto.max_concurrent_inputs if proto.HasField("max_concurrent_inputs") else None,
290
+ target_inputs=proto.target_concurrent_inputs if proto.HasField("target_concurrent_inputs") else None,
291
+ max_batch_size=proto.batch_max_size if proto.HasField("batch_max_size") else None,
292
+ wait_ms=proto.batch_linger_ms if proto.HasField("batch_linger_ms") else None,
293
+ )
294
+
295
+
296
+ @dataclasses.dataclass(frozen=True)
297
+ class _FunctionVariantInfo:
298
+ """Information about a variant of a Function, created by parametrization or `.with_options()`."""
299
+
300
+ function_id: str
301
+ parameters: dict[str, str | int | bytes | bool] | None # None when the parameters can't be decoded
302
+ options: _FunctionOptionsInfo | None
303
+
304
+ @classmethod
305
+ def _from_proto(cls, proto: api_pb2.FunctionVariantInfo) -> "_FunctionVariantInfo":
306
+ parameters: dict[str, str | int | bytes | bool] | None
307
+ try:
308
+ parameters = deserialize_proto_params(proto.serialized_params)
309
+ except (google.protobuf.message.DecodeError, InvalidError):
310
+ parameters = None
311
+
312
+ return cls(
313
+ function_id=proto.function_id,
314
+ parameters=parameters,
315
+ options=_FunctionOptionsInfo._from_proto(proto.function_options)
316
+ if proto.HasField("function_options")
317
+ else None,
318
+ )
319
+
320
+
321
+ @dataclasses.dataclass(frozen=True)
322
+ class _FunctionVariantListing:
323
+ """The variants of a Function, and how the server chose to order them."""
324
+
325
+ variants: list[_FunctionVariantInfo]
326
+ # True when the variants are ordered by how many tasks each is running, busiest first.
327
+ ordered_by_task_count: bool
328
+
329
+
330
+ async def _list_function_variants(
331
+ client: "_Client", function_id: str, *, limit: int | None = None
332
+ ) -> _FunctionVariantListing:
333
+ variants: list[_FunctionVariantInfo] = []
334
+ ordered_by_task_count = False
335
+ cursor = None
336
+ while True:
337
+ request = api_pb2.FunctionListVariantsRequest(function_id=function_id, cursor=cursor, limit=limit or 0)
338
+ response = await client.stub.FunctionListVariants(request)
339
+ # An ordered listing arrives as a single response, so there is nothing to reconcile across
340
+ # pages: every response of a multi-page listing reports itself unordered.
341
+ ordered_by_task_count = response.ordered_by_task_count
342
+ variants.extend(_FunctionVariantInfo._from_proto(info) for info in response.infos)
343
+ if limit is not None and len(variants) >= limit:
344
+ return _FunctionVariantListing(variants[:limit], ordered_by_task_count)
345
+ if not response.HasField("next_cursor"):
346
+ return _FunctionVariantListing(variants, ordered_by_task_count)
347
+ cursor = response.next_cursor
348
+
349
+
193
350
  async def _function_bind_params_cached(
194
351
  base_function: "_Function",
195
352
  req: api_pb2.FunctionBindParamsRequest,
@@ -1,18 +1,23 @@
1
1
  # Copyright Modal Labs 2026
2
+ import dataclasses
2
3
  import json as json_lib
3
4
  from datetime import datetime, timedelta, timezone
4
- from typing import cast
5
+ from typing import Any, cast
5
6
 
6
7
  import click
7
8
  from click import UsageError
9
+ from rich.table import Column
8
10
  from rich.text import Text
9
11
 
10
12
  from modal._environments import ensure_env
13
+ from modal._function_variants import _FunctionOptionsInfo, _list_function_variants
11
14
  from modal._object import _get_environment_name
12
15
  from modal._utils.async_utils import synchronizer
13
16
  from modal._utils.time_utils import parse_duration
14
17
  from modal.client import _Client
15
18
  from modal.output import OutputManager
19
+ from modal.retries import Retries
20
+ from modal.types import CloudBucketMountInfo, VolumeMountInfo
16
21
  from modal_proto import api_pb2
17
22
 
18
23
  from ._help import ModalGroup
@@ -32,7 +37,7 @@ from ._stats import (
32
37
  stats_style,
33
38
  success_style,
34
39
  )
35
- from .utils import _resolve_function_id, env_option
40
+ from .utils import _resolve_function_id, display_table, env_option, humanize_filesize
36
41
 
37
42
  function_cli = ModalGroup(name="function", help="Inspect Modal Functions.")
38
43
 
@@ -406,6 +411,7 @@ async def logs(
406
411
  function_id, metadata = await _resolve_function_id(
407
412
  client, function_ref, env, object_type="Function", command="logs"
408
413
  )
414
+ app_id = metadata.app_id
409
415
 
410
416
  prefix_fields: list[str] = []
411
417
  if show_function_id:
@@ -430,3 +436,206 @@ async def logs(
430
436
  timestamps=timestamps,
431
437
  prefix_fields=prefix_fields,
432
438
  )
439
+
440
+
441
+ def _overridden_options(options: _FunctionOptionsInfo | None) -> list[tuple[str, Any]]:
442
+ """Return the settings a variant overrides, keyed by the builder-method parameter name."""
443
+ if options is None:
444
+ return []
445
+ fields = ((field.name, getattr(options, field.name)) for field in dataclasses.fields(options))
446
+ return [(name, value) for name, value in fields if value is not None]
447
+
448
+
449
+ def _variant_overrides_json(options: _FunctionOptionsInfo | None) -> dict[str, Any]:
450
+ """Return the overridden settings in a form that can be serialized as JSON."""
451
+ overrides: dict[str, Any] = {}
452
+ for name, value in _overridden_options(options):
453
+ if isinstance(value, Retries):
454
+ value = {
455
+ "max_retries": value.max_retries,
456
+ "backoff_coefficient": value.backoff_coefficient,
457
+ "initial_delay": value.initial_delay.total_seconds(),
458
+ "max_delay": value.max_delay.total_seconds(),
459
+ }
460
+ elif isinstance(value, tuple):
461
+ value = list(value)
462
+ elif name in ("cloud_bucket_mounts", "volumes"):
463
+ value = {path: dataclasses.asdict(mount) for path, mount in value.items()}
464
+ overrides[name] = value
465
+ return overrides
466
+
467
+
468
+ def _format_bucket_mount(mount: CloudBucketMountInfo) -> str:
469
+ """Render a cloud bucket mount as a URI, which is the part that identifies it at a glance."""
470
+ uri = f"{mount.bucket_type}://{mount.bucket_name}"
471
+ if mount.key_prefix:
472
+ uri = f"{uri}/{mount.key_prefix}"
473
+ return f"{uri} (ro)" if mount.read_only else uri
474
+
475
+
476
+ def _format_volume_mount(mount: VolumeMountInfo) -> str:
477
+ """Render a volume mount as its ID plus whatever `.with_mount_options()` changed about it."""
478
+ volume = mount.volume_id or mount.name or ""
479
+ if mount.sub_path:
480
+ volume = f"{volume}:{mount.sub_path}"
481
+ return f"{volume} (ro)" if mount.read_only else volume
482
+
483
+
484
+ def _format_override(value: Any) -> str:
485
+ if isinstance(value, dict):
486
+ return "{" + ", ".join(f"{k}: {_format_override(v)}" for k, v in value.items()) + "}"
487
+ if isinstance(value, (list, tuple)):
488
+ return "[" + ", ".join(_format_override(v) for v in value) + "]"
489
+ return str(value)
490
+
491
+
492
+ def _variant_cell(text: str, style: str = "") -> Text:
493
+ """Render one table cell that is truncated rather than wrapped, so one variant stays in one row."""
494
+ return Text(text, style=style, no_wrap=True, overflow="ellipsis")
495
+
496
+
497
+ def _format_parameter(value: Any) -> str:
498
+ if isinstance(value, bytes):
499
+ # A bytes parameter is an opaque payload; its size is the only part that reads well in a table.
500
+ return f"<bytes: {humanize_filesize(len(value))}>"
501
+ return repr(value)
502
+
503
+
504
+ def _parameters_cell(parameters: dict[str, Any] | None) -> Text:
505
+ if parameters is None:
506
+ return _variant_cell("unavailable", style="dim")
507
+ return _variant_cell(", ".join(f"{name}={_format_parameter(value)}" for name, value in parameters.items()))
508
+
509
+
510
+ def _options_cell(options: _FunctionOptionsInfo | None) -> Text:
511
+ parts = []
512
+ for name, value in _overridden_options(options):
513
+ if isinstance(value, Retries):
514
+ # The retry count is the useful part of a retry policy at a glance.
515
+ value = value.max_retries
516
+ elif name == "cloud_bucket_mounts":
517
+ value = {path: _format_bucket_mount(mount) for path, mount in value.items()}
518
+ elif name == "volumes":
519
+ value = {path: _format_volume_mount(mount) for path, mount in value.items()}
520
+ parts.append(f"{name}={_format_override(value)}")
521
+ return _variant_cell(", ".join(parts))
522
+
523
+
524
+ @function_cli.command("variants", no_args_is_help=True)
525
+ @click.argument("function_ref", metavar="FUNCTION")
526
+ @click.option(
527
+ "-n",
528
+ "--limit",
529
+ "limit",
530
+ default=200,
531
+ show_default=True,
532
+ type=int,
533
+ help="Show at most N variants, those running the most containers first. Use 0 to list every variant, newest first.",
534
+ )
535
+ @click.option("--json", "json", is_flag=True, default=False, help="Output as JSON.")
536
+ @env_option
537
+ @synchronizer.create_blocking
538
+ async def variants(
539
+ function_ref: str,
540
+ limit: int = 200,
541
+ json: bool = False,
542
+ env: str | None = None,
543
+ ):
544
+ """List the variants of a modal Function.
545
+
546
+ Variants are the parameterized instances of a `modal.Cls` and the Functions created with
547
+ `.with_options()`. FUNCTION may be a Function ID or a deployed Function name in the form
548
+ ``APP_NAME/FUNCTION_NAME``.
549
+
550
+ By default, the busiest variants are listed first. If you ask for more variants than can be
551
+ ranked, or for all of them, they are listed newest first instead.
552
+
553
+ Examples:
554
+
555
+ List the busiest variants of a deployed Function:
556
+
557
+ ```
558
+ modal function variants my-app/my-function
559
+ ```
560
+
561
+ List the parameterized instances of a deployed `modal.Cls`:
562
+
563
+ ```
564
+ modal function variants 'my-app/MyClass.*'
565
+ ```
566
+
567
+ Show only the ten busiest variants:
568
+
569
+ ```
570
+ modal function variants my-app/my-function --limit 10
571
+ ```
572
+
573
+ List every variant of a Function ID as JSON:
574
+
575
+ ```
576
+ modal function variants fu-abc123 --limit 0 --json
577
+ ```
578
+ """
579
+ if limit < 0:
580
+ raise UsageError("--limit cannot be negative.")
581
+
582
+ environment_name = _get_environment_name(ensure_env(env))
583
+ client = await _Client.from_env()
584
+ function_id, handle_metadata = await _resolve_function_id(
585
+ client,
586
+ function_ref,
587
+ environment_name,
588
+ command="variants",
589
+ )
590
+ # A variant has no variants of its own, so resolving one lists the family it belongs to.
591
+ base_function_id = handle_metadata.base_function_id or function_id
592
+ effective_limit = limit or None
593
+ listing = await _list_function_variants(client, base_function_id, limit=effective_limit)
594
+ function_variants = listing.variants
595
+ show_parameters = bool(handle_metadata.method_handle_metadata)
596
+
597
+ output = OutputManager.get()
598
+ if json:
599
+ payload: list[dict[str, Any]] = []
600
+ for variant in function_variants:
601
+ entry: dict[str, Any] = {"function_id": variant.function_id}
602
+ if show_parameters:
603
+ entry["parameters"] = variant.parameters
604
+ entry["options"] = _variant_overrides_json(variant.options)
605
+ payload.append(entry)
606
+ # Parameter values may include bytes, which have no JSON representation of their own.
607
+ output.print_json(json_lib.dumps(payload, default=repr))
608
+ return
609
+
610
+ if not function_variants:
611
+ output.print(f"No variants found for {base_function_id}.")
612
+ return
613
+
614
+ rows: list[list[Text | str | None]] = []
615
+ for variant in function_variants:
616
+ options_cell = _options_cell(variant.options)
617
+ if show_parameters:
618
+ rows.append(
619
+ [
620
+ variant.function_id,
621
+ _parameters_cell(variant.parameters),
622
+ options_cell,
623
+ ]
624
+ )
625
+ else:
626
+ rows.append([variant.function_id, options_cell])
627
+
628
+ id_column = Column("Function ID", no_wrap=True)
629
+ columns = (
630
+ [id_column, Column("Parameters"), Column("Options")] if show_parameters else [id_column, Column("Options")]
631
+ )
632
+ ordering = f"{len(rows)} busiest" if listing.ordered_by_task_count else f"{len(rows)} newest"
633
+ display_table(columns, rows, title=f"Variants of {base_function_id} · {ordering}")
634
+
635
+ if effective_limit is not None and len(function_variants) == effective_limit:
636
+ output.print(
637
+ Text(
638
+ f"Showing {effective_limit} variants; use --limit 0 to list every variant.",
639
+ style="dim",
640
+ )
641
+ )
@@ -132,6 +132,18 @@ async def _fetch_app_logs(
132
132
  await _drain_batches(output_mgr, batches, prefix_fields or [], filters.search_text)
133
133
 
134
134
 
135
+ def humanize_filesize(value: int) -> str:
136
+ if value < 0:
137
+ raise ValueError("value should be >= 0")
138
+ base = 1024
139
+ size = float(value)
140
+ for unit in ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"):
141
+ if size < base:
142
+ return f"{size:.0f} {unit}" if unit == "B" else f"{size:.1f} {unit}"
143
+ size /= base
144
+ return f"{size:.1f} ZiB"
145
+
146
+
135
147
  def _plain(text: "Text | str | bool | None") -> "str | bool | None":
136
148
  return text.plain if isinstance(text, Text) else text
137
149
 
@@ -216,7 +228,7 @@ async def _resolve_function_id(
216
228
  environment_name: str,
217
229
  *,
218
230
  object_type: Literal["Function", "Server"] = "Function",
219
- command: Literal["logs", "stats"],
231
+ command: Literal["logs", "stats", "variants"],
220
232
  ) -> tuple[str, api_pb2.FunctionHandleMetadata]:
221
233
  identifier_label = object_type.upper()
222
234
  usage = (
@@ -13,7 +13,7 @@ from modal._utils.async_utils import synchronizer
13
13
  from modal._utils.browser_utils import open_url_and_display
14
14
  from modal._utils.time_utils import timestamp_to_localized_str
15
15
  from modal.cli._download import _volume_download
16
- from modal.cli.utils import display_table, env_option, yes_option
16
+ from modal.cli.utils import display_table, env_option, humanize_filesize, yes_option
17
17
  from modal.output import OutputManager
18
18
  from modal.volume import _AbstractVolumeUploadContextManager, _Volume
19
19
  from modal_proto import api_pb2
@@ -30,18 +30,6 @@ volume_cli = ModalGroup(
30
30
  )
31
31
 
32
32
 
33
- def humanize_filesize(value: int) -> str:
34
- if value < 0:
35
- raise ValueError("value should be >= 0")
36
- base = 1024
37
- size = float(value)
38
- for unit in ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"):
39
- if size < base:
40
- return f"{size:.0f} {unit}" if unit == "B" else f"{size:.1f} {unit}"
41
- size /= base
42
- return f"{size:.1f} ZiB"
43
-
44
-
45
33
  @volume_cli.command("create", help="Create a named, persistent modal.Volume.", panel="Management", no_args_is_help=True)
46
34
  @click.argument("name")
47
35
  @env_option
@@ -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.dev22",
80
+ version: str = "1.5.6.dev23",
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.dev22",
280
+ version: str = "1.5.6.dev23",
281
281
  *,
282
282
  oauth_credentials: typing.Optional[_OAuthCredentials] = None,
283
283
  ):
@@ -653,7 +653,7 @@ class Function(
653
653
 
654
654
  _call_generator: ___call_generator_spec
655
655
 
656
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
656
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
657
657
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
658
658
  """Calls the function remotely, executing it with the given arguments and returning the execution's result.
659
659
 
@@ -678,7 +678,7 @@ class Function(
678
678
  """
679
679
  ...
680
680
 
681
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
681
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
682
682
 
683
683
  class __remote_gen_spec(typing_extensions.Protocol):
684
684
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -728,7 +728,7 @@ class Function(
728
728
  """
729
729
  ...
730
730
 
731
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
731
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
732
732
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
733
733
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
734
734
 
@@ -761,7 +761,7 @@ class Function(
761
761
  """
762
762
  ...
763
763
 
764
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
764
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
765
765
 
766
766
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
767
767
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -769,7 +769,7 @@ class Function(
769
769
 
770
770
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
771
771
 
772
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
772
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
773
773
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
774
774
  """Calls the function with the given arguments, without waiting for the results.
775
775
 
@@ -802,7 +802,7 @@ class Function(
802
802
  """
803
803
  ...
804
804
 
805
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
805
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
806
806
 
807
807
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
808
808
  """Return the inner Python object wrapped by this Modal Function.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.6.dev22
3
+ Version: 1.5.6.dev23
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,4 @@
1
1
  # Copyright Modal Labs 2026
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.5.6.dev22"
4
+ __version__ = "1.5.6.dev23"
File without changes
File without changes
File without changes
File without changes