modal 1.5.3.dev12__tar.gz → 1.5.4.dev0__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 (236) hide show
  1. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/PKG-INFO +1 -1
  2. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_clustered_functions.py +11 -0
  3. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_clustered_functions.pyi +6 -2
  4. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_environments.py +67 -37
  5. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_grpc_client.py +1 -1
  6. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_object.py +5 -4
  7. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_output/rich.py +3 -3
  8. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_resources.py +4 -4
  9. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_runtime/user_code_imports.py +2 -1
  10. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_traceback.py +1 -1
  11. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/async_utils.py +5 -0
  12. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/blob_utils.py +1 -0
  13. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/bytes_io_segment_payload.py +2 -1
  14. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/pattern_utils.py +2 -2
  15. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/time_utils.py +3 -1
  16. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/app.py +3 -1
  17. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/app.pyi +4 -0
  18. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/cluster.py +1 -1
  19. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/dashboard.py +1 -1
  20. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/endpoint.py +2 -2
  21. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/environment.py +104 -42
  22. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/launch.py +1 -1
  23. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/network_file_system.py +1 -1
  24. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/profile.py +3 -3
  25. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/shell.py +2 -2
  26. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/client.pyi +2 -2
  27. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cloud_bucket_mount.py +3 -2
  28. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/environments.py +3 -0
  29. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/environments.pyi +68 -53
  30. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/experimental/__init__.py +11 -1
  31. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/file_pattern_matcher.py +1 -1
  32. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/functions.pyi +6 -6
  33. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/mount.py +89 -31
  34. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/mount.pyi +18 -6
  35. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/secret.py +2 -2
  36. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/serving.py +3 -3
  37. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal.egg-info/PKG-INFO +1 -1
  38. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_proto/api_pb2.py +274 -274
  39. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_proto/api_pb2.pyi +6 -1
  40. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_proto/task_command_router_pb2.py +32 -32
  41. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_proto/task_command_router_pb2.pyi +5 -1
  42. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_version/__init__.py +1 -1
  43. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/pyproject.toml +48 -1
  44. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/LICENSE +0 -0
  45. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/README.md +0 -0
  46. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/__init__.py +0 -0
  47. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/__main__.py +0 -0
  48. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_billing.py +0 -0
  49. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_container_entrypoint.py +0 -0
  50. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_function_variants.py +0 -0
  51. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_functions.py +0 -0
  52. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_image.py +0 -0
  53. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_ipython.py +0 -0
  54. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_load_context.py +0 -0
  55. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_location.py +0 -0
  56. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_logs.py +0 -0
  57. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_logs_manager.py +0 -0
  58. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_logs_manager.pyi +0 -0
  59. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_output/__init__.py +0 -0
  60. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_output/manager.py +0 -0
  61. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_output/pty.py +0 -0
  62. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_output/status.py +0 -0
  63. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_partial_function.py +0 -0
  64. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_resolver.py +0 -0
  65. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_runtime/__init__.py +0 -0
  66. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_runtime/asgi.py +0 -0
  67. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_runtime/container_io_manager.py +0 -0
  68. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
  69. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_runtime/execution_context.py +0 -0
  70. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_runtime/execution_context.pyi +0 -0
  71. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  72. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_runtime/task_lifecycle_manager.py +0 -0
  73. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  74. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_runtime/telemetry.py +0 -0
  75. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
  76. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_serialization.py +0 -0
  77. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_server.py +0 -0
  78. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_supports_logs.py +0 -0
  79. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_tunnel.py +0 -0
  80. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_tunnel.pyi +0 -0
  81. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_type_manager.py +0 -0
  82. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/__init__.py +0 -0
  83. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/app_utils.py +0 -0
  84. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/auth_token_manager.py +0 -0
  85. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/browser_utils.py +0 -0
  86. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/curl_utils.py +0 -0
  87. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/deprecation.py +0 -0
  88. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/display_utils.py +0 -0
  89. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/docker_utils.py +0 -0
  90. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/function_utils.py +0 -0
  91. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/git_utils.py +0 -0
  92. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/grpc_testing.py +0 -0
  93. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/grpc_utils.py +0 -0
  94. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/hash_utils.py +0 -0
  95. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/http_utils.py +0 -0
  96. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/jwt_utils.py +0 -0
  97. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/logger.py +0 -0
  98. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/mount_utils.py +0 -0
  99. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/name_utils.py +0 -0
  100. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/package_utils.py +0 -0
  101. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/proxy_support.py +0 -0
  102. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/rand_pb_testing.py +0 -0
  103. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/sandbox_fs_utils.py +0 -0
  104. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/shell_utils.py +0 -0
  105. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_utils/task_command_router_client.py +0 -0
  106. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_vendor/__init__.py +0 -0
  107. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  108. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_vendor/cloudpickle.py +0 -0
  109. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_vendor/tblib.py +0 -0
  110. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_vendor/version.py +0 -0
  111. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_watcher.py +0 -0
  112. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/_workspace.py +0 -0
  113. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/billing.py +0 -0
  114. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/billing.pyi +0 -0
  115. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/builder/2023.12.312.txt +0 -0
  116. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/builder/2023.12.txt +0 -0
  117. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/builder/2024.04.txt +0 -0
  118. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/builder/2024.10.txt +0 -0
  119. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/builder/2025.06.txt +0 -0
  120. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/builder/PREVIEW.txt +0 -0
  121. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/builder/README.md +0 -0
  122. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/builder/base-images.json +0 -0
  123. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/call_graph.py +0 -0
  124. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/__init__.py +0 -0
  125. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/_download.py +0 -0
  126. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/_help.py +0 -0
  127. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/_traceback.py +0 -0
  128. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/app.py +0 -0
  129. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/billing.py +0 -0
  130. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/bootstrap.py +0 -0
  131. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/changelog.py +0 -0
  132. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/config.py +0 -0
  133. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/container.py +0 -0
  134. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/curl.py +0 -0
  135. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/dict.py +0 -0
  136. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/entry_point.py +0 -0
  137. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/image.py +0 -0
  138. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/import_refs.py +0 -0
  139. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/logo.py +0 -0
  140. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/programs/__init__.py +0 -0
  141. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/programs/run_jupyter.py +0 -0
  142. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/programs/vscode.py +0 -0
  143. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/queues.py +0 -0
  144. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/run.py +0 -0
  145. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/secret.py +0 -0
  146. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/selector.py +0 -0
  147. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/skills.py +0 -0
  148. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/token.py +0 -0
  149. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/utils.py +0 -0
  150. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/volume.py +0 -0
  151. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cli/workspace.py +0 -0
  152. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/client.py +0 -0
  153. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cloud_bucket_mount.pyi +0 -0
  154. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cls.py +0 -0
  155. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/cls.pyi +0 -0
  156. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/config.py +0 -0
  157. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/container_process.py +0 -0
  158. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/container_process.pyi +0 -0
  159. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/dict.py +0 -0
  160. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/dict.pyi +0 -0
  161. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/exception.py +0 -0
  162. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/experimental/flash.py +0 -0
  163. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/experimental/flash.pyi +0 -0
  164. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/experimental/ipython.py +0 -0
  165. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/file_io.py +0 -0
  166. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/file_io.pyi +0 -0
  167. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/functions.py +0 -0
  168. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/image.py +0 -0
  169. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/image.pyi +0 -0
  170. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/io_streams.py +0 -0
  171. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/io_streams.pyi +0 -0
  172. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/network_file_system.py +0 -0
  173. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/network_file_system.pyi +0 -0
  174. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/object.py +0 -0
  175. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/object.pyi +0 -0
  176. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/output.py +0 -0
  177. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/parallel_map.py +0 -0
  178. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/parallel_map.pyi +0 -0
  179. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/partial_function.py +0 -0
  180. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/partial_function.pyi +0 -0
  181. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/proxy.py +0 -0
  182. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/proxy.pyi +0 -0
  183. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/py.typed +0 -0
  184. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/queue.py +0 -0
  185. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/queue.pyi +0 -0
  186. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/retries.py +0 -0
  187. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/runner.py +0 -0
  188. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/runner.pyi +0 -0
  189. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/running_app.py +0 -0
  190. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/sandbox.py +0 -0
  191. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/sandbox.pyi +0 -0
  192. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/sandbox_fs.py +0 -0
  193. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/sandbox_fs.pyi +0 -0
  194. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/schedule.py +0 -0
  195. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/scheduler_placement.py +0 -0
  196. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/secret.pyi +0 -0
  197. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/server.py +0 -0
  198. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/server.pyi +0 -0
  199. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/serving.pyi +0 -0
  200. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/skills/modal/SKILL.md +0 -0
  201. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/snapshot.py +0 -0
  202. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/snapshot.pyi +0 -0
  203. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/stream_type.py +0 -0
  204. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/token_flow.py +0 -0
  205. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/token_flow.pyi +0 -0
  206. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/types.py +0 -0
  207. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/volume.py +0 -0
  208. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/volume.pyi +0 -0
  209. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/workspace.py +0 -0
  210. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal/workspace.pyi +0 -0
  211. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal.egg-info/SOURCES.txt +0 -0
  212. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal.egg-info/dependency_links.txt +0 -0
  213. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal.egg-info/entry_points.txt +0 -0
  214. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal.egg-info/requires.txt +0 -0
  215. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal.egg-info/top_level.txt +0 -0
  216. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_docs/__init__.py +0 -0
  217. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_docs/gen_cli_docs.py +0 -0
  218. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_docs/gen_cli_docs_main.py +0 -0
  219. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_docs/gen_reference_docs.py +0 -0
  220. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_docs/gen_reference_docs_main.py +0 -0
  221. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_docs/mdmd/__init__.py +0 -0
  222. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_docs/mdmd/mdmd.py +0 -0
  223. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_docs/mdmd/signatures.py +0 -0
  224. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_docs/mdmd/types.py +0 -0
  225. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_docs/reference_intro.md +0 -0
  226. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_proto/__init__.py +0 -0
  227. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_proto/api_grpc.py +0 -0
  228. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_proto/api_pb2_grpc.py +0 -0
  229. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_proto/api_pb2_grpc.pyi +0 -0
  230. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_proto/modal_api_grpc.py +0 -0
  231. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_proto/py.typed +0 -0
  232. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_proto/task_command_router_grpc.py +0 -0
  233. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  234. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  235. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/modal_version/__main__.py +0 -0
  236. {modal-1.5.3.dev12 → modal-1.5.4.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.3.dev12
3
+ Version: 1.5.4.dev0
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -15,6 +15,7 @@ class ClusterInfo:
15
15
  cluster_id: str
16
16
  container_ips: list[str]
17
17
  container_ipv4_ips: list[str]
18
+ fabric_ids: list[str]
18
19
 
19
20
 
20
21
  cluster_info: ClusterInfo | None = None
@@ -29,6 +30,14 @@ def get_cluster_info() -> ClusterInfo:
29
30
  return cluster_info
30
31
 
31
32
 
33
+ def get_fabric_peers() -> list[int]:
34
+ info = get_cluster_info()
35
+ if info.rank >= len(info.fabric_ids) or not info.fabric_ids[info.rank]:
36
+ return [info.rank]
37
+ own_fabric_id = info.fabric_ids[info.rank]
38
+ return [rank for rank, fabric_id in enumerate(info.fabric_ids) if fabric_id == own_fabric_id]
39
+
40
+
32
41
  async def _initialize_clustered_function(client: _Client, task_id: str, world_size: int):
33
42
  global cluster_info
34
43
 
@@ -70,6 +79,7 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
70
79
  cluster_id=resp.cluster_id,
71
80
  container_ips=resp.container_ips,
72
81
  container_ipv4_ips=resp.container_ipv4_ips,
82
+ fabric_ids=resp.fabric_ids,
73
83
  )
