modal 0.74.55__tar.gz → 0.74.57__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 (184) hide show
  1. {modal-0.74.55 → modal-0.74.57}/PKG-INFO +1 -1
  2. {modal-0.74.55 → modal-0.74.57}/modal/_functions.py +3 -2
  3. {modal-0.74.55 → modal-0.74.57}/modal/_partial_function.py +3 -2
  4. {modal-0.74.55 → modal-0.74.57}/modal/_resources.py +2 -0
  5. {modal-0.74.55 → modal-0.74.57}/modal/app.py +3 -0
  6. modal-0.74.57/modal/cli/cluster.py +82 -0
  7. {modal-0.74.55 → modal-0.74.57}/modal/cli/entry_point.py +3 -0
  8. {modal-0.74.55 → modal-0.74.57}/modal/client.pyi +2 -2
  9. {modal-0.74.55 → modal-0.74.57}/modal/functions.pyi +7 -6
  10. {modal-0.74.55 → modal-0.74.57}/modal.egg-info/PKG-INFO +1 -1
  11. {modal-0.74.55 → modal-0.74.57}/modal.egg-info/SOURCES.txt +1 -0
  12. {modal-0.74.55 → modal-0.74.57}/modal_version/_version_generated.py +1 -1
  13. {modal-0.74.55 → modal-0.74.57}/LICENSE +0 -0
  14. {modal-0.74.55 → modal-0.74.57}/README.md +0 -0
  15. {modal-0.74.55 → modal-0.74.57}/modal/__init__.py +0 -0
  16. {modal-0.74.55 → modal-0.74.57}/modal/__main__.py +0 -0
  17. {modal-0.74.55 → modal-0.74.57}/modal/_clustered_functions.py +0 -0
  18. {modal-0.74.55 → modal-0.74.57}/modal/_clustered_functions.pyi +0 -0
  19. {modal-0.74.55 → modal-0.74.57}/modal/_container_entrypoint.py +0 -0
  20. {modal-0.74.55 → modal-0.74.57}/modal/_ipython.py +0 -0
  21. {modal-0.74.55 → modal-0.74.57}/modal/_location.py +0 -0
  22. {modal-0.74.55 → modal-0.74.57}/modal/_object.py +0 -0
  23. {modal-0.74.55 → modal-0.74.57}/modal/_output.py +0 -0
  24. {modal-0.74.55 → modal-0.74.57}/modal/_pty.py +0 -0
  25. {modal-0.74.55 → modal-0.74.57}/modal/_resolver.py +0 -0
  26. {modal-0.74.55 → modal-0.74.57}/modal/_runtime/__init__.py +0 -0
  27. {modal-0.74.55 → modal-0.74.57}/modal/_runtime/asgi.py +0 -0
  28. {modal-0.74.55 → modal-0.74.57}/modal/_runtime/container_io_manager.py +0 -0
  29. {modal-0.74.55 → modal-0.74.57}/modal/_runtime/container_io_manager.pyi +0 -0
  30. {modal-0.74.55 → modal-0.74.57}/modal/_runtime/execution_context.py +0 -0
  31. {modal-0.74.55 → modal-0.74.57}/modal/_runtime/execution_context.pyi +0 -0
  32. {modal-0.74.55 → modal-0.74.57}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  33. {modal-0.74.55 → modal-0.74.57}/modal/_runtime/telemetry.py +0 -0
  34. {modal-0.74.55 → modal-0.74.57}/modal/_runtime/user_code_imports.py +0 -0
  35. {modal-0.74.55 → modal-0.74.57}/modal/_serialization.py +0 -0
  36. {modal-0.74.55 → modal-0.74.57}/modal/_traceback.py +0 -0
  37. {modal-0.74.55 → modal-0.74.57}/modal/_tunnel.py +0 -0
  38. {modal-0.74.55 → modal-0.74.57}/modal/_tunnel.pyi +0 -0
  39. {modal-0.74.55 → modal-0.74.57}/modal/_type_manager.py +0 -0
  40. {modal-0.74.55 → modal-0.74.57}/modal/_utils/__init__.py +0 -0
  41. {modal-0.74.55 → modal-0.74.57}/modal/_utils/app_utils.py +0 -0
  42. {modal-0.74.55 → modal-0.74.57}/modal/_utils/async_utils.py +0 -0
  43. {modal-0.74.55 → modal-0.74.57}/modal/_utils/blob_utils.py +0 -0
  44. {modal-0.74.55 → modal-0.74.57}/modal/_utils/bytes_io_segment_payload.py +0 -0
  45. {modal-0.74.55 → modal-0.74.57}/modal/_utils/deprecation.py +0 -0
  46. {modal-0.74.55 → modal-0.74.57}/modal/_utils/docker_utils.py +0 -0
  47. {modal-0.74.55 → modal-0.74.57}/modal/_utils/function_utils.py +0 -0
  48. {modal-0.74.55 → modal-0.74.57}/modal/_utils/git_utils.py +0 -0
  49. {modal-0.74.55 → modal-0.74.57}/modal/_utils/grpc_testing.py +0 -0
  50. {modal-0.74.55 → modal-0.74.57}/modal/_utils/grpc_utils.py +0 -0
  51. {modal-0.74.55 → modal-0.74.57}/modal/_utils/hash_utils.py +0 -0
  52. {modal-0.74.55 → modal-0.74.57}/modal/_utils/http_utils.py +0 -0
  53. {modal-0.74.55 → modal-0.74.57}/modal/_utils/jwt_utils.py +0 -0
  54. {modal-0.74.55 → modal-0.74.57}/modal/_utils/logger.py +0 -0
  55. {modal-0.74.55 → modal-0.74.57}/modal/_utils/mount_utils.py +0 -0
  56. {modal-0.74.55 → modal-0.74.57}/modal/_utils/name_utils.py +0 -0
  57. {modal-0.74.55 → modal-0.74.57}/modal/_utils/package_utils.py +0 -0
  58. {modal-0.74.55 → modal-0.74.57}/modal/_utils/pattern_utils.py +0 -0
  59. {modal-0.74.55 → modal-0.74.57}/modal/_utils/rand_pb_testing.py +0 -0
  60. {modal-0.74.55 → modal-0.74.57}/modal/_utils/shell_utils.py +0 -0
  61. {modal-0.74.55 → modal-0.74.57}/modal/_vendor/__init__.py +0 -0
  62. {modal-0.74.55 → modal-0.74.57}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  63. {modal-0.74.55 → modal-0.74.57}/modal/_vendor/cloudpickle.py +0 -0
  64. {modal-0.74.55 → modal-0.74.57}/modal/_vendor/tblib.py +0 -0
  65. {modal-0.74.55 → modal-0.74.57}/modal/_watcher.py +0 -0
  66. {modal-0.74.55 → modal-0.74.57}/modal/app.pyi +0 -0
  67. {modal-0.74.55 → modal-0.74.57}/modal/call_graph.py +0 -0
  68. {modal-0.74.55 → modal-0.74.57}/modal/cli/__init__.py +0 -0
  69. {modal-0.74.55 → modal-0.74.57}/modal/cli/_download.py +0 -0
  70. {modal-0.74.55 → modal-0.74.57}/modal/cli/_traceback.py +0 -0
  71. {modal-0.74.55 → modal-0.74.57}/modal/cli/app.py +0 -0
  72. {modal-0.74.55 → modal-0.74.57}/modal/cli/config.py +0 -0
  73. {modal-0.74.55 → modal-0.74.57}/modal/cli/container.py +0 -0
  74. {modal-0.74.55 → modal-0.74.57}/modal/cli/dict.py +0 -0
  75. {modal-0.74.55 → modal-0.74.57}/modal/cli/environment.py +0 -0
  76. {modal-0.74.55 → modal-0.74.57}/modal/cli/import_refs.py +0 -0
  77. {modal-0.74.55 → modal-0.74.57}/modal/cli/launch.py +0 -0
  78. {modal-0.74.55 → modal-0.74.57}/modal/cli/network_file_system.py +0 -0
  79. {modal-0.74.55 → modal-0.74.57}/modal/cli/profile.py +0 -0
  80. {modal-0.74.55 → modal-0.74.57}/modal/cli/programs/__init__.py +0 -0
  81. {modal-0.74.55 → modal-0.74.57}/modal/cli/programs/run_jupyter.py +0 -0
  82. {modal-0.74.55 → modal-0.74.57}/modal/cli/programs/vscode.py +0 -0
  83. {modal-0.74.55 → modal-0.74.57}/modal/cli/queues.py +0 -0
  84. {modal-0.74.55 → modal-0.74.57}/modal/cli/run.py +0 -0
  85. {modal-0.74.55 → modal-0.74.57}/modal/cli/secret.py +0 -0
  86. {modal-0.74.55 → modal-0.74.57}/modal/cli/token.py +0 -0
  87. {modal-0.74.55 → modal-0.74.57}/modal/cli/utils.py +0 -0
  88. {modal-0.74.55 → modal-0.74.57}/modal/cli/volume.py +0 -0
  89. {modal-0.74.55 → modal-0.74.57}/modal/client.py +0 -0
  90. {modal-0.74.55 → modal-0.74.57}/modal/cloud_bucket_mount.py +0 -0
  91. {modal-0.74.55 → modal-0.74.57}/modal/cloud_bucket_mount.pyi +0 -0
  92. {modal-0.74.55 → modal-0.74.57}/modal/cls.py +0 -0
  93. {modal-0.74.55 → modal-0.74.57}/modal/cls.pyi +0 -0
  94. {modal-0.74.55 → modal-0.74.57}/modal/config.py +0 -0
  95. {modal-0.74.55 → modal-0.74.57}/modal/container_process.py +0 -0
  96. {modal-0.74.55 → modal-0.74.57}/modal/container_process.pyi +0 -0
  97. {modal-0.74.55 → modal-0.74.57}/modal/dict.py +0 -0
  98. {modal-0.74.55 → modal-0.74.57}/modal/dict.pyi +0 -0
  99. {modal-0.74.55 → modal-0.74.57}/modal/environments.py +0 -0
  100. {modal-0.74.55 → modal-0.74.57}/modal/environments.pyi +0 -0
  101. {modal-0.74.55 → modal-0.74.57}/modal/exception.py +0 -0
  102. {modal-0.74.55 → modal-0.74.57}/modal/experimental/__init__.py +0 -0
  103. {modal-0.74.55 → modal-0.74.57}/modal/experimental/ipython.py +0 -0
  104. {modal-0.74.55 → modal-0.74.57}/modal/file_io.py +0 -0
  105. {modal-0.74.55 → modal-0.74.57}/modal/file_io.pyi +0 -0
  106. {modal-0.74.55 → modal-0.74.57}/modal/file_pattern_matcher.py +0 -0
  107. {modal-0.74.55 → modal-0.74.57}/modal/functions.py +0 -0
  108. {modal-0.74.55 → modal-0.74.57}/modal/gpu.py +0 -0
  109. {modal-0.74.55 → modal-0.74.57}/modal/image.py +0 -0
  110. {modal-0.74.55 → modal-0.74.57}/modal/image.pyi +0 -0
  111. {modal-0.74.55 → modal-0.74.57}/modal/io_streams.py +0 -0
  112. {modal-0.74.55 → modal-0.74.57}/modal/io_streams.pyi +0 -0
  113. {modal-0.74.55 → modal-0.74.57}/modal/mount.py +0 -0
  114. {modal-0.74.55 → modal-0.74.57}/modal/mount.pyi +0 -0
  115. {modal-0.74.55 → modal-0.74.57}/modal/network_file_system.py +0 -0
  116. {modal-0.74.55 → modal-0.74.57}/modal/network_file_system.pyi +0 -0
  117. {modal-0.74.55 → modal-0.74.57}/modal/object.py +0 -0
  118. {modal-0.74.55 → modal-0.74.57}/modal/object.pyi +0 -0
  119. {modal-0.74.55 → modal-0.74.57}/modal/output.py +0 -0
  120. {modal-0.74.55 → modal-0.74.57}/modal/parallel_map.py +0 -0
  121. {modal-0.74.55 → modal-0.74.57}/modal/parallel_map.pyi +0 -0
  122. {modal-0.74.55 → modal-0.74.57}/modal/partial_function.py +0 -0
  123. {modal-0.74.55 → modal-0.74.57}/modal/partial_function.pyi +0 -0
  124. {modal-0.74.55 → modal-0.74.57}/modal/proxy.py +0 -0
  125. {modal-0.74.55 → modal-0.74.57}/modal/proxy.pyi +0 -0
  126. {modal-0.74.55 → modal-0.74.57}/modal/py.typed +0 -0
  127. {modal-0.74.55 → modal-0.74.57}/modal/queue.py +0 -0
  128. {modal-0.74.55 → modal-0.74.57}/modal/queue.pyi +0 -0
  129. {modal-0.74.55 → modal-0.74.57}/modal/requirements/2023.12.312.txt +0 -0
  130. {modal-0.74.55 → modal-0.74.57}/modal/requirements/2023.12.txt +0 -0
  131. {modal-0.74.55 → modal-0.74.57}/modal/requirements/2024.04.txt +0 -0
  132. {modal-0.74.55 → modal-0.74.57}/modal/requirements/2024.10.txt +0 -0
  133. {modal-0.74.55 → modal-0.74.57}/modal/requirements/PREVIEW.txt +0 -0
  134. {modal-0.74.55 → modal-0.74.57}/modal/requirements/README.md +0 -0
  135. {modal-0.74.55 → modal-0.74.57}/modal/requirements/base-images.json +0 -0
  136. {modal-0.74.55 → modal-0.74.57}/modal/retries.py +0 -0
  137. {modal-0.74.55 → modal-0.74.57}/modal/runner.py +0 -0
  138. {modal-0.74.55 → modal-0.74.57}/modal/runner.pyi +0 -0
  139. {modal-0.74.55 → modal-0.74.57}/modal/running_app.py +0 -0
  140. {modal-0.74.55 → modal-0.74.57}/modal/sandbox.py +0 -0
  141. {modal-0.74.55 → modal-0.74.57}/modal/sandbox.pyi +0 -0
  142. {modal-0.74.55 → modal-0.74.57}/modal/schedule.py +0 -0
  143. {modal-0.74.55 → modal-0.74.57}/modal/scheduler_placement.py +0 -0
  144. {modal-0.74.55 → modal-0.74.57}/modal/secret.py +0 -0
  145. {modal-0.74.55 → modal-0.74.57}/modal/secret.pyi +0 -0
  146. {modal-0.74.55 → modal-0.74.57}/modal/serving.py +0 -0
  147. {modal-0.74.55 → modal-0.74.57}/modal/serving.pyi +0 -0
  148. {modal-0.74.55 → modal-0.74.57}/modal/snapshot.py +0 -0
  149. {modal-0.74.55 → modal-0.74.57}/modal/snapshot.pyi +0 -0
  150. {modal-0.74.55 → modal-0.74.57}/modal/stream_type.py +0 -0
  151. {modal-0.74.55 → modal-0.74.57}/modal/token_flow.py +0 -0
  152. {modal-0.74.55 → modal-0.74.57}/modal/token_flow.pyi +0 -0
  153. {modal-0.74.55 → modal-0.74.57}/modal/volume.py +0 -0
  154. {modal-0.74.55 → modal-0.74.57}/modal/volume.pyi +0 -0
  155. {modal-0.74.55 → modal-0.74.57}/modal.egg-info/dependency_links.txt +0 -0
  156. {modal-0.74.55 → modal-0.74.57}/modal.egg-info/entry_points.txt +0 -0
  157. {modal-0.74.55 → modal-0.74.57}/modal.egg-info/requires.txt +0 -0
  158. {modal-0.74.55 → modal-0.74.57}/modal.egg-info/top_level.txt +0 -0
  159. {modal-0.74.55 → modal-0.74.57}/modal_docs/__init__.py +0 -0
  160. {modal-0.74.55 → modal-0.74.57}/modal_docs/gen_cli_docs.py +0 -0
  161. {modal-0.74.55 → modal-0.74.57}/modal_docs/gen_reference_docs.py +0 -0
  162. {modal-0.74.55 → modal-0.74.57}/modal_docs/mdmd/__init__.py +0 -0
  163. {modal-0.74.55 → modal-0.74.57}/modal_docs/mdmd/mdmd.py +0 -0
  164. {modal-0.74.55 → modal-0.74.57}/modal_docs/mdmd/signatures.py +0 -0
  165. {modal-0.74.55 → modal-0.74.57}/modal_proto/__init__.py +0 -0
  166. {modal-0.74.55 → modal-0.74.57}/modal_proto/api.proto +0 -0
  167. {modal-0.74.55 → modal-0.74.57}/modal_proto/api_grpc.py +0 -0
  168. {modal-0.74.55 → modal-0.74.57}/modal_proto/api_pb2.py +0 -0
  169. {modal-0.74.55 → modal-0.74.57}/modal_proto/api_pb2.pyi +0 -0
  170. {modal-0.74.55 → modal-0.74.57}/modal_proto/api_pb2_grpc.py +0 -0
  171. {modal-0.74.55 → modal-0.74.57}/modal_proto/api_pb2_grpc.pyi +0 -0
  172. {modal-0.74.55 → modal-0.74.57}/modal_proto/modal_api_grpc.py +0 -0
  173. {modal-0.74.55 → modal-0.74.57}/modal_proto/modal_options_grpc.py +0 -0
  174. {modal-0.74.55 → modal-0.74.57}/modal_proto/options.proto +0 -0
  175. {modal-0.74.55 → modal-0.74.57}/modal_proto/options_grpc.py +0 -0
  176. {modal-0.74.55 → modal-0.74.57}/modal_proto/options_pb2.py +0 -0
  177. {modal-0.74.55 → modal-0.74.57}/modal_proto/options_pb2.pyi +0 -0
  178. {modal-0.74.55 → modal-0.74.57}/modal_proto/options_pb2_grpc.py +0 -0
  179. {modal-0.74.55 → modal-0.74.57}/modal_proto/options_pb2_grpc.pyi +0 -0
  180. {modal-0.74.55 → modal-0.74.57}/modal_proto/py.typed +0 -0
  181. {modal-0.74.55 → modal-0.74.57}/modal_version/__init__.py +0 -0
  182. {modal-0.74.55 → modal-0.74.57}/modal_version/__main__.py +0 -0
  183. {modal-0.74.55 → modal-0.74.57}/pyproject.toml +0 -0
  184. {modal-0.74.55 → modal-0.74.57}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 0.74.55
