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
modal/dict.pyi CHANGED
@@ -1,195 +1,200 @@
1
+ import collections.abc
1
2
  import modal.client
2
3
  import modal.object
3
4
  import synchronicity.combined_types
4
5
  import typing
5
6
  import typing_extensions
6
7
 
7
- def _serialize_dict(data):
8
- ...
9
-
8
+ def _serialize_dict(data): ...
10
9
 
11
10
  class _Dict(modal.object._Object):
12
- @staticmethod
13
- def new(data: typing.Union[dict, None] = None) -> _Dict:
14
- ...
15
-
16
- def __init__(self, data={}):
17
- ...
18
-
11
+ def __init__(self, data={}): ...
19
12
  @classmethod
20
- def ephemeral(cls: typing.Type[_Dict], data: typing.Union[dict, None] = None, client: typing.Union[modal.client._Client, None] = None, environment_name: typing.Union[str, None] = None, _heartbeat_sleep: float = 300) -> typing.AsyncContextManager[_Dict]:
21
- ...
22
-
13
+ def ephemeral(
14
+ cls: type[_Dict],
15
+ data: typing.Optional[dict] = None,
16
+ client: typing.Optional[modal.client._Client] = None,
17
+ environment_name: typing.Optional[str] = None,
18
+ _heartbeat_sleep: float = 300,
19
+ ) -> typing.AsyncContextManager[_Dict]: ...
23
20
  @staticmethod
24
- def from_name(label: str, data: typing.Union[dict, None] = None, namespace=1, environment_name: typing.Union[str, None] = None, create_if_missing: bool = False) -> _Dict:
25
- ...
26
-
21
+ def from_name(
22
+ name: str,
23
+ data: typing.Optional[dict] = None,
24
+ namespace=1,
25
+ environment_name: typing.Optional[str] = None,
26
+ create_if_missing: bool = False,
27
+ ) -> _Dict: ...
27
28
  @staticmethod
28
- def persisted(label: str, namespace=1, environment_name: typing.Union[str, None] = None) -> _Dict:
29
- ...
30
-
29
+ async def lookup(
30
+ name: str,
31
+ data: typing.Optional[dict] = None,
32
+ namespace=1,
33
+ client: typing.Optional[modal.client._Client] = None,
34
+ environment_name: typing.Optional[str] = None,
35
+ create_if_missing: bool = False,
36
+ ) -> _Dict: ...
31
37
  @staticmethod
32
- async def lookup(label: str, data: typing.Union[dict, None] = None, namespace=1, client: typing.Union[modal.client._Client, None] = None, environment_name: typing.Union[str, None] = None, create_if_missing: bool = False) -> _Dict:
33
- ...
34
-
35
- async def clear(self) -> None:
36
- ...
37
-
38
- async def get(self, key: typing.Any, default: typing.Union[typing.Any, None] = None) -> typing.Any:
39
- ...
40
-
41
- async def contains(self, key: typing.Any) -> bool:
42
- ...
43
-
44
- async def len(self) -> int:
45
- ...
46
-
47
- async def __getitem__(self, key: typing.Any) -> typing.Any:
48
- ...
49
-
50
- async def update(self, **kwargs) -> None:
51
- ...
52
-
53
- async def put(self, key: typing.Any, value: typing.Any) -> None:
54
- ...
55
-
56
- async def __setitem__(self, key: typing.Any, value: typing.Any) -> None:
57
- ...
58
-
59
- async def pop(self, key: typing.Any) -> typing.Any:
60
- ...
61
-
62
- async def __delitem__(self, key: typing.Any) -> typing.Any:
63
- ...
64
-
65
- async def __contains__(self, key: typing.Any) -> bool:
66
- ...
67
-
38
+ async def delete(
39
+ name: str,
40
+ *,
41
+ client: typing.Optional[modal.client._Client] = None,
42
+ environment_name: typing.Optional[str] = None,
43
+ ): ...
44
+ async def clear(self) -> None: ...
45
+ async def get(self, key: typing.Any, default: typing.Optional[typing.Any] = None) -> typing.Any: ...
46
+ async def contains(self, key: typing.Any) -> bool: ...
47
+ async def len(self) -> int: ...
48
+ async def __getitem__(self, key: typing.Any) -> typing.Any: ...
49
+ async def update(self, **kwargs) -> None: ...
50
+ async def put(self, key: typing.Any, value: typing.Any) -> None: ...
51
+ async def __setitem__(self, key: typing.Any, value: typing.Any) -> None: ...
52
+ async def pop(self, key: typing.Any) -> typing.Any: ...
53
+ async def __delitem__(self, key: typing.Any) -> typing.Any: ...
54
+ async def __contains__(self, key: typing.Any) -> bool: ...
55
+ def keys(self) -> collections.abc.AsyncIterator[typing.Any]: ...
56
+ def values(self) -> collections.abc.AsyncIterator[typing.Any]: ...
57
+ def items(self) -> collections.abc.AsyncIterator[tuple[typing.Any, typing.Any]]: ...
68
58
 
