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/client_test.py DELETED
@@ -1,203 +0,0 @@
1
- # Copyright Modal Labs 2022
2
- import platform
3
- import pytest
4
- import subprocess
5
- import sys
6
-
7
- from google.protobuf.empty_pb2 import Empty
8
-
9
- import modal.exception
10
- from modal import Client
11
- from modal.exception import AuthError, ConnectionError, DeprecationError, InvalidError, VersionError
12
- from modal_proto import api_pb2
13
-
14
- from .supports.skip import skip_windows_unix_socket
15
-
16
- TEST_TIMEOUT = 4.0 # align this with the container client timeout in client.py
17
-
18
-
19
- def test_client_type(servicer, client):
20
- assert len(servicer.requests) == 1
21
- assert isinstance(servicer.requests[0], Empty)
22
- assert servicer.client_create_metadata["x-modal-client-type"] == str(api_pb2.CLIENT_TYPE_CLIENT)
23
-
24
-
25
- def test_client_platform_string(servicer, client):
26
- platform_str = servicer.client_create_metadata["x-modal-platform"]
27
- system, release, machine = platform_str.split("-")
28
- if platform.system() == "Darwin":
29
- assert system == "macOS"
30
- assert release == platform.mac_ver()[0].replace("-", "_")
31
- else:
32
- assert system == platform.system().replace("-", "_")
33
- assert release == platform.release().replace("-", "_")
34
- assert machine == platform.machine().replace("-", "_")
35
-
36
-
37
- @pytest.mark.asyncio
38
- @skip_windows_unix_socket
39
- async def test_container_client_type(unix_servicer, container_client):
40
- assert len(unix_servicer.requests) == 1 # no heartbeat, just ClientHello
41
- assert isinstance(unix_servicer.requests[0], Empty)
42
- assert unix_servicer.client_create_metadata["x-modal-client-type"] == str(api_pb2.CLIENT_TYPE_CONTAINER)
43
-
44
-
45
- @pytest.mark.asyncio
46
- @pytest.mark.timeout(TEST_TIMEOUT)
47
- async def test_client_dns_failure():
48
- with pytest.raises(ConnectionError) as excinfo:
49
- async with Client("https://xyz.invalid", api_pb2.CLIENT_TYPE_CONTAINER, None):
50
- pass
51
- assert excinfo.value
52
-
53
-
54
- @pytest.mark.asyncio
55
- @pytest.mark.timeout(TEST_TIMEOUT)
56
- @skip_windows_unix_socket
57
- async def test_client_connection_failure():
58
- with pytest.raises(ConnectionError) as excinfo:
59
- async with Client("https://localhost:443", api_pb2.CLIENT_TYPE_CONTAINER, None):
60
- pass
61
- assert excinfo.value
62
-
63
-
64
- @pytest.mark.asyncio
65
- @pytest.mark.timeout(TEST_TIMEOUT)
66
- @skip_windows_unix_socket
67
- async def test_client_connection_failure_unix_socket():
68
- with pytest.raises(ConnectionError) as excinfo:
69
- async with Client("unix:/tmp/xyz.txt", api_pb2.CLIENT_TYPE_CONTAINER, None):
70
- pass
71
- assert excinfo.value
72
-
73
-
74
- @pytest.mark.asyncio
75
- @pytest.mark.timeout(TEST_TIMEOUT)
76
- @skip_windows_unix_socket
77
- async def test_client_connection_timeout(unix_servicer, monkeypatch):
78
- monkeypatch.setattr("modal.client.CLIENT_CREATE_ATTEMPT_TIMEOUT", 1.0)
79
- monkeypatch.setattr("modal.client.CLIENT_CREATE_TOTAL_TIMEOUT", 3.0)
80
- with pytest.raises(ConnectionError) as excinfo:
81
- async with Client(unix_servicer.remote_addr, api_pb2.CLIENT_TYPE_CONTAINER, None, version="timeout"):
82
- pass
83
-
84
- # The HTTP lookup will return 400 because the GRPC server rejects the http request
85
- assert "deadline" in str(excinfo.value).lower()
86
-
87
-
88
- @pytest.mark.asyncio
89
- @pytest.mark.timeout(TEST_TIMEOUT)
90
- async def test_client_server_error(servicer):
91
- with pytest.raises(ConnectionError) as excinfo:
92
- async with Client("https://github.com", api_pb2.CLIENT_TYPE_CLIENT, None):
93
- pass
94
- # Can't connect over gRPC, but the HTTP lookup should succeed
95
- assert "HTTP status: 200" in str(excinfo.value)
96
-
97
-
98
- @pytest.mark.asyncio
99
- async def test_client_old_version(servicer):
100
- with pytest.raises(VersionError):
101
- async with Client(servicer.remote_addr, api_pb2.CLIENT_TYPE_CLIENT, ("foo-id", "foo-secret"), version="0.0.0"):
102
- pass
103
-
104
-
105
- @pytest.mark.asyncio
106
- async def test_client_deprecated(servicer):
107
- with pytest.warns(modal.exception.DeprecationError):
108
- async with Client(
109
- servicer.remote_addr, api_pb2.CLIENT_TYPE_CLIENT, ("foo-id", "foo-secret"), version="deprecated"
110
- ):
111
- pass
112
-
113
-
114
- @pytest.mark.asyncio
115
- async def test_client_unauthenticated(servicer):
116
- with pytest.raises(AuthError):
117
- async with Client(servicer.remote_addr, api_pb2.CLIENT_TYPE_CLIENT, None, version="unauthenticated"):
118
- pass
119
-
120
-
121
- def client_from_env(remote_addr):
122
- _override_config = {
123
- "server_url": remote_addr,
124
- "token_id": "foo-id",
125
- "token_secret": "foo-secret",
126
- "task_id": None,
127
- "task_secret": None,
128
- }
129
- return Client.from_env(_override_config=_override_config)
130
-
131
-
132
- def test_client_from_env(servicer):
133
- try:
134
- # First, a failing one
135
- with pytest.raises(ConnectionError):
136
- client_from_env("https://foo.invalid")
137
-
138
- # Make sure later clients can still succeed
139
- client_1 = client_from_env(servicer.remote_addr)
140
- client_2 = client_from_env(servicer.remote_addr)
141
- assert isinstance(client_1, Client)
142
- assert isinstance(client_2, Client)
143
- assert client_1 == client_2
144
-
145
- finally:
146
- Client.set_env_client(None)
147
-
148
- try:
149
- # After stopping, creating a new client should return a new one
150
- client_3 = client_from_env(servicer.remote_addr)
151
- client_4 = client_from_env(servicer.remote_addr)
152
- assert client_3 != client_1
153
- assert client_4 == client_3
154
- finally:
155
- Client.set_env_client(None)
156
-
157
-
158
- def test_multiple_profile_error(servicer, modal_config):
159
- config = """
160
- [prof-1]
161
- token_id = 'ak-abc'
162
- token_secret = 'as_xyz'
163
- active = true
164
-
165
- [prof-2]
166
- token_id = 'ak-abc'
167
- token_secret = 'as_xyz'
168
- active = true
169
- """
170
- with modal_config(config):
171
- with pytest.raises(InvalidError, match="More than one Modal profile is active"):
172
- Client.verify(servicer.remote_addr, None)
173
-
174
-
175
- def test_implicit_default_profile_warning(servicer, modal_config):
176
- config = """
177
- [default]
178
- token_id = 'ak-abc'
179
- token_secret = 'as_xyz'
180
-
181
- [other]
182
- token_id = 'ak-abc'
183
- token_secret = 'as_xyz'
184
- """
185
- with modal_config(config):
186
- with pytest.warns(DeprecationError, match="Support for using an implicit 'default' profile is deprecated."):
187
- Client.verify(servicer.remote_addr, None)
188
-
189
- config = """
190
- [default]
191
- token_id = 'ak-abc'
192
- token_secret = 'as_xyz'
193
- """
194
- with modal_config(config):
195
- # A single profile should be fine, even if not explicitly active and named 'default'
196
- Client.verify(servicer.remote_addr, None)
197
-
198
-
199
- def test_import_modal_from_thread(supports_dir):
200
- # this mainly ensures that we don't make any assumptions about which thread *imports* modal
201
- # For example, in Python <3.10, creating loop-bound asyncio primitives in global scope would
202
- # trigger an exception if there is no event loop in the thread (and it's not the main thread)
203
- subprocess.check_call([sys.executable, supports_dir / "import_modal_from_thread.py"])
@@ -1,22 +0,0 @@
1
- # Copyright Modal Labs 2024
2
- import modal
3
-
4
-
5
- def dummy():
6
- pass
7
-
8
-
9
- def test_volume_mount(client, servicer):
10
- app = modal.App()
11
- secret = modal.Secret.from_dict({"AWS_ACCESS_KEY_ID": "1", "AWS_SECRET_ACCESS_KEY": "2"})
12
- cld_bckt_mnt = modal.CloudBucketMount(
13
- bucket_name="foo",
14
- bucket_endpoint_url="https://1234.r2.cloudflarestorage.com",
15
- secret=secret,
16
- read_only=False,
17
- )
18
-
19
- _ = app.function(volumes={"/root/foo": cld_bckt_mnt})(dummy)
20
-
21
- with app.run(client=client):
22
- pass