flyte 0.2.0b1__py3-none-any.whl → 2.0.0b46__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 (266) hide show
  1. flyte/__init__.py +83 -30
  2. flyte/_bin/connect.py +61 -0
  3. flyte/_bin/debug.py +38 -0
  4. flyte/_bin/runtime.py +87 -19
  5. flyte/_bin/serve.py +351 -0
  6. flyte/_build.py +3 -2
  7. flyte/_cache/cache.py +6 -5
  8. flyte/_cache/local_cache.py +216 -0
  9. flyte/_code_bundle/_ignore.py +31 -5
  10. flyte/_code_bundle/_packaging.py +42 -11
  11. flyte/_code_bundle/_utils.py +57 -34
  12. flyte/_code_bundle/bundle.py +130 -27
  13. flyte/_constants.py +1 -0
  14. flyte/_context.py +21 -5
  15. flyte/_custom_context.py +73 -0
  16. flyte/_debug/constants.py +37 -0
  17. flyte/_debug/utils.py +17 -0
  18. flyte/_debug/vscode.py +315 -0
  19. flyte/_deploy.py +396 -75
  20. flyte/_deployer.py +109 -0
  21. flyte/_environment.py +94 -11
  22. flyte/_excepthook.py +37 -0
  23. flyte/_group.py +2 -1
  24. flyte/_hash.py +1 -16
  25. flyte/_image.py +544 -231
  26. flyte/_initialize.py +456 -316
  27. flyte/_interface.py +40 -5
  28. flyte/_internal/controllers/__init__.py +22 -8
  29. flyte/_internal/controllers/_local_controller.py +159 -35
  30. flyte/_internal/controllers/_trace.py +18 -10
  31. flyte/_internal/controllers/remote/__init__.py +38 -9
  32. flyte/_internal/controllers/remote/_action.py +82 -12
  33. flyte/_internal/controllers/remote/_client.py +6 -2
  34. flyte/_internal/controllers/remote/_controller.py +290 -64
  35. flyte/_internal/controllers/remote/_core.py +155 -95
  36. flyte/_internal/controllers/remote/_informer.py +40 -20
  37. flyte/_internal/controllers/remote/_service_protocol.py +2 -2
  38. flyte/_internal/imagebuild/__init__.py +2 -10
  39. flyte/_internal/imagebuild/docker_builder.py +391 -84
  40. flyte/_internal/imagebuild/image_builder.py +111 -55
  41. flyte/_internal/imagebuild/remote_builder.py +409 -0
  42. flyte/_internal/imagebuild/utils.py +79 -0
  43. flyte/_internal/resolvers/_app_env_module.py +92 -0
  44. flyte/_internal/resolvers/_task_module.py +5 -38
  45. flyte/_internal/resolvers/app_env.py +26 -0
  46. flyte/_internal/resolvers/common.py +8 -1
  47. flyte/_internal/resolvers/default.py +2 -2
  48. flyte/_internal/runtime/convert.py +319 -36
  49. flyte/_internal/runtime/entrypoints.py +106 -18
  50. flyte/_internal/runtime/io.py +71 -23
  51. flyte/_internal/runtime/resources_serde.py +21 -7
  52. flyte/_internal/runtime/reuse.py +125 -0
  53. flyte/_internal/runtime/rusty.py +196 -0
  54. flyte/_internal/runtime/task_serde.py +239 -66
  55. flyte/_internal/runtime/taskrunner.py +48 -8
  56. flyte/_internal/runtime/trigger_serde.py +162 -0
  57. flyte/_internal/runtime/types_serde.py +7 -16
  58. flyte/_keyring/file.py +115 -0
  59. flyte/_link.py +30 -0
  60. flyte/_logging.py +241 -42
  61. flyte/_map.py +312 -0
  62. flyte/_metrics.py +59 -0
  63. flyte/_module.py +74 -0
  64. flyte/_pod.py +30 -0
  65. flyte/_resources.py +296 -33
  66. flyte/_retry.py +1 -7
  67. flyte/_reusable_environment.py +72 -7
  68. flyte/_run.py +462 -132
  69. flyte/_secret.py +47 -11
  70. flyte/_serve.py +333 -0
  71. flyte/_task.py +245 -56
  72. flyte/_task_environment.py +219 -97
  73. flyte/_task_plugins.py +47 -0
  74. flyte/_tools.py +8 -8
  75. flyte/_trace.py +15 -24
  76. flyte/_trigger.py +1027 -0
  77. flyte/_utils/__init__.py +12 -1
  78. flyte/_utils/asyn.py +3 -1
  79. flyte/_utils/async_cache.py +139 -0
  80. flyte/_utils/coro_management.py +5 -4
  81. flyte/_utils/description_parser.py +19 -0
  82. flyte/_utils/docker_credentials.py +173 -0
  83. flyte/_utils/helpers.py +45 -19
  84. flyte/_utils/module_loader.py +123 -0
  85. flyte/_utils/org_discovery.py +57 -0
  86. flyte/_utils/uv_script_parser.py +8 -1
  87. flyte/_version.py +16 -3
  88. flyte/app/__init__.py +27 -0
  89. flyte/app/_app_environment.py +362 -0
  90. flyte/app/_connector_environment.py +40 -0
  91. flyte/app/_deploy.py +130 -0
  92. flyte/app/_parameter.py +343 -0
  93. flyte/app/_runtime/__init__.py +3 -0
  94. flyte/app/_runtime/app_serde.py +383 -0
  95. flyte/app/_types.py +113 -0
  96. flyte/app/extras/__init__.py +9 -0
  97. flyte/app/extras/_auth_middleware.py +217 -0
  98. flyte/app/extras/_fastapi.py +93 -0
  99. flyte/app/extras/_model_loader/__init__.py +3 -0
  100. flyte/app/extras/_model_loader/config.py +7 -0
  101. flyte/app/extras/_model_loader/loader.py +288 -0
  102. flyte/cli/__init__.py +12 -0
  103. flyte/cli/_abort.py +28 -0
  104. flyte/cli/_build.py +114 -0
  105. flyte/cli/_common.py +493 -0
  106. flyte/cli/_create.py +371 -0
  107. flyte/cli/_delete.py +45 -0
  108. flyte/cli/_deploy.py +401 -0
  109. flyte/cli/_gen.py +316 -0
  110. flyte/cli/_get.py +446 -0
  111. flyte/cli/_option.py +33 -0
  112. flyte/{_cli → cli}/_params.py +57 -17
  113. flyte/cli/_plugins.py +209 -0
  114. flyte/cli/_prefetch.py +292 -0
  115. flyte/cli/_run.py +690 -0
  116. flyte/cli/_serve.py +338 -0
  117. flyte/cli/_update.py +86 -0
  118. flyte/cli/_user.py +20 -0
  119. flyte/cli/main.py +246 -0
  120. flyte/config/__init__.py +2 -167
  121. flyte/config/_config.py +215 -163
  122. flyte/config/_internal.py +10 -1
  123. flyte/config/_reader.py +225 -0
  124. flyte/connectors/__init__.py +11 -0
  125. flyte/connectors/_connector.py +330 -0
  126. flyte/connectors/_server.py +194 -0
  127. flyte/connectors/utils.py +159 -0
  128. flyte/errors.py +134 -2
  129. flyte/extend.py +24 -0
  130. flyte/extras/_container.py +69 -56
  131. flyte/git/__init__.py +3 -0
  132. flyte/git/_config.py +279 -0
  133. flyte/io/__init__.py +8 -1
  134. flyte/io/{structured_dataset → _dataframe}/__init__.py +32 -30
  135. flyte/io/{structured_dataset → _dataframe}/basic_dfs.py +75 -68
  136. flyte/io/{structured_dataset/structured_dataset.py → _dataframe/dataframe.py} +207 -242
  137. flyte/io/_dir.py +575 -113
  138. flyte/io/_file.py +587 -141
  139. flyte/io/_hashing_io.py +342 -0
  140. flyte/io/extend.py +7 -0
  141. flyte/models.py +635 -0
  142. flyte/prefetch/__init__.py +22 -0
  143. flyte/prefetch/_hf_model.py +563 -0
  144. flyte/remote/__init__.py +14 -3
  145. flyte/remote/_action.py +879 -0
  146. flyte/remote/_app.py +346 -0
  147. flyte/remote/_auth_metadata.py +42 -0
  148. flyte/remote/_client/_protocols.py +62 -4
  149. flyte/remote/_client/auth/_auth_utils.py +19 -0
  150. flyte/remote/_client/auth/_authenticators/base.py +8 -2
  151. flyte/remote/_client/auth/_authenticators/device_code.py +4 -5
  152. flyte/remote/_client/auth/_authenticators/factory.py +4 -0
  153. flyte/remote/_client/auth/_authenticators/passthrough.py +79 -0
  154. flyte/remote/_client/auth/_authenticators/pkce.py +17 -18
  155. flyte/remote/_client/auth/_channel.py +47 -18
  156. flyte/remote/_client/auth/_client_config.py +5 -3
  157. flyte/remote/_client/auth/_keyring.py +15 -2
  158. flyte/remote/_client/auth/_token_client.py +3 -3
  159. flyte/remote/_client/controlplane.py +206 -18
  160. flyte/remote/_common.py +66 -0
  161. flyte/remote/_data.py +107 -22
  162. flyte/remote/_logs.py +116 -33
  163. flyte/remote/_project.py +21 -19
  164. flyte/remote/_run.py +164 -631
  165. flyte/remote/_secret.py +72 -29
  166. flyte/remote/_task.py +387 -46
  167. flyte/remote/_trigger.py +368 -0
  168. flyte/remote/_user.py +43 -0
  169. flyte/report/_report.py +10 -6
  170. flyte/storage/__init__.py +13 -1
  171. flyte/storage/_config.py +237 -0
  172. flyte/storage/_parallel_reader.py +289 -0
  173. flyte/storage/_storage.py +268 -59
  174. flyte/syncify/__init__.py +56 -0
  175. flyte/syncify/_api.py +414 -0
  176. flyte/types/__init__.py +39 -0
  177. flyte/types/_interface.py +22 -7
  178. flyte/{io/pickle/transformer.py → types/_pickle.py} +37 -9
  179. flyte/types/_string_literals.py +8 -9
  180. flyte/types/_type_engine.py +226 -126
  181. flyte/types/_utils.py +1 -1
  182. flyte-2.0.0b46.data/scripts/debug.py +38 -0
  183. flyte-2.0.0b46.data/scripts/runtime.py +194 -0
  184. flyte-2.0.0b46.dist-info/METADATA +352 -0
  185. flyte-2.0.0b46.dist-info/RECORD +221 -0
  186. flyte-2.0.0b46.dist-info/entry_points.txt +8 -0
  187. flyte-2.0.0b46.dist-info/licenses/LICENSE +201 -0
  188. flyte/_api_commons.py +0 -3
  189. flyte/_cli/_common.py +0 -299
  190. flyte/_cli/_create.py +0 -42
  191. flyte/_cli/_delete.py +0 -23
  192. flyte/_cli/_deploy.py +0 -140
  193. flyte/_cli/_get.py +0 -235
  194. flyte/_cli/_run.py +0 -174
  195. flyte/_cli/main.py +0 -98
  196. flyte/_datastructures.py +0 -342
  197. flyte/_internal/controllers/pbhash.py +0 -39
  198. flyte/_protos/common/authorization_pb2.py +0 -66
  199. flyte/_protos/common/authorization_pb2.pyi +0 -108
  200. flyte/_protos/common/authorization_pb2_grpc.py +0 -4
  201. flyte/_protos/common/identifier_pb2.py +0 -71
  202. flyte/_protos/common/identifier_pb2.pyi +0 -82
  203. flyte/_protos/common/identifier_pb2_grpc.py +0 -4
  204. flyte/_protos/common/identity_pb2.py +0 -48
  205. flyte/_protos/common/identity_pb2.pyi +0 -72
  206. flyte/_protos/common/identity_pb2_grpc.py +0 -4
  207. flyte/_protos/common/list_pb2.py +0 -36
  208. flyte/_protos/common/list_pb2.pyi +0 -69
  209. flyte/_protos/common/list_pb2_grpc.py +0 -4
  210. flyte/_protos/common/policy_pb2.py +0 -37
  211. flyte/_protos/common/policy_pb2.pyi +0 -27
  212. flyte/_protos/common/policy_pb2_grpc.py +0 -4
  213. flyte/_protos/common/role_pb2.py +0 -37
  214. flyte/_protos/common/role_pb2.pyi +0 -53
  215. flyte/_protos/common/role_pb2_grpc.py +0 -4
  216. flyte/_protos/common/runtime_version_pb2.py +0 -28
  217. flyte/_protos/common/runtime_version_pb2.pyi +0 -24
  218. flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
  219. flyte/_protos/logs/dataplane/payload_pb2.py +0 -96
  220. flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -168
  221. flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
  222. flyte/_protos/secret/definition_pb2.py +0 -49
  223. flyte/_protos/secret/definition_pb2.pyi +0 -93
  224. flyte/_protos/secret/definition_pb2_grpc.py +0 -4
  225. flyte/_protos/secret/payload_pb2.py +0 -62
  226. flyte/_protos/secret/payload_pb2.pyi +0 -94
  227. flyte/_protos/secret/payload_pb2_grpc.py +0 -4
  228. flyte/_protos/secret/secret_pb2.py +0 -38
  229. flyte/_protos/secret/secret_pb2.pyi +0 -6
  230. flyte/_protos/secret/secret_pb2_grpc.py +0 -198
  231. flyte/_protos/secret/secret_pb2_grpc_grpc.py +0 -198
  232. flyte/_protos/validate/validate/validate_pb2.py +0 -76
  233. flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
  234. flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
  235. flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
  236. flyte/_protos/workflow/queue_service_pb2.py +0 -106
  237. flyte/_protos/workflow/queue_service_pb2.pyi +0 -141
  238. flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
  239. flyte/_protos/workflow/run_definition_pb2.py +0 -128
  240. flyte/_protos/workflow/run_definition_pb2.pyi +0 -310
  241. flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
  242. flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
  243. flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
  244. flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
  245. flyte/_protos/workflow/run_service_pb2.py +0 -133
  246. flyte/_protos/workflow/run_service_pb2.pyi +0 -175
  247. flyte/_protos/workflow/run_service_pb2_grpc.py +0 -412
  248. flyte/_protos/workflow/state_service_pb2.py +0 -58
  249. flyte/_protos/workflow/state_service_pb2.pyi +0 -71
  250. flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
  251. flyte/_protos/workflow/task_definition_pb2.py +0 -72
  252. flyte/_protos/workflow/task_definition_pb2.pyi +0 -65
  253. flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
  254. flyte/_protos/workflow/task_service_pb2.py +0 -44
  255. flyte/_protos/workflow/task_service_pb2.pyi +0 -31
  256. flyte/_protos/workflow/task_service_pb2_grpc.py +0 -104
  257. flyte/io/_dataframe.py +0 -0
  258. flyte/io/pickle/__init__.py +0 -0
  259. flyte/remote/_console.py +0 -18
  260. flyte-0.2.0b1.dist-info/METADATA +0 -179
  261. flyte-0.2.0b1.dist-info/RECORD +0 -204
  262. flyte-0.2.0b1.dist-info/entry_points.txt +0 -3
  263. /flyte/{_cli → _debug}/__init__.py +0 -0
  264. /flyte/{_protos → _keyring}/__init__.py +0 -0
  265. {flyte-0.2.0b1.dist-info → flyte-2.0.0b46.dist-info}/WHEEL +0 -0
  266. {flyte-0.2.0b1.dist-info → flyte-2.0.0b46.dist-info}/top_level.txt +0 -0