69
59
  class Dict(modal.object.Object):
70
- def __init__(self, data={}):
71
- ...
72
-
73
- @staticmethod
74
- def new(data: typing.Union[dict, None] = None) -> Dict:
75
- ...
76
-
60
+ def __init__(self, data={}): ...
77
61
  @classmethod
78
- def ephemeral(cls: typing.Type[Dict], data: typing.Union[dict, None] = None, client: typing.Union[modal.client.Client, None] = None, environment_name: typing.Union[str, None] = None, _heartbeat_sleep: float = 300) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Dict]:
79
- ...
80
-
81
- @staticmethod
82
- def from_name(label: str, data: typing.Union[dict, None] = None, namespace=1, environment_name: typing.Union[str, None] = None, create_if_missing: bool = False) -> Dict:
83
- ...
84
-
62
+ def ephemeral(
63
+ cls: type[Dict],
64
+ data: typing.Optional[dict] = None,
65
+ client: typing.Optional[modal.client.Client] = None,
66
+ environment_name: typing.Optional[str] = None,
67
+ _heartbeat_sleep: float = 300,
68
+ ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Dict]: ...
85
69
  @staticmethod
86
- def persisted(label: str, namespace=1, environment_name: typing.Union[str, None] = None) -> Dict:
87
- ...
70
+ def from_name(
71
+ name: str,
72
+ data: typing.Optional[dict] = None,
73
+ namespace=1,
74
+ environment_name: typing.Optional[str] = None,
75
+ create_if_missing: bool = False,
76
+ ) -> Dict: ...
88
77
 
89
78
  class __lookup_spec(typing_extensions.Protocol):
90
- def __call__(self, label: str, data: typing.Union[dict, None] = None, namespace=1, client: typing.Union[modal.client.Client, None] = None, environment_name: typing.Union[str, None] = None, create_if_missing: bool = False) -> Dict:
91
- ...
92
-
93
- async def aio(self, *args, **kwargs) -> Dict:
94
- ...
79
+ def __call__(
80
+ self,
81
+ name: str,
82
+ data: typing.Optional[dict] = None,
83
+ namespace=1,
84
+ client: typing.Optional[modal.client.Client] = None,
85
+ environment_name: typing.Optional[str] = None,
86
+ create_if_missing: bool = False,
87
+ ) -> Dict: ...
88
+ async def aio(
89
+ self,
90
+ name: str,
91
+ data: typing.Optional[dict] = None,
92
+ namespace=1,
93
+ client: typing.Optional[modal.client.Client] = None,
94
+ environment_name: typing.Optional[str] = None,
95
+ create_if_missing: bool = False,
96
+ ) -> Dict: ...
95
97
 
96
98
  lookup: __lookup_spec
97
99
 
98
- class __clear_spec(typing_extensions.Protocol):
99
- def __call__(self) -> None:
100
- ...
100
+ class __delete_spec(typing_extensions.Protocol):
101
+ def __call__(
102
+ self,
103
+ name: str,
104
+ *,
105
+ client: typing.Optional[modal.client.Client] = None,
106
+ environment_name: typing.Optional[str] = None,
107
+ ): ...
108
+ async def aio(
109
+ self,
110
+ name: str,
111
+ *,
112
+ client: typing.Optional[modal.client.Client] = None,
113
+ environment_name: typing.Optional[str] = None,
114
+ ): ...
115
+
116
+ delete: __delete_spec
101
117
 
