modal 1.5.6.dev13__tar.gz → 1.5.6.dev14__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 (241) hide show
  1. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/PKG-INFO +1 -1
  2. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_functions.py +49 -5
  3. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/_logs.py +0 -7
  4. modal-1.5.6.dev14/modal/cli/_stats.py +141 -0
  5. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/function.py +24 -137
  6. modal-1.5.6.dev14/modal/cli/server.py +519 -0
  7. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/utils.py +85 -10
  8. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/client.pyi +2 -2
  9. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/config.py +4 -0
  10. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/functions.pyi +64 -12
  11. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/sandbox_fs.py +62 -54
  12. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/sandbox_fs.pyi +182 -158
  13. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/types.py +61 -2
  14. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/volume.py +101 -48
  15. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/volume.pyi +16 -2
  16. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal.egg-info/PKG-INFO +1 -1
  17. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/api_grpc.py +16 -0
  18. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/api_pb2.py +1523 -1396
  19. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/api_pb2.pyi +284 -20
  20. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/api_pb2_grpc.py +34 -0
  21. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/api_pb2_grpc.pyi +12 -0
  22. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/modal_api_grpc.py +1 -0
  23. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_version/__init__.py +1 -1
  24. modal-1.5.6.dev13/modal/cli/_stats.py +0 -50
  25. modal-1.5.6.dev13/modal/cli/server.py +0 -136
  26. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/LICENSE +0 -0
  27. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/README.md +0 -0
  28. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/__init__.py +0 -0
  29. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/__main__.py +0 -0
  30. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_billing.py +0 -0
  31. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_clustered_functions.py +0 -0
  32. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_clustered_functions.pyi +0 -0
  33. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_container_entrypoint.py +0 -0
  34. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_environments.py +0 -0
  35. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_function_variants.py +0 -0
  36. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_grpc_client.py +0 -0
  37. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_image.py +0 -0
  38. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_ipython.py +0 -0
  39. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_load_context.py +0 -0
  40. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_location.py +0 -0
  41. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_logs.py +0 -0
  42. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_logs_manager.py +0 -0
  43. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_logs_manager.pyi +0 -0
  44. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_object.py +0 -0
  45. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_output/__init__.py +0 -0
  46. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_output/manager.py +0 -0
  47. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_output/pty.py +0 -0
  48. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_output/rich.py +0 -0
  49. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_output/status.py +0 -0
  50. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_partial_function.py +0 -0
  51. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_resolver.py +0 -0
  52. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_resources.py +0 -0
  53. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/__init__.py +0 -0
  54. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/asgi.py +0 -0
  55. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/container_io_manager.py +0 -0
  56. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/container_io_manager.pyi +0 -0
  57. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/execution_context.py +0 -0
  58. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/execution_context.pyi +0 -0
  59. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  60. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/task_lifecycle_manager.py +0 -0
  61. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  62. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/telemetry.py +0 -0
  63. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/user_code_event_loop.py +0 -0
  64. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_runtime/user_code_imports.py +0 -0
  65. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_serialization.py +0 -0
  66. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_server.py +0 -0
  67. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_supports_logs.py +0 -0
  68. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_traceback.py +0 -0
  69. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_tunnel.py +0 -0
  70. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_tunnel.pyi +0 -0
  71. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_type_manager.py +0 -0
  72. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/__init__.py +0 -0
  73. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/app_utils.py +0 -0
  74. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/async_utils.py +0 -0
  75. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/auth_token_manager.py +0 -0
  76. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/blob_utils.py +0 -0
  77. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/browser_utils.py +0 -0
  78. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/bytes_io_segment_payload.py +0 -0
  79. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/curl_utils.py +0 -0
  80. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/deprecation.py +0 -0
  81. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/display_utils.py +0 -0
  82. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/docker_utils.py +0 -0
  83. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/function_utils.py +0 -0
  84. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/git_utils.py +0 -0
  85. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/grpc_testing.py +0 -0
  86. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/grpc_utils.py +0 -0
  87. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/hash_utils.py +0 -0
  88. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/http_utils.py +0 -0
  89. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/idle_countdown.py +0 -0
  90. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/jwt_utils.py +0 -0
  91. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/logger.py +0 -0
  92. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/mount_utils.py +0 -0
  93. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/name_utils.py +0 -0
  94. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/package_utils.py +0 -0
  95. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/pattern_utils.py +0 -0
  96. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/proxy_support.py +0 -0
  97. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/rand_pb_testing.py +0 -0
  98. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/sandbox_fs_utils.py +0 -0
  99. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/shell_utils.py +0 -0
  100. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/task_command_router_client.py +0 -0
  101. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_utils/time_utils.py +0 -0
  102. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_vendor/__init__.py +0 -0
  103. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  104. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_vendor/cloudpickle.py +0 -0
  105. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_vendor/tblib.py +0 -0
  106. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_vendor/version.py +0 -0
  107. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_watcher.py +0 -0
  108. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/_workspace.py +0 -0
  109. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/app.py +0 -0
  110. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/app.pyi +0 -0
  111. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/billing.py +0 -0
  112. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/billing.pyi +0 -0
  113. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/2023.12.312.txt +0 -0
  114. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/2023.12.txt +0 -0
  115. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/2024.04.txt +0 -0
  116. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/2024.10.txt +0 -0
  117. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/2025.06.txt +0 -0
  118. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/PREVIEW.txt +0 -0
  119. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/README.md +0 -0
  120. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/builder/base-images.json +0 -0
  121. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/call_graph.py +0 -0
  122. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/__init__.py +0 -0
  123. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/_download.py +0 -0
  124. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/_help.py +0 -0
  125. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/_traceback.py +0 -0
  126. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/app.py +0 -0
  127. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/billing.py +0 -0
  128. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/bootstrap.py +0 -0
  129. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/changelog.py +0 -0
  130. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/cluster.py +0 -0
  131. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/config.py +0 -0
  132. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/container.py +0 -0
  133. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/curl.py +0 -0
  134. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/dashboard.py +0 -0
  135. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/dict.py +0 -0
  136. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/endpoint.py +0 -0
  137. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/entry_point.py +0 -0
  138. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/environment.py +0 -0
  139. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/image.py +0 -0
  140. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/import_refs.py +0 -0
  141. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/launch.py +0 -0
  142. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/logo.py +0 -0
  143. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/network_file_system.py +0 -0
  144. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/profile.py +0 -0
  145. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/programs/__init__.py +0 -0
  146. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/programs/run_jupyter.py +0 -0
  147. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/programs/vscode.py +0 -0
  148. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/queues.py +0 -0
  149. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/run.py +0 -0
  150. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/secret.py +0 -0
  151. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/selector.py +0 -0
  152. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/shell.py +0 -0
  153. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/skills.py +0 -0
  154. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/token.py +0 -0
  155. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/volume.py +0 -0
  156. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cli/workspace.py +0 -0
  157. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/client.py +0 -0
  158. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cloud_bucket_mount.py +0 -0
  159. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cloud_bucket_mount.pyi +0 -0
  160. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cls.py +0 -0
  161. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/cls.pyi +0 -0
  162. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/container_process.py +0 -0
  163. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/container_process.pyi +0 -0
  164. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/dict.py +0 -0
  165. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/dict.pyi +0 -0
  166. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/environments.py +0 -0
  167. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/environments.pyi +0 -0
  168. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/exception.py +0 -0
  169. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/experimental/__init__.py +0 -0
  170. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/experimental/flash.py +0 -0
  171. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/experimental/flash.pyi +0 -0
  172. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/experimental/ipython.py +0 -0
  173. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/file_pattern_matcher.py +0 -0
  174. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/functions.py +0 -0
  175. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/image.py +0 -0
  176. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/image.pyi +0 -0
  177. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/io_streams.py +0 -0
  178. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/io_streams.pyi +0 -0
  179. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/mount.py +0 -0
  180. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/mount.pyi +0 -0
  181. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/network_file_system.py +0 -0
  182. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/network_file_system.pyi +0 -0
  183. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/object.py +0 -0
  184. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/object.pyi +0 -0
  185. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/output.py +0 -0
  186. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/parallel_map.py +0 -0
  187. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/parallel_map.pyi +0 -0
  188. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/partial_function.py +0 -0
  189. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/partial_function.pyi +0 -0
  190. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/proxy.py +0 -0
  191. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/proxy.pyi +0 -0
  192. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/py.typed +0 -0
  193. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/queue.py +0 -0
  194. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/queue.pyi +0 -0
  195. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/retries.py +0 -0
  196. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/runner.py +0 -0
  197. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/runner.pyi +0 -0
  198. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/running_app.py +0 -0
  199. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/sandbox.py +0 -0
  200. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/sandbox.pyi +0 -0
  201. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/schedule.py +0 -0
  202. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/scheduler_placement.py +0 -0
  203. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/secret.py +0 -0
  204. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/secret.pyi +0 -0
  205. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/server.py +0 -0
  206. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/server.pyi +0 -0
  207. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/serving.py +0 -0
  208. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/serving.pyi +0 -0
  209. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/skills/modal/SKILL.md +0 -0
  210. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/snapshot.py +0 -0
  211. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/snapshot.pyi +0 -0
  212. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/stream_type.py +0 -0
  213. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/token_flow.py +0 -0
  214. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/token_flow.pyi +0 -0
  215. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/workspace.py +0 -0
  216. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal/workspace.pyi +0 -0
  217. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal.egg-info/SOURCES.txt +0 -0
  218. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal.egg-info/dependency_links.txt +0 -0
  219. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal.egg-info/entry_points.txt +0 -0
  220. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal.egg-info/requires.txt +0 -0
  221. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal.egg-info/top_level.txt +0 -0
  222. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/__init__.py +0 -0
  223. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/gen_cli_docs.py +0 -0
  224. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/gen_cli_docs_main.py +0 -0
  225. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/gen_reference_docs.py +0 -0
  226. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/gen_reference_docs_main.py +0 -0
  227. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/mdmd/__init__.py +0 -0
  228. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/mdmd/mdmd.py +0 -0
  229. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/mdmd/signatures.py +0 -0
  230. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/mdmd/types.py +0 -0
  231. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_docs/reference_intro.md +0 -0
  232. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/__init__.py +0 -0
  233. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/py.typed +0 -0
  234. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/task_command_router_grpc.py +0 -0
  235. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/task_command_router_pb2.py +0 -0
  236. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/task_command_router_pb2.pyi +0 -0
  237. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  238. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  239. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/modal_version/__main__.py +0 -0
  240. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/pyproject.toml +0 -0
  241. {modal-1.5.6.dev13 → modal-1.5.6.dev14}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.6.dev13
