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
@@ -1,378 +0,0 @@
1
- import asyncio.queues
2
- import collections.abc
3
- import modal.app
4
- import modal.client
5
- import modal.functions
6
- import modal.stub
7
- import modal_proto.api_pb2
8
- import typing
9
- import typing_extensions
10
-
11
- class UserException(Exception):
12
- ...
13
-
14
- class UserCodeEventLoop:
15
- def __enter__(self):
16
- ...
17
-
18
- def __exit__(self, exc_type, exc_value, traceback):
19
- ...
20
-
21
- def run(self, coro):
22
- ...
23
-
24
-
25
- class _FunctionIOManager:
26
- def __init__(self, container_args: modal_proto.api_pb2.ContainerArguments, client: modal.client._Client):
27
- ...
28
-
29
- async def initialize_app(self) -> modal.app._ContainerApp:
30
- ...
31
-
32
- async def _run_heartbeat_loop(self):
33
- ...
34
-
35
- async def _heartbeat_handle_cancellations(self) -> bool:
36
- ...
37
-
38
- def heartbeats(self):
39
- ...
40
-
41
- def stop_heartbeat(self):
42
- ...
43
-
44
- async def get_serialized_function(self) -> typing.Tuple[typing.Union[typing.Any, None], typing.Callable]:
45
- ...
46
-
47
- def serialize(self, obj: typing.Any) -> bytes:
48
- ...
49
-
50
- def deserialize(self, data: bytes) -> typing.Any:
51
- ...
52
-
53
- def serialize_data_format(self, obj: typing.Any, data_format: int) -> bytes:
54
- ...
55
-
56
- def deserialize_data_format(self, data: bytes, data_format: int) -> typing.Any:
57
- ...
58
-
59
- def get_data_in(self, function_call_id: str) -> typing.AsyncIterator[typing.Any]:
60
- ...
61
-
62
- async def put_data_out(self, function_call_id: str, start_index: int, data_format: int, messages_bytes: typing.List[typing.Any]) -> None:
63
- ...
64
-
65
- async def generator_output_task(self, function_call_id: str, data_format: int, message_rx: asyncio.queues.Queue) -> None:
66
- ...
67
-
68
- async def _queue_create(self, size: int) -> asyncio.queues.Queue:
69
- ...
70
-
71
- async def _queue_put(self, queue: asyncio.queues.Queue, value: typing.Any) -> None:
72
- ...
73
-
74
- async def populate_input_blobs(self, item: modal_proto.api_pb2.FunctionInput):
75
- ...
76
-
77
- def get_average_call_time(self) -> float:
78
- ...
79
-
80
- def get_max_inputs_to_fetch(self):
81
- ...
82
-
83
- def _generate_inputs(self) -> typing.AsyncIterator[typing.Tuple[str, str, modal_proto.api_pb2.FunctionInput]]:
84
- ...
85
-
86
- def run_inputs_outputs(self, input_concurrency: int = 1) -> typing.AsyncIterator[typing.Tuple[str, str, typing.Any, typing.Any]]:
87
- ...
88
-
89
- async def _push_output(self, input_id, started_at: float, data_format=0, **kwargs):
90
- ...
91
-
92
- def serialize_exception(self, exc: BaseException) -> typing.Union[bytes, None]:
93
- ...
94
-
95
- def serialize_traceback(self, exc: BaseException) -> typing.Tuple[typing.Union[bytes, None], typing.Union[bytes, None]]:
96
- ...
97
-
98
- def handle_user_exception(self) -> typing.AsyncGenerator[None, None]:
99
- ...
100
-
101
- def handle_input_exception(self, input_id, started_at: float) -> typing.AsyncGenerator[None, None]:
102
- ...
103
-
104
- async def complete_call(self, started_at):
105
- ...
106
-
107
- async def push_output(self, input_id, started_at: float, data: typing.Any, data_format: int) -> None:
108
- ...
109
-
110
- async def restore(self) -> None:
111
- ...
112
-
113
- async def checkpoint(self) -> None:
114
- ...
115
-
116
- async def volume_commit(self, volume_ids: typing.List[str]) -> None:
117
- ...
118
-
119
-
120
- class FunctionIOManager:
121
- def __init__(self, container_args: modal_proto.api_pb2.ContainerArguments, client: modal.client.Client):
122
- ...
123
-
124
- class __initialize_app_spec(typing_extensions.Protocol):
125
- def __call__(self) -> modal.app.ContainerApp:
126
- ...
127
-
128
- async def aio(self, *args, **kwargs) -> modal.app.ContainerApp:
129
- ...
130
-
131
- initialize_app: __initialize_app_spec
132
-
133
- class ___run_heartbeat_loop_spec(typing_extensions.Protocol):
134
- def __call__(self):
135
- ...
136
-
137
- async def aio(self, *args, **kwargs):
138
- ...
139
-
140
- _run_heartbeat_loop: ___run_heartbeat_loop_spec
141
-
142
- class ___heartbeat_handle_cancellations_spec(typing_extensions.Protocol):
143
- def __call__(self) -> bool:
144
- ...
145
-
146
- async def aio(self, *args, **kwargs) -> bool:
147
- ...
148
-
149
- _heartbeat_handle_cancellations: ___heartbeat_handle_cancellations_spec
150
-
151
- def heartbeats(self):
152
- ...
153
-
154
- def stop_heartbeat(self):
155
- ...
156
-
157
- class __get_serialized_function_spec(typing_extensions.Protocol):
158
- def __call__(self) -> typing.Tuple[typing.Union[typing.Any, None], typing.Callable]:
159
- ...
160
-
161
- async def aio(self, *args, **kwargs) -> typing.Tuple[typing.Union[typing.Any, None], typing.Callable]:
162
- ...
163
-
164
- get_serialized_function: __get_serialized_function_spec
165
-
166
- def serialize(self, obj: typing.Any) -> bytes:
167
- ...
168
-
169
- def deserialize(self, data: bytes) -> typing.Any:
170
- ...
171
-
172
- def serialize_data_format(self, obj: typing.Any, data_format: int) -> bytes:
173
- ...
174
-
175
- def deserialize_data_format(self, data: bytes, data_format: int) -> typing.Any:
176
- ...
177
-
178
- class __get_data_in_spec(typing_extensions.Protocol):
179
- def __call__(self, function_call_id: str) -> typing.Iterator[typing.Any]:
180
- ...
181
-
182
- def aio(self, function_call_id: str) -> typing.AsyncIterator[typing.Any]:
183
- ...
184
-
185
- get_data_in: __get_data_in_spec
186
-
187
- class __put_data_out_spec(typing_extensions.Protocol):
188
- def __call__(self, function_call_id: str, start_index: int, data_format: int, messages_bytes: typing.List[typing.Any]) -> None:
189
- ...
190
-
191
- async def aio(self, *args, **kwargs) -> None:
192
- ...
193
-
194
- put_data_out: __put_data_out_spec
195
-
196
- class __generator_output_task_spec(typing_extensions.Protocol):
197
- def __call__(self, function_call_id: str, data_format: int, message_rx: asyncio.queues.Queue) -> None:
198
- ...
199
-
200
- async def aio(self, *args, **kwargs) -> None:
201
- ...
202
-
203
- generator_output_task: __generator_output_task_spec
204
-
205
- class ___queue_create_spec(typing_extensions.Protocol):
206
- def __call__(self, size: int) -> asyncio.queues.Queue:
207
- ...
208
-
209
- async def aio(self, *args, **kwargs) -> asyncio.queues.Queue:
210
- ...
211
-
212
- _queue_create: ___queue_create_spec
213
-
214
- class ___queue_put_spec(typing_extensions.Protocol):
215
- def __call__(self, queue: asyncio.queues.Queue, value: typing.Any) -> None:
216
- ...
217
-
218
- async def aio(self, *args, **kwargs) -> None:
219
- ...
220
-
221
- _queue_put: ___queue_put_spec
222
-
223
- class __populate_input_blobs_spec(typing_extensions.Protocol):
224
- def __call__(self, item: modal_proto.api_pb2.FunctionInput):
225
- ...
226
-
227
- async def aio(self, *args, **kwargs):
228
- ...
229
-
230
- populate_input_blobs: __populate_input_blobs_spec
231
-
232
- def get_average_call_time(self) -> float:
233
- ...
234
-
235
- def get_max_inputs_to_fetch(self):
236
- ...
237
-
238
- class ___generate_inputs_spec(typing_extensions.Protocol):
239
- def __call__(self) -> typing.Iterator[typing.Tuple[str, str, modal_proto.api_pb2.FunctionInput]]:
240
- ...
241
-
242
- def aio(self) -> typing.AsyncIterator[typing.Tuple[str, str, modal_proto.api_pb2.FunctionInput]]:
243
- ...
244
-
245
- _generate_inputs: ___generate_inputs_spec
246
-
247
- class __run_inputs_outputs_spec(typing_extensions.Protocol):
248
- def __call__(self, input_concurrency: int = 1) -> typing.Iterator[typing.Tuple[str, str, typing.Any, typing.Any]]:
249
- ...
250
-
251
- def aio(self, input_concurrency: int = 1) -> typing.AsyncIterator[typing.Tuple[str, str, typing.Any, typing.Any]]:
252
- ...
253
-
254
- run_inputs_outputs: __run_inputs_outputs_spec
255
-
256
- class ___push_output_spec(typing_extensions.Protocol):
257
- def __call__(self, input_id, started_at: float, data_format=0, **kwargs):
258
- ...
259
-
260
- async def aio(self, *args, **kwargs):
261
- ...
262
-
263
- _push_output: ___push_output_spec
264
-
265
- def serialize_exception(self, exc: BaseException) -> typing.Union[bytes, None]:
266
- ...
267
-
268
- def serialize_traceback(self, exc: BaseException) -> typing.Tuple[typing.Union[bytes, None], typing.Union[bytes, None]]:
269
- ...
270
-
271
- class __handle_user_exception_spec(typing_extensions.Protocol):
272
- def __call__(self) -> typing.Generator[None, None, None]:
273
- ...
274
-
275
- def aio(self) -> typing.AsyncGenerator[None, None]:
276
- ...
277
-
278
- handle_user_exception: __handle_user_exception_spec
279
-
280
- class __handle_input_exception_spec(typing_extensions.Protocol):
281
- def __call__(self, input_id, started_at: float) -> typing.Generator[None, None, None]:
282
- ...
283
-
284
- def aio(self, input_id, started_at: float) -> typing.AsyncGenerator[None, None]:
285
- ...
286
-
287
- handle_input_exception: __handle_input_exception_spec
288
-
289
- class __complete_call_spec(typing_extensions.Protocol):
290
- def __call__(self, started_at):
291
- ...
292
-
293
- async def aio(self, *args, **kwargs):
294
- ...
295
-
296
- complete_call: __complete_call_spec
297
-
298
- class __push_output_spec(typing_extensions.Protocol):
299
- def __call__(self, input_id, started_at: float, data: typing.Any, data_format: int) -> None:
300
- ...
301
-
302
- async def aio(self, *args, **kwargs) -> None:
303
- ...
304
-
305
- push_output: __push_output_spec
306
-
307
- class __restore_spec(typing_extensions.Protocol):
308
- def __call__(self) -> None:
309
- ...
310
-
311
- async def aio(self, *args, **kwargs) -> None:
312
- ...
313
-
314
- restore: __restore_spec
315
-
316
- class __checkpoint_spec(typing_extensions.Protocol):
317
- def __call__(self) -> None:
318
- ...
319
-
320
- async def aio(self, *args, **kwargs) -> None:
321
- ...
322
-
323
- checkpoint: __checkpoint_spec
324
-
325
- class __volume_commit_spec(typing_extensions.Protocol):
326
- def __call__(self, volume_ids: typing.List[str]) -> None:
327
- ...
328
-
329
- async def aio(self, *args, **kwargs) -> None:
330
- ...
331
-
332
- volume_commit: __volume_commit_spec
333
-
334
-
335
- def call_function_sync(function_io_manager, imp_fun: ImportedFunction):
336
- ...
337
-
338
-
339
- async def call_function_async(function_io_manager, imp_fun: ImportedFunction):
340
- ...
341
-
342
-
343
- class ImportedFunction:
344
- obj: typing.Any
345
- fun: typing.Callable
346
- stub: typing.Union[modal.stub._Stub, None]
347
- is_async: bool
348
- is_generator: bool
349
- data_format: int
350
- input_concurrency: int
351
- is_auto_snapshot: bool
352
- function: modal.functions._Function
353
-
354
- def __init__(self, obj: typing.Any, fun: typing.Callable, stub: typing.Union[modal.stub._Stub, None], is_async: bool, is_generator: bool, data_format: int, input_concurrency: int, is_auto_snapshot: bool, function: modal.functions._Function) -> None:
355
- ...
356
-
357
- def __repr__(self):
358
- ...
359
-
360
- def __eq__(self, other):
361
- ...
362
-
363
-
364
- def import_function(function_def: modal_proto.api_pb2.Function, ser_cls, ser_fun, ser_params: typing.Union[bytes, None], function_io_manager, client: modal.client.Client) -> ImportedFunction:
365
- ...
366
-
367
-
368
- def call_lifecycle_functions(event_loop: UserCodeEventLoop, function_io_manager, funcs: collections.abc.Iterable[typing.Callable]) -> None:
369
- ...
370
-
371
-
372
- def main(container_args: modal_proto.api_pb2.ContainerArguments, client: modal.client.Client):
373
- ...
374
-
375
-
376
- MAX_OUTPUT_BATCH_SIZE: 'int'
377
-
378
- RTT_S: 'float'
modal/_container_exec.py DELETED
@@ -1,128 +0,0 @@
1
- # Copyright Modal Labs 2024
2
- import asyncio
3
- import platform
4
- from typing import List, Optional
5
-
6
- import rich
7
- import rich.status
8
- from grpclib import Status
9
- from grpclib.exceptions import GRPCError, StreamTerminatedError
10
- from rich.console import Console
11
-
12
- from modal_proto import api_pb2
13
-
14
- from ._pty import get_pty_info
15
- from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES, retry_transient_errors, unary_stream
16
- from ._utils.shell_utils import connect_to_terminal, write_to_fd
17
- from .client import _Client
18
- from .config import config
19
- from .exception import NotFoundError
20
-
21
-
22
- async def container_exec(task_id: str, command: List[str], *, pty: bool, client: _Client):
23
- """Execute a command inside an active container"""
24
- if platform.system() == "Windows":
25
- print("container exec is not currently supported on Windows.")
26
- return
27
-
28
- client = await _Client.from_env()
29
-
30
- console = Console()
31
- connecting_status = console.status("Connecting...")
32
- connecting_status.start()
33
-
34
- try:
35
- res: api_pb2.ContainerExecResponse = await client.stub.ContainerExec(
36
- api_pb2.ContainerExecRequest(
37
- task_id=task_id,
38
- command=command,
39
- pty_info=get_pty_info(shell=True) if pty else None,
40
- runtime_debug=config.get("function_runtime_debug"),
41
- )
42
- )
43
- except GRPCError as err:
44
- connecting_status.stop()
45
- if err.status == Status.NOT_FOUND:
46
- raise NotFoundError(f"Container ID {task_id} not found")
47
- raise
48
-
49
- await connect_to_exec(res.exec_id, pty, connecting_status)
50
-
51
-
52
- async def connect_to_exec(exec_id: str, pty: bool = False, connecting_status: Optional[rich.status.Status] = None):
53
- """
54
- Connects the current terminal to the given exec id.
55
-
56
- If connecting_status is given, this function will stop the status spinner upon connection or error.
57
- """
58
-
59
- client = await _Client.from_env()
60
-
61
- async def _stream_to_stdout(on_connect: asyncio.Event) -> int:
62
- return await _handle_exec_output(client, exec_id, on_connect)
63
-
64
- async def _handle_input(data: bytes, message_index: int):
65
- await retry_transient_errors(
66
- client.stub.ContainerExecPutInput,
67
- api_pb2.ContainerExecPutInputRequest(
68
- exec_id=exec_id, input=api_pb2.RuntimeInputMessage(message=data, message_index=message_index)
69
- ),
70
- total_timeout=10,
71
- )
72
-
73
- await connect_to_terminal(_handle_input, _stream_to_stdout, pty, connecting_status)
74
-
75
-
76
- async def _handle_exec_output(client: _Client, exec_id: str, on_connect: Optional[asyncio.Event] = None) -> int:
77
- """
78
- Streams exec output to current terminal's stdout.
79
-
80
- The on_connect event will be set when the client connects to the running process,
81
- and the event loop will be released.
82
-
83
- Returns the status code of the process.
84
- """
85
-
86
- last_batch_index = 0
87
- exit_status = None
88
-
89
- # we are connected if we received at least one message from the server
90
- # (the server will send an empty message when the process spawns)
91
- connected = False
92
-
93
- async def _get_output():
94
- nonlocal last_batch_index, exit_status, connected
95
- req = api_pb2.ContainerExecGetOutputRequest(
96
- exec_id=exec_id,
97
- timeout=55,
98
- last_batch_index=last_batch_index,
99
- )
100
- async for batch in unary_stream(client.stub.ContainerExecGetOutput, req):
101
- for message in batch.items:
102
- assert message.file_descriptor in [1, 2]
103
-
104
- await write_to_fd(message.file_descriptor, str.encode(message.message))
105
-
106
- if not connected:
107
- connected = True
108
- on_connect.set()
109
- # give up the event loop
110
- await asyncio.sleep(0)
111
-
112
- if batch.HasField("exit_code"):
113
- exit_status = batch.exit_code
114
- break
115
- last_batch_index = batch.batch_index
116
-
117
- while exit_status is None:
118
- try:
119
- await _get_output()
120
- except (GRPCError, StreamTerminatedError) as exc:
121
- if isinstance(exc, GRPCError):
122
- if exc.status in RETRYABLE_GRPC_STATUS_CODES:
123
- continue
124
- elif isinstance(exc, StreamTerminatedError):
125
- continue
126
- raise
127
-
128
- return exit_status
modal/_sandbox_shell.py DELETED
@@ -1,49 +0,0 @@
1
- # Copyright Modal Labs 2024
2
- import asyncio
3
-
4
- from ._utils.shell_utils import connect_to_terminal, write_to_fd
5
- from .sandbox import _Sandbox
6
-
7
-
8
- async def connect_to_sandbox(sandbox: _Sandbox):
9
- """
10
- Connects the current terminal to the Sandbox process.
11
- """
12
-
13
- async def _handle_input(data: bytes, _):
14
- sandbox.stdin.write(data)
15
- await sandbox.stdin.drain.aio() # type: ignore
16
-
17
- async def _stream_to_stdout(on_connect: asyncio.Event) -> int:
18
- return await _stream_logs_to_stdout(sandbox, on_connect)
19
-
20
- await connect_to_terminal(_handle_input, _stream_to_stdout, pty=True)
21
-
22
-
23
- async def _stream_logs_to_stdout(sandbox: _Sandbox, on_connect: asyncio.Event) -> int:
24
- """
25
- Streams sandbox output logs to the current terminal's stdout.
26
-
27
- The on_connect event will be set when the client connects to the running process,
28
- and the event loop will be released.
29
- """
30
-
31
- # we are connected if we received at least one message from the server
32
- # (the server will send an empty message when the process spawns)
33
- connected = False
34
-
35
- # Since the sandbox process will run in a PTY, stderr will go to the PTY
36
- # slave. The PTY shell will then relay data from PTY master to stdout.
37
- # Therefore, we only need to stream from/to stdout here.
38
- async for message in sandbox.stdout:
39
- await write_to_fd(1, message.encode("utf-8"))
40
-
41
- if not connected:
42
- connected = True
43
- on_connect.set()
44
- # give up the event loop
45
- await asyncio.sleep(0)
46
-
47
- # Right now we don't propagate the exit_status to the TaskLogs, so setting
48
- # exit status to 0.
49
- return 0
modal/shared_volume.py DELETED
@@ -1,23 +0,0 @@
1
- # Copyright Modal Labs 2023
2
-
3
- from ._utils.async_utils import synchronize_api
4
- from .exception import deprecation_error
5
-
6
-
7
- class _SharedVolume:
8
- def __init__(self, *args, **kwargs):
9
- """`SharedVolume` is deprecated. We recommend `Volume` (https://modal.com/docs/guide/volumes) instead."""
10
- deprecation_error((2023, 7, 5), _SharedVolume.__init__.__doc__)
11
-
12
- @staticmethod
13
- def new(*args, **kwargs):
14
- """`SharedVolume` is deprecated. We recommend `Volume` (https://modal.com/docs/guide/volumes) instead."""
15
- deprecation_error((2023, 7, 5), _SharedVolume.new.__doc__)
16
-
17
- @staticmethod
18
- def persisted(*args, **kwargs):
19
- """`SharedVolume` is deprecated. We recommend `Volume` (https://modal.com/docs/guide/volumes) instead."""
20
- deprecation_error((2023, 7, 5), _SharedVolume.persisted.__doc__)
21
-
22
-
23
- SharedVolume = synchronize_api(_SharedVolume)
modal/shared_volume.pyi DELETED
@@ -1,24 +0,0 @@
1
- class _SharedVolume:
2
- def __init__(self, *args, **kwargs):
3
- ...
4
-
5
- @staticmethod
6
- def new(*args, **kwargs):
7
- ...
8
-
9
- @staticmethod
10
- def persisted(*args, **kwargs):
11
- ...
12
-
13
-
14
- class SharedVolume:
15
- def __init__(self, *args, **kwargs):
16
- ...
17
-
18
- @staticmethod
19
- def new(*args, **kwargs):
20
- ...
21
-
22
- @staticmethod
23
- def persisted(*args, **kwargs):
24
- ...