modal 0.62.16__py3-none-any.whl → 0.72.11__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 +17 -13
  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 +420 -937
  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 -59
  11. modal/_resources.py +51 -0
  12. modal/_runtime/__init__.py +1 -0
  13. modal/_runtime/asgi.py +519 -0
  14. modal/_runtime/container_io_manager.py +1036 -0
  15. modal/_runtime/execution_context.py +89 -0
  16. modal/_runtime/telemetry.py +169 -0
  17. modal/_runtime/user_code_imports.py +356 -0
  18. modal/_serialization.py +134 -9
  19. modal/_traceback.py +47 -187
  20. modal/_tunnel.py +52 -16
  21. modal/_tunnel.pyi +19 -36
  22. modal/_utils/app_utils.py +3 -17
  23. modal/_utils/async_utils.py +479 -100
  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 +460 -171
  29. modal/_utils/grpc_testing.py +47 -31
  30. modal/_utils/grpc_utils.py +62 -109
  31. modal/_utils/hash_utils.py +61 -19
  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 +5 -7
  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 +14 -12
  43. modal/app.py +1003 -314
  44. modal/app.pyi +540 -264
  45. modal/call_graph.py +7 -6
  46. modal/cli/_download.py +63 -53
  47. modal/cli/_traceback.py +200 -0
  48. modal/cli/app.py +205 -45
  49. modal/cli/config.py +12 -5
  50. modal/cli/container.py +62 -14
  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 +64 -58
  55. modal/cli/launch.py +32 -18
  56. modal/cli/network_file_system.py +64 -83
  57. modal/cli/profile.py +1 -1
  58. modal/cli/programs/run_jupyter.py +35 -10
  59. modal/cli/programs/vscode.py +60 -10
  60. modal/cli/queues.py +131 -0
  61. modal/cli/run.py +234 -131
  62. modal/cli/secret.py +8 -7
  63. modal/cli/token.py +7 -2
  64. modal/cli/utils.py +79 -10
  65. modal/cli/volume.py +110 -109
  66. modal/client.py +250 -144
  67. modal/client.pyi +157 -118
  68. modal/cloud_bucket_mount.py +108 -34
  69. modal/cloud_bucket_mount.pyi +32 -38
  70. modal/cls.py +535 -148
  71. modal/cls.pyi +190 -146
  72. modal/config.py +41 -19
  73. modal/container_process.py +177 -0
  74. modal/container_process.pyi +82 -0
  75. modal/dict.py +111 -65
  76. modal/dict.pyi +136 -131
  77. modal/environments.py +106 -5
  78. modal/environments.pyi +77 -25
  79. modal/exception.py +34 -43
  80. modal/experimental.py +61 -2
  81. modal/extensions/ipython.py +5 -5
  82. modal/file_io.py +537 -0
  83. modal/file_io.pyi +235 -0
  84. modal/file_pattern_matcher.py +197 -0
  85. modal/functions.py +906 -911
  86. modal/functions.pyi +466 -430
  87. modal/gpu.py +57 -44
  88. modal/image.py +1089 -479
  89. modal/image.pyi +584 -228
  90. modal/io_streams.py +434 -0
  91. modal/io_streams.pyi +122 -0
  92. modal/mount.py +314 -101
  93. modal/mount.pyi +241 -235
  94. modal/network_file_system.py +92 -92
  95. modal/network_file_system.pyi +152 -110
  96. modal/object.py +67 -36
  97. modal/object.pyi +166 -143
  98. modal/output.py +63 -0
  99. modal/parallel_map.py +434 -0
  100. modal/parallel_map.pyi +75 -0
  101. modal/partial_function.py +282 -117
  102. modal/partial_function.pyi +222 -129
  103. modal/proxy.py +15 -12
  104. modal/proxy.pyi +3 -8
  105. modal/queue.py +182 -65
  106. modal/queue.pyi +218 -118
  107. modal/requirements/2024.04.txt +29 -0
  108. modal/requirements/2024.10.txt +16 -0
  109. modal/requirements/README.md +21 -0
  110. modal/requirements/base-images.json +22 -0
  111. modal/retries.py +48 -7
  112. modal/runner.py +459 -156
  113. modal/runner.pyi +135 -71
  114. modal/running_app.py +38 -0
  115. modal/sandbox.py +514 -236
  116. modal/sandbox.pyi +397 -169
  117. modal/schedule.py +4 -4
  118. modal/scheduler_placement.py +20 -3
  119. modal/secret.py +56 -31
  120. modal/secret.pyi +62 -42
  121. modal/serving.py +51 -56
  122. modal/serving.pyi +44 -36
  123. modal/stream_type.py +15 -0
  124. modal/token_flow.py +5 -3
  125. modal/token_flow.pyi +37 -32
  126. modal/volume.py +285 -157
  127. modal/volume.pyi +249 -184
  128. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/METADATA +7 -7
  129. modal-0.72.11.dist-info/RECORD +174 -0
  130. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/top_level.txt +0 -1
  131. modal_docs/gen_reference_docs.py +3 -1
  132. modal_docs/mdmd/mdmd.py +0 -1
  133. modal_docs/mdmd/signatures.py +5 -2
  134. modal_global_objects/images/base_images.py +28 -0
  135. modal_global_objects/mounts/python_standalone.py +2 -2
  136. modal_proto/__init__.py +1 -1
  137. modal_proto/api.proto +1288 -533
  138. modal_proto/api_grpc.py +856 -456
  139. modal_proto/api_pb2.py +2165 -1157
  140. modal_proto/api_pb2.pyi +8859 -0
  141. modal_proto/api_pb2_grpc.py +1674 -855
  142. modal_proto/api_pb2_grpc.pyi +1416 -0
  143. modal_proto/modal_api_grpc.py +149 -0
  144. modal_proto/modal_options_grpc.py +3 -0
  145. modal_proto/options_pb2.pyi +20 -0
  146. modal_proto/options_pb2_grpc.pyi +7 -0
  147. modal_proto/py.typed +0 -0
  148. modal_version/__init__.py +1 -1
  149. modal_version/_version_generated.py +2 -2
  150. modal/_asgi.py +0 -370
  151. modal/_container_entrypoint.pyi +0 -378
  152. modal/_container_exec.py +0 -128
  153. modal/_sandbox_shell.py +0 -49
  154. modal/shared_volume.py +0 -23
  155. modal/shared_volume.pyi +0 -24
  156. modal/stub.py +0 -783
  157. modal/stub.pyi +0 -332
  158. modal-0.62.16.dist-info/RECORD +0 -198
  159. modal_global_objects/images/conda.py +0 -15
  160. modal_global_objects/images/debian_slim.py +0 -15
  161. modal_global_objects/images/micromamba.py +0 -15
  162. test/__init__.py +0 -1
  163. test/aio_test.py +0 -12
  164. test/async_utils_test.py +0 -262
  165. test/blob_test.py +0 -67
  166. test/cli_imports_test.py +0 -149
  167. test/cli_test.py +0 -659
  168. test/client_test.py +0 -194
  169. test/cls_test.py +0 -630
  170. test/config_test.py +0 -137
  171. test/conftest.py +0 -1420
  172. test/container_app_test.py +0 -32
  173. test/container_test.py +0 -1389
  174. test/cpu_test.py +0 -23
  175. test/decorator_test.py +0 -85
  176. test/deprecation_test.py +0 -34
  177. test/dict_test.py +0 -33
  178. test/e2e_test.py +0 -68
  179. test/error_test.py +0 -7
  180. test/function_serialization_test.py +0 -32
  181. test/function_test.py +0 -653
  182. test/function_utils_test.py +0 -101
  183. test/gpu_test.py +0 -159
  184. test/grpc_utils_test.py +0 -141
  185. test/helpers.py +0 -42
  186. test/image_test.py +0 -669
  187. test/live_reload_test.py +0 -80
  188. test/lookup_test.py +0 -70
  189. test/mdmd_test.py +0 -329
  190. test/mount_test.py +0 -162
  191. test/mounted_files_test.py +0 -329
  192. test/network_file_system_test.py +0 -181
  193. test/notebook_test.py +0 -66
  194. test/object_test.py +0 -41
  195. test/package_utils_test.py +0 -25
  196. test/queue_test.py +0 -97
  197. test/resolver_test.py +0 -58
  198. test/retries_test.py +0 -67
  199. test/runner_test.py +0 -85
  200. test/sandbox_test.py +0 -191
  201. test/schedule_test.py +0 -15
  202. test/scheduler_placement_test.py +0 -29
  203. test/secret_test.py +0 -78
  204. test/serialization_test.py +0 -42
  205. test/stub_composition_test.py +0 -10
  206. test/stub_test.py +0 -360
  207. test/test_asgi_wrapper.py +0 -234
  208. test/token_flow_test.py +0 -18
  209. test/traceback_test.py +0 -135
  210. test/tunnel_test.py +0 -29
  211. test/utils_test.py +0 -88
  212. test/version_test.py +0 -14
  213. test/volume_test.py +0 -341
  214. test/watcher_test.py +0 -30
  215. test/webhook_test.py +0 -146
  216. /modal/{requirements.312.txt → requirements/2023.12.312.txt} +0 -0
  217. /modal/{requirements.txt → requirements/2023.12.txt} +0 -0
  218. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/LICENSE +0 -0
  219. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/WHEEL +0 -0
  220. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/entry_points.txt +0 -0
modal/__init__.py CHANGED
@@ -1,26 +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
- from .app import container_app, interact, is_local
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 .functions import Function, current_function_call_id, current_input_id
20
+ from .file_pattern_matcher import FilePatternMatcher
21
+ from .functions import Function
20
22
  from .image import Image
21
23
  from .mount import Mount
22
24
  from .network_file_system import NetworkFileSystem
23
- 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
24
27
  from .proxy import Proxy
25
28
  from .queue import Queue
26
29
  from .retries import Retries
@@ -28,8 +31,6 @@ try:
28
31
  from .schedule import Cron, Period
29
32
  from .scheduler_placement import SchedulerPlacement
30
33
  from .secret import Secret
31
- from .shared_volume import SharedVolume
32
- from .stub import Stub
33
34
  from .volume import Volume
34
35
  except Exception:
35
36
  print()
@@ -42,11 +43,13 @@ except Exception:
42
43
 
43
44
  __all__ = [
44
45
  "__version__",
46
+ "App",
45
47
  "Client",
46
48
  "Cls",
47
49
  "Cron",
48
50
  "Dict",
49
51
  "Error",
52
+ "FilePatternMatcher",
50
53
  "Function",
51
54
  "Image",
52
55
  "Mount",
@@ -59,22 +62,23 @@ __all__ = [
59
62
  "Sandbox",
60
63
  "SchedulerPlacement",
61
64
  "Secret",
62
- "SharedVolume",
63
65
  "Stub",
64
66
  "Tunnel",
65
67
  "Volume",
66
68
  "asgi_app",
69
+ "batched",
67
70
  "build",
68
- "container_app",
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",
78
+ "interact",
75
79
  "method",
80
+ "parameter",
76
81
  "web_endpoint",
77
82
  "web_server",
78
83
  "wsgi_app",
79
- "interact",
80
84
  ]
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]