flyte 2.0.0b32__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.

Potentially problematic release.


This version of flyte might be problematic. Click here for more details.

Files changed (204) hide show
  1. flyte/__init__.py +108 -0
  2. flyte/_bin/__init__.py +0 -0
  3. flyte/_bin/debug.py +38 -0
  4. flyte/_bin/runtime.py +195 -0
  5. flyte/_bin/serve.py +178 -0
  6. flyte/_build.py +26 -0
  7. flyte/_cache/__init__.py +12 -0
  8. flyte/_cache/cache.py +147 -0
  9. flyte/_cache/defaults.py +9 -0
  10. flyte/_cache/local_cache.py +216 -0
  11. flyte/_cache/policy_function_body.py +42 -0
  12. flyte/_code_bundle/__init__.py +8 -0
  13. flyte/_code_bundle/_ignore.py +121 -0
  14. flyte/_code_bundle/_packaging.py +218 -0
  15. flyte/_code_bundle/_utils.py +347 -0
  16. flyte/_code_bundle/bundle.py +266 -0
  17. flyte/_constants.py +1 -0
  18. flyte/_context.py +155 -0
  19. flyte/_custom_context.py +73 -0
  20. flyte/_debug/__init__.py +0 -0
  21. flyte/_debug/constants.py +38 -0
  22. flyte/_debug/utils.py +17 -0
  23. flyte/_debug/vscode.py +307 -0
  24. flyte/_deploy.py +408 -0
  25. flyte/_deployer.py +109 -0
  26. flyte/_doc.py +29 -0
  27. flyte/_docstring.py +32 -0
  28. flyte/_environment.py +122 -0
  29. flyte/_excepthook.py +37 -0
  30. flyte/_group.py +32 -0
  31. flyte/_hash.py +8 -0
  32. flyte/_image.py +1055 -0
  33. flyte/_initialize.py +628 -0
  34. flyte/_interface.py +119 -0
  35. flyte/_internal/__init__.py +3 -0
  36. flyte/_internal/controllers/__init__.py +129 -0
  37. flyte/_internal/controllers/_local_controller.py +239 -0
  38. flyte/_internal/controllers/_trace.py +48 -0
  39. flyte/_internal/controllers/remote/__init__.py +58 -0
  40. flyte/_internal/controllers/remote/_action.py +211 -0
  41. flyte/_internal/controllers/remote/_client.py +47 -0
  42. flyte/_internal/controllers/remote/_controller.py +583 -0
  43. flyte/_internal/controllers/remote/_core.py +465 -0
  44. flyte/_internal/controllers/remote/_informer.py +381 -0
  45. flyte/_internal/controllers/remote/_service_protocol.py +50 -0
  46. flyte/_internal/imagebuild/__init__.py +3 -0
  47. flyte/_internal/imagebuild/docker_builder.py +706 -0
  48. flyte/_internal/imagebuild/image_builder.py +277 -0
  49. flyte/_internal/imagebuild/remote_builder.py +386 -0
  50. flyte/_internal/imagebuild/utils.py +78 -0
  51. flyte/_internal/resolvers/__init__.py +0 -0
  52. flyte/_internal/resolvers/_task_module.py +21 -0
  53. flyte/_internal/resolvers/common.py +31 -0
  54. flyte/_internal/resolvers/default.py +28 -0
  55. flyte/_internal/runtime/__init__.py +0 -0
  56. flyte/_internal/runtime/convert.py +486 -0
  57. flyte/_internal/runtime/entrypoints.py +204 -0
  58. flyte/_internal/runtime/io.py +188 -0
  59. flyte/_internal/runtime/resources_serde.py +152 -0
  60. flyte/_internal/runtime/reuse.py +125 -0
  61. flyte/_internal/runtime/rusty.py +193 -0
  62. flyte/_internal/runtime/task_serde.py +362 -0
  63. flyte/_internal/runtime/taskrunner.py +209 -0
  64. flyte/_internal/runtime/trigger_serde.py +160 -0
  65. flyte/_internal/runtime/types_serde.py +54 -0
  66. flyte/_keyring/__init__.py +0 -0
  67. flyte/_keyring/file.py +115 -0
  68. flyte/_logging.py +300 -0
  69. flyte/_map.py +312 -0
  70. flyte/_module.py +72 -0
  71. flyte/_pod.py +30 -0
  72. flyte/_resources.py +473 -0
  73. flyte/_retry.py +32 -0
  74. flyte/_reusable_environment.py +102 -0
  75. flyte/_run.py +724 -0
  76. flyte/_secret.py +96 -0
  77. flyte/_task.py +550 -0
  78. flyte/_task_environment.py +316 -0
  79. flyte/_task_plugins.py +47 -0
  80. flyte/_timeout.py +47 -0
  81. flyte/_tools.py +27 -0
  82. flyte/_trace.py +119 -0
  83. flyte/_trigger.py +1000 -0
  84. flyte/_utils/__init__.py +30 -0
  85. flyte/_utils/asyn.py +121 -0
  86. flyte/_utils/async_cache.py +139 -0
  87. flyte/_utils/coro_management.py +27 -0
  88. flyte/_utils/docker_credentials.py +173 -0
  89. flyte/_utils/file_handling.py +72 -0
  90. flyte/_utils/helpers.py +134 -0
  91. flyte/_utils/lazy_module.py +54 -0
  92. flyte/_utils/module_loader.py +104 -0
  93. flyte/_utils/org_discovery.py +57 -0
  94. flyte/_utils/uv_script_parser.py +49 -0
  95. flyte/_version.py +34 -0
  96. flyte/app/__init__.py +22 -0
  97. flyte/app/_app_environment.py +157 -0
  98. flyte/app/_deploy.py +125 -0
  99. flyte/app/_input.py +160 -0
  100. flyte/app/_runtime/__init__.py +3 -0
  101. flyte/app/_runtime/app_serde.py +347 -0
  102. flyte/app/_types.py +101 -0
  103. flyte/app/extras/__init__.py +3 -0
  104. flyte/app/extras/_fastapi.py +151 -0
  105. flyte/cli/__init__.py +12 -0
  106. flyte/cli/_abort.py +28 -0
  107. flyte/cli/_build.py +114 -0
  108. flyte/cli/_common.py +468 -0
  109. flyte/cli/_create.py +371 -0
  110. flyte/cli/_delete.py +45 -0
  111. flyte/cli/_deploy.py +293 -0
  112. flyte/cli/_gen.py +176 -0
  113. flyte/cli/_get.py +370 -0
  114. flyte/cli/_option.py +33 -0
  115. flyte/cli/_params.py +554 -0
  116. flyte/cli/_plugins.py +209 -0
  117. flyte/cli/_run.py +597 -0
  118. flyte/cli/_serve.py +64 -0
  119. flyte/cli/_update.py +37 -0
  120. flyte/cli/_user.py +17 -0
  121. flyte/cli/main.py +221 -0
  122. flyte/config/__init__.py +3 -0
  123. flyte/config/_config.py +248 -0
  124. flyte/config/_internal.py +73 -0
  125. flyte/config/_reader.py +225 -0
  126. flyte/connectors/__init__.py +11 -0
  127. flyte/connectors/_connector.py +270 -0
  128. flyte/connectors/_server.py +197 -0
  129. flyte/connectors/utils.py +135 -0
  130. flyte/errors.py +243 -0
  131. flyte/extend.py +19 -0
  132. flyte/extras/__init__.py +5 -0
  133. flyte/extras/_container.py +286 -0
  134. flyte/git/__init__.py +3 -0
  135. flyte/git/_config.py +21 -0
  136. flyte/io/__init__.py +29 -0
  137. flyte/io/_dataframe/__init__.py +131 -0
  138. flyte/io/_dataframe/basic_dfs.py +223 -0
  139. flyte/io/_dataframe/dataframe.py +1026 -0
  140. flyte/io/_dir.py +910 -0
  141. flyte/io/_file.py +914 -0
  142. flyte/io/_hashing_io.py +342 -0
  143. flyte/models.py +479 -0
  144. flyte/py.typed +0 -0
  145. flyte/remote/__init__.py +35 -0
  146. flyte/remote/_action.py +738 -0
  147. flyte/remote/_app.py +57 -0
  148. flyte/remote/_client/__init__.py +0 -0
  149. flyte/remote/_client/_protocols.py +189 -0
  150. flyte/remote/_client/auth/__init__.py +12 -0
  151. flyte/remote/_client/auth/_auth_utils.py +14 -0
  152. flyte/remote/_client/auth/_authenticators/__init__.py +0 -0
  153. flyte/remote/_client/auth/_authenticators/base.py +403 -0
  154. flyte/remote/_client/auth/_authenticators/client_credentials.py +73 -0
  155. flyte/remote/_client/auth/_authenticators/device_code.py +117 -0
  156. flyte/remote/_client/auth/_authenticators/external_command.py +79 -0
  157. flyte/remote/_client/auth/_authenticators/factory.py +200 -0
  158. flyte/remote/_client/auth/_authenticators/pkce.py +516 -0
  159. flyte/remote/_client/auth/_channel.py +213 -0
  160. flyte/remote/_client/auth/_client_config.py +85 -0
  161. flyte/remote/_client/auth/_default_html.py +32 -0
  162. flyte/remote/_client/auth/_grpc_utils/__init__.py +0 -0
  163. flyte/remote/_client/auth/_grpc_utils/auth_interceptor.py +288 -0
  164. flyte/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +151 -0
  165. flyte/remote/_client/auth/_keyring.py +152 -0
  166. flyte/remote/_client/auth/_token_client.py +260 -0
  167. flyte/remote/_client/auth/errors.py +16 -0
  168. flyte/remote/_client/controlplane.py +128 -0
  169. flyte/remote/_common.py +30 -0
  170. flyte/remote/_console.py +19 -0
  171. flyte/remote/_data.py +161 -0
  172. flyte/remote/_logs.py +185 -0
  173. flyte/remote/_project.py +88 -0
  174. flyte/remote/_run.py +386 -0
  175. flyte/remote/_secret.py +142 -0
  176. flyte/remote/_task.py +527 -0
  177. flyte/remote/_trigger.py +306 -0
  178. flyte/remote/_user.py +33 -0
  179. flyte/report/__init__.py +3 -0
  180. flyte/report/_report.py +182 -0
  181. flyte/report/_template.html +124 -0
  182. flyte/storage/__init__.py +36 -0
  183. flyte/storage/_config.py +237 -0
  184. flyte/storage/_parallel_reader.py +274 -0
  185. flyte/storage/_remote_fs.py +34 -0
  186. flyte/storage/_storage.py +456 -0
  187. flyte/storage/_utils.py +5 -0
  188. flyte/syncify/__init__.py +56 -0
  189. flyte/syncify/_api.py +375 -0
  190. flyte/types/__init__.py +52 -0
  191. flyte/types/_interface.py +40 -0
  192. flyte/types/_pickle.py +145 -0
  193. flyte/types/_renderer.py +162 -0
  194. flyte/types/_string_literals.py +119 -0
  195. flyte/types/_type_engine.py +2254 -0
  196. flyte/types/_utils.py +80 -0
  197. flyte-2.0.0b32.data/scripts/debug.py +38 -0
  198. flyte-2.0.0b32.data/scripts/runtime.py +195 -0
  199. flyte-2.0.0b32.dist-info/METADATA +351 -0
  200. flyte-2.0.0b32.dist-info/RECORD +204 -0
  201. flyte-2.0.0b32.dist-info/WHEEL +5 -0
  202. flyte-2.0.0b32.dist-info/entry_points.txt +7 -0
  203. flyte-2.0.0b32.dist-info/licenses/LICENSE +201 -0
  204. flyte-2.0.0b32.dist-info/top_level.txt +1 -0
@@ -0,0 +1,381 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from asyncio import Queue
5
+ from typing import AsyncIterator, Callable, Dict, Optional, Tuple, cast
6
+
7
+ import grpc.aio
8
+ from flyteidl2.common import identifier_pb2
9
+ from flyteidl2.workflow import run_definition_pb2, state_service_pb2
10
+
11
+ from flyte._logging import log, logger
12
+
13
+ from ._action import Action
14
+ from ._service_protocol import StateService
15
+
16
+
17
+ class ActionCache:
18
+ """
19
+ Cache for actions, used to store the state of all sub-actions, launched by this parent action.
20
+ This is coroutine-safe.
21
+ """
22
+
23
+ def __init__(self, parent_action_name: str):
24
+ # Cache for actions (sub-actions)
25
+ self._cache: Dict[str, Action] = {}
26
+ # Completion events for actions
27
+ self._completion_events: Dict[str, asyncio.Event] = {}
28
+ # Lock for coroutine safety
29
+ self._lock = asyncio.Lock()
30
+ # Parent action name
31
+ self._parent_action_name = parent_action_name
32
+
33
+ async def has(self, name: str) -> bool:
34
+ """Check if a node is in the cache"""
35
+ async with self._lock:
36
+ return name in self._cache
37
+
38
+ async def observe_state(self, state: state_service_pb2.ActionUpdate) -> Action:
39
+ """
40
+ Add an action to the cache if it doesn't exist. This is invoked by the watch.
41
+ """
42
+ logger.debug(f"Observing phase {run_definition_pb2.Phase.Name(state.phase)} for {state.action_id.name}")
43
+ if state.output_uri:
44
+ logger.debug(f"Output URI: {state.output_uri}")
45
+ else:
46
+ logger.warning(
47
+ f"{state.action_id.name} has no output URI, in phase {run_definition_pb2.Phase.Name(state.phase)}"
48
+ )
49
+ if state.phase == run_definition_pb2.Phase.PHASE_FAILED:
50
+ logger.error(
51
+ f"Action {state.action_id.name} failed with error (msg):"
52
+ f" [{state.error if state.HasField('error') else None}]"
53
+ )
54
+ async with self._lock:
55
+ if state.action_id.name in self._cache:
56
+ self._cache[state.action_id.name].merge_state(state)
57
+ else:
58
+ self._cache[state.action_id.name] = Action.from_state(self._parent_action_name, state)
59
+ return self._cache[state.action_id.name]
60
+
61
+ async def submit(self, action: Action) -> Action:
62
+ """
63
+ Submit a new Action to the cache. This is invoked by the parent_action.
64
+ """
65
+ async with self._lock:
66
+ if action.name in self._cache:
67
+ self._cache[action.name].merge_in_action_from_submit(action)
68
+ else:
69
+ self._cache[action.name] = action
70
+ if action.name not in self._completion_events:
71
+ self._completion_events[action.name] = asyncio.Event()
72
+ return self._cache[action.name]
73
+
74
+ async def get(self, name: str) -> Action | None:
75
+ """Get an action by its name from the cache"""
76
+ async with self._lock:
77
+ return self._cache.get(name, None)
78
+
79
+ async def remove(self, name: str) -> Action | None:
80
+ """Remove an action from the cache"""
81
+ async with self._lock:
82
+ return self._cache.pop(name, None)
83
+
84
+ async def wait_for_completion(self, name: str) -> bool:
85
+ """Wait for an action to complete"""
86
+ async with self._lock:
87
+ if name not in self._completion_events:
88
+ return False
89
+ event = self._completion_events[name]
90
+ return await event.wait()
91
+
92
+ async def fire_all_completion_events(self):
93
+ """Fire all completion events"""
94
+ async with self._lock:
95
+ for name, event in self._completion_events.items():
96
+ event.set()
97
+ self._completion_events.clear()
98
+
99
+ async def fire_completion_event(self, name: str):
100
+ """Fire a completion event for an action"""
101
+ async with self._lock:
102
+ if name in self._completion_events:
103
+ self._completion_events[name].set()
104
+
105
+ async def count_started_pending_terminal_actions(self) -> Tuple[int, int, int]:
106
+ """
107
+ Get all started, pending and terminal actions.
108
+ Started: implies they were submitted to queue service
109
+ Pending: implies they are still not submitted to the queue service
110
+ Terminal: implies completed (success, failure, aborted, timedout) actions
111
+ """
112
+ started = 0
113
+ pending = 0
114
+ terminal = 0
115
+ async with self._lock:
116
+ for name, res in self._cache.items():
117
+ if res.is_started():
118
+ started += 1
119
+ elif res.is_terminal():
120
+ terminal += 1
121
+ else:
122
+ pending += 1
123
+ return started, pending, terminal
124
+
125
+
126
+ class Informer:
127
+ """Remote StateStore watcher and informer for sub-actions."""
128
+
129
+ def __init__(
130
+ self,
131
+ run_id: identifier_pb2.RunIdentifier,
132
+ parent_action_name: str,
133
+ shared_queue: Queue,
134
+ client: Optional[StateService] = None,
135
+ min_watch_backoff: float = 1.0,
136
+ max_watch_backoff: float = 30.0,
137
+ watch_conn_timeout_sec: float = 5.0,
138
+ max_watch_retries: int = 10,
139
+ ):
140
+ self.name = self.mkname(run_name=run_id.name, parent_action_name=parent_action_name)
141
+ self.parent_action_name = parent_action_name
142
+ self._run_id = run_id
143
+ self._client = client
144
+ self._action_cache = ActionCache(parent_action_name)
145
+ self._shared_queue = shared_queue
146
+ self._running = False
147
+ self._watch_task: asyncio.Task | None = None
148
+ self._ready = asyncio.Event()
149
+ self._min_watch_backoff = min_watch_backoff
150
+ self._max_watch_backoff = max_watch_backoff
151
+ self._watch_conn_timeout_sec = watch_conn_timeout_sec
152
+ self._max_watch_retries = max_watch_retries
153
+
154
+ @classmethod
155
+ def mkname(cls, *, run_name: str, parent_action_name: str) -> str:
156
+ """Get the name of the informer"""
157
+ return f"{run_name}.{parent_action_name}"
158
+
159
+ @property
160
+ def watch_task(self) -> asyncio.Task | None:
161
+ """Get the watch task"""
162
+ return self._watch_task
163
+
164
+ def is_running(self) -> bool:
165
+ """Check if informer is running"""
166
+ return self._running
167
+
168
+ async def _set_ready(self):
169
+ """Set the informer as ready"""
170
+ self._ready.set()
171
+
172
+ async def wait_for_cache_sync(self, timeout: Optional[float] = None) -> bool:
173
+ """
174
+ Wait for the informer to be ready. In the case of a timeout, it will return False.
175
+ :param timeout: float time to wait for
176
+ :return: bool
177
+ """
178
+ try:
179
+ await asyncio.wait_for(self._ready.wait(), timeout=timeout)
180
+ return True
181
+ except asyncio.TimeoutError:
182
+ logger.error(f"Informer cache sync timed out, for {self.name}")
183
+ return False
184
+
185
+ async def wait_for_action_completion(self, name: str) -> bool:
186
+ """Wait for an action to complete"""
187
+ return await self._action_cache.wait_for_completion(name)
188
+
189
+ async def fire_completion_event(self, name: str):
190
+ """Fire a completion event for an action"""
191
+ await self._action_cache.fire_completion_event(name)
192
+
193
+ @log
194
+ async def submit(self, action: Action):
195
+ """Add a new resource to watch"""
196
+ node = await self._action_cache.submit(action)
197
+ await self._shared_queue.put(node)
198
+
199
+ @log
200
+ async def remove(self, name: str):
201
+ """Remove a resource from watching"""
202
+ await self._action_cache.remove(name)
203
+
204
+ async def get(self, name: str) -> Action | None:
205
+ """Get a resource by name"""
206
+ return await self._action_cache.get(name)
207
+
208
+ async def has(self, name: str) -> bool:
209
+ """Check if a resource exists"""
210
+ return await self._action_cache.has(name)
211
+
212
+ async def watch(self):
213
+ """
214
+ Watch for updates on all resources - to be implemented by subclasses for watch mode
215
+ """
216
+ # sentinel = False
217
+ retries = 0
218
+ last_exc = None
219
+ while self._running:
220
+ if retries >= self._max_watch_retries:
221
+ logger.error(
222
+ f"Informer watch failure retries crossed threshold {retries}/{self._max_watch_retries}, exiting!"
223
+ )
224
+ raise last_exc
225
+ try:
226
+ if retries >= 1:
227
+ logger.warning(f"Informer watch retrying, attempt {retries}/{self._max_watch_retries}")
228
+ watcher = self._client.Watch(
229
+ state_service_pb2.WatchRequest(
230
+ parent_action_id=identifier_pb2.ActionIdentifier(
231
+ name=self.parent_action_name,
232
+ run=self._run_id,
233
+ ),
234
+ ),
235
+ wait_for_ready=True,
236
+ )
237
+ resp: state_service_pb2.WatchResponse
238
+ async for resp in watcher:
239
+ retries = 0
240
+ if resp.control_message is not None and resp.control_message.sentinel:
241
+ logger.info(f"Received Sentinel, for run {self.name}")
242
+ await self._set_ready()
243
+ continue
244
+ node = await self._action_cache.observe_state(resp.action_update)
245
+ await self._shared_queue.put(node)
246
+ # hack to work in the absence of sentinel
247
+ except asyncio.CancelledError:
248
+ logger.info(f"Watch cancelled: {self.name}")
249
+ return
250
+ except asyncio.TimeoutError as e:
251
+ logger.error(f"Watch timeout: {self.name}", exc_info=e)
252
+ last_exc = e
253
+ retries += 1
254
+ except grpc.aio.AioRpcError as e:
255
+ logger.exception(f"RPC error: {self.name}", exc_info=e)
256
+ last_exc = e
257
+ retries += 1
258
+ except Exception as e:
259
+ logger.exception(f"Watch error: {self.name}", exc_info=e)
260
+ last_exc = e
261
+ retries += 1
262
+ backoff = min(self._min_watch_backoff * (2**retries), self._max_watch_backoff)
263
+ logger.warning(f"Watch for {self.name} failed, retrying in {backoff} seconds...")
264
+ await asyncio.sleep(backoff)
265
+
266
+ @log
267
+ async def start(self, timeout: Optional[float] = None) -> asyncio.Task:
268
+ """Start the informer"""
269
+ if self._running:
270
+ logger.warning("Informer already running")
271
+ return cast(asyncio.Task, self._watch_task)
272
+ self._running = True
273
+ self._watch_task = asyncio.create_task(self.watch(), name=f"InformerWatch-{self.parent_action_name}")
274
+ await self.wait_for_cache_sync(timeout=timeout)
275
+ return self._watch_task
276
+
277
+ async def count_started_pending_terminal_actions(self) -> Tuple[int, int, int]:
278
+ """Get all launched and waiting resources"""
279
+ return await self._action_cache.count_started_pending_terminal_actions()
280
+
281
+ @log
282
+ async def stop(self):
283
+ """Stop the informer"""
284
+ self._running = False
285
+ if self._watch_task:
286
+ self._watch_task.cancel()
287
+ self._watch_task = None
288
+ logger.info("Stopped informer")
289
+
290
+
291
+ class InformerCache:
292
+ """Cache for informers, used to store the state of all subactions for multiple parent_actions.
293
+ This is coroutine-safe.
294
+ """
295
+
296
+ def __init__(self):
297
+ self._cache: Dict[str, Informer] = {}
298
+ self._lock = asyncio.Lock()
299
+
300
+ @log
301
+ async def get_or_create(
302
+ self,
303
+ run_id: identifier_pb2.RunIdentifier,
304
+ parent_action_name: str,
305
+ shared_queue: Queue,
306
+ state_service: StateService,
307
+ fn: Callable[[asyncio.Task], None],
308
+ timeout: Optional[float] = None,
309
+ ) -> Informer:
310
+ """
311
+ Start and add a new informer to the cache
312
+ :param run_id: Run ID
313
+ :param parent_action_name: Parent action name
314
+ :param shared_queue: Shared queue
315
+ :param state_service: State service
316
+ :param fn: Callback function to be called when the informer is done
317
+ :param timeout: Timeout for the informer to be ready
318
+ :return: Tuple of informer and a boolean indicating if it was created. True if created, false if already exists.
319
+ """
320
+ name = Informer.mkname(run_name=run_id.name, parent_action_name=parent_action_name)
321
+ async with self._lock:
322
+ if name in self._cache:
323
+ return self._cache[name]
324
+ informer = Informer(
325
+ run_id=run_id,
326
+ parent_action_name=parent_action_name,
327
+ shared_queue=shared_queue,
328
+ client=state_service,
329
+ )
330
+ self._cache[informer.name] = informer
331
+ # TODO This is a potential perf problem for large number of informers.
332
+ # We can start in only if it is not started. Reason to do this overly optimistic is to avoid,
333
+ # remove from removing the cache.
334
+ task = await informer.start(timeout=timeout)
335
+ if task is None:
336
+ logger.error(f"Informer {name} failed to start")
337
+ raise RuntimeError(f"Informer {name} failed to start")
338
+ task.add_done_callback(fn)
339
+ return informer
340
+
341
+ @log
342
+ async def get(self, *, run_name: str, parent_action_name: str) -> Informer | None:
343
+ """Get an informer by name"""
344
+ async with self._lock:
345
+ return self._cache.get(
346
+ Informer.mkname(run_name=run_name, parent_action_name=parent_action_name),
347
+ None,
348
+ )
349
+
350
+ @log
351
+ async def remove(self, *, run_name: str, parent_action_name: str) -> Informer | None:
352
+ """Remove an informer from the cache"""
353
+ async with self._lock:
354
+ return self._cache.pop(
355
+ Informer.mkname(run_name=run_name, parent_action_name=parent_action_name),
356
+ None,
357
+ )
358
+
359
+ async def has(self, *, run_name: str, parent_action_name: str) -> bool:
360
+ """Check if an informer exists in the cache"""
361
+ async with self._lock:
362
+ return Informer.mkname(run_name=run_name, parent_action_name=parent_action_name) in self._cache
363
+
364
+ async def count_started_pending_terminal_actions(
365
+ self,
366
+ ) -> AsyncIterator[Tuple[int, int, int]]:
367
+ """Log resource stats"""
368
+ async with self._lock:
369
+ for informer in self._cache.values():
370
+ yield await informer.count_started_pending_terminal_actions()
371
+
372
+ async def remove_and_stop_all(self):
373
+ """Stop all informers and remove them from the cache"""
374
+ async with self._lock:
375
+ while self._cache:
376
+ _name, informer = self._cache.popitem()
377
+ try:
378
+ await informer.stop()
379
+ except asyncio.CancelledError:
380
+ pass
381
+ self._cache.clear()
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import AsyncIterator, Protocol
4
+
5
+ from flyteidl2.workflow import queue_service_pb2, state_service_pb2
6
+
7
+
8
+ class StateService(Protocol):
9
+ """
10
+ Interface for the state store client, which stores the history of all subruns.
11
+ """
12
+
13
+ async def Watch(
14
+ self, req: state_service_pb2.WatchRequest, **kwargs
15
+ ) -> AsyncIterator[state_service_pb2.WatchResponse]:
16
+ """Watch for subrun updates"""
17
+
18
+
19
+ class QueueService(Protocol):
20
+ """
21
+ Interface for the remote queue service, which is responsible for managing the queue of tasks.
22
+ """
23
+
24
+ async def EnqueueAction(
25
+ self,
26
+ req: queue_service_pb2.EnqueueActionRequest,
27
+ **kwargs,
28
+ ) -> queue_service_pb2.EnqueueActionResponse:
29
+ """Enqueue a task"""
30
+
31
+ async def AbortQueuedAction(
32
+ self,
33
+ req: queue_service_pb2.AbortQueuedActionRequest,
34
+ **kwargs,
35
+ ) -> queue_service_pb2.AbortQueuedActionResponse:
36
+ """Cancel an enqueued task"""
37
+
38
+
39
+ class ClientSet(Protocol):
40
+ """
41
+ Interface for the remote client set, which is responsible for managing the queue of tasks.
42
+ """
43
+
44
+ @property
45
+ def state_service(self: ClientSet) -> StateService:
46
+ """State service"""
47
+
48
+ @property
49
+ def queue_service(self: ClientSet) -> QueueService:
50
+ """Queue service"""
@@ -0,0 +1,3 @@
1
+ from flyte._internal.imagebuild.image_builder import ImageBuildEngine
2
+
3
+ __all__ = ["ImageBuildEngine"]