modAL 1.0.3__tar.gz → 1.0.3.dev1__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 (185) hide show
  1. {modal-1.0.3 → modal-1.0.3.dev1}/PKG-INFO +2 -2
  2. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_functions.py +2 -3
  3. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_runtime/container_io_manager.py +14 -8
  4. modal-1.0.3.dev1/modal/_runtime/gpu_memory_snapshot.py +101 -0
  5. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/run.py +0 -12
  6. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/secret.py +4 -43
  7. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/volume.py +1 -6
  8. {modal-1.0.3 → modal-1.0.3.dev1}/modal/client.pyi +10 -2
  9. {modal-1.0.3 → modal-1.0.3.dev1}/modal/experimental/__init__.py +4 -6
  10. {modal-1.0.3 → modal-1.0.3.dev1}/modal/image.py +22 -38
  11. {modal-1.0.3 → modal-1.0.3.dev1}/modal/mount.py +4 -128
  12. {modal-1.0.3 → modal-1.0.3.dev1}/modal/mount.pyi +0 -22
  13. {modal-1.0.3 → modal-1.0.3.dev1}/modal/parallel_map.py +23 -47
  14. {modal-1.0.3 → modal-1.0.3.dev1}/modal/runner.py +7 -2
  15. {modal-1.0.3 → modal-1.0.3.dev1}/modal/sandbox.py +0 -13
  16. {modal-1.0.3 → modal-1.0.3.dev1}/modal/sandbox.pyi +0 -8
  17. {modal-1.0.3 → modal-1.0.3.dev1}/modal/volume.py +49 -17
  18. {modal-1.0.3 → modal-1.0.3.dev1}/modal/volume.pyi +43 -11
  19. {modal-1.0.3 → modal-1.0.3.dev1}/modal.egg-info/PKG-INFO +2 -2
  20. {modal-1.0.3 → modal-1.0.3.dev1}/modal.egg-info/requires.txt +1 -1
  21. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/api.proto +4 -19
  22. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/api_pb2.py +585 -591
  23. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/api_pb2.pyi +6 -32
  24. {modal-1.0.3 → modal-1.0.3.dev1}/modal_version/__init__.py +1 -1
  25. {modal-1.0.3 → modal-1.0.3.dev1}/pyproject.toml +1 -1
  26. modal-1.0.3/modal/_runtime/gpu_memory_snapshot.py +0 -199
  27. {modal-1.0.3 → modal-1.0.3.dev1}/LICENSE +0 -0
  28. {modal-1.0.3 → modal-1.0.3.dev1}/README.md +0 -0
  29. {modal-1.0.3 → modal-1.0.3.dev1}/modal/__init__.py +0 -0
  30. {modal-1.0.3 → modal-1.0.3.dev1}/modal/__main__.py +0 -0
  31. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_clustered_functions.py +0 -0
  32. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_clustered_functions.pyi +0 -0
  33. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_container_entrypoint.py +0 -0
  34. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_ipython.py +0 -0
  35. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_location.py +0 -0
  36. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_object.py +0 -0
  37. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_output.py +0 -0
  38. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_partial_function.py +0 -0
  39. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_pty.py +0 -0
  40. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_resolver.py +0 -0
  41. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_resources.py +0 -0
  42. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_runtime/__init__.py +0 -0
  43. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_runtime/asgi.py +0 -0
  44. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_runtime/container_io_manager.pyi +0 -0
  45. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_runtime/execution_context.py +0 -0
  46. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_runtime/execution_context.pyi +0 -0
  47. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_runtime/telemetry.py +0 -0
  48. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_runtime/user_code_imports.py +0 -0
  49. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_serialization.py +0 -0
  50. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_traceback.py +0 -0
  51. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_tunnel.py +0 -0
  52. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_tunnel.pyi +0 -0
  53. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_type_manager.py +0 -0
  54. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/__init__.py +0 -0
  55. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/app_utils.py +0 -0
  56. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/async_utils.py +0 -0
  57. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/blob_utils.py +0 -0
  58. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/bytes_io_segment_payload.py +0 -0
  59. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/deprecation.py +0 -0
  60. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/docker_utils.py +0 -0
  61. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/function_utils.py +0 -0
  62. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/git_utils.py +0 -0
  63. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/grpc_testing.py +0 -0
  64. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/grpc_utils.py +0 -0
  65. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/hash_utils.py +0 -0
  66. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/http_utils.py +0 -0
  67. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/jwt_utils.py +0 -0
  68. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/logger.py +0 -0
  69. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/mount_utils.py +0 -0
  70. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/name_utils.py +0 -0
  71. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/package_utils.py +0 -0
  72. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/pattern_utils.py +0 -0
  73. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/rand_pb_testing.py +0 -0
  74. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/shell_utils.py +0 -0
  75. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_utils/time_utils.py +0 -0
  76. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_vendor/__init__.py +0 -0
  77. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  78. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_vendor/cloudpickle.py +0 -0
  79. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_vendor/tblib.py +0 -0
  80. {modal-1.0.3 → modal-1.0.3.dev1}/modal/_watcher.py +0 -0
  81. {modal-1.0.3 → modal-1.0.3.dev1}/modal/app.py +0 -0
  82. {modal-1.0.3 → modal-1.0.3.dev1}/modal/app.pyi +0 -0
  83. {modal-1.0.3 → modal-1.0.3.dev1}/modal/call_graph.py +0 -0
  84. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/__init__.py +0 -0
  85. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/_download.py +0 -0
  86. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/_traceback.py +0 -0
  87. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/app.py +0 -0
  88. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/cluster.py +0 -0
  89. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/config.py +0 -0
  90. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/container.py +0 -0
  91. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/dict.py +0 -0
  92. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/entry_point.py +0 -0
  93. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/environment.py +0 -0
  94. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/import_refs.py +0 -0
  95. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/launch.py +0 -0
  96. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/network_file_system.py +0 -0
  97. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/profile.py +0 -0
  98. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/programs/__init__.py +0 -0
  99. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/programs/run_jupyter.py +0 -0
  100. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/programs/vscode.py +0 -0
  101. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/queues.py +0 -0
  102. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/token.py +0 -0
  103. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cli/utils.py +0 -0
  104. {modal-1.0.3 → modal-1.0.3.dev1}/modal/client.py +0 -0
  105. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cloud_bucket_mount.py +0 -0
  106. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cloud_bucket_mount.pyi +0 -0
  107. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cls.py +0 -0
  108. {modal-1.0.3 → modal-1.0.3.dev1}/modal/cls.pyi +0 -0
  109. {modal-1.0.3 → modal-1.0.3.dev1}/modal/config.py +0 -0
  110. {modal-1.0.3 → modal-1.0.3.dev1}/modal/container_process.py +0 -0
  111. {modal-1.0.3 → modal-1.0.3.dev1}/modal/container_process.pyi +0 -0
  112. {modal-1.0.3 → modal-1.0.3.dev1}/modal/dict.py +0 -0
  113. {modal-1.0.3 → modal-1.0.3.dev1}/modal/dict.pyi +0 -0
  114. {modal-1.0.3 → modal-1.0.3.dev1}/modal/environments.py +0 -0
  115. {modal-1.0.3 → modal-1.0.3.dev1}/modal/environments.pyi +0 -0
  116. {modal-1.0.3 → modal-1.0.3.dev1}/modal/exception.py +0 -0
  117. {modal-1.0.3 → modal-1.0.3.dev1}/modal/experimental/ipython.py +0 -0
  118. {modal-1.0.3 → modal-1.0.3.dev1}/modal/file_io.py +0 -0
  119. {modal-1.0.3 → modal-1.0.3.dev1}/modal/file_io.pyi +0 -0
  120. {modal-1.0.3 → modal-1.0.3.dev1}/modal/file_pattern_matcher.py +0 -0
  121. {modal-1.0.3 → modal-1.0.3.dev1}/modal/functions.py +0 -0
  122. {modal-1.0.3 → modal-1.0.3.dev1}/modal/functions.pyi +0 -0
  123. {modal-1.0.3 → modal-1.0.3.dev1}/modal/gpu.py +0 -0
  124. {modal-1.0.3 → modal-1.0.3.dev1}/modal/image.pyi +0 -0
  125. {modal-1.0.3 → modal-1.0.3.dev1}/modal/io_streams.py +0 -0
  126. {modal-1.0.3 → modal-1.0.3.dev1}/modal/io_streams.pyi +0 -0
  127. {modal-1.0.3 → modal-1.0.3.dev1}/modal/network_file_system.py +0 -0
  128. {modal-1.0.3 → modal-1.0.3.dev1}/modal/network_file_system.pyi +0 -0
  129. {modal-1.0.3 → modal-1.0.3.dev1}/modal/object.py +0 -0
  130. {modal-1.0.3 → modal-1.0.3.dev1}/modal/object.pyi +0 -0
  131. {modal-1.0.3 → modal-1.0.3.dev1}/modal/output.py +0 -0
  132. {modal-1.0.3 → modal-1.0.3.dev1}/modal/parallel_map.pyi +0 -0
  133. {modal-1.0.3 → modal-1.0.3.dev1}/modal/partial_function.py +0 -0
  134. {modal-1.0.3 → modal-1.0.3.dev1}/modal/partial_function.pyi +0 -0
  135. {modal-1.0.3 → modal-1.0.3.dev1}/modal/proxy.py +0 -0
  136. {modal-1.0.3 → modal-1.0.3.dev1}/modal/proxy.pyi +0 -0
  137. {modal-1.0.3 → modal-1.0.3.dev1}/modal/py.typed +0 -0
  138. {modal-1.0.3 → modal-1.0.3.dev1}/modal/queue.py +0 -0
  139. {modal-1.0.3 → modal-1.0.3.dev1}/modal/queue.pyi +0 -0
  140. {modal-1.0.3 → modal-1.0.3.dev1}/modal/requirements/2023.12.312.txt +0 -0
  141. {modal-1.0.3 → modal-1.0.3.dev1}/modal/requirements/2023.12.txt +0 -0
  142. {modal-1.0.3 → modal-1.0.3.dev1}/modal/requirements/2024.04.txt +0 -0
  143. {modal-1.0.3 → modal-1.0.3.dev1}/modal/requirements/2024.10.txt +0 -0
  144. {modal-1.0.3 → modal-1.0.3.dev1}/modal/requirements/PREVIEW.txt +0 -0
  145. {modal-1.0.3 → modal-1.0.3.dev1}/modal/requirements/README.md +0 -0
  146. {modal-1.0.3 → modal-1.0.3.dev1}/modal/requirements/base-images.json +0 -0
  147. {modal-1.0.3 → modal-1.0.3.dev1}/modal/retries.py +0 -0
  148. {modal-1.0.3 → modal-1.0.3.dev1}/modal/runner.pyi +0 -0
  149. {modal-1.0.3 → modal-1.0.3.dev1}/modal/running_app.py +0 -0
  150. {modal-1.0.3 → modal-1.0.3.dev1}/modal/schedule.py +0 -0
  151. {modal-1.0.3 → modal-1.0.3.dev1}/modal/scheduler_placement.py +0 -0
  152. {modal-1.0.3 → modal-1.0.3.dev1}/modal/secret.py +0 -0
  153. {modal-1.0.3 → modal-1.0.3.dev1}/modal/secret.pyi +0 -0
  154. {modal-1.0.3 → modal-1.0.3.dev1}/modal/serving.py +0 -0
  155. {modal-1.0.3 → modal-1.0.3.dev1}/modal/serving.pyi +0 -0
  156. {modal-1.0.3 → modal-1.0.3.dev1}/modal/snapshot.py +0 -0
  157. {modal-1.0.3 → modal-1.0.3.dev1}/modal/snapshot.pyi +0 -0
  158. {modal-1.0.3 → modal-1.0.3.dev1}/modal/stream_type.py +0 -0
  159. {modal-1.0.3 → modal-1.0.3.dev1}/modal/token_flow.py +0 -0
  160. {modal-1.0.3 → modal-1.0.3.dev1}/modal/token_flow.pyi +0 -0
  161. {modal-1.0.3 → modal-1.0.3.dev1}/modal.egg-info/SOURCES.txt +0 -0
  162. {modal-1.0.3 → modal-1.0.3.dev1}/modal.egg-info/dependency_links.txt +0 -0
  163. {modal-1.0.3 → modal-1.0.3.dev1}/modal.egg-info/entry_points.txt +0 -0
  164. {modal-1.0.3 → modal-1.0.3.dev1}/modal.egg-info/top_level.txt +0 -0
  165. {modal-1.0.3 → modal-1.0.3.dev1}/modal_docs/__init__.py +0 -0
  166. {modal-1.0.3 → modal-1.0.3.dev1}/modal_docs/gen_cli_docs.py +0 -0
  167. {modal-1.0.3 → modal-1.0.3.dev1}/modal_docs/gen_reference_docs.py +0 -0
  168. {modal-1.0.3 → modal-1.0.3.dev1}/modal_docs/mdmd/__init__.py +0 -0
  169. {modal-1.0.3 → modal-1.0.3.dev1}/modal_docs/mdmd/mdmd.py +0 -0
  170. {modal-1.0.3 → modal-1.0.3.dev1}/modal_docs/mdmd/signatures.py +0 -0
  171. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/__init__.py +0 -0
  172. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/api_grpc.py +0 -0
  173. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/api_pb2_grpc.py +0 -0
  174. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/api_pb2_grpc.pyi +0 -0
  175. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/modal_api_grpc.py +0 -0
  176. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/modal_options_grpc.py +0 -0
  177. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/options.proto +0 -0
  178. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/options_grpc.py +0 -0
  179. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/options_pb2.py +0 -0
  180. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/options_pb2.pyi +0 -0
  181. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/options_pb2_grpc.py +0 -0
  182. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/options_pb2_grpc.pyi +0 -0
  183. {modal-1.0.3 → modal-1.0.3.dev1}/modal_proto/py.typed +0 -0
  184. {modal-1.0.3 → modal-1.0.3.dev1}/modal_version/__main__.py +0 -0
  185. {modal-1.0.3 → modal-1.0.3.dev1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.0.3
3
+ Version: 1.0.3.dev1
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -22,7 +22,7 @@ Requires-Dist: click~=8.1.0
22
22
  Requires-Dist: grpclib==0.4.7
23
23
  Requires-Dist: protobuf!=4.24.0,<7.0,>=3.19
24
24
  Requires-Dist: rich>=12.0.0
25
- Requires-Dist: synchronicity~=0.9.13
25
+ Requires-Dist: synchronicity~=0.9.12
26
26
  Requires-Dist: toml
27
27
  Requires-Dist: typer>=0.9
28
28
  Requires-Dist: types-certifi
@@ -101,7 +101,6 @@ if TYPE_CHECKING:
101
101
 
102
102
  MAX_INTERNAL_FAILURE_COUNT = 8
103
103
 
104
-
105
104
  @dataclasses.dataclass
106
105
  class _RetryContext:
107
106
  function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType"
@@ -1662,8 +1661,8 @@ Use the `Function.get_web_url()` method instead.
1662
1661
  async def spawn(self, *args: P.args, **kwargs: P.kwargs) -> "_FunctionCall[ReturnType]":
1663
1662
  """Calls the function with the given arguments, without waiting for the results.
1664
1663
 
1665
- Returns a [`modal.FunctionCall`](/docs/reference/modal.FunctionCall) object, that can later be polled or
1666
- waited for using [`.get(timeout=...)`](/docs/reference/modal.FunctionCall#get).
1664
+ Returns a `modal.FunctionCall` object, that can later be polled or
1665
+ waited for using `.get(timeout=...)`.
1667
1666
  Conceptually similar to `multiprocessing.pool.apply_async`, or a Future/Promise in other contexts.
1668
1667
  """
1669
1668
  self._check_no_web_url("spawn")
@@ -323,7 +323,6 @@ class _ContainerIOManager:
323
323
  self._heartbeat_loop = None
324
324
  self._heartbeat_condition = None
325
325
  self._waiting_for_memory_snapshot = False
326
- self._cuda_checkpoint_session = None
327
326
 
328
327
  self._is_interactivity_enabled = False
329
328
  self._fetching_inputs = True
@@ -882,11 +881,13 @@ class _ContainerIOManager:
882
881
  # Restore GPU memory.
883
882
  if self.function_def._experimental_enable_gpu_snapshot and self.function_def.resources.gpu_config.gpu_type:
884
883
  logger.debug("GPU memory snapshot enabled. Attempting to restore GPU memory.")
885
-
886
- assert self._cuda_checkpoint_session, (
887
- "CudaCheckpointSession not found when attempting to restore GPU memory"
888
- )
889
- self._cuda_checkpoint_session.restore()
884
+ gpu_process_state = gpu_memory_snapshot.get_state()
885
+ if gpu_process_state != gpu_memory_snapshot.CudaCheckpointState.CHECKPOINTED:
886
+ raise ValueError(
887
+ "Cannot restore GPU state if GPU isn't in a 'checkpointed' state. "
888
+ f"Current GPU state: {gpu_process_state}"
889
+ )
890
+ gpu_memory_snapshot.toggle()
890
891
 
891
892
  # Restore input to default state.
892
893
  self.current_input_id = None
@@ -906,9 +907,14 @@ class _ContainerIOManager:
906
907
  # Snapshot GPU memory.
907
908
  if self.function_def._experimental_enable_gpu_snapshot and self.function_def.resources.gpu_config.gpu_type:
908
909
  logger.debug("GPU memory snapshot enabled. Attempting to snapshot GPU memory.")
910
+ gpu_process_state = gpu_memory_snapshot.get_state()
911
+ if gpu_process_state != gpu_memory_snapshot.CudaCheckpointState.RUNNING:
912
+ raise ValueError(
913
+ f"Cannot snapshot GPU state if it isn't running. Current GPU state: {gpu_process_state}"
914
+ )
909
915
 
910
- self._cuda_checkpoint_session = gpu_memory_snapshot.CudaCheckpointSession()
911
- self._cuda_checkpoint_session.checkpoint()
916
+ gpu_memory_snapshot.toggle()
917
+ gpu_memory_snapshot.wait_for_state(gpu_memory_snapshot.CudaCheckpointState.CHECKPOINTED)
912
918
 
913
919
  # Notify the heartbeat loop that the snapshot phase has begun in order to
914
920
  # prevent it from sending heartbeat RPCs
@@ -0,0 +1,101 @@
1
+ # Copyright Modal Labs 2022
2
+ #
3
+ # This module provides a simple interface for creating GPU memory snapshots,
4
+ # provising a convenient interface to `cuda-checkpoint` [1]. This is intended
5
+ # to be used in conjunction with memory snapshots.
6
+ #
7
+ # [1] https://github.com/NVIDIA/cuda-checkpoint
8
+
9
+ import os
10
+ import subprocess
11
+ import time
12
+ from enum import Enum
13
+
14
+ from modal.config import config, logger
15
+
16
+ CUDA_CHECKPOINT_PATH: str = config.get("cuda_checkpoint_path")
17
+
18
+
19
+ class CudaCheckpointState(Enum):
20
+ """State representation from the CUDA API: https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__TYPES.html#group__CUDA__TYPES_1gc96cdda177a2b8c296144567cbea4f23"""
21
+
22
+ RUNNING = "running"
23
+ LOCKED = "locked"
24
+ CHECKPOINTED = "checkpointed"
25
+ FAILED = "failed"
26
+
27
+
28
+ class CudaCheckpointException(Exception):
29
+ pass
30
+
31
+
32
+ def toggle():
33
+ """Toggle CUDA checkpoint state for current process, moving GPU memory to the
34
+ CPU and back depending on the current process state when called."""
35
+ pid = get_own_pid()
36
+ logger.debug(f"Toggling CUDA checkpoint state for PID {pid}")
37
+
38
+ try:
39
+ subprocess.run(
40
+ [
41
+ CUDA_CHECKPOINT_PATH,
42
+ "--toggle",
43
+ "--pid",
44
+ str(pid),
45
+ ],
46
+ check=True,
47
+ capture_output=True,
48
+ text=True,
49
+ )
50
+ logger.debug("Successfully toggled CUDA checkpoint state")
51
+
52
+ except subprocess.CalledProcessError as e:
53
+ logger.debug(f"Failed to toggle CUDA checkpoint state: {e.stderr}")
54
+ raise CudaCheckpointException(e.stderr)
55
+
56
+
57
+ def get_state() -> CudaCheckpointState:
58
+ """Get current CUDA checkpoint state for this process."""
59
+ pid = get_own_pid()
60
+
61
+ try:
62
+ result = subprocess.run(
63
+ [CUDA_CHECKPOINT_PATH, "--get-state", "--pid", str(pid)], check=True, capture_output=True, text=True
64
+ )
65
+
66
+ # Parse output to get state
67
+ state_str = result.stdout.strip().lower()
68
+ return CudaCheckpointState(state_str)
69
+
70
+ except subprocess.CalledProcessError as e:
71
+ logger.debug(f"Failed to get CUDA checkpoint state: {e.stderr}")
72
+ raise CudaCheckpointException(e.stderr)
73
+
74
+
75
+ def wait_for_state(target_state: CudaCheckpointState, timeout_secs: float = 5.0):
76
+ """Wait for CUDA checkpoint to reach a specific state."""
77
+ logger.debug(f"Waiting for CUDA checkpoint state {target_state.value}")
78
+ start_time = time.monotonic()
79
+
80
+ while True:
81
+ current_state = get_state()
82
+
83
+ if current_state == target_state:
84
+ logger.debug(f"Target state {target_state.value} reached")
85
+ break
86
+
87
+ if current_state == CudaCheckpointState.FAILED:
88
+ raise CudaCheckpointException(f"CUDA process state is {current_state}")
89
+
90
+ elapsed = time.monotonic() - start_time
91
+ if elapsed >= timeout_secs:
92
+ raise CudaCheckpointException(f"Timeout after {elapsed:.2f}s waiting for state {target_state.value}")
93
+
94
+ time.sleep(0.1)
95
+
96
+
97
+ def get_own_pid():
98
+ """Returns the Process ID (PID) of the current Python process
99
+ using only the standard library.
100
+ """
101
+ return os.getpid()
@@ -171,14 +171,6 @@ def _write_local_result(result_path: str, res: Any):
171
171
  fid.write(res)
172
172
 
173
173
 
174
- def _validate_interactive_quiet_params(ctx):
175
- interactive = ctx.obj["interactive"]
176
- show_progress = ctx.obj["show_progress"]
177
-
178
- if not show_progress and interactive:
179
- raise InvalidError("To use interactive mode, remove the --quiet flag")
180
-
181
-
182
174
  def _make_click_function(app, signature: CliRunnableSignature, inner: Callable[[tuple[str, ...], dict[str, Any]], Any]):
183
175
  @click.pass_context
184
176
  def f(ctx, **kwargs):
@@ -188,8 +180,6 @@ def _make_click_function(app, signature: CliRunnableSignature, inner: Callable[[
188
180
  else:
189
181
  args = ()
190
182
 
191
- _validate_interactive_quiet_params(ctx)
192
-
193
183
  show_progress: bool = ctx.obj["show_progress"]
194
184
  with enable_output(show_progress):
195
185
  with run_app(
@@ -301,8 +291,6 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
301
291
  assert len(args) == 0 and len(kwargs) == 0
302
292
  args = ctx.args
303
293
 
304
- _validate_interactive_quiet_params(ctx)
305
-
306
294
  show_progress: bool = ctx.obj["show_progress"]
307
295
  with enable_output(show_progress):
308
296
  with run_app(
@@ -1,9 +1,7 @@
1
1
  # Copyright Modal Labs 2022
2
- import json
3
2
  import os
4
3
  import platform
5
4
  import subprocess
6
- from pathlib import Path
7
5
  from tempfile import NamedTemporaryFile
8
6
  from typing import Optional
9
7
 
@@ -50,17 +48,14 @@ async def list_(env: Optional[str] = ENV_OPTION, json: bool = False):
50
48
  @secret_cli.command("create", help="Create a new secret.")
51
49
  @synchronizer.create_blocking
52
50
  async def create(
53
- secret_name: str,
54
- keyvalues: Optional[list[str]] = typer.Argument(default=None, help="Space-separated KEY=VALUE items."),
51
+ secret_name,
52
+ keyvalues: list[str] = typer.Argument(..., help="Space-separated KEY=VALUE items"),
55
53
  env: Optional[str] = ENV_OPTION,
56
- from_dotenv: Optional[Path] = typer.Option(default=None, help="Path to a .env file to load secrets from."),
57
- from_json: Optional[Path] = typer.Option(default=None, help="Path to a JSON file to load secrets from."),
58
54
  force: bool = typer.Option(False, "--force", help="Overwrite the secret if it already exists."),
59
55
  ):
60
56
  env = ensure_env(env)
61
57
  env_dict = {}
62
-
63
- for arg in keyvalues or []:
58
+ for arg in keyvalues:
64
59
  if "=" in arg:
65
60
  key, value = arg.split("=", 1)
66
61
  if value == "-":
@@ -68,51 +63,17 @@ async def create(
68
63
  env_dict[key] = value
69
64
  else:
70
65
  raise click.UsageError(
71
- """Each item should be of the form <KEY>=VALUE. To enter secrets using your $EDITOR, use `<KEY>=-`. To
72
- enter secrets from environment variables, use `<KEY>="$ENV_VAR"`.
66
+ """Each item should be of the form <KEY>=VALUE. To enter secrets using your $EDITOR, use `<KEY>=-`.
73
67
 
74
68
  E.g.
75
69
 
76
70
  modal secret create my-credentials username=john password=-
77
- modal secret create my-credentials username=john password="$PASSWORD"
78
71
  """
79
72
  )
80
73
 
81
- if from_dotenv:
82
- if not from_dotenv.is_file():
83
- raise click.UsageError(f"Could not read .env file at {from_dotenv}")
84
-
85
- try:
86
- from dotenv import dotenv_values
87
- except ImportError:
88
- raise ImportError(
89
- "Need the `python-dotenv` package installed. You can install it by running `pip install python-dotenv`."
90
- )
91
-
92
- try:
93
- env_dict.update(dotenv_values(from_dotenv))
94
- except Exception as e:
95
- raise click.UsageError(f"Could not parse .env file at {from_dotenv}: {e}")
96
-
97
- if from_json:
98
- if not from_json.is_file():
99
- raise click.UsageError(f"Could not read JSON file at {from_json}")
100
-
101
- try:
102
- with from_json.open("r") as f:
103
- env_dict.update(json.load(f))
104
- except Exception as e:
105
- raise click.UsageError(f"Could not parse JSON file at {from_json}: {e}")
106
-
107
74
  if not env_dict:
108
75
  raise click.UsageError("You need to specify at least one key for your secret")
109
76
 
110
- for k, v in env_dict.items():
111
- if not isinstance(k, str) or not k:
112
- raise click.UsageError(f"Invalid key: '{k}'")
113
- if not isinstance(v, str):
114
- raise click.UsageError(f"Non-string value for secret '{k}'")
115
-
116
77
  # Create secret
117
78
  await _Secret.create_deployed(secret_name, env_dict, overwrite=force)
118
79
 
@@ -154,10 +154,6 @@ async def ls(
154
154
  filetype = "dir"
155
155
  elif entry.type == api_pb2.FileEntry.FileType.SYMLINK:
156
156
  filetype = "link"
157
- elif entry.type == api_pb2.FileEntry.FileType.FIFO:
158
- filetype = "fifo"
159
- elif entry.type == api_pb2.FileEntry.FileType.SOCKET:
160
- filetype = "socket"
161
157
  else:
162
158
  filetype = "file"
163
159
  rows.append(
@@ -265,13 +261,12 @@ async def rm(
265
261
  async def cp(
266
262
  volume_name: str,
267
263
  paths: list[str], # accepts multiple paths, last path is treated as destination path
268
- recursive: bool = Option(False, "-r", "--recursive", help="Copy directories recursively"),
269
264
  env: Optional[str] = ENV_OPTION,
270
265
  ):
271
266
  ensure_env(env)
272
267
  volume = _Volume.from_name(volume_name, environment_name=env)
273
268
  *src_paths, dst_path = paths
274
- await volume.copy_files(src_paths, dst_path, recursive)
269
+ await volume.copy_files(src_paths, dst_path)
275
270
 
276
271
 
277
272
  @volume_cli.command(
@@ -27,7 +27,11 @@ class _Client:
27
27
  _snapshotted: bool
28
28
 
29
29
  def __init__(
30
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.0.3"
30
+ self,
31
+ server_url: str,
32
+ client_type: int,
33
+ credentials: typing.Optional[tuple[str, str]],
34
+ version: str = "1.0.3.dev1",
31
35
  ): ...
32
36
  def is_closed(self) -> bool: ...
33
37
  @property
@@ -86,7 +90,11 @@ class Client:
86
90
  _snapshotted: bool
87
91
 
88
92
  def __init__(
89
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.0.3"
93
+ self,
94
+ server_url: str,
95
+ client_type: int,
96
+ credentials: typing.Optional[tuple[str, str]],
97
+ version: str = "1.0.3.dev1",
90
98
  ): ...
91
99
  def is_closed(self) -> bool: ...
92
100
  @property
@@ -94,9 +94,8 @@ async def raw_dockerfile_image(
94
94
 
95
95
  Unlike for `modal.Image.from_dockerfile`, the provided recipe will not be embellished with
96
96
  steps to install dependencies for the Modal client package. As a consequence, the resulting
97
- Image cannot be used with a modal Function unless those dependencies are already included
98
- as part of the base Dockerfile recipe or are added in a subsequent layer. The Image _can_ be
99
- directly used with a modal Sandbox, which does not need the Modal client.
97
+ Image cannot be used with a modal Function unless those dependencies are added in a subsequent
98
+ layer. It _can_ be directly used with a modal Sandbox, which does not need the Modal client.
100
99
 
101
100
  We expect to support this experimental function until the `2025.04` Modal Image Builder is
102
101
  stable, at which point Modal Image recipes will no longer install the client dependencies
@@ -128,9 +127,8 @@ async def raw_registry_image(
128
127
 
129
128
  Unlike for `modal.Image.from_registry`, the provided recipe will not be embellished with
130
129
  steps to install dependencies for the Modal client package. As a consequence, the resulting
131
- Image cannot be used with a modal Function unless those dependencies are already included
132
- as part of the registry Image or are added in a subsequent layer. The Image _can_ be
133
- directly used with a modal Sandbox, which does not need the Modal client.
130
+ Image cannot be used with a modal Function unless those dependencies are added in a subsequent
131
+ layer. It _can_ be directly used with a modal Sandbox, which does not need the Modal client.
134
132
 
135
133
  We expect to support this experimental function until the `2025.04` Modal Image Builder is
136
134
  stable, at which point Modal Image recipes will no longer install the client dependencies
@@ -647,13 +647,7 @@ class _Image(_Object, type_prefix="im"):
647
647
  metadata = resp.metadata
648
648
 
649
649
  if result.status == api_pb2.GenericResult.GENERIC_STATUS_FAILURE:
650
- if result.exception:
651
- raise RemoteError(f"Image build for {image_id} failed with the exception:\n{result.exception}")
652
- else:
653
- msg = f"Image build for {image_id} failed. See build logs for more details."
654
- if not _get_output_manager():
655
- msg += " (Hint: Use `modal.enable_output()` to see logs from the process building the Image.)"
656
- raise RemoteError(msg)
650
+ raise RemoteError(f"Image build for {image_id} failed with the exception:\n{result.exception}")
657
651
  elif result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED:
658
652
  raise RemoteError(f"Image build for {image_id} terminated due to external shut-down. Please try again.")
659
653
  elif result.status == api_pb2.GenericResult.GENERIC_STATUS_TIMEOUT:
@@ -1528,14 +1522,12 @@ class _Image(_Object, type_prefix="im"):
1528
1522
  modal_requirements_commands = []
1529
1523
  if builder_version <= "2024.10":
1530
1524
  # past 2024.10, client dependencies are mounted at runtime
1531
- modal_requirements_commands.extend(
1532
- [
1533
- f"COPY {CONTAINER_REQUIREMENTS_PATH} {CONTAINER_REQUIREMENTS_PATH}",
1534
- f"RUN python -m pip install --upgrade {_base_image_config('package_tools', builder_version)}",
1535
- f"RUN {requirements_prefix}{_get_modal_requirements_command(builder_version)}",
1536
- ]
1537
- )
1538
- if "2024.10" >= builder_version > "2023.12":
1525
+ modal_requirements_commands.extend([
1526
+ f"COPY {CONTAINER_REQUIREMENTS_PATH} {CONTAINER_REQUIREMENTS_PATH}",
1527
+ f"RUN python -m pip install --upgrade {_base_image_config('package_tools', builder_version)}",
1528
+ f"RUN {requirements_prefix}{_get_modal_requirements_command(builder_version)}",
1529
+ ])
1530
+ if "2024.10" >= builder_version > "2023.12":
1539
1531
  modal_requirements_commands.append(f"RUN rm {CONTAINER_REQUIREMENTS_PATH}")
1540
1532
 
1541
1533
  return [
@@ -1838,31 +1830,23 @@ class _Image(_Object, type_prefix="im"):
1838
1830
  f"FROM python:{full_python_version}-slim-{debian_codename}",
1839
1831
  ]
1840
1832
  if version <= "2024.10":
1841
- commands.extend(
1842
- [
1843
- f"COPY {CONTAINER_REQUIREMENTS_PATH} {CONTAINER_REQUIREMENTS_PATH}",
1844
- ]
1845
- )
1846
- commands.extend(
1847
- [
1848
- "RUN apt-get update",
1849
- "RUN apt-get install -y gcc gfortran build-essential",
1850
- f"RUN pip install --upgrade {_base_image_config('package_tools', version)}",
1851
- ]
1852
- )
1833
+ commands.extend([
1834
+ f"COPY {CONTAINER_REQUIREMENTS_PATH} {CONTAINER_REQUIREMENTS_PATH}",
1835
+ ])
1836
+ commands.extend([
1837
+ "RUN apt-get update",
1838
+ "RUN apt-get install -y gcc gfortran build-essential",
1839
+ f"RUN pip install --upgrade {_base_image_config('package_tools', version)}",
1840
+ ])
1853
1841
  if version <= "2024.10":
1854
1842
  # after 2024.10, modal requirements are mounted at runtime
1855
- commands.extend(
1856
- [
1857
- f"RUN {_get_modal_requirements_command(version)}",
1858
- ]
1859
- )
1860
- commands.extend(
1861
- [
1862
- # Set debian front-end to non-interactive to avoid users getting stuck with input prompts.
1863
- "RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections",
1864
- ]
1865
- )
1843
+ commands.extend([
1844
+ f"RUN {_get_modal_requirements_command(version)}",
1845
+ ])
1846
+ commands.extend([
1847
+ # Set debian front-end to non-interactive to avoid users getting stuck with input prompts.
1848
+ "RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections",
1849
+ ])
1866
1850
  if "2024.10" >= version > "2023.12":
1867
1851
  commands.append(f"RUN rm {CONTAINER_REQUIREMENTS_PATH}")
1868
1852
  if version > "2024.10":
@@ -21,7 +21,7 @@ from modal_version import __version__
21
21
 
22
22
  from ._object import _get_environment_name, _Object
23
23
  from ._resolver import Resolver
24
- from ._utils.async_utils import TaskContext, aclosing, async_map, synchronize_api
24
+ from ._utils.async_utils import aclosing, async_map, synchronize_api
25
25
  from ._utils.blob_utils import FileUploadSpec, blob_upload_file, get_file_upload_spec_from_path
26
26
  from ._utils.deprecation import deprecation_warning
27
27
  from ._utils.grpc_utils import retry_transient_errors
@@ -115,8 +115,7 @@ class _MountFile(_MountEntry):
115
115
  def get_files_to_upload(self):
116
116
  local_file = self.local_file.resolve()
117
117
  if not local_file.exists():
118
- msg = f"local file {local_file} does not exist"
119
- raise FileNotFoundError(msg)
118
+ raise FileNotFoundError(local_file)
120
119
 
121
120
  rel_filename = self.remote_path
122
121
  yield local_file, rel_filename
@@ -145,12 +144,10 @@ class _MountDir(_MountEntry):
145
144
  local_dir = self.local_dir.expanduser().absolute()
146
145
 
147
146
  if not local_dir.exists():
148
- msg = f"local dir {local_dir} does not exist"
149
- raise FileNotFoundError(msg)
147
+ raise FileNotFoundError(local_dir)
150
148
 
151
149
  if not local_dir.is_dir():
152
- msg = f"local dir {local_dir} is not a directory"
153
- raise NotADirectoryError(msg)
150
+ raise NotADirectoryError(local_dir)
154
151
 
155
152
  if self.recursive:
156
153
  gen = (os.path.join(root, name) for root, dirs, files in os.walk(local_dir) for name in files)
@@ -813,124 +810,3 @@ def _is_modal_path(remote_path: PurePosixPath):
813
810
  if is_modal_path:
814
811
  return True
815
812
  return False
816
-
817
-
818
- REMOTE_PACKAGES_PATH = "/__modal/deps"
819
- REMOTE_SITECUSTOMIZE_PATH = "/pkg/sitecustomize.py"
820
-
821
- SITECUSTOMIZE_CONTENT = f"""
822
- # This file is automatically generated by Modal.
823
- # It ensures that Modal's python dependencies are available in the Python PATH,
824
- # while prioritizing user-installed packages.
825
- import sys; sys.path.append('{REMOTE_PACKAGES_PATH}')
826
- """.strip()
827
-
828
-
829
- async def _create_single_mount(
830
- client: _Client,
831
- builder_version: str,
832
- python_version: str,
833
- platform: str,
834
- arch: str,
835
- uv_python_platform: str = None,
836
- check_if_exists: bool = True,
837
- ):
838
- import subprocess
839
- import tempfile
840
-
841
- profile_environment = config.get("environment")
842
- abi_tag = "cp" + python_version.replace(".", "")
843
- mount_name = f"{builder_version}-{abi_tag}-{platform}-{arch}"
844
- uv_python_platform = uv_python_platform or f"{arch}-{platform}"
845
-
846
- if check_if_exists:
847
- try:
848
- await Mount.from_name(mount_name, namespace=api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL).hydrate.aio(client)
849
- print(f"✅ Found existing mount {mount_name} in global namespace.")
850
- return
851
- except modal.exception.NotFoundError:
852
- pass
853
-
854
- with tempfile.TemporaryDirectory() as tmpd:
855
- print(f"📦 Building {mount_name}.")
856
- requirements = os.path.join(os.path.dirname(__file__), f"requirements/{builder_version}.txt")
857
- subprocess.run(
858
- [
859
- "uv",
860
- "pip",
861
- "install",
862
- "--strict",
863
- "--no-deps",
864
- "--no-cache",
865
- "-r",
866
- requirements,
867
- "--compile-bytecode",
868
- "--target",
869
- tmpd,
870
- "--python-platform",
871
- uv_python_platform,
872
- "--python-version",
873
- python_version,
874
- ],
875
- check=True,
876
- capture_output=True,
877
- )
878
-
879
- print(f"🌐 Downloaded and unpacked packages to {tmpd}.")
880
-
881
- python_mount = Mount._from_local_dir(tmpd, remote_path=REMOTE_PACKAGES_PATH)
882
-
883
- with tempfile.NamedTemporaryFile() as sitecustomize:
884
- sitecustomize.write(
885
- SITECUSTOMIZE_CONTENT.encode("utf-8"),
886
- )
887
- sitecustomize.flush()
888
-
889
- python_mount = python_mount.add_local_file(
890
- sitecustomize.name,
891
- remote_path=REMOTE_SITECUSTOMIZE_PATH,
892
- )
893
-
894
- await python_mount._deploy.aio(
895
- mount_name,
896
- api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL,
897
- environment_name=profile_environment,
898
- client=client,
899
- )
900
- print(f"✅ Deployed mount {mount_name} to global namespace.")
901
-
902
-
903
- async def _create_client_dependency_mounts(
904
- client=None,
905
- check_if_exists=True,
906
- python_versions: list[str] = list(PYTHON_STANDALONE_VERSIONS),
907
- ):
908
- coros = []
909
- for python_version in python_versions:
910
- # glibc >= 2.17
911
- coros.append(
912
- _create_single_mount(
913
- client,
914
- "PREVIEW",
915
- python_version,
916
- "manylinux_2_17",
917
- "x86_64",
918
- check_if_exists=check_if_exists,
919
- )
920
- )
921
- # musl >= 1.2
922
- coros.append(
923
- _create_single_mount(
924
- client,
925
- "PREVIEW",
926
- python_version,
927
- "musllinux_1_2",
928
- "x86_64",
929
- uv_python_platform="x86_64-unknown-linux-musl",
930
- check_if_exists=check_if_exists,
931
- )
932
- )
933
- await TaskContext.gather(*coros)
934
-
935
-
936
- create_client_dependency_mounts = synchronize_api(_create_client_dependency_mounts)
@@ -308,28 +308,6 @@ def _create_client_mount(): ...
308
308
  def create_client_mount(): ...
309
309
  def _get_client_mount(): ...
310
310
  def _is_modal_path(remote_path: pathlib.PurePosixPath): ...
311
- async def _create_single_mount(
312
- client: modal.client._Client,
313
- builder_version: str,
314
- python_version: str,
315
- platform: str,
316
- arch: str,
317
- uv_python_platform: str = None,
318
- check_if_exists: bool = True,
319
- ): ...
320
- async def _create_client_dependency_mounts(
321
- client=None, check_if_exists=True, python_versions: list[str] = ["3.9", "3.10", "3.11", "3.12", "3.13"]
322
- ): ...
323
-
324
- class __create_client_dependency_mounts_spec(typing_extensions.Protocol):
325
- def __call__(
326
- self, /, client=None, check_if_exists=True, python_versions: list[str] = ["3.9", "3.10", "3.11", "3.12", "3.13"]
327
- ): ...
328
- async def aio(
329
- self, /, client=None, check_if_exists=True, python_versions: list[str] = ["3.9", "3.10", "3.11", "3.12", "3.13"]
330
- ): ...
331
-
332
- create_client_dependency_mounts: __create_client_dependency_mounts_spec
333
311
 
334
312
  ROOT_DIR: pathlib.PurePosixPath
335
313