3
+ Version: 1.5.6.dev14
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -8,6 +8,7 @@ import warnings
8
8
  from collections import OrderedDict
9
9
  from collections.abc import AsyncGenerator, AsyncIterator, Callable, Collection, Sequence
10
10
  from dataclasses import dataclass
11
+ from datetime import datetime, timedelta, timezone
11
12
  from pathlib import PurePosixPath
12
13
  from typing import TYPE_CHECKING, Any
13
14
 
@@ -99,7 +100,12 @@ from .proxy import _Proxy
99
100
  from .retries import Retries, RetryManager
100
101
  from .schedule import Schedule
101
102
  from .secret import _Secret
102
- from .types import FunctionAutoscalerSettings, FunctionStats, ServerAutoscalerSettings
103
+ from .types import (
104
+ FunctionAutoscalerSettings,
105
+ FunctionCurrentStats,
106
+ FunctionStats,
107
+ ServerAutoscalerSettings,
108
+ )
103
109
  from .volume import _Volume, _volume_to_mount_proto
104
110
 
105
111
  if TYPE_CHECKING:
@@ -2148,23 +2154,61 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
2148
2154
  return self._raw_f
2149
2155
 
2150
2156
  @live_method
2151
- async def get_current_stats(self) -> FunctionStats:
2152
- """Return a `FunctionStats` object describing the current function's queue and runner counts.
2157
+ async def get_current_stats(self) -> FunctionCurrentStats:
2158
+ """Return a snapshot of the Function's current input and container state.
2153
2159
 
