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
test/cpu_test.py DELETED
@@ -1,23 +0,0 @@
1
- # Copyright Modal Labs 2023
2
- import pytest
3
-
4
- from modal import App
5
- from modal.exception import InvalidError
6
-
7
-
8
- def dummy():
9
- pass
10
-
11
-
12
- def test_cpu_lower_bound(client, servicer):
13
- app = App()
14
-
15
- app.function(cpu=0.0)(dummy)
16
-
17
- with pytest.raises(InvalidError):
18
- with app.run(client=client):
19
- pass
20
-
21
- app.function(cpu=42)(dummy)
22
- with app.run(client=client):
23
- pass
test/decorator_test.py DELETED
@@ -1,85 +0,0 @@
1
- # Copyright Modal Labs 2023
2
- import pytest
3
-
4
- from modal import App, asgi_app, method, web_endpoint, wsgi_app
5
- from modal.exception import InvalidError
6
-
7
-
8
- def test_local_entrypoint_forgot_parentheses():
9
- app = App()
10
-
11
- with pytest.raises(InvalidError, match="local_entrypoint()"):
12
-
13
- @app.local_entrypoint # type: ignore
14
- def f():
15
- pass
16
-
17
-
18
- def test_function_forgot_parentheses():
19
- app = App()
20
-
21
- with pytest.raises(InvalidError, match="function()"):
22
-
23
- @app.function # type: ignore
24
- def f():
25
- pass
26
-
27
-
28
- def test_cls_forgot_parentheses():
29
- app = App()
30
-
31
- with pytest.raises(InvalidError, match="cls()"):
32
-
33
- @app.cls # type: ignore
34
- class XYZ:
35
- pass
36
-
37
-
38
- def test_method_forgot_parentheses():
39
- app = App()
40
-
41
- with pytest.raises(InvalidError, match="method()"):
42
-
43
- @app.cls()
44
- class XYZ:
45
- @method # type: ignore
46
- def f(self):
47
- pass
48
-
49
-
50
- def test_invalid_web_decorator_usage():
51
- app = App()
52
-
53
- with pytest.raises(InvalidError, match="web_endpoint()"):
54
-
55
- @app.function() # type: ignore
56
- @web_endpoint # type: ignore
57
- def my_handle():
58
- pass
59
-
60
- with pytest.raises(InvalidError, match="asgi_app()"):
61
-
62
- @app.function() # type: ignore
63
- @asgi_app # type: ignore
64
- def my_handle_asgi():
65
- pass
66
-
67
- with pytest.raises(InvalidError, match="wsgi_app()"):
68
-
69
- @app.function() # type: ignore
70
- @wsgi_app # type: ignore
71
- def my_handle_wsgi():
72
- pass
73
-
74
-
75
- def test_web_endpoint_method():
76
- app = App()
77
-
78
- with pytest.raises(InvalidError, match="remove the `@method`"):
79
-
80
- @app.cls()
81
- class Container:
82
- @method() # type: ignore
83
- @web_endpoint()
84
- def generate(self):
85
- pass
test/deprecation_test.py DELETED
@@ -1,34 +0,0 @@
1
- # Copyright Modal Labs 2022
2
- import pytest
3
-
4
- from modal.exception import DeprecationError
5
-
6
- from .supports.functions import deprecated_function
7
-
8
- # Not a pytest unit test, but an extra assertion that we catch issues in global scope too
9
- # See #2228
10
- exc = None
11
- try:
12
- deprecated_function(42)
13
- except Exception as e:
14
- exc = e
15
- finally:
16
- assert isinstance(exc, DeprecationError) # If you see this, try running `pytest client/client_test`
17
-
18
-
19
- def test_deprecation():
20
- # See conftest.py in the root of the repo
21
- # All deprecation warnings in modal during tests will trigger exceptions
22
- with pytest.raises(DeprecationError):
23
- deprecated_function(42)
24
-
25
- # With this context manager, it doesn't raise an exception, but we record
26
- # the warning. This is the normal behavior outside of pytest.
27
- with pytest.warns(DeprecationError) as record:
28
- res = deprecated_function(42)
29
- assert res == 1764
30
-
31
- # Make sure it raises in the right file
32
- from .supports import functions
33
-
34
- assert record[0].filename == functions.__file__
test/dict_test.py DELETED
@@ -1,51 +0,0 @@
1
- # Copyright Modal Labs 2022
2
- import pytest
3
- import time
4
-
5
- from modal import Dict
6
- from modal.exception import NotFoundError
7
-
8
-
9
- def test_dict_app(servicer, client):
10
- d = Dict.lookup("my-amazing-dict", {"xyz": 123}, create_if_missing=True, client=client)
11
- d["foo"] = 42
12
- d["foo"] += 5
13
- assert d["foo"] == 47
14
- assert d.len() == 2
15
-
16
- assert sorted(d.keys()) == ["foo", "xyz"]
17
- assert sorted(d.values()) == [47, 123]
18
- assert sorted(d.items()) == [("foo", 47), ("xyz", 123)]
19
-
20
- d.clear()
21
- assert d.len() == 0
22
- with pytest.raises(KeyError):
23
- _ = d["foo"]
24
-
25
- assert d.get("foo", default=True)
26
- d["foo"] = None
27
- assert d["foo"] is None
28
-
29
- Dict.delete("my-amazing-dict", client=client)
30
- with pytest.raises(NotFoundError):
31
- Dict.lookup("my-amazing-dict", client=client)
32
-
33
-
34
- def test_dict_ephemeral(servicer, client):
35
- assert servicer.n_dict_heartbeats == 0
36
- with Dict.ephemeral({"bar": 123}, client=client, _heartbeat_sleep=1) as d:
37
- d["foo"] = 42
38
- assert d.len() == 2
39
- assert d["foo"] == 42
40
- assert d["bar"] == 123
41
- time.sleep(1.5) # Make time for 2 heartbeats
42
- assert servicer.n_dict_heartbeats == 2
43
-
44
-
45
- def test_dict_lazy_hydrate_named(set_env_client, servicer):
46
- with servicer.intercept() as ctx:
47
- d = Dict.from_name("foo", create_if_missing=True)
48
- assert len(ctx.get_requests("DictGetOrCreate")) == 0 # sanity check that the get request is lazy
49
- d["foo"] = 42
50
- assert d["foo"] == 42
51
- assert len(ctx.get_requests("DictGetOrCreate")) == 1 # just sanity check that object is only hydrated once...
test/e2e_test.py DELETED
@@ -1,68 +0,0 @@
1
- # Copyright Modal Labs 2022
2
- import os
3
- import pathlib
4
- import subprocess
5
- import sys
6
- from typing import Tuple
7
-
8
-
9
- def _cli(args, server_url, extra_env={}, check=True) -> Tuple[int, str, str]:
10
- lib_dir = pathlib.Path(__file__).parent.parent
11
- args = [sys.executable] + args
12
- env = {
13
- "MODAL_SERVER_URL": server_url,
14
- **os.environ,
15
- "PYTHONUTF8": "1", # For windows
16
- **extra_env,
17
- }
18
- ret = subprocess.run(args, cwd=lib_dir, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
19
-
20
- stdout = ret.stdout.decode()
21
- stderr = ret.stderr.decode()
22
- if check and ret.returncode != 0:
23
- raise Exception(f"Failed with {ret.returncode} stdout: {stdout} stderr: {stderr}")
24
- return ret.returncode, stdout, stderr
25
-
26
-
27
- def test_run_e2e(servicer):
28
- _, _, err = _cli(["-m", "test.supports.script"], servicer.remote_addr)
29
- assert err == ""
30
-
31
-
32
- def test_run_progress_info(servicer):
33
- returncode, stdout, stderr = _cli(["-m", "test.supports.progress_info"], servicer.remote_addr)
34
- assert returncode == 0
35
- assert stderr == ""
36
- lines = stdout.splitlines()
37
- assert "Initialized. View run at https://modaltest.com/apps/ap-123" in lines[0]
38
- assert "App completed" in lines[-1]
39
-
40
-
41
- def test_run_profiler(servicer):
42
- _cli(["-m", "cProfile", "-m", "test.supports.script"], servicer.remote_addr)
43
-
44
-
45
- def test_run_unconsumed_map(servicer):
46
- _, _, err = _cli(["-m", "test.supports.unconsumed_map"], servicer.remote_addr)
47
- assert "map" in err
48
- assert "for-loop" in err
49
-
50
- _, _, err = _cli(["-m", "test.supports.consumed_map"], servicer.remote_addr)
51
- assert "map" not in err
52
- assert "for-loop" not in err
53
-
54
-
55
- def test_auth_failure_last_line(servicer):
56
- returncode, out, err = _cli(
57
- ["-m", "test.supports.script"],
58
- servicer.remote_addr,
59
- extra_env={"MODAL_TOKEN_ID": "bad", "MODAL_TOKEN_SECRET": "bad"},
60
- check=False,
61
- )
62
- try:
63
- assert returncode != 0
64
- assert "bad bad bad" in err.strip().split("\n")[-1] # err msg should be on the last line
65
- except Exception:
66
- print("out:", repr(out))
67
- print("err:", repr(err))
68
- raise
test/error_test.py DELETED
@@ -1,7 +0,0 @@
1
- # Copyright Modal Labs 2022
2
- from modal import Error
3
- from modal.exception import NotFoundError
4
-
5
-
6
- def test_modal_errors():
7
- assert issubclass(NotFoundError, Error)
@@ -1,32 +0,0 @@
1
- # Copyright Modal Labs 2023
2
- import pytest
3
-
4
- from modal import App
5
- from modal._serialization import deserialize
6
-
7
-
8
- @pytest.mark.asyncio
9
- async def test_serialize_deserialize_function(servicer, client):
10
- app = App()
11
-
12
- @app.function(serialized=True, name="foo")
13
- def foo():
14
- 2 * foo.remote()
15
-
16
- assert foo.object_id is None
17
-
18
- with app.run(client=client):
19
- object_id = foo.object_id
20
-
21
- assert object_id is not None
22
- assert {object_id} == servicer.precreated_functions
23
-
24
- foo_def = servicer.app_functions[object_id]
25
-
26
- assert len(servicer.client_calls) == 0
27
-
28
- deserialized_function_body = deserialize(foo_def.function_serialized, client)
29
- deserialized_function_body() # call locally as if in container, this should trigger a "remote" foo() call
30
- assert len(servicer.client_calls) == 1
31
- function_call_id = list(servicer.client_calls.keys())[0]
32
- assert servicer.function_id_for_function_call[function_call_id] == object_id