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
@@ -0,0 +1,149 @@
1
+ # Generated by the Modal Protocol Buffers compiler. DO NOT EDIT!
2
+ # source: modal_proto/api.proto
3
+ # plugin: __main__
4
+
5
+ import modal._utils.grpc_utils
6
+ import modal_proto.api_grpc
7
+ import typing
8
+ if typing.TYPE_CHECKING:
9
+ import modal.client
10
+
11
+
12
+ class ModalClientModal:
13
+
14
+ def __init__(self, grpclib_stub: modal_proto.api_grpc.ModalClientStub, client: "modal.client._Client") -> None:
15
+ self.AppClientDisconnect = modal.client.UnaryUnaryWrapper(grpclib_stub.AppClientDisconnect, client)
16
+ self.AppCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.AppCreate, client)
17
+ self.AppDeploy = modal.client.UnaryUnaryWrapper(grpclib_stub.AppDeploy, client)
18
+ self.AppDeploymentHistory = modal.client.UnaryUnaryWrapper(grpclib_stub.AppDeploymentHistory, client)
19
+ self.AppGetByDeploymentName = modal.client.UnaryUnaryWrapper(grpclib_stub.AppGetByDeploymentName, client)
20
+ self.AppGetLayout = modal.client.UnaryUnaryWrapper(grpclib_stub.AppGetLayout, client)
21
+ self.AppGetLogs = modal.client.UnaryStreamWrapper(grpclib_stub.AppGetLogs, client)
22
+ self.AppGetObjects = modal.client.UnaryUnaryWrapper(grpclib_stub.AppGetObjects, client)
23
+ self.AppGetOrCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.AppGetOrCreate, client)
24
+ self.AppHeartbeat = modal.client.UnaryUnaryWrapper(grpclib_stub.AppHeartbeat, client)
25
+ self.AppList = modal.client.UnaryUnaryWrapper(grpclib_stub.AppList, client)
26
+ self.AppLookup = modal.client.UnaryUnaryWrapper(grpclib_stub.AppLookup, client)
27
+ self.AppPublish = modal.client.UnaryUnaryWrapper(grpclib_stub.AppPublish, client)
28
+ self.AppRollback = modal.client.UnaryUnaryWrapper(grpclib_stub.AppRollback, client)
29
+ self.AppSetObjects = modal.client.UnaryUnaryWrapper(grpclib_stub.AppSetObjects, client)
30
+ self.AppStop = modal.client.UnaryUnaryWrapper(grpclib_stub.AppStop, client)
31
+ self.BlobCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.BlobCreate, client)
32
+ self.BlobGet = modal.client.UnaryUnaryWrapper(grpclib_stub.BlobGet, client)
33
+ self.ClassCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.ClassCreate, client)
34
+ self.ClassGet = modal.client.UnaryUnaryWrapper(grpclib_stub.ClassGet, client)
35
+ self.ClientHello = modal.client.UnaryUnaryWrapper(grpclib_stub.ClientHello, client)
36
+ self.ContainerCheckpoint = modal.client.UnaryUnaryWrapper(grpclib_stub.ContainerCheckpoint, client)
37
+ self.ContainerExec = modal.client.UnaryUnaryWrapper(grpclib_stub.ContainerExec, client)
38
+ self.ContainerExecGetOutput = modal.client.UnaryStreamWrapper(grpclib_stub.ContainerExecGetOutput, client)
39
+ self.ContainerExecPutInput = modal.client.UnaryUnaryWrapper(grpclib_stub.ContainerExecPutInput, client)
40
+ self.ContainerExecWait = modal.client.UnaryUnaryWrapper(grpclib_stub.ContainerExecWait, client)
41
+ self.ContainerFilesystemExec = modal.client.UnaryUnaryWrapper(grpclib_stub.ContainerFilesystemExec, client)
42
+ self.ContainerFilesystemExecGetOutput = modal.client.UnaryStreamWrapper(grpclib_stub.ContainerFilesystemExecGetOutput, client)
43
+ self.ContainerHeartbeat = modal.client.UnaryUnaryWrapper(grpclib_stub.ContainerHeartbeat, client)
44
+ self.ContainerHello = modal.client.UnaryUnaryWrapper(grpclib_stub.ContainerHello, client)
45
+ self.ContainerLog = modal.client.UnaryUnaryWrapper(grpclib_stub.ContainerLog, client)
46
+ self.ContainerStop = modal.client.UnaryUnaryWrapper(grpclib_stub.ContainerStop, client)
47
+ self.DictClear = modal.client.UnaryUnaryWrapper(grpclib_stub.DictClear, client)
48
+ self.DictContains = modal.client.UnaryUnaryWrapper(grpclib_stub.DictContains, client)
49
+ self.DictContents = modal.client.UnaryStreamWrapper(grpclib_stub.DictContents, client)
50
+ self.DictDelete = modal.client.UnaryUnaryWrapper(grpclib_stub.DictDelete, client)
51
+ self.DictGet = modal.client.UnaryUnaryWrapper(grpclib_stub.DictGet, client)
52
+ self.DictGetOrCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.DictGetOrCreate, client)
53
+ self.DictHeartbeat = modal.client.UnaryUnaryWrapper(grpclib_stub.DictHeartbeat, client)
54
+ self.DictLen = modal.client.UnaryUnaryWrapper(grpclib_stub.DictLen, client)
55
+ self.DictList = modal.client.UnaryUnaryWrapper(grpclib_stub.DictList, client)
56
+ self.DictPop = modal.client.UnaryUnaryWrapper(grpclib_stub.DictPop, client)
57
+ self.DictUpdate = modal.client.UnaryUnaryWrapper(grpclib_stub.DictUpdate, client)
58
+ self.DomainCertificateVerify = modal.client.UnaryUnaryWrapper(grpclib_stub.DomainCertificateVerify, client)
59
+ self.DomainCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.DomainCreate, client)
60
+ self.DomainList = modal.client.UnaryUnaryWrapper(grpclib_stub.DomainList, client)
61
+ self.EnvironmentCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.EnvironmentCreate, client)
62
+ self.EnvironmentDelete = modal.client.UnaryUnaryWrapper(grpclib_stub.EnvironmentDelete, client)
63
+ self.EnvironmentGetOrCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.EnvironmentGetOrCreate, client)
64
+ self.EnvironmentList = modal.client.UnaryUnaryWrapper(grpclib_stub.EnvironmentList, client)
65
+ self.EnvironmentUpdate = modal.client.UnaryUnaryWrapper(grpclib_stub.EnvironmentUpdate, client)
66
+ self.FunctionAsyncInvoke = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionAsyncInvoke, client)
67
+ self.FunctionBindParams = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionBindParams, client)
68
+ self.FunctionCallCancel = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionCallCancel, client)
69
+ self.FunctionCallGetDataIn = modal.client.UnaryStreamWrapper(grpclib_stub.FunctionCallGetDataIn, client)
70
+ self.FunctionCallGetDataOut = modal.client.UnaryStreamWrapper(grpclib_stub.FunctionCallGetDataOut, client)
71
+ self.FunctionCallList = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionCallList, client)
72
+ self.FunctionCallPutDataOut = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionCallPutDataOut, client)
73
+ self.FunctionCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionCreate, client)
74
+ self.FunctionGet = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionGet, client)
75
+ self.FunctionGetCallGraph = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionGetCallGraph, client)
76
+ self.FunctionGetCurrentStats = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionGetCurrentStats, client)
77
+ self.FunctionGetDynamicConcurrency = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionGetDynamicConcurrency, client)
78
+ self.FunctionGetInputs = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionGetInputs, client)
79
+ self.FunctionGetOutputs = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionGetOutputs, client)
80
+ self.FunctionGetSerialized = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionGetSerialized, client)
81
+ self.FunctionMap = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionMap, client)
82
+ self.FunctionPrecreate = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionPrecreate, client)
83
+ self.FunctionPutInputs = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionPutInputs, client)
84
+ self.FunctionPutOutputs = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionPutOutputs, client)
85
+ self.FunctionRetryInputs = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionRetryInputs, client)
86
+ self.FunctionStartPtyShell = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionStartPtyShell, client)
87
+ self.FunctionUpdateSchedulingParams = modal.client.UnaryUnaryWrapper(grpclib_stub.FunctionUpdateSchedulingParams, client)
88
+ self.ImageFromId = modal.client.UnaryUnaryWrapper(grpclib_stub.ImageFromId, client)
89
+ self.ImageGetOrCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.ImageGetOrCreate, client)
90
+ self.ImageJoinStreaming = modal.client.UnaryStreamWrapper(grpclib_stub.ImageJoinStreaming, client)
91
+ self.MountGetOrCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.MountGetOrCreate, client)
92
+ self.MountPutFile = modal.client.UnaryUnaryWrapper(grpclib_stub.MountPutFile, client)
93
+ self.ProxyCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.ProxyCreate, client)
94
+ self.ProxyDelete = modal.client.UnaryUnaryWrapper(grpclib_stub.ProxyDelete, client)
95
+ self.ProxyGet = modal.client.UnaryUnaryWrapper(grpclib_stub.ProxyGet, client)
96
+ self.ProxyGetOrCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.ProxyGetOrCreate, client)
97
+ self.ProxyList = modal.client.UnaryUnaryWrapper(grpclib_stub.ProxyList, client)
98
+ self.QueueClear = modal.client.UnaryUnaryWrapper(grpclib_stub.QueueClear, client)
99
+ self.QueueDelete = modal.client.UnaryUnaryWrapper(grpclib_stub.QueueDelete, client)
100
+ self.QueueGet = modal.client.UnaryUnaryWrapper(grpclib_stub.QueueGet, client)
101
+ self.QueueGetOrCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.QueueGetOrCreate, client)
102
+ self.QueueHeartbeat = modal.client.UnaryUnaryWrapper(grpclib_stub.QueueHeartbeat, client)
103
+ self.QueueLen = modal.client.UnaryUnaryWrapper(grpclib_stub.QueueLen, client)
104
+ self.QueueList = modal.client.UnaryUnaryWrapper(grpclib_stub.QueueList, client)
105
+ self.QueueNextItems = modal.client.UnaryUnaryWrapper(grpclib_stub.QueueNextItems, client)
106
+ self.QueuePut = modal.client.UnaryUnaryWrapper(grpclib_stub.QueuePut, client)
107
+ self.SandboxCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.SandboxCreate, client)
108
+ self.SandboxGetLogs = modal.client.UnaryStreamWrapper(grpclib_stub.SandboxGetLogs, client)
109
+ self.SandboxGetTaskId = modal.client.UnaryUnaryWrapper(grpclib_stub.SandboxGetTaskId, client)
110
+ self.SandboxGetTunnels = modal.client.UnaryUnaryWrapper(grpclib_stub.SandboxGetTunnels, client)
111
+ self.SandboxList = modal.client.UnaryUnaryWrapper(grpclib_stub.SandboxList, client)
112
+ self.SandboxSnapshotFs = modal.client.UnaryUnaryWrapper(grpclib_stub.SandboxSnapshotFs, client)
113
+ self.SandboxStdinWrite = modal.client.UnaryUnaryWrapper(grpclib_stub.SandboxStdinWrite, client)
114
+ self.SandboxTagsSet = modal.client.UnaryUnaryWrapper(grpclib_stub.SandboxTagsSet, client)
115
+ self.SandboxTerminate = modal.client.UnaryUnaryWrapper(grpclib_stub.SandboxTerminate, client)
116
+ self.SandboxWait = modal.client.UnaryUnaryWrapper(grpclib_stub.SandboxWait, client)
117
+ self.SecretDelete = modal.client.UnaryUnaryWrapper(grpclib_stub.SecretDelete, client)
118
+ self.SecretGetOrCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.SecretGetOrCreate, client)
119
+ self.SecretList = modal.client.UnaryUnaryWrapper(grpclib_stub.SecretList, client)
120
+ self.SharedVolumeDelete = modal.client.UnaryUnaryWrapper(grpclib_stub.SharedVolumeDelete, client)
121
+ self.SharedVolumeGetFile = modal.client.UnaryUnaryWrapper(grpclib_stub.SharedVolumeGetFile, client)
122
+ self.SharedVolumeGetOrCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.SharedVolumeGetOrCreate, client)
123
+ self.SharedVolumeHeartbeat = modal.client.UnaryUnaryWrapper(grpclib_stub.SharedVolumeHeartbeat, client)
124
+ self.SharedVolumeList = modal.client.UnaryUnaryWrapper(grpclib_stub.SharedVolumeList, client)
125
+ self.SharedVolumeListFiles = modal.client.UnaryUnaryWrapper(grpclib_stub.SharedVolumeListFiles, client)
126
+ self.SharedVolumeListFilesStream = modal.client.UnaryStreamWrapper(grpclib_stub.SharedVolumeListFilesStream, client)
127
+ self.SharedVolumePutFile = modal.client.UnaryUnaryWrapper(grpclib_stub.SharedVolumePutFile, client)
128
+ self.SharedVolumeRemoveFile = modal.client.UnaryUnaryWrapper(grpclib_stub.SharedVolumeRemoveFile, client)
129
+ self.TaskClusterHello = modal.client.UnaryUnaryWrapper(grpclib_stub.TaskClusterHello, client)
130
+ self.TaskCurrentInputs = modal.client.UnaryUnaryWrapper(grpclib_stub.TaskCurrentInputs, client)
131
+ self.TaskList = modal.client.UnaryUnaryWrapper(grpclib_stub.TaskList, client)
132
+ self.TaskResult = modal.client.UnaryUnaryWrapper(grpclib_stub.TaskResult, client)
133
+ self.TokenFlowCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.TokenFlowCreate, client)
134
+ self.TokenFlowWait = modal.client.UnaryUnaryWrapper(grpclib_stub.TokenFlowWait, client)
135
+ self.TunnelStart = modal.client.UnaryUnaryWrapper(grpclib_stub.TunnelStart, client)
136
+ self.TunnelStop = modal.client.UnaryUnaryWrapper(grpclib_stub.TunnelStop, client)
137
+ self.VolumeCommit = modal.client.UnaryUnaryWrapper(grpclib_stub.VolumeCommit, client)
138
+ self.VolumeCopyFiles = modal.client.UnaryUnaryWrapper(grpclib_stub.VolumeCopyFiles, client)
139
+ self.VolumeDelete = modal.client.UnaryUnaryWrapper(grpclib_stub.VolumeDelete, client)
140
+ self.VolumeGetFile = modal.client.UnaryUnaryWrapper(grpclib_stub.VolumeGetFile, client)
141
+ self.VolumeGetOrCreate = modal.client.UnaryUnaryWrapper(grpclib_stub.VolumeGetOrCreate, client)
142
+ self.VolumeHeartbeat = modal.client.UnaryUnaryWrapper(grpclib_stub.VolumeHeartbeat, client)
143
+ self.VolumeList = modal.client.UnaryUnaryWrapper(grpclib_stub.VolumeList, client)
144
+ self.VolumeListFiles = modal.client.UnaryStreamWrapper(grpclib_stub.VolumeListFiles, client)
145
+ self.VolumePutFiles = modal.client.UnaryUnaryWrapper(grpclib_stub.VolumePutFiles, client)
146
+ self.VolumeReload = modal.client.UnaryUnaryWrapper(grpclib_stub.VolumeReload, client)
147
+ self.VolumeRemoveFile = modal.client.UnaryUnaryWrapper(grpclib_stub.VolumeRemoveFile, client)
148
+ self.VolumeRename = modal.client.UnaryUnaryWrapper(grpclib_stub.VolumeRename, client)
149
+ self.WorkspaceNameLookup = modal.client.UnaryUnaryWrapper(grpclib_stub.WorkspaceNameLookup, client)
@@ -0,0 +1,3 @@
1
+ # Generated by the Modal Protocol Buffers compiler. DO NOT EDIT!
2
+ # source: modal_proto/options.proto
3
+ # plugin: __main__
@@ -0,0 +1,20 @@
1
+ """
2
+ @generated by mypy-protobuf. Do not edit manually!
3
+ isort:skip_file
4
+ Defines custom options used internally at Modal.
5
+ Custom options must be in the range 50000-99999.
6
+ Reference: https://protobuf.dev/programming-guides/proto2/#customoptions
7
+ """
8
+ import builtins
9
+ import google.protobuf.descriptor
10
+ import google.protobuf.descriptor_pb2
11
+ import google.protobuf.internal.extension_dict
12
+
13
+ DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
14
+
15
+ AUDIT_TARGET_ATTR_FIELD_NUMBER: builtins.int
16
+ AUDIT_EVENT_NAME_FIELD_NUMBER: builtins.int
17
+ AUDIT_EVENT_DESCRIPTION_FIELD_NUMBER: builtins.int
18
+ audit_target_attr: google.protobuf.internal.extension_dict._ExtensionFieldDescriptor[google.protobuf.descriptor_pb2.FieldOptions, builtins.bool]
19
+ audit_event_name: google.protobuf.internal.extension_dict._ExtensionFieldDescriptor[google.protobuf.descriptor_pb2.MethodOptions, builtins.str]
20
+ audit_event_description: google.protobuf.internal.extension_dict._ExtensionFieldDescriptor[google.protobuf.descriptor_pb2.MethodOptions, builtins.str]
@@ -0,0 +1,7 @@
1
+ """
2
+ @generated by mypy-protobuf. Do not edit manually!
3
+ isort:skip_file
4
+ Defines custom options used internally at Modal.
5
+ Custom options must be in the range 50000-99999.
6
+ Reference: https://protobuf.dev/programming-guides/proto2/#customoptions
7
+ """
modal_proto/py.typed ADDED
File without changes
modal_version/__init__.py CHANGED
@@ -7,7 +7,7 @@ from ._version_generated import build_number
7
7
  major_number = 0
8
8
 
9
9
  # Bump this manually on breaking changes, then reset the number in _version_generated.py
10
- minor_number = 62
10
+ minor_number = 72
11
11
 
12
12
  # Right now, automatically increment the patch number in CI
13
13
  __version__ = f"{major_number}.{minor_number}.{max(build_number, 0)}"
@@ -1,4 +1,4 @@
1
- # Copyright Modal Labs 2024
1
+ # Copyright Modal Labs 2025
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 115 # git: 8843452
4
+ build_number = 13 # git: 580929c
modal/_asgi.py DELETED
@@ -1,370 +0,0 @@
1
- # Copyright Modal Labs 2022
2
- import asyncio
3
- from typing import Any, AsyncGenerator, Callable, Dict, Optional, cast
4
-
5
- import aiohttp
6
-
7
- from ._utils.async_utils import TaskContext
8
- from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
9
- from .config import logger
10
- from .exception import ExecutionError, InvalidError
11
- from .execution_context import current_function_call_id
12
- from .experimental import stop_fetching_inputs
13
-
14
- FIRST_MESSAGE_TIMEOUT_SECONDS = 5.0
15
-
16
-
17
- def asgi_app_wrapper(asgi_app, function_io_manager) -> Callable[..., AsyncGenerator]:
18
- async def fn(scope):
19
- function_call_id = current_function_call_id()
20
- assert function_call_id, "internal error: function_call_id not set in asgi_app() scope"
21
-
22
- # TODO: Add support for the ASGI lifecycle spec.
23
- messages_from_app: asyncio.Queue[Dict[str, Any]] = asyncio.Queue(1)
24
- messages_to_app: asyncio.Queue[Dict[str, Any]] = asyncio.Queue(1)
25
-
26
- async def disconnect_app():
27
- if scope["type"] == "http":
28
- await messages_to_app.put({"type": "http.disconnect"})
29
- elif scope["type"] == "websocket":
30
- await messages_to_app.put({"type": "websocket.disconnect"})
31
-
32
- async def handle_first_input_timeout():
33
- if scope["type"] == "http":
34
- await messages_from_app.put({"type": "http.response.start", "status": 502})
35
- await messages_from_app.put(
36
- {
37
- "type": "http.response.body",
38
- "body": b"Missing request, possibly due to cancellation or crash",
39
- }
40
- )
41
- elif scope["type"] == "websocket":
42
- await messages_from_app.put(
43
- {
44
- "type": "websocket.close",
45
- "code": 1011,
46
- "reason": "Missing request, possibly due to cancellation or crash",
47
- }
48
- )
49
- await disconnect_app()
50
-
51
- async def fetch_data_in():
52
- # Cancel an ASGI app call if the initial message is not received within a short timeout.
53
- #
54
- # This initial message, "http.request" or "websocket.connect", should be sent
55
- # immediately after starting the ASGI app's function call. If it is not received, that
56
- # indicates a request cancellation or other abnormal circumstance.
57
- message_gen = function_io_manager.get_data_in.aio(function_call_id)
58
-
59
- try:
60
- first_message = await asyncio.wait_for(message_gen.__anext__(), FIRST_MESSAGE_TIMEOUT_SECONDS)
61
- except (asyncio.TimeoutError, StopAsyncIteration):
62
- # About `StopAsyncIteration` above: The generator shouldn't typically exit,
63
- # but if it does, we handle it like a timeout in that case.
64
- await handle_first_input_timeout()
65
- return
66
- except Exception:
67
- logger.exception("Internal error in asgi_app_wrapper")
68
- await disconnect_app()
69
- return
70
-
71
- await messages_to_app.put(first_message)
72
- async for message in message_gen:
73
- await messages_to_app.put(message)
74
-
75
- async def send(msg):
76
- # Automatically split body chunks that are greater than the output size limit, to
77
- # prevent them from being uploaded to S3.
78
- if msg["type"] == "http.response.body":
79
- body_chunk_size = MAX_OBJECT_SIZE_BYTES - 1024 # reserve 1 KiB for framing
80
- body_chunk_limit = 20 * body_chunk_size
81
- s3_chunk_size = 150 * body_chunk_size
82
-
83
- size = len(msg.get("body", b""))
84
- if size <= body_chunk_limit:
85
- chunk_size = body_chunk_size
86
- else:
87
- # If the body is _very large_, we should still split it up to avoid sending all
88
- # of the data in a huge chunk in S3.
89
- chunk_size = s3_chunk_size
90
-
91
- if size > chunk_size:
92
- indices = list(range(0, size, chunk_size))
93
- for i in indices[:-1]:
94
- chunk = msg["body"][i : i + chunk_size]
95
- await messages_from_app.put({"type": "http.response.body", "body": chunk, "more_body": True})
96
- msg["body"] = msg["body"][indices[-1] :]
97
-
98
- await messages_from_app.put(msg)
99
-
100
- # Run the ASGI app, while draining the send message queue at the same time,
101
- # and yielding results.
102
- async with TaskContext(grace=0.01) as tc:
103
- fetch_data_in_task = tc.create_task(fetch_data_in())
104
-
105
- async def receive():
106
- return await messages_to_app.get()
107
-
108
- app_task = tc.create_task(asgi_app(scope, receive, send))
109
- pop_task = None
110
- try:
111
- while True:
112
- pop_task = tc.create_task(messages_from_app.get())
113
-
114
- try:
115
- done, pending = await asyncio.wait([pop_task, app_task], return_when=asyncio.FIRST_COMPLETED)
116
- except asyncio.CancelledError:
117
- break
118
-
119
- if pop_task in done:
120
- yield pop_task.result()
121
- else:
122
- pop_task.cancel() # clean up the popping task, or we will leak unresolved tasks every loop iteration
123
-
124
- if app_task in done:
125
- while not messages_from_app.empty():
126
- yield messages_from_app.get_nowait()
127
- app_task.result() # consume/raise exceptions if there are any!
128
- break
129
- finally:
130
- fetch_data_in_task.cancel()
131
- # Only cancel tasks if they're not done. This lets tracebacks from potential errors
132
- # still get displayed when the task gets garbage collected.
133
- if not app_task.done():
134
- app_task.cancel()
135
- if not pop_task.done():
136
- pop_task.cancel()
137
-
138
- return fn
139
-
140
-
141
- def wsgi_app_wrapper(wsgi_app, function_io_manager):
142
- from ._vendor.a2wsgi_wsgi import WSGIMiddleware
143
-
144
- asgi_app = WSGIMiddleware(wsgi_app, workers=10000, send_queue_size=1) # unlimited workers
145
- return asgi_app_wrapper(asgi_app, function_io_manager)
146
-
147
-
148
- def webhook_asgi_app(fn: Callable, method: str):
149
- """Return a FastAPI app wrapping a function handler."""
150
- # Pulls in `fastapi` module, which is slow to import.
151
- from fastapi import FastAPI
152
- from fastapi.middleware.cors import CORSMiddleware
153
-
154
- app = FastAPI(docs_url=None, redoc_url=None)
155
- app.add_middleware(
156
- CORSMiddleware,
157
- allow_origins=["*"],
158
- allow_credentials=True,
159
- allow_methods=["*"],
160
- allow_headers=["*"],
161
- )
162
- app.add_api_route("/", fn, methods=[method])
163
- return app
164
-
165
-
166
- def get_ip_address(ifname: bytes):
167
- """Get the IP address associated with a network interface in Linux."""
168
- import fcntl
169
- import socket
170
- import struct
171
-
172
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
173
- return socket.inet_ntoa(
174
- fcntl.ioctl(
175
- s.fileno(),
176
- 0x8915, # SIOCGIFADDR
177
- struct.pack("256s", ifname[:15]),
178
- )[20:24]
179
- )
180
-
181
-
182
- def wait_for_web_server(host: str, port: int, *, timeout: float) -> None:
183
- """Wait until a web server port starts accepting TCP connections."""
184
- import socket
185
- import time
186
-
187
- start_time = time.monotonic()
188
- while True:
189
- try:
190
- with socket.create_connection((host, port), timeout=timeout):
191
- break
192
- except OSError as ex:
193
- time.sleep(0.01)
194
- if time.monotonic() - start_time >= timeout:
195
- raise TimeoutError(
196
- f"Waited too long for port {port} to start accepting connections. "
197
- + "Make sure the web server is listening on all interfaces, or adjust `startup_timeout`."
198
- ) from ex
199
-
200
-
201
- async def _proxy_http_request(session: aiohttp.ClientSession, scope, receive, send) -> None:
202
- proxy_response: Optional[aiohttp.ClientResponse] = None
203
-
204
- async def request_generator():
205
- while True:
206
- message = await receive()
207
- if message["type"] == "http.request":
208
- body = message.get("body", b"")
209
- if body:
210
- yield body
211
- if not message.get("more_body", False):
212
- break
213
- elif message["type"] == "http.disconnect":
214
- proxy_response.connection.transport.abort() # Abort the connection.
215
- break
216
- else:
217
- raise ExecutionError(f"Unexpected message type: {message['type']}")
218
-
219
- path = scope["path"]
220
- if scope.get("query_string"):
221
- path += "?" + scope["query_string"].decode()
222
-
223
- proxy_response = await session.request(
224
- method=scope["method"],
225
- url=path,
226
- headers=[(k.decode(), v.decode()) for k, v in scope["headers"]],
227
- data=None if scope["method"] in aiohttp.ClientRequest.GET_METHODS else request_generator(),
228
- allow_redirects=False,
229
- )
230
-
231
- async def send_response():
232
- msg = {
233
- "type": "http.response.start",
234
- "status": proxy_response.status,
235
- "headers": [(k.encode(), v.encode()) for k, v in proxy_response.headers.items()],
236
- }
237
- await send(msg)
238
- async for data in proxy_response.content.iter_any():
239
- msg = {"type": "http.response.body", "body": data, "more_body": True}
240
- await send(msg)
241
- await send({"type": "http.response.body"})
242
-
243
- async def listen_for_disconnect():
244
- while True:
245
- message = await receive()
246
- if message["type"] == "http.disconnect":
247
- proxy_response.connection.transport.abort()
248
-
249
- try:
250
- send_response_task = asyncio.create_task(send_response())
251
- disconnect_task = asyncio.create_task(listen_for_disconnect())
252
- await asyncio.wait([send_response_task, disconnect_task], return_when=asyncio.FIRST_COMPLETED)
253
- finally:
254
- if send_response_task.done():
255
- send_response_task.result()
256
- else:
257
- send_response_task.cancel()
258
- if disconnect_task.done():
259
- disconnect_task.result()
260
- else:
261
- disconnect_task.cancel()
262
-
263
-
264
- async def _proxy_websocket_request(session: aiohttp.ClientSession, scope, receive, send) -> None:
265
- first_message = await receive() # Consume the initial "websocket.connect" message.
266
- if first_message["type"] == "websocket.disconnect":
267
- return
268
- elif first_message["type"] != "websocket.connect":
269
- raise ExecutionError(f"Unexpected message type: {first_message['type']}")
270
-
271
- path = scope["path"]
272
- if scope.get("query_string"):
273
- path += "?" + scope["query_string"].decode()
274
-
275
- async with session.ws_connect(
276
- url=path,
277
- headers=[(k.decode(), v.decode()) for k, v in scope["headers"]], # type: ignore
278
- protocols=scope.get("subprotocols", []),
279
- ) as upstream_ws:
280
-
281
- async def client_to_upstream():
282
- while True:
283
- client_message = await receive()
284
- if client_message["type"] == "websocket.disconnect":
285
- await upstream_ws.close(code=client_message.get("code", 1005))
286
- break
287
- elif client_message["type"] == "websocket.receive":
288
- if client_message.get("text") is not None:
289
- await upstream_ws.send_str(client_message["text"])
290
- elif client_message.get("bytes") is not None:
291
- await upstream_ws.send_bytes(client_message["bytes"])
292
- else:
293
- raise ExecutionError(f"Unexpected message type: {client_message['type']}")
294
-
295
- async def upstream_to_client():
296
- msg: Dict[str, Any] = {
297
- "type": "websocket.accept",
298
- "subprotocol": upstream_ws.protocol,
299
- }
300
- await send(msg)
301
-
302
- while True:
303
- upstream_message = await upstream_ws.receive()
304
- if upstream_message.type == aiohttp.WSMsgType.closed:
305
- msg = {"type": "websocket.close"}
306
- if upstream_message.data is not None:
307
- msg["code"] = cast(aiohttp.WSCloseCode, upstream_message.data).value
308
- msg["reason"] = upstream_message.extra
309
- await send(msg)
310
- break
311
- elif upstream_message.type == aiohttp.WSMsgType.text:
312
- await send({"type": "websocket.send", "text": upstream_message.data})
313
- elif upstream_message.type == aiohttp.WSMsgType.binary:
314
- await send({"type": "websocket.send", "bytes": upstream_message.data})
315
- else:
316
- pass # Ignore all other upstream WebSocket message types.
317
-
318
- client_to_upstream_task = asyncio.create_task(client_to_upstream())
319
- upstream_to_client_task = asyncio.create_task(upstream_to_client())
320
- try:
321
- await asyncio.wait([client_to_upstream_task, upstream_to_client_task], return_when=asyncio.FIRST_COMPLETED)
322
- finally:
323
- if client_to_upstream_task.done():
324
- client_to_upstream_task.result()
325
- else:
326
- client_to_upstream_task.cancel()
327
- if upstream_to_client_task.done():
328
- upstream_to_client_task.result()
329
- else:
330
- upstream_to_client_task.cancel()
331
-
332
-
333
- def web_server_proxy(host: str, port: int):
334
- """Return an ASGI app that proxies requests to a web server running on the same host."""
335
- if not 0 < port < 65536:
336
- raise InvalidError(f"Invalid port number: {port}")
337
-
338
- base_url = f"http://{host}:{port}"
339
- session: Optional[aiohttp.ClientSession] = None
340
-
341
- async def web_server_proxy_app(scope, receive, send):
342
- nonlocal session
343
- if session is None:
344
- # TODO: We currently create the ClientSession on container startup and never close it.
345
- # This outputs an "Unclosed client session" warning during runner termination. We should
346
- # properly close the session once we implement the ASGI lifespan protocol.
347
- session = aiohttp.ClientSession(
348
- base_url,
349
- cookie_jar=aiohttp.DummyCookieJar(),
350
- timeout=aiohttp.ClientTimeout(total=3600),
351
- auto_decompress=False,
352
- read_bufsize=1024 * 1024, # 1 MiB
353
- )
354
-
355
- try:
356
- if scope["type"] == "lifespan":
357
- pass # Do nothing for lifespan events.
358
- elif scope["type"] == "http":
359
- await _proxy_http_request(session, scope, receive, send)
360
- elif scope["type"] == "websocket":
361
- await _proxy_websocket_request(session, scope, receive, send)
362
- else:
363
- raise NotImplementedError(f"Scope {scope} is not understood")
364
-
365
- except aiohttp.ClientConnectorError as exc:
366
- # If the server is not running or not reachable, we should stop fetching new inputs.
367
- logger.warning(f"Terminating runner due to @web_server connection issue: {exc}")
368
- stop_fetching_inputs()
369
-
370
- return web_server_proxy_app