102
- async def aio(self, *args, **kwargs) -> None:
103
- ...
118
+ class __clear_spec(typing_extensions.Protocol):
119
+ def __call__(self) -> None: ...
120
+ async def aio(self) -> None: ...
104
121
 
105
122
  clear: __clear_spec
106
123
 
107
124
  class __get_spec(typing_extensions.Protocol):
108
- def __call__(self, key: typing.Any, default: typing.Union[typing.Any, None] = None) -> typing.Any:
109
- ...
110
-
111
- async def aio(self, *args, **kwargs) -> typing.Any:
112
- ...
125
+ def __call__(self, key: typing.Any, default: typing.Optional[typing.Any] = None) -> typing.Any: ...
126
+ async def aio(self, key: typing.Any, default: typing.Optional[typing.Any] = None) -> typing.Any: ...
113
127
 
114
128
  get: __get_spec
115
129
 
116
130
  class __contains_spec(typing_extensions.Protocol):
117
- def __call__(self, key: typing.Any) -> bool:
118
- ...
119
-
120
- async def aio(self, *args, **kwargs) -> bool:
121
- ...
131
+ def __call__(self, key: typing.Any) -> bool: ...
132
+ async def aio(self, key: typing.Any) -> bool: ...
122
133
 
123
134
  contains: __contains_spec
124
135
 
125
136
  class __len_spec(typing_extensions.Protocol):
126
- def __call__(self) -> int:
127
- ...
128
-
129
- async def aio(self, *args, **kwargs) -> int:
130
- ...
137
+ def __call__(self) -> int: ...
138
+ async def aio(self) -> int: ...
131
139
 
132
140
  len: __len_spec
133
141
 
134
142
  class ____getitem___spec(typing_extensions.Protocol):
135
- def __call__(self, key: typing.Any) -> typing.Any:
136
- ...
137
-
138
- async def aio(self, *args, **kwargs) -> typing.Any:
139
- ...
143
+ def __call__(self, key: typing.Any) -> typing.Any: ...
144
+ async def aio(self, key: typing.Any) -> typing.Any: ...
140
145
 
141
146
  __getitem__: ____getitem___spec
142
147
 
143
148
  class __update_spec(typing_extensions.Protocol):
144
- def __call__(self, **kwargs) -> None:
145
- ...
146
-
147
- async def aio(self, *args, **kwargs) -> None:
148
- ...
149
+ def __call__(self, **kwargs) -> None: ...
150
+ async def aio(self, **kwargs) -> None: ...
149
151
 
150
152
  update: __update_spec
151
153
 
152
154
  class __put_spec(typing_extensions.Protocol):
153
- def __call__(self, key: typing.Any, value: typing.Any) -> None:
154
- ...
155
-
156
- async def aio(self, *args, **kwargs) -> None:
157
- ...
155
+ def __call__(self, key: typing.Any, value: typing.Any) -> None: ...
156
+ async def aio(self, key: typing.Any, value: typing.Any) -> None: ...
158
157
 
159
158
  put: __put_spec
160
159
 
161
160
  class ____setitem___spec(typing_extensions.Protocol):
162
- def __call__(self, key: typing.Any, value: typing.Any) -> None:
163
- ...
164
-
165
- async def aio(self, *args, **kwargs) -> None:
166
- ...
161
+ def __call__(self, key: typing.Any, value: typing.Any) -> None: ...
162
+ async def aio(self, key: typing.Any, value: typing.Any) -> None: ...
167
163
 
168
164
  __setitem__: ____setitem___spec
169
165
 
170
166
  class __pop_spec(typing_extensions.Protocol):
171
- def __call__(self, key: typing.Any) -> typing.Any:
172
- ...
173
-
174
- async def aio(self, *args, **kwargs) -> typing.Any:
175
- ...
167
+ def __call__(self, key: typing.Any) -> typing.Any: ...
168
+ async def aio(self, key: typing.Any) -> typing.Any: ...
176
169
 
177
170
  pop: __pop_spec
178
171
 
179
172
  class ____delitem___spec(typing_extensions.Protocol):
180
- def __call__(self, key: typing.Any) -> typing.Any:
181
- ...
182
-
183
- async def aio(self, *args, **kwargs) -> typing.Any:
184
- ...
173
+ def __call__(self, key: typing.Any) -> typing.Any: ...
174
+ async def aio(self, key: typing.Any) -> typing.Any: ...
185
175
 