flyte/remote/_app.py ADDED
@@ -0,0 +1,346 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import AsyncIterator, Literal, Mapping, Tuple, cast
4
+
5
+ import grpc
6
+ import rich.repr
7
+ from flyteidl2.app import app_definition_pb2, app_payload_pb2
8
+ from flyteidl2.common import identifier_pb2, list_pb2
9
+
10
+ from flyte._initialize import ensure_client, get_client, get_init_config
11
+ from flyte._logging import logger
12
+ from flyte.syncify import syncify
13
+
14
+ from ._common import ToJSONMixin, filtering, sorting
15
+
16
+ WaitFor = Literal["activated", "deactivated"]
17
+
18
+
19
+ def _is_active(state: app_definition_pb2.Status.DeploymentStatus) -> bool:
20
+ return state in [
21
+ app_definition_pb2.Status.DeploymentStatus.DEPLOYMENT_STATUS_ACTIVE,
22
+ app_definition_pb2.Status.DeploymentStatus.DEPLOYMENT_STATUS_STARTED,
23
+ ]
24
+
25
+
26
+ def _is_deactivated(state: app_definition_pb2.Status.DeploymentStatus) -> bool:
27
+ return state in [
28
+ app_definition_pb2.Status.DeploymentStatus.DEPLOYMENT_STATUS_UNASSIGNED,
29
+ app_definition_pb2.Status.DeploymentStatus.DEPLOYMENT_STATUS_STOPPED,
30
+ ]
31
+
32
+
33
+ class App(ToJSONMixin):
34
+ pb2: app_definition_pb2.App
35
+
36
+ def __init__(self, pb2: app_definition_pb2.App):
37
+ self.pb2 = pb2
38
+
39
+ @property
40
+ def name(self) -> str:
41
+ """
42
+ Get the name of the app.
43
+ """
44
+ return self.pb2.metadata.id.name
45
+
46
+ @property
47
+ def revision(self) -> int:
48
+ """
49
+ Get the revision number of the app.
50
+ """
51
+ return self.pb2.metadata.revision
52
+
53
+ @property
54
+ def endpoint(self) -> str:
55
+ """
56
+ Get the public endpoint URL of the app.
57
+ """
58
+ return self.pb2.status.ingress.public_url
59
+
60
+ @property
61
+ def deployment_status(self) -> app_definition_pb2.Status.DeploymentStatus:
62
+ """
63
+ Get the deployment status of the app
64
+ Returns:
65
+
66
+ """
67
+ if len(self.pb2.status.conditions) > 0:
68
+ return self.pb2.status.conditions[-1].deployment_status
69
+ else:
70
+ return app_definition_pb2.Status.DeploymentStatus.DEPLOYMENT_STATUS_UNSPECIFIED
71
+
72
+ @property
73
+ def desired_state(self) -> app_definition_pb2.Spec.DesiredState:
74
+ """
75
+ Get the desired state of the app.
76
+ """
77
+ return self.pb2.spec.desired_state
78
+
79
+ def is_active(self) -> bool:
80
+ """
81
+ Check if the app is currently active or started.
82
+ """
83
+ return _is_active(self.deployment_status)
84
+
85
+ def is_deactivated(self) -> bool:
86
+ """
87
+ Check if the app is currently deactivated or stopped.
88
+ """
89
+ return _is_deactivated(self.deployment_status)
90
+
91
+ @property
92
+ def url(self) -> str:
93
+ """
94
+ Get the console URL for viewing the app.
95
+ """
96
+ client = get_client()
97
+ return client.console.app_url(
98
+ project=self.pb2.metadata.id.project,
99
+ domain=self.pb2.metadata.id.domain,
100
+ app_name=self.name,
101
+ )
102
+
103
+ @syncify
104
+ async def watch(self, wait_for: WaitFor = "activated") -> App:
105
+ """
106
+ Watch for the app to reach activated or deactivated state.
107
+ :param wait_for: ["activated", "deactivated"]
108
+
109
+ Returns: The app in the desired state.
110
+ Raises: RuntimeError if the app did not reach desired state and failed!
111
+ """
112
+
113
+ if wait_for == "activated" and self.is_active():
114
+ return self
115
+ elif wait_for == "deactivated" and self.is_deactivated():
116
+ return self
117
+
118
+ call = cast(
119
+ AsyncIterator[app_payload_pb2.WatchResponse],
120
+ get_client().app_service.Watch(
121
+ request=app_payload_pb2.WatchRequest(
122
+ app_id=self.pb2.metadata.id,
123
+ )
124
+ ),
125
+ )
126
+ async for resp in call:
127
+ if resp.update_event:
128
+ updated_app = resp.update_event.updated_app
129
+ current_status = updated_app.status.conditions[-1].deployment_status
130
+ if current_status == app_definition_pb2.Status.DeploymentStatus.DEPLOYMENT_STATUS_FAILED:
131
+ raise RuntimeError(f"App deployment for app {self.name} has failed!")
132
+ if wait_for == "activated":
133
+ if _is_active(current_status):
134
+ return App(updated_app)
135
+ elif _is_deactivated(current_status):
136
+ raise RuntimeError(f"App deployment for app {self.name} has failed!")
137
+ elif wait_for == "deactivated":
138
+ if _is_deactivated(current_status):
139
+ return App(updated_app)
140
+ raise RuntimeError(f"App deployment for app {self.name} stalled!")
141
+
142
+ async def _update(
143
+ self, desired_state: app_definition_pb2.Spec.DesiredState, reason: str, wait_for: WaitFor | None = None
144
+ ) -> App:
145
+ """
146
+ Internal method to update the app's desired state.
147
+
148
+ :param desired_state: The new desired state for the app.
149
+ :param reason: Reason for the update.
150
+ :param wait_for: Optional state to wait for after update.
151
+ :return: The updated app.
152
+ """
153
+ new_pb2 = app_definition_pb2.App()
154
+ new_pb2.CopyFrom(self.pb2)
155
+ new_pb2.spec.desired_state = desired_state
156
+ updated_app = await App.update.aio(new_pb2, reason=reason)
157
+ if wait_for:
158
+ await updated_app.watch.aio(wait_for)
159
+ return updated_app
160
+
161
+ @syncify
162
+ async def activate(self, wait: bool = False) -> App:
163
+ """
164
+ Start the app
165
+ :param wait: Wait for the app to reach started state
166
+
167
+ """
168
+ if self.is_active():
169
+ return self
170
+ return await self._update(
171
+ app_definition_pb2.Spec.DESIRED_STATE_STARTED,
172
+ "User requested to activate app from flyte-sdk",
173
+ "activated" if wait else None,
174
+ )
175
+
176
+ @syncify
177
+ async def deactivate(self, wait: bool = False):
178
+ """
179
+ Stop the app
180
+ :param wait: Wait for the app to reach the deactivated state
181
+ """
182
+ if self.is_deactivated():
183
+ return
184
+ return await self._update(
185
+ app_definition_pb2.Spec.DESIRED_STATE_STOPPED,
186
+ "User requested to deactivate app from flyte-sdk",
187
+ "deactivated" if wait else None,
188
+ )
189
+
190
+ def __rich_repr__(self) -> rich.repr.Result:
191
+ """
192
+ Rich representation of the App object for pretty printing.
193
+ """
194
+ yield "name", self.name
195
+ yield "revision", self.revision
196
+ yield "endpoint", self.endpoint
197
+ yield (
198
+ "deployment_status",
199
+ app_definition_pb2.Status.DeploymentStatus.Name(self.deployment_status)[len("DEPLOYMENT_STATUS_") :],
200
+ )
201
+ yield "desired_state", app_definition_pb2.Spec.DesiredState.Name(self.desired_state)[len("DESIRED_STATE_") :]
202
+
203
+ @syncify
204
+ @classmethod
205
+ async def update(cls, updated_app_proto: app_definition_pb2.App, reason: str) -> App:
206
+ ensure_client()
207
+ resp = await get_client().app_service.Update(
208
+ request=app_payload_pb2.UpdateRequest(
209
+ app=updated_app_proto,
210
+ reason=reason,
211
+ )
212
+ )
213
+ return App(pb2=resp.app)
214
+
215
+ @syncify
216
+ @classmethod
217
+ async def replace(
218
+ cls,
219
+ name: str,
220
+ updated_app_spec: app_definition_pb2.Spec,
221
+ reason: str,
222
+ labels: Mapping[str, str] | None = None,
223
+ project: str | None = None,
224
+ domain: str | None = None,
225
+ ) -> App:
226
+ """
227
+ Replace an existing app's that matches the given name, with a new spec and optionally labels.
228
+ :param name: Name of the new app
229
+ :param updated_app_spec: Updated app spec
230
+ :param labels: Optional labels for the new app
231
+ :param project: Optional project for the new app
232
+ :param domain: Optional domain for the new app
233
+ :return: A new app
234
+ """
235
+ ensure_client()
236
+ app = await cls.get.aio(name=name, project=project, domain=domain)
237
+ updated_app_spec.creator.CopyFrom(app.pb2.spec.creator)
238
+ new_app = app_definition_pb2.App(
239
+ metadata=app_definition_pb2.Meta(
240
+ id=app.pb2.metadata.id,
241
+ revision=app.revision,
242
+ labels=labels if labels else app.pb2.metadata.labels,
243
+ ),
244
+ spec=updated_app_spec,
245
+ status=app.pb2.status,
246
+ )
247
+ return await cls.update.aio(new_app, reason=reason)
248
+
249
+ @syncify
250
+ @classmethod
251
+ async def get(
252
+ cls,
253
+ name: str,
254
+ project: str | None = None,
255
+ domain: str | None = None,
256
+ ) -> App:
257
+ """
258
+ Get an app by name.
259
+
260
+ :param name: The name of the app.
261
+ :param project: The project of the app.
262
+ :param domain: The domain of the app.
263
+ :return: The app remote object.
264
+ """
265
+ ensure_client()
266
+ cfg = get_init_config()
267
+ resp = await get_client().app_service.Get(
268
+ request=app_payload_pb2.GetRequest(
269
+ app_id=app_definition_pb2.Identifier(
270
+ org=cfg.org,
271
+ project=project or cfg.project,
272
+ domain=domain or cfg.domain,
273
+ name=name,
274
+ ),
275
+ )
276
+ )
277
+ return cls(pb2=resp.app)
278
+
279
+ @syncify
280
+ @classmethod
281
+ async def listall(
282
+ cls,
283
+ created_by_subject: str | None = None,
284
+ sort_by: Tuple[str, Literal["asc", "desc"]] | None = None,
285
+ limit: int = 100,
286
+ ) -> AsyncIterator[App]:
287
+ ensure_client()
288
+ cfg = get_init_config()
289
+ i = 0
290
+ token = None
291
+ sort_pb2 = sorting(sort_by)
292
+ filters = filtering(created_by_subject)
293
+ project = None
294
+ if cfg.project:
295
+ project = identifier_pb2.ProjectIdentifier(
296
+ organization=cfg.org,
297
+ name=cfg.project,
298
+ domain=cfg.domain,
299
+ )
300
+ while True:
301
+ req = app_payload_pb2.ListRequest(
302
+ request=list_pb2.ListRequest(
303
+ limit=min(100, limit),
304
+ token=token,
305
+ sort_by=sort_pb2,
306
+ filters=filters,
307
+ ),
308
+ org=cfg.org,
309
+ project=project,
310
+ )
311
+ resp = await get_client().app_service.List(
312
+ request=req,
313
+ )
314
+ token = resp.token
315
+ for a in resp.apps:
316
+ i += 1
317
+ if i > limit:
318
+ return
319
+ yield cls(a)
320
+ if not token:
321
+ break
322
+
323
+ @syncify
324
+ @classmethod
325
+ async def create(cls, app: app_definition_pb2.App) -> App:
326
+ ensure_client()
327
+ try:
328
+ resp = await get_client().app_service.Create(app_payload_pb2.CreateRequest(app=app))
329
+ created_app = cls(resp.app)
330
+ logger.info(f"Deployed app {created_app.name} with revision {created_app.revision}")
331
+ return created_app
332
+ except grpc.aio.AioRpcError as e:
333
+ if e.code() in [grpc.StatusCode.ABORTED, grpc.StatusCode.ALREADY_EXISTS]:
334
+ if e.code() == grpc.StatusCode.ALREADY_EXISTS:
335
+ logger.warning(f"App {app.metadata.id.name} already exists, updating...")
336
+ elif e.code() == grpc.StatusCode.ABORTED:
337
+ logger.warning(f"Create App {app.metadata.id.name} was aborted on server, check state!")
338
+ return await App.replace.aio(
339
+ name=app.metadata.id.name,
340
+ labels=app.metadata.labels,
341
+ updated_app_spec=app.spec,
342
+ reason="User requested serve from sdk",
343
+ project=app.metadata.id.project,
344
+ domain=app.metadata.id.domain,
345
+ )
346
+ raise
@@ -0,0 +1,42 @@
1
+ from contextlib import contextmanager
2
+ from typing import Tuple
3
+
4
+
5
+ @contextmanager
6
+ def auth_metadata(*kv: Tuple[str, str]):
7
+ """
8
+ This context manager allows you to pass contextualized auth metadata downstream to the Flyte authentication system.
9
+
10
+ This is only useful if flyte.init_passthrough() has been called.
11
+
12
+ Example:
13
+ ```python
14
+
15
+ flyte.init_passthrough("my-endpoint")
16
+
17
+ ...
18
+
19
+ with auth_metadata((key1, value1), (key2, value2)):
20
+ ...
21
+ ```
22
+
23
+ Args:
24
+ *kv: Tuple of auth metadata key/value pairs.
25
+ """
26
+ if not kv:
27
+ raise ValueError("No auth metadata provided.")
28
+ from flyte._context import internal_ctx
29
+
30
+ ctx = internal_ctx()
31
+ with ctx.new_metadata(kv):
32
+ yield
33
+
34
+
35
+ def get_auth_metadata() -> Tuple[Tuple[str, str], ...]:
36
+ """
37
+ Returns auth metadata as tuple.
38
+ """
39
+ from flyte._context import internal_ctx
40
+
41
+ ctx = internal_ctx()
42
+ return ctx.data.metadata or ()
@@ -1,13 +1,15 @@
1
1
  from typing import AsyncIterator, Protocol
