modal 0.62.115__py3-none-any.whl → 0.72.13__py3-none-any.whl

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 (220) hide show
  1. modal/__init__.py +13 -9
  2. modal/__main__.py +41 -3
  3. modal/_clustered_functions.py +80 -0
  4. modal/_clustered_functions.pyi +22 -0
  5. modal/_container_entrypoint.py +402 -398
  6. modal/_ipython.py +3 -13
  7. modal/_location.py +17 -10
  8. modal/_output.py +243 -99
  9. modal/_pty.py +2 -2
  10. modal/_resolver.py +55 -60
  11. modal/_resources.py +26 -7
  12. modal/_runtime/__init__.py +1 -0
  13. modal/_runtime/asgi.py +519 -0
  14. modal/_runtime/container_io_manager.py +1025 -0
  15. modal/{execution_context.py → _runtime/execution_context.py} +11 -2
  16. modal/_runtime/telemetry.py +169 -0
  17. modal/_runtime/user_code_imports.py +356 -0
  18. modal/_serialization.py +123 -6
  19. modal/_traceback.py +47 -187
  20. modal/_tunnel.py +50 -14
  21. modal/_tunnel.pyi +19 -36
  22. modal/_utils/app_utils.py +3 -17
  23. modal/_utils/async_utils.py +386 -104
  24. modal/_utils/blob_utils.py +157 -186
  25. modal/_utils/bytes_io_segment_payload.py +97 -0
  26. modal/_utils/deprecation.py +89 -0
  27. modal/_utils/docker_utils.py +98 -0
  28. modal/_utils/function_utils.py +299 -98
  29. modal/_utils/grpc_testing.py +47 -34
  30. modal/_utils/grpc_utils.py +54 -21
  31. modal/_utils/hash_utils.py +51 -10
  32. modal/_utils/http_utils.py +39 -9
  33. modal/_utils/logger.py +2 -1
  34. modal/_utils/mount_utils.py +34 -16
  35. modal/_utils/name_utils.py +58 -0
  36. modal/_utils/package_utils.py +14 -1
  37. modal/_utils/pattern_utils.py +205 -0
  38. modal/_utils/rand_pb_testing.py +3 -3
  39. modal/_utils/shell_utils.py +15 -49
  40. modal/_vendor/a2wsgi_wsgi.py +62 -72
  41. modal/_vendor/cloudpickle.py +1 -1
  42. modal/_watcher.py +12 -10
  43. modal/app.py +561 -323
  44. modal/app.pyi +474 -262
  45. modal/call_graph.py +7 -6
  46. modal/cli/_download.py +22 -6
  47. modal/cli/_traceback.py +200 -0
  48. modal/cli/app.py +203 -42
  49. modal/cli/config.py +12 -5
  50. modal/cli/container.py +61 -13
  51. modal/cli/dict.py +128 -0
  52. modal/cli/entry_point.py +26 -13
  53. modal/cli/environment.py +40 -9
  54. modal/cli/import_refs.py +21 -48
  55. modal/cli/launch.py +28 -14
  56. modal/cli/network_file_system.py +57 -21
  57. modal/cli/profile.py +1 -1
  58. modal/cli/programs/run_jupyter.py +34 -9
  59. modal/cli/programs/vscode.py +58 -8
  60. modal/cli/queues.py +131 -0
  61. modal/cli/run.py +199 -96
  62. modal/cli/secret.py +5 -4
  63. modal/cli/token.py +7 -2
  64. modal/cli/utils.py +74 -8
  65. modal/cli/volume.py +97 -56
  66. modal/client.py +248 -144
  67. modal/client.pyi +156 -124
  68. modal/cloud_bucket_mount.py +43 -30
  69. modal/cloud_bucket_mount.pyi +32 -25
  70. modal/cls.py +528 -141
  71. modal/cls.pyi +189 -145
  72. modal/config.py +32 -15
  73. modal/container_process.py +177 -0
  74. modal/container_process.pyi +82 -0
  75. modal/dict.py +50 -54
  76. modal/dict.pyi +120 -164
  77. modal/environments.py +106 -5
  78. modal/environments.pyi +77 -25
  79. modal/exception.py +30 -43
  80. modal/experimental.py +62 -2
  81. modal/file_io.py +537 -0
  82. modal/file_io.pyi +235 -0
  83. modal/file_pattern_matcher.py +196 -0
  84. modal/functions.py +846 -428
  85. modal/functions.pyi +446 -387
  86. modal/gpu.py +57 -44
  87. modal/image.py +943 -417
  88. modal/image.pyi +584 -245
  89. modal/io_streams.py +434 -0
  90. modal/io_streams.pyi +122 -0
  91. modal/mount.py +223 -90
  92. modal/mount.pyi +241 -243
  93. modal/network_file_system.py +85 -86
  94. modal/network_file_system.pyi +151 -110
  95. modal/object.py +66 -36
  96. modal/object.pyi +166 -143
  97. modal/output.py +63 -0
  98. modal/parallel_map.py +73 -47
  99. modal/parallel_map.pyi +51 -63
  100. modal/partial_function.py +272 -107
  101. modal/partial_function.pyi +219 -120
  102. modal/proxy.py +15 -12
  103. modal/proxy.pyi +3 -8
  104. modal/queue.py +96 -72
  105. modal/queue.pyi +210 -135
  106. modal/requirements/2024.04.txt +2 -1
  107. modal/requirements/2024.10.txt +16 -0
  108. modal/requirements/README.md +21 -0
  109. modal/requirements/base-images.json +22 -0
  110. modal/retries.py +45 -4
  111. modal/runner.py +325 -203
  112. modal/runner.pyi +124 -110
  113. modal/running_app.py +27 -4
  114. modal/sandbox.py +509 -231
  115. modal/sandbox.pyi +396 -169
  116. modal/schedule.py +2 -2
  117. modal/scheduler_placement.py +20 -3
  118. modal/secret.py +41 -25
  119. modal/secret.pyi +62 -42
  120. modal/serving.py +39 -49
  121. modal/serving.pyi +37 -43
  122. modal/stream_type.py +15 -0
  123. modal/token_flow.py +5 -3
  124. modal/token_flow.pyi +37 -32
  125. modal/volume.py +123 -137
  126. modal/volume.pyi +228 -221
  127. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/METADATA +5 -5
  128. modal-0.72.13.dist-info/RECORD +174 -0
  129. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/top_level.txt +0 -1
  130. modal_docs/gen_reference_docs.py +3 -1
  131. modal_docs/mdmd/mdmd.py +0 -1
  132. modal_docs/mdmd/signatures.py +1 -2
  133. modal_global_objects/images/base_images.py +28 -0
  134. modal_global_objects/mounts/python_standalone.py +2 -2
  135. modal_proto/__init__.py +1 -1
  136. modal_proto/api.proto +1231 -531
  137. modal_proto/api_grpc.py +750 -430
  138. modal_proto/api_pb2.py +2102 -1176
  139. modal_proto/api_pb2.pyi +8859 -0
  140. modal_proto/api_pb2_grpc.py +1329 -675
  141. modal_proto/api_pb2_grpc.pyi +1416 -0
  142. modal_proto/modal_api_grpc.py +149 -0
  143. modal_proto/modal_options_grpc.py +3 -0
  144. modal_proto/options_pb2.pyi +20 -0
  145. modal_proto/options_pb2_grpc.pyi +7 -0
  146. modal_proto/py.typed +0 -0
  147. modal_version/__init__.py +1 -1
  148. modal_version/_version_generated.py +2 -2
  149. modal/_asgi.py +0 -370
  150. modal/_container_exec.py +0 -128
  151. modal/_container_io_manager.py +0 -646
  152. modal/_container_io_manager.pyi +0 -412
  153. modal/_sandbox_shell.py +0 -49
  154. modal/app_utils.py +0 -20
  155. modal/app_utils.pyi +0 -17
  156. modal/execution_context.pyi +0 -37
  157. modal/shared_volume.py +0 -23
  158. modal/shared_volume.pyi +0 -24
  159. modal-0.62.115.dist-info/RECORD +0 -207
  160. modal_global_objects/images/conda.py +0 -15
  161. modal_global_objects/images/debian_slim.py +0 -15
  162. modal_global_objects/images/micromamba.py +0 -15
  163. test/__init__.py +0 -1
  164. test/aio_test.py +0 -12
  165. test/async_utils_test.py +0 -279
  166. test/blob_test.py +0 -67
  167. test/cli_imports_test.py +0 -149
  168. test/cli_test.py +0 -674
  169. test/client_test.py +0 -203
  170. test/cloud_bucket_mount_test.py +0 -22
  171. test/cls_test.py +0 -636
  172. test/config_test.py +0 -149
  173. test/conftest.py +0 -1485
  174. test/container_app_test.py +0 -50
  175. test/container_test.py +0 -1405
  176. test/cpu_test.py +0 -23
  177. test/decorator_test.py +0 -85
  178. test/deprecation_test.py +0 -34
  179. test/dict_test.py +0 -51
  180. test/e2e_test.py +0 -68
  181. test/error_test.py +0 -7
  182. test/function_serialization_test.py +0 -32
  183. test/function_test.py +0 -791
  184. test/function_utils_test.py +0 -101
  185. test/gpu_test.py +0 -159
  186. test/grpc_utils_test.py +0 -82
  187. test/helpers.py +0 -47
  188. test/image_test.py +0 -814
  189. test/live_reload_test.py +0 -80
  190. test/lookup_test.py +0 -70
  191. test/mdmd_test.py +0 -329
  192. test/mount_test.py +0 -162
  193. test/mounted_files_test.py +0 -327
  194. test/network_file_system_test.py +0 -188
  195. test/notebook_test.py +0 -66
  196. test/object_test.py +0 -41
  197. test/package_utils_test.py +0 -25
  198. test/queue_test.py +0 -115
  199. test/resolver_test.py +0 -59
  200. test/retries_test.py +0 -67
  201. test/runner_test.py +0 -85
  202. test/sandbox_test.py +0 -191
  203. test/schedule_test.py +0 -15
  204. test/scheduler_placement_test.py +0 -57
  205. test/secret_test.py +0 -89
  206. test/serialization_test.py +0 -50
  207. test/stub_composition_test.py +0 -10
  208. test/stub_test.py +0 -361
  209. test/test_asgi_wrapper.py +0 -234
  210. test/token_flow_test.py +0 -18
  211. test/traceback_test.py +0 -135
  212. test/tunnel_test.py +0 -29
  213. test/utils_test.py +0 -88
  214. test/version_test.py +0 -14
  215. test/volume_test.py +0 -397
  216. test/watcher_test.py +0 -58
  217. test/webhook_test.py +0 -145
  218. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/LICENSE +0 -0
  219. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/WHEEL +0 -0
  220. {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/entry_points.txt +0 -0
modal/__init__.py CHANGED
@@ -1,27 +1,29 @@
1
1
  # Copyright Modal Labs 2022
2
2
  import sys
3
3
 
4
- if sys.version_info[:2] < (3, 8):
5
- raise RuntimeError("This version of Modal requires at least Python 3.8")
6
- if sys.version_info[:2] >= (3, 13):
7
- raise RuntimeError("This version of Modal does not support Python 3.13+")
4
+ if sys.version_info[:2] < (3, 9):
5
+ raise RuntimeError("This version of Modal requires at least Python 3.9")
6
+ if sys.version_info[:2] >= (3, 14):
7
+ raise RuntimeError("This version of Modal does not support Python 3.14+")
8
8
 
9
9
  from modal_version import __version__
10
10
 
11
11
  try:
12
+ from ._runtime.execution_context import current_function_call_id, current_input_id, interact, is_local
12
13
  from ._tunnel import Tunnel, forward
13
14
  from .app import App, Stub
14
15
  from .client import Client
15
16
  from .cloud_bucket_mount import CloudBucketMount
16
- from .cls import Cls
17
+ from .cls import Cls, parameter
17
18
  from .dict import Dict
18
19
  from .exception import Error
19
- from .execution_context import current_function_call_id, current_input_id, interact, is_local
20
+ from .file_pattern_matcher import FilePatternMatcher
20
21
  from .functions import Function
21
22
  from .image import Image
22
23
  from .mount import Mount
23
24
  from .network_file_system import NetworkFileSystem
24
- from .partial_function import asgi_app, build, enter, exit, method, web_endpoint, web_server, wsgi_app
25
+ from .output import enable_output
26
+ from .partial_function import asgi_app, batched, build, enter, exit, method, web_endpoint, web_server, wsgi_app
25
27
  from .proxy import Proxy
26
28
  from .queue import Queue
27
29
  from .retries import Retries
@@ -29,7 +31,6 @@ try:
29
31
  from .schedule import Cron, Period
30
32
  from .scheduler_placement import SchedulerPlacement
31
33
  from .secret import Secret
32
- from .shared_volume import SharedVolume
33
34
  from .volume import Volume
34
35
  except Exception:
35
36
  print()
@@ -48,6 +49,7 @@ __all__ = [
48
49
  "Cron",
49
50
  "Dict",
50
51
  "Error",
52
+ "FilePatternMatcher",
51
53
  "Function",
52
54
  "Image",
53
55
  "Mount",
@@ -60,20 +62,22 @@ __all__ = [
60
62
  "Sandbox",
61
63
  "SchedulerPlacement",
62
64
  "Secret",
63
- "SharedVolume",
64
65
  "Stub",
65
66
  "Tunnel",
66
67
  "Volume",
67
68
  "asgi_app",
69
+ "batched",
68
70
  "build",
69
71
  "current_function_call_id",
70
72
  "current_input_id",
73
+ "enable_output",
71
74
  "enter",
72
75
  "exit",
73
76
  "forward",
74
77
  "is_local",
75
78
  "interact",
76
79
  "method",
80
+ "parameter",
77
81
  "web_endpoint",
78
82
  "web_server",
79
83
  "wsgi_app",
modal/__main__.py CHANGED
@@ -1,7 +1,8 @@
1
1
  # Copyright Modal Labs 2022
2
2
  import sys
3
3
 
4
- from ._traceback import highlight_modal_deprecation_warnings, reduce_traceback_to_user_code, setup_rich_traceback
4
+ from ._traceback import reduce_traceback_to_user_code
5
+ from .cli._traceback import highlight_modal_deprecation_warnings, setup_rich_traceback
5
6
  from .cli.entry_point import entrypoint_cli
6
7
  from .cli.import_refs import _CliUserExecutionError
7
8
  from .config import config
@@ -19,19 +20,56 @@ def main():
19
20
  if config.get("traceback"):
20
21
  raise
21
22
 
23
+ assert exc.__cause__ # We should always raise this class from another error
22
24
  tb = reduce_traceback_to_user_code(exc.__cause__.__traceback__, exc.user_source)
23
25
  sys.excepthook(type(exc.__cause__), exc.__cause__, tb)
24
26
  sys.exit(1)
25
27
 
26
28
  except Exception as exc:
27
- if config.get("traceback"):
29
+ if (
30
+ # User has asked to alway see full tracebacks
31
+ config.get("traceback")
32
+ # The exception message is empty, so we need to provide _some_ actionable information
33
+ or not str(exc)
34
+ ):
28
35
  raise
29
36
 
37
+ from grpclib import GRPCError, Status
30
38
  from rich.console import Console
31
39
  from rich.panel import Panel
40
+ from rich.text import Text
41
+
42
+ if isinstance(exc, GRPCError):
43
+ status_map = {
44
+ Status.ABORTED: "Aborted",
45
+ Status.ALREADY_EXISTS: "Already exists",
46
+ Status.CANCELLED: "Cancelled",
47
+ Status.DATA_LOSS: "Data loss",
48
+ Status.DEADLINE_EXCEEDED: "Deadline exceeded",
49
+ Status.FAILED_PRECONDITION: "Failed precondition",
50
+ Status.INTERNAL: "Internal",
51
+ Status.INVALID_ARGUMENT: "Invalid",
52
+ Status.NOT_FOUND: "Not found",
53
+ Status.OUT_OF_RANGE: "Out of range",
54
+ Status.PERMISSION_DENIED: "Permission denied",
55
+ Status.RESOURCE_EXHAUSTED: "Resource exhausted",
56
+ Status.UNAUTHENTICATED: "Unauthenticaed",
57
+ Status.UNAVAILABLE: "Unavailable",
58
+ Status.UNIMPLEMENTED: "Unimplemented",
59
+ Status.UNKNOWN: "Unknown",
60
+ }
61
+ title = f"Error: {status_map.get(exc.status, 'Unknown')}"
62
+ content = str(exc.message)
63
+ if exc.details:
64
+ content += f"\n\nDetails: {exc.details}"
65
+ else:
66
+ title = "Error"
67
+ content = str(exc)
68
+ if notes := getattr(exc, "__notes__", []):
69
+ content = f"{content}\n\nNote: {' ' .join(notes)}"
32
70
 
33
71
  console = Console(stderr=True)
34
- panel = Panel(str(exc), border_style="red", title="Error", title_align="left")
72
+ panel = Panel(Text(content), title=title, title_align="left", border_style="red")
35
73
  console.print(panel, highlight=False)
36
74
  sys.exit(1)
37
75
 
@@ -0,0 +1,80 @@
1
+ # Copyright Modal Labs 2024
2
+ import os
3
+ import socket
4
+ from dataclasses import dataclass
5
+ from typing import Optional
6
+
7
+ from modal._utils.async_utils import synchronize_api
8
+ from modal._utils.grpc_utils import retry_transient_errors
9
+ from modal.client import _Client
10
+ from modal.exception import InvalidError
11
+ from modal_proto import api_pb2
12
+
13
+
14
+ @dataclass
15
+ class ClusterInfo:
16
+ rank: int
17
+ container_ips: list[str]
18
+
19
+
20
+ cluster_info: Optional[ClusterInfo] = None
21
+
22
+
23
+ def get_cluster_info() -> ClusterInfo:
24
+ if cluster_info is None:
25
+ raise InvalidError(
26
+ "Cluster info not initialized. Please ensure that you are "
27
+ "calling get_cluster_info() from a clustered function."
28
+ )
29
+ return cluster_info
30
+
31
+
32
+ async def _initialize_clustered_function(client: _Client, task_id: str, world_size: int):
33
+ global cluster_info
34
+
35
+ def get_i6pn():
36
+ """Returns the ipv6 address assigned to this container."""
37
+ return socket.getaddrinfo("i6pn.modal.local", None, socket.AF_INET6)[0][4][0]
38
+
39
+ hostname = socket.gethostname()
40
+ container_ip = get_i6pn()
41
+
42
+ # nccl's default host ID is $(hostname)$(cat /proc/sys/kernel/random/boot_id).
43
+ # on runc, if two i6pn-linked containers get scheduled on the same worker,
44
+ # their boot ID and hostname will both be identical, causing nccl to break.
45
+ # As a workaround, we can explicitly specify a unique host ID here.
46
+ # See MOD-4067.
47
+ os.environ["NCCL_HOSTID"] = f"{hostname}{container_ip}"
48
+
49
+ # We found these settings to work well in most cases. You may be able to achieve
50
+ # better performance by tuning these settings.
51
+ if os.environ["MODAL_CLOUD_PROVIDER"] in ("CLOUD_PROVIDER_GCP", "CLOUD_PROVIDER_OCI"):
52
+ os.environ["NCCL_SOCKET_NTHREADS"] = "4"
53
+ os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
54
+ elif os.environ["MODAL_CLOUD_PROVIDER"] == "CLOUD_PROVIDER_AWS":
55
+ os.environ["NCCL_SOCKET_NTHREADS"] = "2"
56
+ os.environ["NCCL_NSOCKS_PERTHREAD"] = "8"
57
+ else:
58
+ os.environ["NCCL_SOCKET_NTHREADS"] = "1"
59
+ os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
60
+
61
+ if world_size > 1:
62
+ resp: api_pb2.TaskClusterHelloResponse = await retry_transient_errors(
63
+ client.stub.TaskClusterHello,
64
+ api_pb2.TaskClusterHelloRequest(
65
+ task_id=task_id,
66
+ container_ip=container_ip,
67
+ ),
68
+ )
69
+ cluster_info = ClusterInfo(
70
+ rank=resp.cluster_rank,
71
+ container_ips=resp.container_ips,
72
+ )
73
+ else:
74
+ cluster_info = ClusterInfo(
75
+ rank=0,
76
+ container_ips=[container_ip],
77
+ )
78
+
79
+
80
+ initialize_clustered_function = synchronize_api(_initialize_clustered_function)
@@ -0,0 +1,22 @@
1
+ import modal.client
2
+ import typing
3
+ import typing_extensions
4
+
5
+ class ClusterInfo:
6
+ rank: int
7
+ container_ips: list[str]
8
+
9
+ def __init__(self, rank: int, container_ips: list[str]) -> None: ...
10
+ def __repr__(self): ...
11
+ def __eq__(self, other): ...
12
+
13
+ def get_cluster_info() -> ClusterInfo: ...
14
+ async def _initialize_clustered_function(client: modal.client._Client, task_id: str, world_size: int): ...
15
+
16
+ class __initialize_clustered_function_spec(typing_extensions.Protocol):
17
+ def __call__(self, client: modal.client.Client, task_id: str, world_size: int): ...
18
+ async def aio(self, client: modal.client.Client, task_id: str, world_size: int): ...
19
+
20
+ initialize_clustered_function: __initialize_clustered_function_spec
21
+
22
+ cluster_info: typing.Optional[ClusterInfo]