186
176
  __delitem__: ____delitem___spec
187
177
 
188
178
  class ____contains___spec(typing_extensions.Protocol):
189
- def __call__(self, key: typing.Any) -> bool:
190
- ...
191
-
192
- async def aio(self, *args, **kwargs) -> bool:
193
- ...
179
+ def __call__(self, key: typing.Any) -> bool: ...
180
+ async def aio(self, key: typing.Any) -> bool: ...
194
181
 
195
182
  __contains__: ____contains___spec
183
+
184
+ class __keys_spec(typing_extensions.Protocol):
185
+ def __call__(self) -> typing.Iterator[typing.Any]: ...
186
+ def aio(self) -> collections.abc.AsyncIterator[typing.Any]: ...
187
+
188
+ keys: __keys_spec
189
+
190
+ class __values_spec(typing_extensions.Protocol):
191
+ def __call__(self) -> typing.Iterator[typing.Any]: ...
192
+ def aio(self) -> collections.abc.AsyncIterator[typing.Any]: ...
193
+
194
+ values: __values_spec
195
+
196
+ class __items_spec(typing_extensions.Protocol):
197
+ def __call__(self) -> typing.Iterator[tuple[typing.Any, typing.Any]]: ...
198
+ def aio(self) -> collections.abc.AsyncIterator[tuple[typing.Any, typing.Any]]: ...
199
+
200
+ items: __items_spec
modal/environments.py CHANGED
@@ -1,14 +1,115 @@
1
1
  # Copyright Modal Labs 2023
2
- from typing import List, Optional
2
+ from dataclasses import dataclass
3
+ from typing import Optional
3
4
 
4
5
  from google.protobuf.empty_pb2 import Empty
6
+ from google.protobuf.message import Message
5
7
  from google.protobuf.wrappers_pb2 import StringValue
6
8
 
7
- from modal.client import _Client
8
- from modal.config import config
9
9
  from modal_proto import api_pb2
10
10
 
11
- from ._utils.async_utils import synchronizer
11
+ from ._resolver import Resolver
12
+ from ._utils.async_utils import synchronize_api, synchronizer
13
+ from ._utils.deprecation import renamed_parameter
14
+ from ._utils.grpc_utils import retry_transient_errors
15
+ from ._utils.name_utils import check_object_name
16
+ from .client import _Client
17
+ from .config import config, logger
18
+ from .object import _Object
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class EnvironmentSettings:
23
+ image_builder_version: str # Ideally would be typed with ImageBuilderVersion literal
24
+ webhook_suffix: str
25
+
26
+
27
+ class _Environment(_Object, type_prefix="en"):
28
+ _settings: EnvironmentSettings
29
+
30
+ def __init__(self):
31
+ """mdmd:hidden"""
32
+ raise RuntimeError(
33
+ "`Environment(...)` constructor is not allowed."
34
+ " Please use `Environment.from_name` or `Environment.lookup` instead."
35
+ )
36
+
37
+ # TODO(michael) Keeping this private for now until we decide what else should be in it
38
+ # And what the rules should be about updates / mutability
39
+ # @property
40
+ # def settings(self) -> EnvironmentSettings:
41
+ # return self._settings
42
+
43
+ def _hydrate_metadata(self, metadata: Message):
44
+ # Overridden concrete implementation of base class method
45
+ assert metadata and isinstance(metadata, api_pb2.EnvironmentMetadata)
46
+ # TODO(michael) should probably expose the `name` from the metadata
47
+ # as the way to discover the name of the "default" environment
48
+
49
+ # Is there a simpler way to go Message -> Dataclass?
50
+ self._settings = EnvironmentSettings(
51
+ image_builder_version=metadata.settings.image_builder_version,
52
+ webhook_suffix=metadata.settings.webhook_suffix,
53
+ )
54
+
55
+ @staticmethod
56
+ @renamed_parameter((2024, 12, 18), "label", "name")
57
+ async def from_name(
58
+ name: str,
59
+ create_if_missing: bool = False,
60
+ ):
61
+ if name:
62
+ # Allow null names for the case where we want to look up the "default" environment,
63
+ # which is defined by the server. It feels messy to have "from_name" without a name, though?
64
+ # We're adding this mostly for internal use right now. We could consider an environment-only
65
+ # alternate constructor, like `Environment.get_default`, rather than exposing "unnamed"
66
+ # environments as part of public API when we make this class more useful.
67
+ check_object_name(name, "Environment")
68
+
69
+ async def _load(self: _Environment, resolver: Resolver, existing_object_id: Optional[str]):
70
+ request = api_pb2.EnvironmentGetOrCreateRequest(
71
+ deployment_name=name,
72
+ object_creation_type=(
73
+ api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING
74
+ if create_if_missing
75
+ else api_pb2.OBJECT_CREATION_TYPE_UNSPECIFIED
76
+ ),
77
+ )
78
+ response = await retry_transient_errors(resolver.client.stub.EnvironmentGetOrCreate, request)
79
+ logger.debug(f"Created environment with id {response.environment_id}")
80
+ self._hydrate(response.environment_id, resolver.client, response.metadata)
81
+
82
+ # TODO environment name (and id?) in the repr? (We should make reprs consistently more useful)
83
+ return _Environment._from_loader(_load, "Environment()", is_another_app=True, hydrate_lazily=True)
84
+
85
+ @staticmethod
86
+ @renamed_parameter((2024, 12, 18), "label", "name")
87
+ async def lookup(
88
+ name: str,
89
+ client: Optional[_Client] = None,
90
+ create_if_missing: bool = False,
91
+ ):
92
+ obj = await _Environment.from_name(name, create_if_missing=create_if_missing)
93
+ if client is None:
94
+ client = await _Client.from_env()
95
+ resolver = Resolver(client=client)
96
+ await resolver.load(obj)
97
+ return obj
98
+
99
+
100
+ Environment = synchronize_api(_Environment)
101
+
102
+
103
+ # Needs to be after definition; synchronicity interferes with forward references?
104
+ ENVIRONMENT_CACHE: dict[str, _Environment] = {}
105
+
106
+
107
+ async def _get_environment_cached(name: str, client: _Client) -> _Environment:
108
+ if name in ENVIRONMENT_CACHE:
109
+ return ENVIRONMENT_CACHE[name]
110
+ environment = await _Environment.lookup(name, client)
111
+ ENVIRONMENT_CACHE[name] = environment
112
+ return environment
12
113
 