2
2
 
3
3
  from flyteidl.admin import project_attributes_pb2, project_pb2, version_pb2
4
- from flyteidl.service import dataproxy_pb2
4
+ from flyteidl.service import dataproxy_pb2, identity_pb2
5
+ from flyteidl2.app import app_payload_pb2
6
+ from flyteidl2.secret import payload_pb2
7
+ from flyteidl2.task import task_service_pb2
8
+ from flyteidl2.trigger import trigger_service_pb2
9
+ from flyteidl2.workflow import run_logs_service_pb2, run_service_pb2
5
10
  from grpc.aio import UnaryStreamCall
6
11
  from grpc.aio._typing import RequestType
7
12
 
8
- from flyte._protos.secret import payload_pb2
9
- from flyte._protos.workflow import run_logs_service_pb2, run_service_pb2, task_service_pb2
10
-
11
13
 
12
14
  class MetadataServiceProtocol(Protocol):
13
15
  async def GetVersion(self, request: version_pb2.GetVersionRequest) -> version_pb2.GetVersionResponse: ...
@@ -58,6 +60,28 @@ class TaskService(Protocol):
58
60
  self, request: task_service_pb2.GetTaskDetailsRequest
59
61
  ) -> task_service_pb2.GetTaskDetailsResponse: ...
60
62
 
63
+ async def ListTasks(self, request: task_service_pb2.ListTasksRequest) -> task_service_pb2.ListTasksResponse: ...
64
+
65
+
66
+ class AppService(Protocol):
67
+ async def Create(self, request: app_payload_pb2.CreateRequest) -> app_payload_pb2.CreateResponse: ...
68
+
69
+ async def Get(self, request: app_payload_pb2.GetRequest) -> app_payload_pb2.GetResponse: ...
70
+
71
+ async def Update(self, request: app_payload_pb2.UpdateRequest) -> app_payload_pb2.UpdateResponse: ...
72
+
73
+ async def UpdateStatus(
74
+ self, request: app_payload_pb2.UpdateStatusRequest
75
+ ) -> app_payload_pb2.UpdateStatusResponse: ...
76
+
77
+ async def Delete(self, request: app_payload_pb2.DeleteRequest) -> app_payload_pb2.DeleteResponse: ...
78
+
79
+ async def List(self, request: app_payload_pb2.ListRequest) -> app_payload_pb2.ListResponse: ...
80
+
81
+ async def Watch(self, request: app_payload_pb2.WatchRequest) -> app_payload_pb2.WatchResponse: ...
82
+
83
+ async def Lease(self, request: app_payload_pb2.LeaseRequest) -> app_payload_pb2.LeaseResponse: ...
84
+
61
85
 
62
86
  class RunService(Protocol):
63
87
  async def CreateRun(self, request: run_service_pb2.CreateRunRequest) -> run_service_pb2.CreateRunResponse: ...
@@ -129,3 +153,37 @@ class SecretService(Protocol):
129
153
  async def ListSecrets(self, request: payload_pb2.ListSecretsRequest) -> payload_pb2.ListSecretsResponse: ...
130
154
 
131
155
  async def DeleteSecret(self, request: payload_pb2.DeleteSecretRequest) -> payload_pb2.DeleteSecretResponse: ...
156
+
157
+
158
+ class IdentityService(Protocol):
159
+ async def UserInfo(self, request: identity_pb2.UserInfoRequest) -> identity_pb2.UserInfoResponse: ...
160
+
161
+
162
+ class TriggerService(Protocol):
163
+ async def DeployTrigger(
164
+ self, request: trigger_service_pb2.DeployTriggerRequest
165
+ ) -> trigger_service_pb2.DeployTriggerResponse: ...
166
+
167
+ async def GetTriggerDetails(
168
+ self, request: trigger_service_pb2.GetTriggerDetailsRequest
169
+ ) -> trigger_service_pb2.GetTriggerDetailsResponse: ...
170
+
171
+ async def GetTriggerRevisionDetails(
172
+ self, request: trigger_service_pb2.GetTriggerRevisionDetailsRequest
173
+ ) -> trigger_service_pb2.GetTriggerRevisionDetailsResponse: ...
174
+
175
+ async def ListTriggers(
176
+ self, request: trigger_service_pb2.ListTriggersRequest
177
+ ) -> trigger_service_pb2.ListTriggersResponse: ...
178
+
179
+ async def GetTriggerRevisionHistory(
180
+ self, request: trigger_service_pb2.GetTriggerRevisionHistoryRequest
181
+ ) -> trigger_service_pb2.GetTriggerRevisionHistoryResponse: ...
182
+
183
+ async def UpdateTriggers(
184
+ self, request: trigger_service_pb2.UpdateTriggersRequest
185
+ ) -> trigger_service_pb2.UpdateTriggersResponse: ...
186
+
187
+ async def DeleteTriggers(
188
+ self, request: trigger_service_pb2.DeleteTriggersRequest
189
+ ) -> trigger_service_pb2.DeleteTriggersResponse: ...
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ from typing import Literal
5
+
6
+
7
+ def decode_api_key(encoded_str: str) -> tuple[str, str, str, str | Literal["None"]]:
8
+ """Decode encoded base64 string into app credentials. endpoint, client_id, client_secret, org"""
9
+ # Split with maxsplit=3 to handle endpoints with colons (e.g., dns:///endpoint.com)
10
+ parts = base64.b64decode(encoded_str.encode("utf-8")).decode("utf-8").split(":", 3)
11
+ if len(parts) != 4:
12
+ raise ValueError(f"Invalid API key format. Expected 4 parts separated by ':', got {len(parts)}")
13
+
14
+ endpoint, client_id, client_secret, org = parts
15
+ # For consistency, let's make sure org is always a non-empty string
16
+ if not org:
17
+ org = "None"
18
+
19
+ return endpoint, client_id, client_secret, org
@@ -34,6 +34,7 @@ class Authenticator(object):
34
34
  http_proxy_url: typing.Optional[str] = None,