2154
2160
  Returns:
2155
- Snapshot counts for backlog, runners, and running inputs.
2161
+ A `FunctionCurrentStats` object containing live input and container counts.
2156
2162
  """
2157
2163
  resp = await self.client.stub.FunctionGetCurrentStats(
2158
2164
  api_pb2.FunctionGetCurrentStatsRequest(function_id=self.object_id),
2159
2165
  retry=Retry(total_timeout=10.0),
2160
2166
  )
2161
- return FunctionStats(
2167
+ return FunctionCurrentStats(
2162
2168
  backlog=resp.backlog,
2163
2169
  num_total_runners=resp.num_total_tasks,
2164
2170
  num_running_inputs=resp.num_running_inputs,
2165
2171
  input_headroom=resp.input_headroom,
2166
2172
  )
2167
2173
 
2174
+ @live_method
2175
+ async def stats(
2176
+ self, *, since: datetime | None = None, until: datetime | None = None, container: str | None = None
2177
+ ) -> FunctionStats:
2178
+ """Return statistics for a modal Function.
2179
+
2180
+ The default time range is the most recent hour. The maximum time range is 7 days.
2181
+
2182
+ Args:
2183
+ since: The beginning of the time range, inclusive. If omitted, this defaults to an hour before `until`.
2184
+ Values without a timezone are interpeted as local time.
2185
+ until: The end of the time range, exclusive. If omitted, this defaults to current time.
2186
+ Values without a timezone are interpeted as local time.
2187
+ container: If passed in, the stats are computed for only this container. Default None.
2188
+
2189
+ Returns:
2190
+ A `FunctionStats` object
2191
+ """
2192
+ until = until or datetime.now(timezone.utc)
2193
+ if until.tzinfo is None:
2194
+ until = until.astimezone()
2195
+ until = until.astimezone(timezone.utc)
2196
+
2197
+ since = since or until - timedelta(hours=1)
2198
+ if since.tzinfo is None:
2199
+ since = since.astimezone()
2200
+ since = since.astimezone(timezone.utc)
2201
+ if since >= until:
2202
+ raise InvalidError("`since` must be before `until`.")
2203
+
2204
+ historical_request = api_pb2.FunctionGetTimeRangeStatsRequest(function_id=self.object_id)
2205
+ historical_request.since.FromDatetime(since)
2206
+ historical_request.until.FromDatetime(until)
2207
+ if container:
2208
+ historical_request.container_id = container
2209
+ stats = await self.client.stub.FunctionGetTimeRangeStats(historical_request)
2210
+ return FunctionStats._from_proto(stats)
2211
+
2168
2212
  @live_method
2169
2213
  async def _get_schema(self) -> api_pb2.FunctionSchema:
2170
2214
  """Returns recorded schema for function, internal use only for now"""
@@ -5,7 +5,6 @@ from click import UsageError
5
5
 
6
6
  from modal._logs import _FETCH_LIMIT, _MAX_FETCH_RANGE, LogsFilters
7
7
  from modal._utils.time_utils import locale_tz, parse_duration
8
- from modal.client import _Client
9
8
  from modal_proto import api_pb2
10
9
 
11
10
  from .utils import _fetch_app_logs, _stream_app_logs, _tail_app_logs
@@ -19,12 +18,6 @@ _SOURCE_OPTIONS = {
19
18
  }
20
19
 
21
20
 
22
- async def _get_app_id_for_function_id(function_id: str) -> str:
23
- client = await _Client.from_env()
24
- response = await client.stub.FunctionGetById(api_pb2.FunctionGetByIdRequest(function_id=function_id))
25
- return response.handle_metadata.app_id
26
-
27
-
28
21
  def _parse_time_arg(value: str | None, default: datetime) -> datetime:
29
22
  """Parse a time argument that can be a relative duration (e.g. '2h', '30m') or ISO 8601 datetime.