13
114
 
14
115
  @synchronizer.create_blocking
@@ -53,7 +154,7 @@ async def create_environment(name: str, client: Optional[_Client] = None):
53
154
 
54
155
 
55
156
  @synchronizer.create_blocking
56
- async def list_environments(client: Optional[_Client] = None) -> List[api_pb2.EnvironmentListItem]:
157
+ async def list_environments(client: Optional[_Client] = None) -> list[api_pb2.EnvironmentListItem]:
57
158
  if client is None:
58
159
  client = await _Client.from_env()
59
160
  resp = await client.stub.EnvironmentList(Empty())
modal/environments.pyi CHANGED
@@ -1,47 +1,99 @@
1
+ import google.protobuf.message
1
2
  import modal.client
3
+ import modal.object
2
4
  import modal_proto.api_pb2
3
5
  import typing
4
6
  import typing_extensions
5
7
 
6
- class __delete_environment_spec(typing_extensions.Protocol):
7
- def __call__(self, name: str, client: typing.Union[modal.client.Client, None] = None):
8
- ...
8
+ class EnvironmentSettings:
9
+ image_builder_version: str
10
+ webhook_suffix: str
9
11
 
10
- async def aio(self, *args, **kwargs):
11
- ...
12
+ def __init__(self, image_builder_version: str, webhook_suffix: str) -> None: ...
13
+ def __repr__(self): ...
14
+ def __eq__(self, other): ...
15
+ def __setattr__(self, name, value): ...
16
+ def __delattr__(self, name): ...
17
+ def __hash__(self): ...
12
18
 
13
- delete_environment: __delete_environment_spec
19
+ class _Environment(modal.object._Object):
20
+ _settings: EnvironmentSettings
14
21
 
22
+ def __init__(self): ...
23
+ def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
24
+ @staticmethod
25
+ async def from_name(name: str, create_if_missing: bool = False): ...
26
+ @staticmethod
27
+ async def lookup(
28
+ name: str, client: typing.Optional[modal.client._Client] = None, create_if_missing: bool = False
29
+ ): ...
15
30
 