74
84
  else:
75
85
  cluster_info = ClusterInfo(
@@ -77,6 +87,7 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
77
87
  cluster_id="", # No cluster ID for single-node # TODO(irfansharif): Is this right?
78
88
  container_ips=[container_ip],
79
89
  container_ipv4_ips=[], # No IPv4 IPs for single-node
90
+ fabric_ids=[],
80
91
  )
81
92
 
82
93
 
@@ -3,14 +3,17 @@ import types
3
3
  import typing_extensions
4
4
 
5
5
  class ClusterInfo:
6
- """ClusterInfo(rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str])"""
6
+ """ClusterInfo(rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str], fabric_ids: list[str])"""
7
7
 
8
8
  rank: int
9
9
  cluster_id: str
10
10
  container_ips: list[str]
11
11
  container_ipv4_ips: list[str]
12
+ fabric_ids: list[str]
12
13
 
13
- def __init__(self, rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str]) -> None:
14
+ def __init__(
15
+ self, rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str], fabric_ids: list[str]
16
+ ) -> None:
14
17
  """Initialize self. See help(type(self)) for accurate signature."""
15
18
  ...
16
19
 
@@ -23,6 +26,7 @@ class ClusterInfo:
23
26
  ...
24
27
 
25
28
  def get_cluster_info() -> ClusterInfo: ...
29
+ def get_fabric_peers() -> list[int]: ...
26
30
  async def _initialize_clustered_function(client: modal.client._Client, task_id: str, world_size: int): ...
27
31
 
28
32
  class __initialize_clustered_function_spec(typing_extensions.Protocol):
@@ -16,6 +16,7 @@ from modal_proto import api_pb2
16
16
  from ._load_context import LoadContext
17
17
  from ._object import _Object
18
18
  from ._resolver import Resolver
19
+ from ._utils.deprecation import deprecation_warning
19
20
  from ._utils.name_utils import check_environment_name
20
21
  from ._utils.time_utils import is_utc_month_aligned, parse_billing_cycle
21
22
  from .client import _Client
@@ -104,7 +105,7 @@ class _EnvironmentManager:
104
105
  await client.stub.EnvironmentDelete(api_pb2.EnvironmentDeleteRequest(name=name))
105
106
 
106
107
 
107
- class _EnvironmentMembersManager:
108
+ class _EnvironmentRolesManager:
108
109
  """mdmd:namespace"""
109
110
 
110
111
  def __init__(self, environment: "_Environment"):
@@ -112,13 +113,13 @@ class _EnvironmentMembersManager:
112
113
  self._environment = environment
113
114
 
114
115
  async def list(self) -> dict[Literal["users", "service_users"], dict[str, str]]:
115
- """Enumerate the environment roles for each user and service user in the workspace.
116
+ """Enumerate the Environment Role for each user and service user in the workspace.
116
117
 
117
118
  **Examples:**
118
119
 
119
120
  ```python notest
120
- members = modal.Environment.from_name("my-env").members.list()
121
- print(members)
121
+ roles = modal.Environment.from_name("my-env").roles.list()
122
+ print(roles)
122
123
  # {
123
124
  # "users": {"alice": "contributor", "bob": "viewer", "carol": "contributor"},
124
125
  # "service_users": {"alice-bot": "contributor", "ops-bot": "viewer", "ci-bot": "no-access"},
@@ -145,16 +146,17 @@ class _EnvironmentMembersManager:
145
146
  users: Mapping[str, str] | None = None,
146
147
  service_users: Mapping[str, str] | None = None,
147
148
  ) -> None:
148
- """Add or modify roles for members of a restricted Environment.
149
+ """Update the Environment Role of users and service users.
149
150
 
150
- Each user or service user will be added to the Environment if not currently a member;
151
- if already a member, the user or service user's role will be updated.
151
+ Each role is one of 'contributor', 'viewer', or 'no-access'. Service users can be
152
+ assigned a role on any Environment, while workspace members can only be assigned a
153
+ role on restricted Environments.
152
154
 
153
155
  **Examples:**
154
156
 
155
157
  ```python notest
156
158
  env = modal.Environment.from_name("my-restricted-env")
157
- env.members.update(
159
+ env.roles.update(
158
160
  users={"alice": "contributor", "bob": "viewer"},
159
161
  service_users={"alice-bot": "contributor"},
160
162
  )
@@ -196,24 +198,59 @@ class _EnvironmentMembersManager:
196
198
 
197
199
  await self._dispatch_role_updates(requests)
198
200
 
201
+ async def _dispatch_role_updates(self, requests: dict[str, api_pb2.EnvironmentRoleSetRequest]) -> None:
202
+ """Send batched EnvironmentRoleSet RPCs and report all errors encountered."""
203
+ results = await asyncio.gather(
204
+ *(self._environment.client.stub.EnvironmentRoleSet(req) for req in requests.values()),
205
+ return_exceptions=True,
206
+ )
207
+ errors = [(label, result) for label, result in zip(requests.keys(), results) if isinstance(result, Exception)]
208
+ if errors:
209
+ n = len(errors)
210
+ header = f"{n} error{'s' if n != 1 else ''} occurred while updating Environment roles:"
211
+ details = "\n".join(f" - {label}: {e}" for label, e in errors)
212
+ raise WorkspaceManagementError(f"{header}\n{details}")
213
+
214
+
215
+ # Deprecated alias for `_EnvironmentRolesManager`; each method warns and delegates.
216
+ class _EnvironmentMembersManager:
217
+ """mdmd:hidden"""
218
+
219
+ def __init__(self, environment: "_Environment"):
220
+ """mdmd:hidden"""
221
+ self._environment = environment
222
+
223
+ async def list(self) -> dict[Literal["users", "service_users"], dict[str, str]]:
224
+ deprecation_warning(
225
+ (2026, 7, 23),
226
+ "`Environment.members.list()` is deprecated; use `Environment.roles.list()` instead.",
227
+ )
228
+ return await _EnvironmentRolesManager(self._environment).list()
229
+
230
+ async def update(
231
+ self,
232
+ *,
233
+ users: Mapping[str, str] | None = None,
234
+ service_users: Mapping[str, str] | None = None,
235
+ ) -> None:
236
+ deprecation_warning(
237
+ (2026, 7, 23),
238
+ "`Environment.members.update()` is deprecated; use `Environment.roles.update()` instead.",
239
+ )
240
+ await _EnvironmentRolesManager(self._environment).update(users=users, service_users=service_users)
241
+
199
242
  async def remove(
200
243
  self,
201
244
  *,
202
245
  users: Iterable[str] | None = None,
203
246
  service_users: Iterable[str] | None = None,
204
247
  ) -> None:
205
- """Remove members from a restricted Environment.
206
-
207
- **Examples:**
208
-
209
- ```python notest
210
- env = modal.Environment.from_name("my-restricted-env")
211
- env.members.remove(
212
- users=["alice"],
213
- service_users=["alice-bot"],
248
+ """Remove the Environment Role of users and service users, reverting them to the default."""
249
+ deprecation_warning(
250
+ (2026, 7, 23),
251
+ "`Environment.members.remove()` is deprecated. Environment Roles are now explicit; "
252
+ "set a role (e.g. 'no-access') with `Environment.roles.update()` instead.",
214
253
  )
215
- ```
216
- """
217
254
  await self._environment.hydrate()
218
255
  users = users or []
219
256
  service_users = service_users or []
@@ -247,20 +284,7 @@ class _EnvironmentMembersManager:
247
284
  role=api_pb2.ENVIRONMENT_ROLE_UNSPECIFIED,
248
285
  )
249
286
 
250
- await self._dispatch_role_updates(requests)
251
-
252
- async def _dispatch_role_updates(self, requests: dict[str, api_pb2.EnvironmentRoleSetRequest]) -> None:
253
- """Send batched EnvironmentRoleSet RPCs and report all errors encountered."""
254
- results = await asyncio.gather(
255
- *(self._environment.client.stub.EnvironmentRoleSet(req) for req in requests.values()),
256
- return_exceptions=True,
257
- )
258
- errors = [(label, result) for label, result in zip(requests.keys(), results) if isinstance(result, Exception)]
259
- if errors:
260
- n = len(errors)
261
- header = f"{n} error{'s' if n != 1 else ''} occurred while updating Environment members:"
262
- details = "\n".join(f" - {label}: {e}" for label, e in errors)
263
- raise WorkspaceManagementError(f"{header}\n{details}")
287
+ await _EnvironmentRolesManager(self._environment)._dispatch_role_updates(requests)
264
288
 
265
289
 
266
290
  class _Environment(_Object, type_prefix="en"):
@@ -284,11 +308,17 @@ class _Environment(_Object, type_prefix="en"):
284
308
  return _EnvironmentManager()
285
309
 
286
310
  @property
287
- def members(self) -> "_EnvironmentMembersManager":
288
- """Namespace with methods for managing the membership of a restricted Environment.
311
+ def roles(self) -> "_EnvironmentRolesManager":
312
+ """Namespace with methods for managing the Environment Roles of users and service users.
289
313
 