3
+ Version: 0.74.57
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -477,6 +477,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
477
477
  i6pn_enabled: bool = False,
478
478
  # Experimental: Clustered functions
479
479
  cluster_size: Optional[int] = None,
480
+ rdma: Optional[bool] = None,
480
481
  max_inputs: Optional[int] = None,
481
482
  ephemeral_disk: Optional[int] = None,
482
483
  # current default: first-party, future default: main-package
@@ -899,7 +900,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
899
900
 
900
901
  function_definition_copy.resources.CopyFrom(
901
902
  convert_fn_config_to_resources_config(
902
- cpu=cpu, memory=memory, gpu=_gpu, ephemeral_disk=ephemeral_disk
903
+ cpu=cpu, memory=memory, gpu=_gpu, ephemeral_disk=ephemeral_disk, rdma=rdma
903
904
  ),
904
905
  )
905
906
  ranked_function = api_pb2.FunctionData.RankedFunction(
@@ -914,7 +915,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
914
915
  # assert isinstance(gpu, GPU_T) # includes the case where gpu==None case
915
916
  function_definition.resources.CopyFrom(
916
917
  convert_fn_config_to_resources_config(
917
- cpu=cpu, memory=memory, gpu=gpu, ephemeral_disk=ephemeral_disk
918
+ cpu=cpu, memory=memory, gpu=gpu, ephemeral_disk=ephemeral_disk, rdma=rdma
918
919
  ),
919
920
  )
920
921
 
@@ -77,6 +77,7 @@ class _PartialFunctionParams:
77
77
  max_concurrent_inputs: Optional[int] = None
78
78
  target_concurrent_inputs: Optional[int] = None
79
79
  build_timeout: Optional[int] = None
80
+ rdma: Optional[bool] = None
80
81
 
81
82
  def update(self, other: "_PartialFunctionParams") -> None:
82
83
  """Update self with params set in other."""
@@ -900,7 +901,7 @@ def _concurrent(
900
901
 
901
902
 
902
903
  # NOTE: clustered is currently exposed through modal.experimental, not the top-level namespace
903
- def _clustered(size: int, broadcast: bool = True):
904
+ def _clustered(size: int, broadcast: bool = True, rdma: bool = False):
904
905
  """Provision clusters of colocated and networked containers for the Function.
905
906
 
906
907
  Parameters:
@@ -918,7 +919,7 @@ def _clustered(size: int, broadcast: bool = True):
918
919
  raise ValueError("cluster size must be greater than 0")
919
920
 
920
921
  flags = _PartialFunctionFlags.CLUSTERED
921
- params = _PartialFunctionParams(cluster_size=size)
922
+ params = _PartialFunctionParams(cluster_size=size, rdma=rdma)
922
923
 
923
924
  def wrapper(
924
925
  obj: Union[_PartialFunction[P, ReturnType, ReturnType], Callable[P, ReturnType]],
@@ -13,6 +13,7 @@ def convert_fn_config_to_resources_config(
13
13
  memory: Optional[Union[int, tuple[int, int]]],
14
14
  gpu: GPU_T,
15
15
  ephemeral_disk: Optional[int],
16
+ rdma: Optional[bool] = None,
16
17
  ) -> api_pb2.Resources:
17
18
  gpu_config = parse_gpu_config(gpu)
18
19
  if cpu and isinstance(cpu, tuple):
@@ -48,4 +49,5 @@ def convert_fn_config_to_resources_config(
48
49
  memory_mb=memory_mb,
49
50
  memory_mb_max=memory_mb_max,
50
51
  ephemeral_disk_mb=ephemeral_disk,
52
+ rdma=rdma or False,
51
53
  )
@@ -747,6 +747,7 @@ class _App:
747
747
  )
748
748
  i6pn_enabled = i6pn or (f.flags & _PartialFunctionFlags.CLUSTERED)
749
749
  cluster_size = f.params.cluster_size # Experimental: Clustered functions
750
+ rdma = f.params.rdma
750
751
 
751
752
  info = FunctionInfo(f.raw_f, serialized=serialized, name_override=name)
752
753
  raw_f = f.raw_f
@@ -799,6 +800,7 @@ class _App:
799
800
  target_concurrent_inputs = None
800
801
 
801
802
  cluster_size = None # Experimental: Clustered functions
803
+ rdma = None
802
804
  i6pn_enabled = i6pn
803
805
 
804
806
  if info.function_name.endswith(".app"):
@@ -850,6 +852,7 @@ class _App:
850
852
  scheduler_placement=scheduler_placement,
851
853
  i6pn_enabled=i6pn_enabled,
852
854
  cluster_size=cluster_size, # Experimental: Clustered functions
855
+ rdma=rdma,
853
856
  include_source=include_source if include_source is not None else self._include_source_default,
854
857
  experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
855
858
  _experimental_proxy_ip=_experimental_proxy_ip,
@@ -0,0 +1,82 @@
1
+ # Copyright Modal Labs 2022
2
+ from typing import Optional, Union
3
+
4
+ import typer
5
+ from rich.console import Console
6
+ from rich.text import Text
7
+
8
+ from modal._object import _get_environment_name
9
+ from modal._pty import get_pty_info
10
+ from modal._utils.async_utils import synchronizer
11
+ from modal.cli.utils import ENV_OPTION, display_table, is_tty, timestamp_to_local
12
+ from modal.client import _Client
13
+ from modal.config import config
14
+ from modal.container_process import _ContainerProcess
15
+ from modal.environments import ensure_env
16
+ from modal.stream_type import StreamType
17
+ from modal_proto import api_pb2
18
+
19
+ cluster_cli = typer.Typer(
20
+ name="cluster", help="Manage and connect to running multi-node clusters.", no_args_is_help=True
21
+ )
22
+
23
+
24
+ @cluster_cli.command("list")
25
+ @synchronizer.create_blocking
26
+ async def list_(env: Optional[str] = ENV_OPTION, json: bool = False):
27
+ """List all clusters that are currently running."""
28
+ env = ensure_env(env)
29
+ client = await _Client.from_env()
30
+ environment_name = _get_environment_name(env)
31
+ res: api_pb2.ClusterListResponse = await client.stub.ClusterList(
32
+ api_pb2.ClusterListRequest(environment_name=environment_name)
33
+ )
34
+
35
+ column_names = ["Cluster ID", "App ID", "Start Time", "Nodes"]
36
+ rows: list[list[Union[Text, str]]] = []
37
+ res.clusters.sort(key=lambda c: c.started_at, reverse=True)
38
+
39
+ for c in res.clusters:
40
+ rows.append(
41
+ [
42
+ c.cluster_id,
43
+ c.app_id,
44
+ timestamp_to_local(c.started_at, json) if c.started_at else "Pending",
45
+ str(len(c.task_ids)),
46
+ ]
47
+ )
48
+
49
+ display_table(column_names, rows, json=json, title=f"Active Multi-node Clusters in environment: {environment_name}")
50
+
51
+
52
+ @cluster_cli.command("shell")
53
+ @synchronizer.create_blocking
54
+ async def shell(
55
+ cluster_id: str = typer.Argument(help="Cluster ID"),
56
+ rank: int = typer.Option(default=0, help="Rank of the node to shell into"),
57
+ ):
58
+ """Open a shell to a multi-node cluster node."""
59
+ client = await _Client.from_env()
60
+ res: api_pb2.ClusterGetResponse = await client.stub.ClusterGet(api_pb2.ClusterGetRequest(cluster_id=cluster_id))
61
+ if len(res.cluster.task_ids) <= rank:
62
+ raise typer.Abort(f"No node with rank {rank} in cluster {cluster_id}")
63
+ task_id = res.cluster.task_ids[rank]
64
+ console = Console()
65
+ is_main = "(main)" if rank == 0 else ""
66
+ console.print(
67
+ f"Opening shell to node {rank} {is_main} of cluster {cluster_id} (container {task_id})", style="green"
68
+ )
69
+
70
+ pty = is_tty()
71
+ req = api_pb2.ContainerExecRequest(
72
+ task_id=task_id,
73
+ command=["/bin/bash"],
74
+ pty_info=get_pty_info(shell=True) if pty else None,
75
+ runtime_debug=config.get("function_runtime_debug"),
76
+ )
77
+ exec_res: api_pb2.ContainerExecResponse = await client.stub.ContainerExec(req)
78
+ if pty:
79
+ await _ContainerProcess(exec_res.exec_id, client).attach()
80
+ else:
81
+ # TODO: redirect stderr to its own stream?
82
+ await _ContainerProcess(exec_res.exec_id, client, stdout=StreamType.STDOUT, stderr=StreamType.STDOUT).wait()
@@ -10,6 +10,7 @@ from modal._utils.async_utils import synchronizer
10
10
 
11
11
  from . import run
12
12
  from .app import app_cli
13
+ from .cluster import cluster_cli
13
14
  from .config import config_cli
14
15
  from .container import container_cli
15
16
  from .dict import dict_cli
@@ -92,6 +93,8 @@ entrypoint_cli_typer.add_typer(launch_cli)
92
93
  # Deployments
93
94
  entrypoint_cli_typer.add_typer(app_cli, rich_help_panel="Deployments")
94
95
  entrypoint_cli_typer.add_typer(container_cli, rich_help_panel="Deployments")
96
+ # TODO: cluster is hidden while multi-node is in beta/experimental
97
+ entrypoint_cli_typer.add_typer(cluster_cli, rich_help_panel="Deployments", hidden=True)
95
98
 
96
99
  # Storage
97
100
  entrypoint_cli_typer.add_typer(dict_cli, rich_help_panel="Storage")
@@ -27,7 +27,7 @@ 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 = "0.74.55"
30
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.57"
31
31
  ): ...
32
32
  def is_closed(self) -> bool: ...
33
33
  @property
@@ -86,7 +86,7 @@ class Client:
86
86
  _snapshotted: bool
87
87
 
88
88
  def __init__(
89
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.55"
89
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.57"
90
90
  ): ...
91
91
  def is_closed(self) -> bool: ...
92
92
  @property
@@ -94,6 +94,7 @@ class Function(
94
94
  restrict_modal_access: bool = False,
95
95
  i6pn_enabled: bool = False,
96
96
  cluster_size: typing.Optional[int] = None,
97
+ rdma: typing.Optional[bool] = None,
97
98
  max_inputs: typing.Optional[int] = None,
98
99
  ephemeral_disk: typing.Optional[int] = None,
99
100
  include_source: typing.Optional[bool] = None,
@@ -222,11 +223,11 @@ class Function(
222
223
 
223
224
  _call_generator_nowait: ___call_generator_nowait_spec[typing_extensions.Self]
224
225
 
225
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
226
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
226
227
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
227
228
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
228
229
 
229
- remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
230
+ remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
230
231
 
231
232
  class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
232
233
  def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
@@ -241,12 +242,12 @@ class Function(
241
242
  self, *args: modal._functions.P.args, **kwargs: modal._functions.P.kwargs
242
243
  ) -> modal._functions.OriginalReturnType: ...
243
244
 
244
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
245
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
245
246
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
246
247
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
247
248
 
248
249
  _experimental_spawn: ___experimental_spawn_spec[
249
- modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
250
+ modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
250
251
  ]
251
252
 
252
253
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
@@ -255,11 +256,11 @@ class Function(
255
256
 
256
257
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
257
258
 
258
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
259
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
259
260
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
260
261
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
261
262
 
262
- spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
263
+ spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
263
264
 
264
265
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]: ...
265
266
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 0.74.55
3
+ Version: 0.74.57
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -127,6 +127,7 @@ modal/cli/__init__.py
127
127
  modal/cli/_download.py
128
128
  modal/cli/_traceback.py
129
129
  modal/cli/app.py
130
+ modal/cli/cluster.py
130
131
  modal/cli/config.py
131
132
  modal/cli/container.py
132
133
  modal/cli/dict.py
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 55 # git: aa1b399
4
+ build_number = 57 # git: 747bcf5
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes