modal 1.4.3.dev13__tar.gz → 1.4.3.dev15__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 (216) hide show
  1. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/PKG-INFO +1 -1
  2. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/__init__.py +2 -0
  3. modal-1.4.3.dev15/modal/_environments.py +470 -0
  4. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/name_utils.py +1 -1
  5. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/app.py +1 -1
  6. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/cluster.py +1 -1
  7. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/container.py +1 -1
  8. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/dict.py +1 -1
  9. modal-1.4.3.dev15/modal/cli/environment.py +179 -0
  10. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/network_file_system.py +1 -1
  11. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/queues.py +1 -1
  12. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/run.py +1 -1
  13. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/secret.py +1 -1
  14. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/shell.py +1 -1
  15. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/volume.py +1 -1
  16. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/client.pyi +2 -2
  17. modal-1.4.3.dev15/modal/environments.py +31 -0
  18. modal-1.4.3.dev15/modal/environments.pyi +343 -0
  19. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/exception.py +4 -0
  20. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/functions.pyi +6 -6
  21. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/image.py +1 -1
  22. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/runner.py +1 -1
  23. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal.egg-info/PKG-INFO +1 -1
  24. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal.egg-info/SOURCES.txt +1 -0
  25. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_version/__init__.py +1 -1
  26. modal-1.4.3.dev13/modal/cli/environment.py +0 -117
  27. modal-1.4.3.dev13/modal/environments.py +0 -164
  28. modal-1.4.3.dev13/modal/environments.pyi +0 -120
  29. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/LICENSE +0 -0
  30. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/README.md +0 -0
  31. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/__main__.py +0 -0
  32. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_billing.py +0 -0
  33. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_clustered_functions.py +0 -0
  34. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_clustered_functions.pyi +0 -0
  35. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_container_entrypoint.py +0 -0
  36. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_functions.py +0 -0
  37. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_grpc_client.py +0 -0
  38. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_ipython.py +0 -0
  39. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_load_context.py +0 -0
  40. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_location.py +0 -0
  41. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_logs.py +0 -0
  42. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_object.py +0 -0
  43. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_output/__init__.py +0 -0
  44. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_output/manager.py +0 -0
  45. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_output/pty.py +0 -0
  46. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_output/rich.py +0 -0
  47. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_output/status.py +0 -0
  48. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_partial_function.py +0 -0
  49. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_resolver.py +0 -0
  50. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_resources.py +0 -0
  51. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_runtime/__init__.py +0 -0
  52. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_runtime/asgi.py +0 -0
  53. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_runtime/container_io_manager.py +0 -0
  54. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_runtime/container_io_manager.pyi +0 -0
  55. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_runtime/execution_context.py +0 -0
  56. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_runtime/execution_context.pyi +0 -0
  57. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  58. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_runtime/telemetry.py +0 -0
  59. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_runtime/user_code_event_loop.py +0 -0
  60. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_runtime/user_code_imports.py +0 -0
  61. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_serialization.py +0 -0
  62. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_server.py +0 -0
  63. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_traceback.py +0 -0
  64. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_tunnel.py +0 -0
  65. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_tunnel.pyi +0 -0
  66. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_type_manager.py +0 -0
  67. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/__init__.py +0 -0
  68. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/app_utils.py +0 -0
  69. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/async_utils.py +0 -0
  70. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/auth_token_manager.py +0 -0
  71. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/blob_utils.py +0 -0
  72. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/browser_utils.py +0 -0
  73. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/bytes_io_segment_payload.py +0 -0
  74. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/deprecation.py +0 -0
  75. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/docker_utils.py +0 -0
  76. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/function_utils.py +0 -0
  77. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/git_utils.py +0 -0
  78. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/grpc_testing.py +0 -0
  79. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/grpc_utils.py +0 -0
  80. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/hash_utils.py +0 -0
  81. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/http_utils.py +0 -0
  82. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/jwt_utils.py +0 -0
  83. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/logger.py +0 -0
  84. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/mount_utils.py +0 -0
  85. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/package_utils.py +0 -0
  86. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/pattern_utils.py +0 -0
  87. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/rand_pb_testing.py +0 -0
  88. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/sandbox_fs_utils.py +0 -0
  89. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/shell_utils.py +0 -0
  90. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/task_command_router_client.py +0 -0
  91. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_utils/time_utils.py +0 -0
  92. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_vendor/__init__.py +0 -0
  93. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  94. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_vendor/cloudpickle.py +0 -0
  95. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_vendor/tblib.py +0 -0
  96. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_vendor/version.py +0 -0
  97. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/_watcher.py +0 -0
  98. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/app.py +0 -0
  99. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/app.pyi +0 -0
  100. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/billing.py +0 -0
  101. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/builder/2023.12.312.txt +0 -0
  102. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/builder/2023.12.txt +0 -0
  103. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/builder/2024.04.txt +0 -0
  104. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/builder/2024.10.txt +0 -0
  105. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/builder/2025.06.txt +0 -0
  106. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/builder/PREVIEW.txt +0 -0
  107. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/builder/README.md +0 -0
  108. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/builder/base-images.json +0 -0
  109. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/call_graph.py +0 -0
  110. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/__init__.py +0 -0
  111. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/_download.py +0 -0
  112. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/_help.py +0 -0
  113. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/_traceback.py +0 -0
  114. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/billing.py +0 -0
  115. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/bootstrap.py +0 -0
  116. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/changelog.py +0 -0
  117. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/config.py +0 -0
  118. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/dashboard.py +0 -0
  119. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/entry_point.py +0 -0
  120. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/import_refs.py +0 -0
  121. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/launch.py +0 -0
  122. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/logo.py +0 -0
  123. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/profile.py +0 -0
  124. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/programs/__init__.py +0 -0
  125. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/programs/run_jupyter.py +0 -0
  126. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/programs/vscode.py +0 -0
  127. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/selector.py +0 -0
  128. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/token.py +0 -0
  129. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cli/utils.py +0 -0
  130. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/client.py +0 -0
  131. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cloud_bucket_mount.py +0 -0
  132. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cloud_bucket_mount.pyi +0 -0
  133. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cls.py +0 -0
  134. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/cls.pyi +0 -0
  135. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/config.py +0 -0
  136. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/container_process.py +0 -0
  137. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/container_process.pyi +0 -0
  138. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/dict.py +0 -0
  139. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/dict.pyi +0 -0
  140. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/experimental/__init__.py +0 -0
  141. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/experimental/flash.py +0 -0
  142. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/experimental/flash.pyi +0 -0
  143. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/experimental/ipython.py +0 -0
  144. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/file_io.py +0 -0
  145. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/file_io.pyi +0 -0
  146. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/file_pattern_matcher.py +0 -0
  147. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/functions.py +0 -0
  148. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/image.pyi +0 -0
  149. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/io_streams.py +0 -0
  150. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/io_streams.pyi +0 -0
  151. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/mount.py +0 -0
  152. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/mount.pyi +0 -0
  153. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/network_file_system.py +0 -0
  154. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/network_file_system.pyi +0 -0
  155. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/object.py +0 -0
  156. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/object.pyi +0 -0
  157. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/output.py +0 -0
  158. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/parallel_map.py +0 -0
  159. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/parallel_map.pyi +0 -0
  160. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/partial_function.py +0 -0
  161. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/partial_function.pyi +0 -0
  162. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/proxy.py +0 -0
  163. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/proxy.pyi +0 -0
  164. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/py.typed +0 -0
  165. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/queue.py +0 -0
  166. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/queue.pyi +0 -0
  167. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/retries.py +0 -0
  168. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/runner.pyi +0 -0
  169. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/running_app.py +0 -0
  170. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/sandbox.py +0 -0
  171. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/sandbox.pyi +0 -0
  172. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/sandbox_fs.py +0 -0
  173. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/sandbox_fs.pyi +0 -0
  174. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/schedule.py +0 -0
  175. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/scheduler_placement.py +0 -0
  176. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/secret.py +0 -0
  177. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/secret.pyi +0 -0
  178. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/server.py +0 -0
  179. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/server.pyi +0 -0
  180. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/serving.py +0 -0
  181. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/serving.pyi +0 -0
  182. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/snapshot.py +0 -0
  183. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/snapshot.pyi +0 -0
  184. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/stream_type.py +0 -0
  185. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/token_flow.py +0 -0
  186. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/token_flow.pyi +0 -0
  187. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/volume.py +0 -0
  188. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal/volume.pyi +0 -0
  189. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal.egg-info/dependency_links.txt +0 -0
  190. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal.egg-info/entry_points.txt +0 -0
  191. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal.egg-info/requires.txt +0 -0
  192. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal.egg-info/top_level.txt +0 -0
  193. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_docs/__init__.py +0 -0
  194. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_docs/gen_cli_docs.py +0 -0
  195. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_docs/gen_cli_docs_main.py +0 -0
  196. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_docs/gen_reference_docs.py +0 -0
  197. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_docs/gen_reference_docs_main.py +0 -0
  198. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_docs/mdmd/__init__.py +0 -0
  199. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_docs/mdmd/mdmd.py +0 -0
  200. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_docs/mdmd/signatures.py +0 -0
  201. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_proto/__init__.py +0 -0
  202. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_proto/api_grpc.py +0 -0
  203. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_proto/api_pb2.py +0 -0
  204. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_proto/api_pb2.pyi +0 -0
  205. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_proto/api_pb2_grpc.py +0 -0
  206. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_proto/api_pb2_grpc.pyi +0 -0
  207. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_proto/modal_api_grpc.py +0 -0
  208. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_proto/py.typed +0 -0
  209. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_proto/task_command_router_grpc.py +0 -0
  210. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_proto/task_command_router_pb2.py +0 -0
  211. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_proto/task_command_router_pb2.pyi +0 -0
  212. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  213. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  214. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/modal_version/__main__.py +0 -0
  215. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/pyproject.toml +0 -0
  216. {modal-1.4.3.dev13 → modal-1.4.3.dev15}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.4.3.dev13
3
+ Version: 1.4.3.dev15
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -17,6 +17,7 @@ try:
17
17
  from .cloud_bucket_mount import CloudBucketMount
18
18
  from .cls import Cls, parameter
19
19
  from .dict import Dict
20
+ from .environments import Environment
20
21
  from .exception import Error
21
22
  from .file_pattern_matcher import FilePatternMatcher
22
23
  from .functions import Function, FunctionCall
@@ -61,6 +62,7 @@ __all__ = [
61
62
  "Cls",
62
63
  "Cron",
63
64
  "Dict",
65
+ "Environment",
64
66
  "Error",
65
67
  "FilePatternMatcher",
66
68
  "Function",
@@ -0,0 +1,470 @@
1
+ # Copyright Modal Labs 2023
2
+ import asyncio
3
+ import builtins
4
+ from collections.abc import Iterable, Mapping
5
+ from dataclasses import dataclass
6
+ from typing import Literal, Optional
7
+
8
+ from google.protobuf.empty_pb2 import Empty
9
+ from google.protobuf.message import Message
10
+ from google.protobuf.wrappers_pb2 import StringValue
11
+ from synchronicity import classproperty
12
+
13
+ from modal_proto import api_pb2
14
+
15
+ from ._load_context import LoadContext
16
+ from ._object import _Object
17
+ from ._resolver import Resolver
18
+ from ._utils.name_utils import check_environment_name
19
+ from .client import _Client
20
+ from .config import config, logger
21
+ from .exception import InvalidError, WorkspaceManagementError
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class EnvironmentSettings:
26
+ image_builder_version: str
27
+ webhook_suffix: str
28
+
29
+
30
+ class _EnvironmentManager:
31
+ """Namespace with methods for managing Environment objects."""
32
+
33
+ async def create(
34
+ self,
35
+ name: str, # Name to use for the new Environment
36
+ *,
37
+ restricted: bool = False, # If True, enable RBAC restrictions on the Environment
38
+ client: Optional[_Client] = None, # Optional client with Modal credentials
39
+ ) -> None:
40
+ """Create a new Environment.
41
+
42
+ **Examples:**
43
+
44
+ ```python notest
45
+ modal.Environment.objects.create("my-environment")
46
+ ```
47
+ """
48
+ check_environment_name(name)
49
+ client = await _Client.from_env() if client is None else client
50
+ await client.stub.EnvironmentCreate(api_pb2.EnvironmentCreateRequest(name=name, is_managed=restricted))
51
+
52
+ async def list(
53
+ self,
54
+ *,
55
+ client: Optional[_Client] = None, # Optional client with Modal credentials
56
+ ) -> builtins.list["_Environment"]:
57
+ """Return a list of hydrated Environment objects.
58
+
59
+ **Examples:**
60
+
61
+ ```python notest
62
+ environments = modal.Environment.objects.list()
63
+ print([e.name for e in environments])
64
+ ```
65
+ """
66
+ client = await _Client.from_env() if client is None else client
67
+ resp = await client.stub.EnvironmentList(Empty())
68
+ environments = []
69
+ for item in resp.items:
70
+ metadata = api_pb2.EnvironmentMetadata(
71
+ name=item.name,
72
+ settings=api_pb2.EnvironmentSettings(webhook_suffix=item.webhook_suffix),
73
+ )
74
+ env = _Environment._new_hydrated(
75
+ item.environment_id,
76
+ client,
77
+ metadata,
78
+ is_another_app=True,
79
+ rep=f"Environment.from_name({item.name!r})",
80
+ )
81
+ environments.append(env)
82
+ return environments
83
+
84
+ async def delete(
85
+ self,
86
+ name: str, # Name of the Environment to delete
87
+ *,
88
+ client: Optional[_Client] = None, # Optional client with Modal credentials
89
+ ) -> None:
90
+ """Delete a named Environment.
91
+
92
+ Warning: This is irreversible and will transitively delete all objects in the Environment.
93
+
94
+ **Examples:**
95
+
96
+ ```python notest
97
+ modal.Environment.objects.delete("my-environment")
98
+ ```
99
+ """
100
+ client = await _Client.from_env() if client is None else client
101
+ await client.stub.EnvironmentDelete(api_pb2.EnvironmentDeleteRequest(name=name))
102
+
103
+
104
+ MemberRole = Literal["viewer", "contributor"]
105
+
106
+
107
+ def _role_to_proto(role: str) -> api_pb2.EnvironmentRole.ValueType:
108
+ match role:
109
+ case "viewer":
110
+ return api_pb2.ENVIRONMENT_ROLE_VIEWER
111
+ case "contributor":
112
+ return api_pb2.ENVIRONMENT_ROLE_CONTRIBUTOR
113
+ case _:
114
+ raise InvalidError(f"Invalid Environment role: {role!r} (expected 'viewer' or 'contributor')")
115
+
116
+
117
+ def _role_from_proto(proto_value: int) -> MemberRole:
118
+ match proto_value:
119
+ case int(v) if v == api_pb2.ENVIRONMENT_ROLE_VIEWER:
120
+ return "viewer"
121
+ case int(v) if v == api_pb2.ENVIRONMENT_ROLE_CONTRIBUTOR:
122
+ return "contributor"
123
+ case _:
124
+ raise ValueError(f"Unknown environment role: {proto_value}")
125
+
126
+
127
+ class _EnvironmentMembersManager:
128
+ """mdmd:namespace
129
+ Namespace with methods for managing the membership of a restricted Environment.
130
+
131
+ See https://modal.com/docs/guide/rbac for more information on restricted Environments.
132
+ """
133
+
134
+ def __init__(self, environment: "_Environment"):
135
+ """mdmd:hidden"""
136
+ self._environment = environment
137
+
138
+ async def list(self) -> dict[Literal["users", "service_users"], dict[str, MemberRole]]:
139
+ """Return the members of a restricted Environment with their roles.
140
+
141
+ **Examples:**
142
+
143
+ ```python notest
144
+ members = modal.Environment.from_name("my-restricted-env").members.list()
145
+ print(members)
146
+ # {
147
+ # "users": {"alice": "contributor", "bob": "viewer"},
148
+ # "service_users": {"alice-bot": "contributor"},
149
+ # }
150
+ ```
151
+ """
152
+ await self._environment.hydrate()
153
+ req = api_pb2.EnvironmentGetManagedRequest(environment_id=self._environment.object_id)
154
+ resp = await self._environment.client.stub.EnvironmentGetManaged(req)
155
+
156
+ users: dict[str, MemberRole] = {}
157
+ service_users: dict[str, MemberRole] = {}
158
+ for principal in resp.principal_roles:
159
+ role = _role_from_proto(principal.role)
160
+ if principal.user_id:
161
+ users[principal.user_name] = role
162
+ elif principal.service_user_id:
163
+ service_users[principal.service_user_name] = role
164
+
165
+ return {"users": users, "service_users": service_users}
166
+
167
+ async def update(
168
+ self,
169
+ *,
170
+ users: Optional[Mapping[str, MemberRole]] = None,
171
+ service_users: Optional[Mapping[str, MemberRole]] = None,
172
+ ) -> None:
173
+ """Add or modify roles for members of a restricted Environment.
174
+
175
+ Each user or service user will be added to the Environment if not currently a member;
176
+ if already a member, the user or service user's role will be updated.
177
+
178
+ **Examples:**
179
+
180
+ ```python notest
181
+ env = modal.Environment.from_name("my-restricted-env")
182
+ env.members.update(
183
+ users={"alice": "contributor", "bob": "viewer"},
184
+ service_users={"alice-bot": "contributor"},
185
+ )
186
+ ```
187
+ """
188
+ await self._environment.hydrate()
189
+ users = users or {}
190
+ service_users = service_users or {}
191
+
192
+ req = api_pb2.EnvironmentGetManagedRequest(environment_id=self._environment.object_id)
193
+ resp = await self._environment.client.stub.EnvironmentGetManaged(req)
194
+
195
+ # Both current members and additional eligible workspace principals can be assigned a role
196
+ user_name_to_id: dict[str, str] = {}
197
+ service_user_name_to_id: dict[str, str] = {}
198
+ for principal in [*resp.principal_roles, *resp.additional_roles]:
199
+ if principal.user_id:
200
+ user_name_to_id[principal.user_name] = principal.user_id
201
+ elif principal.service_user_id:
202
+ service_user_name_to_id[principal.service_user_name] = principal.service_user_id
203
+
204
+ requests: dict[str, api_pb2.EnvironmentRoleSetRequest] = {}
205
+ for name, role in users.items():
206
+ if name not in user_name_to_id:
207
+ raise InvalidError(f"User {name!r} not found in workspace")
208
+ requests[f"User {name!r}"] = api_pb2.EnvironmentRoleSetRequest(
209
+ environment_id=self._environment.object_id,
210
+ user_id=user_name_to_id[name],
211
+ role=_role_to_proto(role),
212
+ )
213
+ for name, role in service_users.items():
214
+ if name not in service_user_name_to_id:
215
+ raise InvalidError(f"Service user {name!r} not found in workspace")
216
+ requests[f"Service user {name!r}"] = api_pb2.EnvironmentRoleSetRequest(
217
+ environment_id=self._environment.object_id,
218
+ service_user_id=service_user_name_to_id[name],
219
+ role=_role_to_proto(role),
220
+ )
221
+
222
+ await self._dispatch_role_updates(requests)
223
+
224
+ async def remove(
225
+ self,
226
+ *,
227
+ users: Optional[Iterable[str]] = None,
228
+ service_users: Optional[Iterable[str]] = None,
229
+ ) -> None:
230
+ """Remove members from a restricted Environment.
231
+
232
+ **Examples:**
233
+
234
+ ```python notest
235
+ env = modal.Environment.from_name("my-restricted-env")
236
+ env.members.remove(
237
+ users=["alice"],
238
+ service_users=["alice-bot"],
239
+ )
240
+ ```
241
+ """
242
+ await self._environment.hydrate()
243
+ users = users or []
244
+ service_users = service_users or []
245
+
246
+ req = api_pb2.EnvironmentGetManagedRequest(environment_id=self._environment.object_id)
247
+ resp = await self._environment.client.stub.EnvironmentGetManaged(req)
248
+
249
+ user_name_to_id: dict[str, str] = {}
250
+ service_user_name_to_id: dict[str, str] = {}
251
+ for principal in resp.principal_roles:
252
+ if principal.user_id:
253
+ user_name_to_id[principal.user_name] = principal.user_id
254
+ elif principal.service_user_id:
255
+ service_user_name_to_id[principal.service_user_name] = principal.service_user_id
256
+
257
+ requests: dict[str, api_pb2.EnvironmentRoleSetRequest] = {}
258
+ for name in users:
259
+ if name not in user_name_to_id:
260
+ raise InvalidError(f"User {name!r} is not a member of this Environment")
261
+ requests[f"User {name!r}"] = api_pb2.EnvironmentRoleSetRequest(
262
+ environment_id=self._environment.object_id,
263
+ user_id=user_name_to_id[name],
264
+ role=api_pb2.ENVIRONMENT_ROLE_UNSPECIFIED,
265
+ )
266
+ for name in service_users:
267
+ if name not in service_user_name_to_id:
268
+ raise InvalidError(f"Service user {name!r} is not a member of this Environment")
269
+ requests[f"Service user {name!r}"] = api_pb2.EnvironmentRoleSetRequest(
270
+ environment_id=self._environment.object_id,
271
+ service_user_id=service_user_name_to_id[name],
272
+ role=api_pb2.ENVIRONMENT_ROLE_UNSPECIFIED,
273
+ )
274
+
275
+ await self._dispatch_role_updates(requests)
276
+
277
+ async def _dispatch_role_updates(self, requests: dict[str, api_pb2.EnvironmentRoleSetRequest]) -> None:
278
+ """Send batched EnvironmentRoleSet RPCs and report all errors encountered."""
279
+ results = await asyncio.gather(
280
+ *(self._environment.client.stub.EnvironmentRoleSet(req) for req in requests.values()),
281
+ return_exceptions=True,
282
+ )
283
+ errors = [(label, result) for label, result in zip(requests.keys(), results) if isinstance(result, Exception)]
284
+ if errors:
285
+ n = len(errors)
286
+ header = f"{n} error{'s' if n != 1 else ''} occurred while updating Environment members:"
287
+ details = "\n".join(f" - {label}: {e}" for label, e in errors)
288
+ raise WorkspaceManagementError(f"{header}\n{details}")
289
+
290
+
291
+ class _Environment(_Object, type_prefix="en"):
292
+ _name: Optional[str] = None
293
+ _settings: EnvironmentSettings
294
+
295
+ def __init__(self):
296
+ """mdmd:hidden"""
297
+ raise RuntimeError(
298
+ "`Environment(...)` constructor is not allowed. "
299
+ "Use `Environment.from_name` or `Environment.from_context` instead."
300
+ )
301
+
302
+ @property
303
+ def name(self) -> Optional[str]:
304
+ return self._name
305
+
306
+ @classproperty
307
+ @classmethod
308
+ def objects(cls) -> _EnvironmentManager:
309
+ return _EnvironmentManager()
310
+
311
+ @property
312
+ def members(self) -> "_EnvironmentMembersManager":
313
+ return _EnvironmentMembersManager(self)
314
+
315
+ # TODO(michael) Keeping this private for now until we decide what else should be in it
316
+ # And what the rules should be about updates / mutability
317
+ # @property
318
+ # def settings(self) -> EnvironmentSettings:
319
+ # return self._settings
320
+
321
+ def _hydrate_metadata(self, metadata: Message):
322
+ # Overridden concrete implementation of base class method
323
+ assert metadata and isinstance(metadata, api_pb2.EnvironmentMetadata)
324
+ self._name = metadata.name or None
325
+
326
+ # Is there a simpler way to go Message -> Dataclass?
327
+ self._settings = EnvironmentSettings(
328
+ image_builder_version=metadata.settings.image_builder_version,
329
+ webhook_suffix=metadata.settings.webhook_suffix,
330
+ )
331
+
332
+ @staticmethod
333
+ def _get_or_create(
334
+ name: str, repr: str, create_if_missing: bool = False, client: Optional[_Client] = None
335
+ ) -> "_Environment":
336
+ async def _load(
337
+ self: _Environment, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
338
+ ):
339
+ request = api_pb2.EnvironmentGetOrCreateRequest(
340
+ deployment_name=name,
341
+ object_creation_type=(
342
+ api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING
343
+ if create_if_missing
344
+ else api_pb2.OBJECT_CREATION_TYPE_UNSPECIFIED
345
+ ),
346
+ )
347
+ response = await load_context.client.stub.EnvironmentGetOrCreate(request)
348
+ logger.debug(f"Created environment with id {response.environment_id}")
349
+ self._hydrate(response.environment_id, load_context.client, response.metadata)
350
+
351
+ return _Environment._from_loader(
352
+ _load,
353
+ repr,
354
+ is_another_app=True,
355
+ hydrate_lazily=True,
356
+ name=name,
357
+ load_context_overrides=LoadContext(client=client),
358
+ )
359
+
360
+ @staticmethod
361
+ def from_context(*, client: Optional[_Client] = None) -> "_Environment":
362
+ """Look up an Environment object using the current context.
363
+
364
+ This method returns the Environment that is defined by the local configuration
365
+ (i.e., your active profile or the `MODAL_ENVIRONMENT` environment variable), or
366
+ it fetches the default environment from the server when not defined locally.
367
+ If called inside a Modal container, it will return the Environment that container
368
+ is associated with.
369
+
370
+ """
371
+ name = config.get("environment") or "" # null string falls back to server default
372
+ return _Environment._get_or_create(
373
+ name=name,
374
+ repr="Environment.from_context()",
375
+ create_if_missing=False,
376
+ client=client,
377
+ )
378
+
379
+ @staticmethod
380
+ def from_name(
381
+ name: str,
382
+ *,
383
+ create_if_missing: bool = False,
384
+ client: Optional[_Client] = None,
385
+ ) -> "_Environment":
386
+ """Look up an Environment object using its name."""
387
+ check_environment_name(name)
388
+ return _Environment._get_or_create(
389
+ name=name,
390
+ repr=f"Environment.from_name({name!r})",
391
+ create_if_missing=create_if_missing,
392
+ client=client,
393
+ )
394
+
395
+
396
+ ENVIRONMENT_CACHE: dict[str, _Environment] = {}
397
+
398
+
399
+ async def _get_environment_cached(name: str, client: _Client) -> _Environment:
400
+ if name in ENVIRONMENT_CACHE:
401
+ return ENVIRONMENT_CACHE[name]
402
+ if name:
403
+ environment = await _Environment.from_name(name, client=client).hydrate()
404
+ else:
405
+ environment = await _Environment.from_context(client=client).hydrate()
406
+ ENVIRONMENT_CACHE[name] = environment
407
+ return environment
408
+
409
+
410
+ # The following internal functions are functionally public API as users have come to
411
+ # depend on them while we did not have a proper Environment API. We can deprecate them
412
+ # and migrate users to the new object-oriented API, but that should happen gracefully.
413
+
414
+
415
+ async def _delete_environment(name: str, client: Optional[_Client] = None):
416
+ if client is None:
417
+ client = await _Client.from_env()
418
+ await client.stub.EnvironmentDelete(api_pb2.EnvironmentDeleteRequest(name=name))
419
+
420
+
421
+ async def _update_environment(
422
+ current_name: str,
423
+ *,
424
+ new_name: Optional[str] = None,
425
+ new_web_suffix: Optional[str] = None,
426
+ client: Optional[_Client] = None,
427
+ ):
428
+ new_name_pb2 = None
429
+ new_web_suffix_pb2 = None
430
+ if new_name is not None:
431
+ if len(new_name) < 1:
432
+ raise ValueError("The new environment name cannot be empty")
433
+
434
+ new_name_pb2 = StringValue(value=new_name)
435
+
436
+ if new_web_suffix is not None:
437
+ new_web_suffix_pb2 = StringValue(value=new_web_suffix)
438
+
439
+ update_payload = api_pb2.EnvironmentUpdateRequest(
440
+ current_name=current_name, name=new_name_pb2, web_suffix=new_web_suffix_pb2
441
+ )
442
+ if client is None:
443
+ client = await _Client.from_env()
444
+ await client.stub.EnvironmentUpdate(update_payload)
445
+
446
+
447
+ async def _create_environment(name: str, client: Optional[_Client] = None):
448
+ if client is None:
449
+ client = await _Client.from_env()
450
+ await client.stub.EnvironmentCreate(api_pb2.EnvironmentCreateRequest(name=name))
451
+
452
+
453
+ async def _list_environments(client: Optional[_Client] = None) -> list[api_pb2.EnvironmentListItem]:
454
+ if client is None:
455
+ client = await _Client.from_env()
456
+ resp = await client.stub.EnvironmentList(Empty())
457
+ return list(resp.items)
458
+
459
+
460
+ def ensure_env(environment_name: Optional[str] = None) -> str:
461
+ """Override config environment with environment from environment_name
462
+
463
+ This is necessary since a cli command that runs Modal code, without explicit
464
+ environment specification wouldn't pick up the environment specified in a
465
+ command line flag otherwise, e.g. when doing `modal run --env=foo`
466
+ """
467
+ if environment_name is not None:
468
+ config.override_locally("environment", environment_name)
469
+
470
+ return config.get("environment")
@@ -63,7 +63,7 @@ def check_object_name(name: str, object_type: str) -> None:
63
63
 
64
64
  def check_environment_name(name: str) -> None:
65
65
  message = (
66
- f"Invalid environment name: '{name}'."
66
+ f"Invalid Environment name: '{name}'."
67
67
  "\n\nEnvironment names can only start with alphanumeric characters,"
68
68
  " may contain only alphanumeric characters, dashes, periods, and underscores,"
69
69
  " and must be shorter than 64 characters."
@@ -12,12 +12,12 @@ from click import UsageError
12
12
  from rich.table import Column
13
13
  from rich.text import Text
14
14
 
15
+ from modal._environments import ensure_env
15
16
  from modal._object import _get_environment_name
16
17
  from modal._traceback import print_server_warnings
17
18
  from modal._utils.async_utils import synchronizer
18
19
  from modal._utils.browser_utils import open_url_and_display
19
20
  from modal.client import _Client
20
- from modal.environments import ensure_env
21
21
  from modal.exception import InvalidError, NotFoundError
22
22
  from modal.output import OutputManager
23
23
  from modal.runner import DEPLOYMENT_STRATEGY_TYPE, _stop_and_wait_for_containers
@@ -6,6 +6,7 @@ import click
6
6
  from rich.table import Column
7
7
  from rich.text import Text
8
8
 
9
+ from modal._environments import ensure_env
9
10
  from modal._object import _get_environment_name
10
11
  from modal._output.pty import get_pty_info
11
12
  from modal._utils.async_utils import synchronizer
@@ -15,7 +16,6 @@ from modal.cli.utils import display_table, env_option, is_tty
15
16
  from modal.client import _Client
16
17
  from modal.config import config
17
18
  from modal.container_process import _ContainerProcess
18
- from modal.environments import ensure_env
19
19
  from modal.exception import InvalidError
20
20
  from modal.output import OutputManager
21
21
  from modal.stream_type import StreamType
@@ -9,6 +9,7 @@ from click import UsageError
9
9
  from rich.table import Column
10
10
  from rich.text import Text
11
11
 
12
+ from modal._environments import ensure_env
12
13
  from modal._logs import _FETCH_LIMIT, _MAX_FETCH_RANGE, LogsFilters
13
14
  from modal._object import _get_environment_name
14
15
  from modal._output.pty import get_pty_info
@@ -29,7 +30,6 @@ from modal.cli.utils import (
29
30
  from modal.client import _Client
30
31
  from modal.config import config
31
32
  from modal.container_process import _ContainerProcess
32
- from modal.environments import ensure_env
33
33
  from modal.exception import InvalidError
34
34
  from modal.stream_type import StreamType
35
35
  from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
@@ -4,6 +4,7 @@ from typing import Optional
4
4
 
5
5
  import click
6
6
 
7
+ from modal._environments import ensure_env
7
8
  from modal._load_context import LoadContext
8
9
  from modal._resolver import Resolver
9
10
  from modal._utils.async_utils import TaskContext, synchronizer
@@ -11,7 +12,6 @@ from modal._utils.time_utils import timestamp_to_localized_str
11
12
  from modal.cli.utils import display_table, env_option, yes_option
12
13
  from modal.client import _Client
13
14
  from modal.dict import _Dict
14
- from modal.environments import ensure_env
15
15
  from modal.output import OutputManager
16
16
 
17
17
  from ._help import ModalGroup