16
- class __update_environment_spec(typing_extensions.Protocol):
17
- def __call__(self, current_name: str, *, new_name: typing.Union[str, None] = None, new_web_suffix: typing.Union[str, None] = None, client: typing.Union[modal.client.Client, None] = None):
18
- ...
31
+ class Environment(modal.object.Object):
32
+ _settings: EnvironmentSettings
19
33
 
20
- async def aio(self, *args, **kwargs):
21
- ...
34
+ def __init__(self): ...
35
+ def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
22
36
 
23
- update_environment: __update_environment_spec
37
+ class __from_name_spec(typing_extensions.Protocol):
38
+ def __call__(self, name: str, create_if_missing: bool = False): ...
39
+ async def aio(self, name: str, create_if_missing: bool = False): ...
24
40
 
41
+ from_name: __from_name_spec
25
42
 
26
- class __create_environment_spec(typing_extensions.Protocol):
27
- def __call__(self, name: str, client: typing.Union[modal.client.Client, None] = None):
28
- ...
43
+ class __lookup_spec(typing_extensions.Protocol):
44
+ def __call__(
45
+ self, name: str, client: typing.Optional[modal.client.Client] = None, create_if_missing: bool = False
46
+ ): ...
47
+ async def aio(
48
+ self, name: str, client: typing.Optional[modal.client.Client] = None, create_if_missing: bool = False
49
+ ): ...
29
50
 
30
- async def aio(self, *args, **kwargs):
31
- ...
51
+ lookup: __lookup_spec
32
52
 
33
- create_environment: __create_environment_spec
53
+ async def _get_environment_cached(name: str, client: modal.client._Client) -> _Environment: ...
34
54
 
55
+ class __delete_environment_spec(typing_extensions.Protocol):
56
+ def __call__(self, name: str, client: typing.Optional[modal.client.Client] = None): ...
57
+ async def aio(self, name: str, client: typing.Optional[modal.client.Client] = None): ...
35
58
 
36
- class __list_environments_spec(typing_extensions.Protocol):
37
- def __call__(self, client: typing.Union[modal.client.Client, None] = None) -> typing.List[modal_proto.api_pb2.EnvironmentListItem]:
38
- ...
59
+ delete_environment: __delete_environment_spec
39
60
 
40
- async def aio(self, *args, **kwargs) -> typing.List[modal_proto.api_pb2.EnvironmentListItem]:
41
- ...
61
+ class __update_environment_spec(typing_extensions.Protocol):
62
+ def __call__(
63
+ self,
64
+ current_name: str,
65
+ *,
66
+ new_name: typing.Optional[str] = None,
67
+ new_web_suffix: typing.Optional[str] = None,
68
+ client: typing.Optional[modal.client.Client] = None,
69
+ ): ...
70
+ async def aio(
71
+ self,
72
+ current_name: str,
73
+ *,
74
+ new_name: typing.Optional[str] = None,
75
+ new_web_suffix: typing.Optional[str] = None,
76
+ client: typing.Optional[modal.client.Client] = None,
77
+ ): ...
78
+
79
+ update_environment: __update_environment_spec
80
+
81
+ class __create_environment_spec(typing_extensions.Protocol):
82
+ def __call__(self, name: str, client: typing.Optional[modal.client.Client] = None): ...
83
+ async def aio(self, name: str, client: typing.Optional[modal.client.Client] = None): ...
84
+
85
+ create_environment: __create_environment_spec
86
+
87
+ class __list_environments_spec(typing_extensions.Protocol):
88
+ def __call__(
89
+ self, client: typing.Optional[modal.client.Client] = None
90
+ ) -> list[modal_proto.api_pb2.EnvironmentListItem]: ...
91
+ async def aio(
92
+ self, client: typing.Optional[modal.client.Client] = None
93
+ ) -> list[modal_proto.api_pb2.EnvironmentListItem]: ...
42
94
 
43
95
  list_environments: __list_environments_spec
44
96
 
97
+ def ensure_env(environment_name: typing.Optional[str] = None) -> str: ...
45
98
 
46
- def ensure_env(environment_name: typing.Union[str, None] = None) -> str:
47
- ...
99
+ ENVIRONMENT_CACHE: dict[str, _Environment]