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
@@ -0,0 +1,89 @@
1
+ # Copyright Modal Labs 2024
2
+ import functools
3
+ import sys
4
+ import warnings
5
+ from datetime import date
6
+ from typing import Any, Callable, TypeVar
7
+
8
+ from typing_extensions import ParamSpec # Needed for Python 3.9
9
+
10
+ from ..exception import DeprecationError, PendingDeprecationError
11
+
12
+ _INTERNAL_MODULES = ["modal", "synchronicity"]
13
+
14
+
15
+ def _is_internal_frame(frame):
16
+ module = frame.f_globals["__name__"].split(".")[0]
17
+ return module in _INTERNAL_MODULES
18
+
19
+
20
+ def deprecation_error(deprecated_on: tuple[int, int, int], msg: str):
21
+ raise DeprecationError(f"Deprecated on {date(*deprecated_on)}: {msg}")
22
+
23
+
24
+ def deprecation_warning(
25
+ deprecated_on: tuple[int, int, int], msg: str, *, pending: bool = False, show_source: bool = True
26
+ ) -> None:
27
+ """Issue a Modal deprecation warning with source optionally attributed to user code.
28
+
29
+ See the implementation of the built-in [warnings.warn](https://docs.python.org/3/library/warnings.html#available-functions).
30
+ """
31
+ filename, lineno = "<unknown>", 0
32
+ if show_source:
33
+ # Find the last non-Modal line that triggered the warning
34
+ try:
35
+ frame = sys._getframe()
36
+ while frame is not None and _is_internal_frame(frame):
37
+ frame = frame.f_back
38
+ if frame is not None:
39
+ filename = frame.f_code.co_filename
40
+ lineno = frame.f_lineno
41
+ except ValueError:
42
+ # Use the defaults from above
43
+ pass
44
+
45
+ warning_cls = PendingDeprecationError if pending else DeprecationError
46
+
47
+ # This is a lower-level function that warnings.warn uses
48
+ warnings.warn_explicit(f"{date(*deprecated_on)}: {msg}", warning_cls, filename, lineno)
49
+
50
+
51
+ P = ParamSpec("P")
52
+ R = TypeVar("R")
53
+
54
+
55
+ def renamed_parameter(
56
+ date: tuple[int, int, int],
57
+ old_name: str,
58
+ new_name: str,
59
+ show_source: bool = True,
60
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
61
+ """Decorator for semi-gracefully changing a parameter name.
62
+
63
+ Functions wrapped with this decorator can be defined using only the `new_name` of the parameter.
64
+ If the function is invoked with the `old_name`, the wrapper will pass the value as a keyword
65
+ argument for `new_name` and issue a Modal deprecation warning about the change.
66
+
67
+ Note that this only prevents parameter renamings from breaking code at runtime.
68
+ Type checking will fail when code uses `old_name`. To avoid this, the `old_name` can be
69
+ preserved in the function signature with an `Annotated` type hint indicating the renaming.
70
+ """
71
+
72
+ def decorator(func: Callable[P, R]) -> Callable[P, R]:
73
+ @functools.wraps(func)
74
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
75
+ mut_kwargs: dict[str, Any] = locals()["kwargs"] # Avoid referencing kwargs directly due to bug in sigtools
76
+ if old_name in mut_kwargs:
77
+ mut_kwargs[new_name] = mut_kwargs.pop(old_name)
78
+ func_name = func.__qualname__.removeprefix("_") # Avoid confusion when synchronicity-wrapped
79
+ message = (
80
+ f"The '{old_name}' parameter of `{func_name}` has been renamed to '{new_name}'."
81
+ "\nUsing the old name will become an error in a future release. Please update your code."
82
+ )
83
+ deprecation_warning(date, message, show_source=show_source)
84
+
85
+ return func(*args, **kwargs)
86
+
87
+ return wrapper
88
+
89
+ return decorator
@@ -0,0 +1,98 @@
1
+ # Copyright Modal Labs 2024
2
+ import re
3
+ import shlex
4
+ from pathlib import Path
5
+ from typing import Optional, Sequence
6
+
7
+ from ..exception import InvalidError
8
+
9
+
10
+ def extract_copy_command_patterns(dockerfile_lines: Sequence[str]) -> list[str]:
11
+ """
12
+ Extract all COPY command sources from a Dockerfile.
13
+ Combines multiline COPY commands into a single line.
14
+ """
15
+ copy_source_patterns: set[str] = set()
16
+ current_command = ""
17
+ copy_pattern = re.compile(r"^\s*COPY\s+(.+)$", re.IGNORECASE)
18
+
19
+ # First pass: handle line continuations and collect full commands
20
+ for line in dockerfile_lines:
21
+ line = line.strip()
22
+ if not line or line.startswith("#"):
23
+ # ignore comments and empty lines
24
+ continue
25
+
26
+ if current_command:
27
+ # Continue previous line
28
+ current_command += " " + line.rstrip("\\").strip()
29
+ else:
30
+ # Start new command
31
+ current_command = line.rstrip("\\").strip()
32
+
33
+ if not line.endswith("\\"):
34
+ # Command is complete
35
+
36
+ match = copy_pattern.match(current_command)
37
+ if match:
38
+ args = match.group(1)
39
+ parts = shlex.split(args)
40
+
41
+ # COPY --from=... commands reference external sources and do not need a context mount.
42
+ # https://docs.docker.com/reference/dockerfile/#copy---from
43
+ if parts[0].startswith("--from="):
44
+ current_command = ""
45
+ continue
46
+
47
+ if len(parts) >= 2:
48
+ # Last part is destination, everything else is a mount source
49
+ sources = parts[:-1]
50
+
51
+ for source in sources:
52
+ special_pattern = re.compile(r"^\s*--|\$\s*")
53
+ if special_pattern.match(source):
54
+ raise InvalidError(
55
+ f"COPY command: {source} using special flags/arguments/variables are not supported"
56
+ )
57
+
58
+ if source == ".":
59
+ copy_source_patterns.add("./**")
60
+ else:
61
+ copy_source_patterns.add(source)
62
+
63
+ current_command = ""
64
+
65
+ return list(copy_source_patterns)
66
+
67
+
68
+ def find_dockerignore_file(context_directory: Path, dockerfile_path: Optional[Path] = None) -> Optional[Path]:
69
+ """
70
+ Find dockerignore file relative to current context directory
71
+ and if dockerfile path is provided, check if specific <dockerfile_name>.dockerignore
72
+ file exists in the same directory as <dockerfile_name>
73
+ Finds the most specific dockerignore file that exists.
74
+ """
75
+
76
+ def valid_dockerignore_file(fp):
77
+ # fp has to exist
78
+ if not fp.exists():
79
+ return False
80
+ # fp has to be subpath to current working directory
81
+ if not fp.is_relative_to(context_directory):
82
+ return False
83
+
84
+ return True
85
+
86
+ generic_name = ".dockerignore"
87
+ possible_locations = []
88
+ if dockerfile_path:
89
+ specific_name = f"{dockerfile_path.name}.dockerignore"
90
+ # 1. check if specific <dockerfile_name>.dockerignore file exists in the same directory as <dockerfile_name>
91
+ possible_locations.append(dockerfile_path.parent / specific_name)
92
+ # 2. check if generic .dockerignore file exists in the same directory as <dockerfile_name>
93
+ possible_locations.append(dockerfile_path.parent / generic_name)
94
+
95
+ # 3. check if generic .dockerignore file exists in current working directory
96
+ possible_locations.append(context_directory / generic_name)
97
+
98
+ return next((e for e in possible_locations if valid_dockerignore_file(e)), None)