30
23
 
@@ -0,0 +1,141 @@
1
+ # Copyright Modal Labs 2026
2
+ """Shared helpers for stats CLI commands."""
3
+
4
+ from datetime import datetime, timedelta, timezone
5
+
6
+ from google.protobuf.timestamp_pb2 import Timestamp
7
+ from rich.style import Style
8
+ from rich.table import Table
9
+ from rich.text import Text
10
+
11
+ from modal_proto import api_pb2
12
+
13
+ STATS_HEADING_STYLE = "white"
14
+ STATS_METADATA_STYLE = "bright_black"
15
+ STATS_SECTION_STYLE = "bold white"
16
+ STATS_SUCCESS_STYLE = "green"
17
+ STATS_PROGRESS_STYLE = "cyan"
18
+ STATS_WARNING_STYLE = "yellow"
19
+ STATS_PROBLEM_STYLE = "red"
20
+
21
+ _HIGH_PROBLEM_RATE = 0.05
22
+
23
+ _DEFAULT_STATS_WINDOW = timedelta(hours=1)
24
+ _DEFAULT_METRIC_PRECISION = 2
25
+ _DISPLAYED_PERCENTILES = ((5000, "p50"), (9000, "p90"), (9900, "p99"))
26
+
27
+
28
+ def stats_style(style: str, use_color: bool) -> str | Style:
29
+ if use_color:
30
+ return style
31
+ if style == STATS_SECTION_STYLE:
32
+ return "bold"
33
+ return Style()
34
+
35
+
36
+ def success_style(count: int, total: int, use_color: bool) -> str | None:
37
+ if use_color and total and count / total > 0.5:
38
+ return STATS_SUCCESS_STYLE
39
+ return None
40
+
41
+
42
+ def progress_style(count: int, use_color: bool) -> str | None:
43
+ if use_color and count:
44
+ return STATS_PROGRESS_STYLE
45
+ return None
46
+
47
+
48
+ def problem_style(count: int, total: int, use_color: bool) -> str | None:
49
+ if not use_color or count == 0:
50
+ return None
51
+ if not total or count / total >= _HIGH_PROBLEM_RATE:
52
+ return STATS_PROBLEM_STYLE
53
+ return STATS_WARNING_STYLE
54
+
55
+
56
+ def percentile_style(p50: float | None, p99: float | None, use_color: bool) -> str | Style:
57
+ if not use_color or p50 is None or p99 is None or p99 <= 0:
58
+ return Style()
59
+ if p50 == 0 or p99 >= 2 * p50:
60
+ return STATS_WARNING_STYLE
61
+ return Style()
62
+
63
+
64
+ def _count_with_percentage(count: int, total: int, label: str) -> str:
65
+ percentage = count / total if total else 0
66
+ return f"{count:,} {label} ({percentage:.1%})"
67
+
68
+
69
+ def _timestamp(value: datetime) -> Timestamp:
70
+ timestamp = Timestamp()
71
+ timestamp.FromDatetime(value.astimezone(timezone.utc))
72
+ return timestamp
73
+
74
+
75
+ def _percentile_table(rows: list[tuple[Text, Text, Text, Text]], use_color: bool) -> Table:
76
+ table = Table(box=None, pad_edge=False, padding=(0, 2), header_style="")
77
+ table.add_column("", min_width=30)
78
+ percentile_header_style = stats_style(STATS_METADATA_STYLE, use_color)
79
+ table.add_column(Text("p50", style=percentile_header_style), justify="right", min_width=8)
80
+ table.add_column(Text("p90", style=percentile_header_style), justify="right", min_width=8)
81
+ table.add_column(Text("p99", style=percentile_header_style), justify="right", min_width=8)
82
+ for row in rows:
83
+ table.add_row(*row)
84
+ return table
85
+
86
+
87
+ def _percentile_name(percentile_basis_points: int) -> str:
88
+ return f"p{percentile_basis_points / 100:g}"
89
+
90
+
91
+ def _distribution_json(distribution: api_pb2.StatsPercentileDistribution) -> dict[str, object]:
92
+ return {
93
+ "unit": distribution.unit,
94
+ "percentiles": {
95
+ _percentile_name(percentile.percentile_basis_points): percentile.value
96
+ for percentile in distribution.percentiles
97
+ },
98
+ }
99
+
100
+
101
+ def _distribution_map_json(
102
+ distributions: dict[str, api_pb2.StatsPercentileDistribution],
103
+ ) -> dict[str, object]:
104
+ return {name: _distribution_json(distribution) for name, distribution in distributions.items()}
105
+
106
+
107
+ def _metric_rows(
108
+ distributions: dict[str, api_pb2.StatsPercentileDistribution],
109
+ expected_order: tuple[str, ...],
110
+ use_color: bool,
111
+ ) -> list[tuple[Text, Text, Text, Text]]:
112
+ rows: list[tuple[Text, Text, Text, Text]] = []
113
+ ordered_names = [name for name in expected_order if name in distributions]
114
+ ordered_names.extend(sorted(set(distributions) - set(expected_order)))
115
+ for name in ordered_names:
116
+ distribution = distributions[name]
117
+ values_by_percentile = {
118
+ percentile.percentile_basis_points: percentile.value for percentile in distribution.percentiles
119
+ }
120
+ if not any(basis_points in values_by_percentile for basis_points, _ in _DISPLAYED_PERCENTILES):
121
+ continue
122
+
123
+ values = [
124
+ (
125
+ f"{values_by_percentile[basis_points]:.{_DEFAULT_METRIC_PRECISION}f}"
126
+ if basis_points in values_by_percentile
127
+ else "—"
128
+ )
129
+ for basis_points, _ in _DISPLAYED_PERCENTILES
130
+ ]
131
+ p50 = values_by_percentile.get(5000)
132
+ p99 = values_by_percentile.get(9900)
133
+ rows.append(
134
+ (
135
+ Text(f" {name}", style=stats_style(STATS_METADATA_STYLE, use_color)),
136
+ Text(values[0]),
137
+ Text(values[1]),
138
+ Text(values[2], style=percentile_style(p50, p99, use_color)),
139
+ )
140
+ )
141
+ return rows
@@ -5,41 +5,37 @@ from typing import cast
5
5
 