35
35
  verify: bool = True,
36
36
  ca_cert_path: typing.Optional[str] = None,
37
+ default_header_key: str = "authorization",
37
38
  **kwargs,
38
39
  ):
39
40
  """
@@ -80,6 +81,7 @@ class Authenticator(object):
80
81
  self._http_session = http_session or get_async_session(**kwargs)
81
82
  # Id for tracking credential refresh state
82
83
  self._creds_id = self._creds.id if self._creds else None
84
+ self._default_header_key = default_header_key
83
85
 
84
86
  async def _resolve_config(self) -> ClientConfig:
85
87
  """
@@ -131,10 +133,14 @@ class Authenticator(object):
131
133
  """
132
134
  creds = self.get_credentials()
133
135
  if creds:
134
- cfg = await self._resolve_config()
136
+ header_key = self._default_header_key
137
+ if self._resolved_config is not None:
138
+ # We only resolve the config during authentication flow, to avoid unnecessary network calls
139
+ # and usually the header_key is consistent.
140
+ header_key = self._resolved_config.header_key
135
141
  return GrpcAuthMetadata(
136
142
  creds_id=creds.id,
137
- pairs=Metadata((cfg.header_key, f"Bearer {creds.access_token}")),
143
+ pairs=Metadata((header_key, f"Bearer {creds.access_token}")),
138
144
  )
139
145
  return None
140
146
 
@@ -1,4 +1,4 @@
1
- import click
1
+ from rich import print as rich_print
2
2
 
3
3
  from flyte._logging import logger
4
4
  from flyte.remote._client.auth import _token_client as token_client
@@ -81,7 +81,7 @@ class DeviceCodeAuthenticator(Authenticator):
81
81
  for_endpoint=self._endpoint,
82
82
  )
83
83
  except (AuthenticationError, AuthenticationPending):
84
- logger.warning("Failed to refresh token. Kicking off a full authorization flow.")
84
+ logger.warning("Logging in...")
85
85
 
86
86
  """Fall back to device flow"""
87
87
  resp = await token_client.get_device_code(
@@ -94,10 +94,9 @@ class DeviceCodeAuthenticator(Authenticator):
94
94
 
95
95
  full_uri = f"{resp.verification_uri}?user_code={resp.user_code}"
96
96
  text = (
97
- f"To Authenticate, navigate in a browser to the following URL: "
98
- f"{click.style(full_uri, fg='blue', underline=True)}"
97
+ f"To Authenticate, navigate in a browser to the following URL: [blue link={full_uri}]{full_uri}[/blue link]"
99
98
  )
100
- click.secho(text)
99
+ rich_print(text)
101
100
  try:
102
101
  token, refresh_token, expires_in = await token_client.poll_token_endpoint(
103
102
  resp,
@@ -169,6 +169,10 @@ def get_async_authenticator(
169
169
  from flyte.remote._client.auth._authenticators.device_code import DeviceCodeAuthenticator
170
170
 
171
171
  return DeviceCodeAuthenticator(endpoint=endpoint, cfg_store=cfg_store, verify=verify, **kwargs)
172
+ case "Passthrough":
173
+ from flyte.remote._client.auth._authenticators.passthrough import PassthroughAuthenticator
174
+
175
+ return PassthroughAuthenticator(endpoint=endpoint, **kwargs)
172
176
  case _:
173
177
  raise ValueError(
174
178
  f"Invalid auth mode [{auth_type}] specified. Please update the creds config to use a valid value"
@@ -0,0 +1,79 @@
1
+ import typing
2
+
3
+ from grpc.aio import Metadata
4
+
5
+ from flyte.remote._client.auth._authenticators.base import Authenticator, GrpcAuthMetadata
6
+ from flyte.remote._client.auth._keyring import Credentials
7
+
8
+
9
+ class PassthroughAuthenticator(Authenticator):
10
+ """
11
+ Passthrough authenticator that extracts headers from the context and passes them
12
+ to the gRPC calls without performing any authentication flow.
13
+
14
+ This authenticator is used when you want to pass custom authentication metadata
15
+ using the flyte.remote.auth_metadata() context manager.
16
+ """
17
+
18
+ def __init__(self, endpoint: str, **kwargs):
19
+ """
20
+ Initialize the passthrough authenticator.
21
+
22
+ We knowingly skip calling super, as that initializes a bunch of things that are not needed.!
23
+
24
+ :param endpoint: The endpoint URL
25
+ :param kwargs: Additional arguments (ignored for passthrough auth)
26
+ """
27
+ # Don't call parent __init__ to avoid unnecessary setup for passthrough auth
28
+ self._endpoint = endpoint
29
+ # We don't need credentials, config store, or HTTP session for passthrough
30
+ # We will create dummy creds
31
+ self._creds = Credentials(
32
+ access_token="passthrough",
33
+ for_endpoint=self._endpoint,
34
+ )
35
+ self._creds_id: str = "passthrough"
36
+
37
+ def refresh_credentials(self, creds_id: str | None = None):
38
+ return
39
+
40
+ def get_credentials(self) -> typing.Optional[Credentials]:
41
+ """
42
+ Passthrough authenticator doesn't use traditional credentials.
43
+ Returns a dummy credential to signal that metadata is available.
44
+ """
45
+ # Return a dummy credential so the interceptor knows to call get_grpc_call_auth_metadata
46
+ return self._creds
47
+
48
+ async def get_grpc_call_auth_metadata(self) -> typing.Optional[GrpcAuthMetadata]:
49
+ """
50
+ Fetch the authentication metadata from the context.
51
+
52
+ :return: GrpcAuthMetadata with the metadata from the context, or None if no metadata is available
53
+ """
54
+ # Lazy import to avoid circular dependencies
55
+ from flyte.remote._auth_metadata import get_auth_metadata
56
+
57
+ # Get metadata from context
58
+ metadata_tuples = get_auth_metadata()
59
+
60
+ if not metadata_tuples:
61
+ return None
62
+
63
+ return GrpcAuthMetadata(
64
+ creds_id=self._creds_id,
65
+ pairs=Metadata(*metadata_tuples),
66
+ )
67
+
68
+ async def _do_refresh_credentials(self) -> Credentials:
69
+ """
70
+ Passthrough authenticator doesn't need to refresh credentials.
71
+ This method should never be called in practice.
72
+ """
73
+ if self._creds is None:
74
+ # Just to satisfy mypy
75
+ return Credentials(
76
+ access_token="passthrough",
77
+ for_endpoint=self._endpoint,
78
+ )
79
+ return self._creds