290
- See https://modal.com/docs/guide/rbac for more information on restricted Environments.
314
+ See https://modal.com/docs/guide/rbac for more information on Environment Roles.
291
315
  """
316
+ return _EnvironmentRolesManager(self)
317
+
318
+ @property
319
+ def members(self) -> "_EnvironmentMembersManager":
320
+ """mdmd:hidden"""
321
+ # Deprecated alias for `Environment.roles`.
292
322
  return _EnvironmentMembersManager(self)
293
323
 
294
324
  # TODO(michael) Keeping this private for now until we decide what else should be in it
@@ -591,4 +621,4 @@ def ensure_env(environment_name: str | None = None) -> str:
591
621
  if environment_name is not None:
592
622
  config.override_locally("environment", environment_name)
593
623
 
594
- return config.get("environment")
624
+ return config.get("environment") or ""
@@ -54,7 +54,7 @@ class grpc_error_converter:
54
54
  with suppress_tb_frame():
55
55
  if isinstance(exc, GRPCError):
56
56
  modal_exc = _STATUS_TO_EXCEPTION[exc.status](exc.message)
57
- modal_exc._grpc_message = exc.message
57
+ modal_exc._grpc_message = exc.message or ""
58
58
  modal_exc._grpc_status = exc.status
59
59
  modal_exc._grpc_details = exc.details
60
60
  if use_full_traceback:
@@ -23,19 +23,20 @@ EPHEMERAL_OBJECT_HEARTBEAT_SLEEP: int = 300
23
23
 
24
24
  def _get_environment_name(
25
25
  environment_name: str | None = None,
26
- ) -> str | None:
26
+ ) -> str:
27
27
  """Get environment name from various sources.
28
28
 
29
29
  Args:
30
- environment_name: Explicitly provided environment name (highest priority)
30
+ environment_name: Explicitly provided environment name (highest priority).
31
31
 
32
32
  Returns:
33
- Environment name from first available source, or config default
33
+ Environment name from as passed in, from config, or as a null string
34
+ (which implies workspace-level default).
34
35
  """
35
36
  if environment_name:
36
37
  return environment_name
37
38
  else:
38
- return config.get("environment")
39
+ return config.get("environment") or ""
39
40
 
40
41
 
41
42
  def live_method(method):
@@ -81,11 +81,11 @@ class FunctionQueuingColumn(ProgressColumn):
81
81
  super().__init__()
82
82
 
83
83
  def render(self, task) -> Text:
84
- self.lag = max(task.completed - task.elapsed, self.lag)
84
+ self.lag = max(task.completed - (task.elapsed or 0), self.lag)
85
85
  if task.finished:
86
- elapsed = max(task.finished_time, task.completed)
86
+ elapsed = max(task.finished_time or 0, task.completed)
87
87
  else:
88
- elapsed = task.elapsed + self.lag
88
+ elapsed = (task.elapsed or 0) + self.lag
89
89
  delta = timedelta(seconds=int(elapsed))
90
90
  return Text(str(delta), style="progress.elapsed")
91
91
 
@@ -26,10 +26,10 @@ def convert_fn_config_to_resources_config(
26
26
  raise InvalidError(f"Cannot specify a CPU limit lower than request: {milli_cpu_max} < {milli_cpu}")
27
27
  elif cpu and isinstance(cpu, (float, int)):
28
28
  milli_cpu = int(1000 * cpu)
29
- milli_cpu_max = None
29
+ milli_cpu_max = 0
30
30
  else:
31
- milli_cpu = None
32
- milli_cpu_max = None
31
+ milli_cpu = 0
32
+ milli_cpu_max = 0
33
33
 
34
34
  if memory and isinstance(memory, int):
35
35
  memory_mb = memory
@@ -47,6 +47,6 @@ def convert_fn_config_to_resources_config(
47
47
  gpu_config=parse_gpu_config(gpu),
48
48
  memory_mb=memory_mb,
49
49
  memory_mb_max=memory_mb_max,
50
- ephemeral_disk_mb=ephemeral_disk,
50
+ ephemeral_disk_mb=ephemeral_disk or 0,
51
51
  rdma=rdma or False,
52
52
  )
@@ -549,7 +549,8 @@ def _get_cls_or_user_cls(
549
549
  else:
550
550
  # Load the module dynamically for non-serialized class.
551
551
  module = importlib.import_module(function_def.module_name)
552
- qual_name: str = function_def.function_name
552
+ # Always use implementation_name to find the class, default to function_name if implementation_name not set.
553
+ qual_name: str = function_def.implementation_name or function_def.function_name
553
554
 
554
555
  if not is_global_object(qual_name):
555
556
  raise LocalFunctionError("Attempted to load a class defined in a function scope")
@@ -75,7 +75,7 @@ def append_modal_tb(exc: BaseException, tb_dict: TBDictType, line_cache: LineCac
75
75
  setattr(exc, "__line_cache__", line_cache)
76
76
 
77
77
 
78
- def reduce_traceback_to_user_code(tb: TracebackType | None, user_source: str) -> TracebackType:
78
+ def reduce_traceback_to_user_code(tb: TracebackType | None, user_source: str) -> TracebackType | None:
79
79
  """Return a traceback that does not contain modal entrypoint or synchronicity frames."""
80
80
 
81
81
  # Step forward all the way through the traceback and drop any "Modal support" frames
@@ -1266,3 +1266,8 @@ def deprecate_aio_usage(deprecation_date: tuple[int, int, int], readable_sync_ca
1266
1266
  return MethodWithAio(sync_implementation, _async_proxy, synchronizer, is_classmethod=is_classmethod)
1267
1267
 
1268
1268
  return deco
1269
+
1270
+
1271
+ async def async_generator_from_sync(lst: Iterable[T]) -> AsyncGenerator[T, None]:
1272
+ for item in lst:
1273
+ yield item
@@ -444,6 +444,7 @@ class FileUploadSpec:
444
444
  mode: int # file permission bits (last 12 bits of st_mode)
445
445
  size: int
446
446
  content: bytes | None = None # Set for very small files to avoid double-read
447
+ exists_in_store: bool | None = None # set after existence check
447
448
 
448
449
  def read_content(self) -> bytes:
449
450
  """Read content from source."""
@@ -45,7 +45,8 @@ class BytesIOSegmentPayload(Payload):
45
45
  self.segment_length = segment_length
46
46
  # seek to start of file segment we are interested in, in order to make .size() evaluate correctly
47
47
  self._value.seek(self.initial_seek_pos + segment_start)
48
- assert self.segment_length <= super().size
48
+ base_size = super().size
49
+ assert base_size is not None and self.segment_length <= base_size
49
50
  self.chunk_size = chunk_size
50
51
  self.progress_report_cb = progress_report_cb or (lambda *_, **__: None)
51
52
  self.reset_state()
@@ -143,12 +143,12 @@ class Pattern:
143
143
  else:
144
144
  return False
145
145
  elif self.match_type == MatchType.REGEXP:
146
- return self.regexp.match(path) is not None
146
+ return self.regexp is not None and self.regexp.match(path) is not None
147
147
  else:
148
148
  return False
149
149
 
150
150
 
151
- def read_ignorefile(reader: TextIO) -> list[str]:
151
+ def read_ignorefile(reader: TextIO | None) -> list[str]:
152
152
  """Read an ignore file from a reader and return the list of file patterns to
153
153
  ignore, applying the following rules:
154
154
 
@@ -294,7 +294,9 @@ def relative_timestamp(dt: datetime) -> str:
294
294
 
295
295
 
296
296
  def locale_tz() -> tzinfo:
297
- return datetime.now().astimezone().tzinfo
297
+ tz = datetime.now().astimezone().tzinfo
298
+ assert tz is not None
299
+ return tz
298
300
 
299
301
 
300
302
  def as_timestamp(arg: datetime | str | None) -> float:
@@ -1295,6 +1295,7 @@ class _App:
1295
1295
  scaleup_window: int | None = None, # Stabilization window (seconds) of sustained demand before scaling up
1296
1296
  scaledown_window: int | None = None, # Max idle time before scaling down (seconds)
1297
1297
  startup_timeout: int = 30, # Maximum container startup time in seconds
1298
+ name: str | None = None, # Name of the server
1298
1299
  port: int = 8000, # Port the HTTP server listens on
1299
1300
  unauthenticated: bool = False, # Whether the endpoint requires proxy authentication, required by default.
1300
1301
  h2_enabled: bool = False, # Enable HTTP/2
@@ -1341,6 +1342,7 @@ class _App:
1341
1342
  scaleup_window: Seconds of sustained demand required before scaling up new containers.
1342
1343
  scaledown_window: Maximum duration (in seconds) idle containers wait before scaling down.
1343
1344
  startup_timeout: Maximum container startup time in seconds.
1345
+ name: Sets the Modal name of the function within the app, defaults to class name.
1344
1346
  port: Port the HTTP server listens on.
1345
1347
  unauthenticated: Whether the endpoint requires proxy authentication; required by default.
1346
1348
  h2_enabled: Enable HTTP/2.
@@ -1423,7 +1425,7 @@ class _App:
1423
1425
  local_state = self._local_state
1424
1426
 
1425
1427
  # Create the FunctionInfo for the server, note we treat FunctionInfo as a class for servers
1426
- info = FunctionInfo(None, serialized=serialized, user_cls=user_cls, name_override=user_cls.__name__)
1428
+ info = FunctionInfo(None, serialized=serialized, user_cls=user_cls, name_override=name or user_cls.__name__)
1427
1429
  # Create the service function
1428
1430
  service_function = _Function.from_local(
1429
1431
  info,
@@ -751,6 +751,7 @@ class _App:
751
751
  scaleup_window: typing.Optional[int] = None,
752
752
  scaledown_window: typing.Optional[int] = None,
753
753
  startup_timeout: int = 30,
754
+ name: typing.Optional[str] = None,
754
755
  port: int = 8000,
755
756
  unauthenticated: bool = False,
756
757
  h2_enabled: bool = False,
@@ -797,6 +798,7 @@ class _App:
797
798
  scaleup_window: Seconds of sustained demand required before scaling up new containers.
798
799
  scaledown_window: Maximum duration (in seconds) idle containers wait before scaling down.
799
800
  startup_timeout: Maximum container startup time in seconds.
801
+ name: Sets the Modal name of the function within the app, defaults to class name.
800
802
  port: Port the HTTP server listens on.
801
803
  unauthenticated: Whether the endpoint requires proxy authentication; required by default.
802
804
  h2_enabled: Enable HTTP/2.
@@ -1755,6 +1757,7 @@ class App:
1755
1757
  scaleup_window: typing.Optional[int] = None,
1756
1758
  scaledown_window: typing.Optional[int] = None,
1757
1759
  startup_timeout: int = 30,
1760
+ name: typing.Optional[str] = None,
1758
1761
  port: int = 8000,
1759
1762
  unauthenticated: bool = False,
1760
1763
  h2_enabled: bool = False,
@@ -1799,6 +1802,7 @@ class App:
1799
1802
  scaleup_window: Seconds of sustained demand required before scaling up new containers.
1800
1803
  scaledown_window: Maximum duration (in seconds) idle containers wait before scaling down.
1801
1804
  startup_timeout: Maximum container startup time in seconds.
1805
+ name: Sets the Modal name of the function within the app, defaults to class name.
1802
1806
  port: Port the HTTP server listens on.
1803
1807
  unauthenticated: Whether the endpoint requires proxy authentication; required by default.
1804
1808
  h2_enabled: Enable HTTP/2.
@@ -51,7 +51,7 @@ async def list_(env: str | None = None, json: bool = False):
51
51
  [
52
52
  c.cluster_id,
53
53
  c.app_id,
54
- timestamp_to_localized_str(c.started_at, json) if c.started_at else "Pending",
54
+ timestamp_to_localized_str(c.started_at, json) or "Pending",
55
55
  str(len(c.task_ids)),
56
56
  ]
57
57
  )
@@ -21,7 +21,7 @@ async def dashboard(
21
21
  if object_id:
22
22
  url = f"https://modal.com/id/{object_id}"
23
23
  else:
24
- env = config.get("environment")
24
+ env = config.get("environment") or ""
25
25
  request = api_pb2.WorkspaceDashboardUrlRequest(environment_name=env)
26
26
  client = await _Client.from_env()
27
27
  response = await client.stub.WorkspaceDashboardUrlGet(request)
@@ -207,7 +207,7 @@ async def create(
207
207
  raise click.UsageError("--custom-volume-path requires --custom-volume-name.")
208
208
 
209
209
  env_name = ensure_env(env)
210
- environment_name = _get_environment_name(env_name) or ""
210
+ environment_name = _get_environment_name(env_name)
211
211
  client = await _Client.from_env()
212
212
 
213
213
  compute_region_spec = api_pb2.EndpointComputeRegionSpec()
@@ -271,7 +271,7 @@ async def create(
271
271
  async def list_(*, json: bool = False, env: Optional[str] = None):
272
272
  """List Endpoints that are provisioning or running in an environment."""
273
273
  env_name = ensure_env(env)
274
- environment_name = _get_environment_name(env_name) or ""
274
+ environment_name = _get_environment_name(env_name)
275
275
  client = await _Client.from_env()
276
276
 
277
277
  items: list[api_pb2.EndpointListItem] = []