6
6
  import click
7
7
  from click import UsageError
8
- from google.protobuf.timestamp_pb2 import Timestamp
9
- from rich.style import Style
10
- from rich.table import Table
11
8
  from rich.text import Text
12
9
 
13
10
  from modal._environments import ensure_env
14
11
  from modal._object import _get_environment_name
15
12
  from modal._utils.async_utils import synchronizer
16
- from modal._utils.grpc_utils import is_class_function_lookup_error
17
13
  from modal._utils.time_utils import parse_duration
18
14
  from modal.client import _Client
19
- from modal.exception import NotFoundError
20
- from modal.functions import Function
21
15
  from modal.output import OutputManager
22
16
  from modal_proto import api_pb2
23
17
 
24
18
  from ._help import ModalGroup
25
- from ._logs import _get_app_id_for_function_id, _parse_time_arg, _run_logs_command, _validate_logs_args
19
+ from ._logs import _parse_time_arg, _run_logs_command, _validate_logs_args
26
20
  from ._stats import (
21
+ _DEFAULT_STATS_WINDOW,
27
22
  STATS_HEADING_STYLE,
28
23
  STATS_METADATA_STYLE,
29
24
  STATS_SECTION_STYLE,
30
- percentile_style,
25
+ _count_with_percentage,
26
+ _distribution_map_json,
27
+ _metric_rows,
28
+ _percentile_table,
29
+ _timestamp,
31
30
  problem_style,
32
31
  progress_style,
33
32
  stats_style,
34
33
  success_style,
35
34
  )
