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