36
- from .utils import _is_function_id, _parse_function_or_server_ref, env_option
35
+ from .utils import _resolve_function_id, env_option
37
36
 
38
37
  function_cli = ModalGroup(name="function", help="Inspect Modal Functions.")
39
38
 
40
- _DEFAULT_STATS_WINDOW = timedelta(hours=1)
41
- _DEFAULT_METRIC_PRECISION = 2
42
- _DISPLAYED_PERCENTILES = ((5000, "p50"), (9000, "p90"), (9900, "p99"))
43
39
  _INPUT_METRIC_ORDER = ("Execution time (s)", "End-to-end latency (s)")
44
40
  _CONTAINER_METRIC_ORDER = (
45
41
  "Startup time (s)",
@@ -49,83 +45,6 @@ _CONTAINER_METRIC_ORDER = (
49
45
  )
50
46
 
51
47
 
52
- async def _resolve_function_id(client: _Client, function_identifier: str, environment_name: str) -> str:
53
- if function_identifier == "":
54
- raise UsageError("FUNCTION must be a Function ID (fu-…) or a deployed Function name (APP_NAME/FUNCTION_NAME).")
55
-
56
- if "/" in function_identifier:
57
- app_name, function_name = function_identifier.split("/", 1)
58
-
59
- if not (app_name and function_name):
60
- raise UsageError(
61
- "FUNCTION must be a Function ID (fu-…) or a deployed Function name (APP_NAME/FUNCTION_NAME)."
62
- )
63
-
64
- try:
65
- response = await client.stub.FunctionGet(
66
- api_pb2.FunctionGetRequest(
67
- app_name=app_name,
68
- object_tag=function_name,
69
- environment_name=environment_name,
70
- )
71
- )
72
- except NotFoundError as exc:
73
- if is_class_function_lookup_error(exc):
74
- raise NotFoundError(
75
- f"'{function_identifier}' is a modal.Cls. Use\n modal function stats '{function_identifier}.*'"
76
- ) from None
77
- raise
78
- return response.function_id
79
-
80
- if function_identifier.startswith("fu-"):
81
- return function_identifier
82
-
83
- raise UsageError("FUNCTION must be a Function ID (fu-…) or a deployed Function name (APP_NAME/FUNCTION_NAME).")
84
-
85
-
86
- def _timestamp(value: datetime) -> Timestamp:
87
- timestamp = Timestamp()
88
- timestamp.FromDatetime(value.astimezone(timezone.utc))
89
- return timestamp
90
-
91
-
92
- def _percentile_table(rows: list[tuple[Text, Text, Text, Text]], use_color: bool) -> Table:
93
- table = Table(box=None, pad_edge=False, padding=(0, 2), header_style="")
94
- table.add_column("", min_width=30)
95
- percentile_header_style = stats_style(STATS_METADATA_STYLE, use_color)
96
- table.add_column(Text("p50", style=percentile_header_style), justify="right", min_width=8)
97
- table.add_column(Text("p90", style=percentile_header_style), justify="right", min_width=8)
98
- table.add_column(Text("p99", style=percentile_header_style), justify="right", min_width=8)
99
- for row in rows:
100
- table.add_row(*row)
101
- return table
102
-
103
-
104
- def _count_with_percentage(count: int, total: int, label: str) -> str:
105
- percentage = count / total if total else 0
106
- return f"{count:,} {label} ({percentage:.1%})"
107
-
108
-
109
- def _percentile_name(percentile_basis_points: int) -> str:
110
- return f"p{percentile_basis_points / 100:g}"
111
-
112
-
113
- def _distribution_json(distribution: api_pb2.StatsPercentileDistribution) -> dict[str, object]:
114
- return {
115
- "unit": distribution.unit,
116
- "percentiles": {
117
- _percentile_name(percentile.percentile_basis_points): percentile.value
118
- for percentile in distribution.percentiles
119
- },
120
- }
121
-
122
-
123
- def _distribution_map_json(
124
- distributions: dict[str, api_pb2.StatsPercentileDistribution],
125
- ) -> dict[str, object]:
126
- return {name: _distribution_json(distribution) for name, distribution in distributions.items()}
127
-
128
-
129
48
  def _time_range_stats_json(response: api_pb2.FunctionGetTimeRangeStatsResponse) -> dict[str, object]:
130
49
  return {
131
50
  "since": response.since.ToDatetime(tzinfo=timezone.utc).isoformat(),
@@ -159,53 +78,24 @@ def _stats_json(
159
78
  }
160
79
 
161
80
 
162
- def _metric_rows(
163
- distributions: dict[str, api_pb2.StatsPercentileDistribution],
164
- expected_order: tuple[str, ...],
165
- use_color: bool,
166
- ) -> list[tuple[Text, Text, Text, Text]]:
167
- rows: list[tuple[Text, Text, Text, Text]] = []
168
- ordered_names = [name for name in expected_order if name in distributions]
169
- ordered_names.extend(sorted(set(distributions) - set(expected_order)))
170
- for name in ordered_names:
171
- distribution = distributions[name]
172
- values_by_percentile = {
173
- percentile.percentile_basis_points: percentile.value for percentile in distribution.percentiles
174
- }
175
- if not any(basis_points in values_by_percentile for basis_points, _ in _DISPLAYED_PERCENTILES):
176
- continue
177
-
178
- p50 = values_by_percentile.get(5000)
179
- p99 = values_by_percentile.get(9900)
180
- values = []
181
- for basis_points, _ in _DISPLAYED_PERCENTILES:
182
- value = values_by_percentile.get(basis_points)
183
- value_text = "—" if value is None else f"{value:.{_DEFAULT_METRIC_PRECISION}f}"
184
- style = percentile_style(p50, p99, use_color) if basis_points == 9900 else Style()
185
- values.append(Text(value_text, style=style))
186
- rows.append(
187
- (
188
- Text(f" {name}", style=stats_style(STATS_METADATA_STYLE, use_color)),
189
- values[0],
190
- values[1],
191
- values[2],
192
- )
193
- )
194
- return rows
195
-
196
-
197
81
  @function_cli.command("stats", no_args_is_help=True)
198
82
  @click.argument("function_identifier", metavar="FUNCTION")
199
83
  @click.option(
200
84
  "--since",
201
85
  default=None,
202
- help="Start of time range. Treated as UTC if timezone not supplied."
203
- "Accepts an ISO 8601 datetime or relative time such as '2h' or '30m'.",
86
+ help=(
87
+ "Start of time range. Treated as local time "
88
+ "if a timezone is not supplied."
89
+ "Accepts an ISO 8601 datetime or relative time such as '2h' or '30m'."
90
+ ),
204
91
  )
205
92
  @click.option(
206
93
  "--until",
207
94
  default=None,
208
- help="End of time range. Treated as UTC if timezone not supplied. Accepts the same argument types as --since.",
95
+ help=(
96
+ "End of time range. Treated as local time if a "
97
+ "timezone is not supplied. Accepts the same argument types as --since."
98
+ ),
209
99
  )
210
100
  @click.option(
211
101
  "--all-variants",
@@ -303,7 +193,9 @@ async def stats(
303
193
 
304
194
  environment_name = _get_environment_name(ensure_env(env))
305
195
  client = await _Client.from_env()
306
- function_id = await _resolve_function_id(client, function_identifier, environment_name)
196
+ function_id = await _resolve_function_id(
197
+ client, function_identifier, environment_name, command="stats", include_app_id=False
198
+ )
307
199
  req = api_pb2.FunctionGetTimeRangeStatsRequest(
308
200
  function_id=function_id,
309
201
  since=_timestamp(since_dt),
@@ -507,15 +399,10 @@ async def logs(
507
399
  env = ensure_env(env)
508
400
  _validate_logs_args(follow=follow, since=since, until=until, tail=tail)
509
401
 
510
- if _is_function_id(function_ref):
511
- function_id = function_ref
512
- app_id = await _get_app_id_for_function_id(function_id)
513
- else:
514
- app_name, function_name = _parse_function_or_server_ref(function_ref, "Function")
515
- function = Function.from_name(app_name, function_name, environment_name=env)
516
- query_data = await function._get_log_query_data.aio()
517
- function_id = query_data.source_object_id
518
- app_id = query_data.app_id
402
+ client = await _Client.from_env()
403
+ function_id, app_id = await _resolve_function_id(
404
+ client, function_ref, env, object_type="Function", command="logs", include_app_id=True
405
+ )
519
406
 
520
407
  prefix_fields: list[str] = []
521
408
  if show_function_id: