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.
- flyte/__init__.py +108 -0
- flyte/_bin/__init__.py +0 -0
- flyte/_bin/debug.py +38 -0
- flyte/_bin/runtime.py +195 -0
- flyte/_bin/serve.py +178 -0
- flyte/_build.py +26 -0
- flyte/_cache/__init__.py +12 -0
- flyte/_cache/cache.py +147 -0
- flyte/_cache/defaults.py +9 -0
- flyte/_cache/local_cache.py +216 -0
- flyte/_cache/policy_function_body.py +42 -0
- flyte/_code_bundle/__init__.py +8 -0
- flyte/_code_bundle/_ignore.py +121 -0
- flyte/_code_bundle/_packaging.py +218 -0
- flyte/_code_bundle/_utils.py +347 -0
- flyte/_code_bundle/bundle.py +266 -0
- flyte/_constants.py +1 -0
- flyte/_context.py +155 -0
- flyte/_custom_context.py +73 -0
- flyte/_debug/__init__.py +0 -0
- flyte/_debug/constants.py +38 -0
- flyte/_debug/utils.py +17 -0
- flyte/_debug/vscode.py +307 -0
- flyte/_deploy.py +408 -0
- flyte/_deployer.py +109 -0
- flyte/_doc.py +29 -0
- flyte/_docstring.py +32 -0
- flyte/_environment.py +122 -0
- flyte/_excepthook.py +37 -0
- flyte/_group.py +32 -0
- flyte/_hash.py +8 -0
- flyte/_image.py +1055 -0
- flyte/_initialize.py +628 -0
- flyte/_interface.py +119 -0
- flyte/_internal/__init__.py +3 -0
- flyte/_internal/controllers/__init__.py +129 -0
- flyte/_internal/controllers/_local_controller.py +239 -0
- flyte/_internal/controllers/_trace.py +48 -0
- flyte/_internal/controllers/remote/__init__.py +58 -0
- flyte/_internal/controllers/remote/_action.py +211 -0
- flyte/_internal/controllers/remote/_client.py +47 -0
- flyte/_internal/controllers/remote/_controller.py +583 -0
- flyte/_internal/controllers/remote/_core.py +465 -0
- flyte/_internal/controllers/remote/_informer.py +381 -0
- flyte/_internal/controllers/remote/_service_protocol.py +50 -0
- flyte/_internal/imagebuild/__init__.py +3 -0
- flyte/_internal/imagebuild/docker_builder.py +706 -0
- flyte/_internal/imagebuild/image_builder.py +277 -0
- flyte/_internal/imagebuild/remote_builder.py +386 -0
- flyte/_internal/imagebuild/utils.py +78 -0
- flyte/_internal/resolvers/__init__.py +0 -0
- flyte/_internal/resolvers/_task_module.py +21 -0
- flyte/_internal/resolvers/common.py +31 -0
- flyte/_internal/resolvers/default.py +28 -0
- flyte/_internal/runtime/__init__.py +0 -0
- flyte/_internal/runtime/convert.py +486 -0
- flyte/_internal/runtime/entrypoints.py +204 -0
- flyte/_internal/runtime/io.py +188 -0
- flyte/_internal/runtime/resources_serde.py +152 -0
- flyte/_internal/runtime/reuse.py +125 -0
- flyte/_internal/runtime/rusty.py +193 -0
- flyte/_internal/runtime/task_serde.py +362 -0
- flyte/_internal/runtime/taskrunner.py +209 -0
- flyte/_internal/runtime/trigger_serde.py +160 -0
- flyte/_internal/runtime/types_serde.py +54 -0
- flyte/_keyring/__init__.py +0 -0
- flyte/_keyring/file.py +115 -0
- flyte/_logging.py +300 -0
- flyte/_map.py +312 -0
- flyte/_module.py +72 -0
- flyte/_pod.py +30 -0
- flyte/_resources.py +473 -0
- flyte/_retry.py +32 -0
- flyte/_reusable_environment.py +102 -0
- flyte/_run.py +724 -0
- flyte/_secret.py +96 -0
- flyte/_task.py +550 -0
- flyte/_task_environment.py +316 -0
- flyte/_task_plugins.py +47 -0
- flyte/_timeout.py +47 -0
- flyte/_tools.py +27 -0
- flyte/_trace.py +119 -0
- flyte/_trigger.py +1000 -0
- flyte/_utils/__init__.py +30 -0
- flyte/_utils/asyn.py +121 -0
- flyte/_utils/async_cache.py +139 -0
- flyte/_utils/coro_management.py +27 -0
- flyte/_utils/docker_credentials.py +173 -0
- flyte/_utils/file_handling.py +72 -0
- flyte/_utils/helpers.py +134 -0
- flyte/_utils/lazy_module.py +54 -0
- flyte/_utils/module_loader.py +104 -0
- flyte/_utils/org_discovery.py +57 -0
- flyte/_utils/uv_script_parser.py +49 -0
- flyte/_version.py +34 -0
- flyte/app/__init__.py +22 -0
- flyte/app/_app_environment.py +157 -0
- flyte/app/_deploy.py +125 -0
- flyte/app/_input.py +160 -0
- flyte/app/_runtime/__init__.py +3 -0
- flyte/app/_runtime/app_serde.py +347 -0
- flyte/app/_types.py +101 -0
- flyte/app/extras/__init__.py +3 -0
- flyte/app/extras/_fastapi.py +151 -0
- flyte/cli/__init__.py +12 -0
- flyte/cli/_abort.py +28 -0
- flyte/cli/_build.py +114 -0
- flyte/cli/_common.py +468 -0
- flyte/cli/_create.py +371 -0
- flyte/cli/_delete.py +45 -0
- flyte/cli/_deploy.py +293 -0
- flyte/cli/_gen.py +176 -0
- flyte/cli/_get.py +370 -0
- flyte/cli/_option.py +33 -0
- flyte/cli/_params.py +554 -0
- flyte/cli/_plugins.py +209 -0
- flyte/cli/_run.py +597 -0
- flyte/cli/_serve.py +64 -0
- flyte/cli/_update.py +37 -0
- flyte/cli/_user.py +17 -0
- flyte/cli/main.py +221 -0
- flyte/config/__init__.py +3 -0
- flyte/config/_config.py +248 -0
- flyte/config/_internal.py +73 -0
- flyte/config/_reader.py +225 -0
- flyte/connectors/__init__.py +11 -0
- flyte/connectors/_connector.py +270 -0
- flyte/connectors/_server.py +197 -0
- flyte/connectors/utils.py +135 -0
- flyte/errors.py +243 -0
- flyte/extend.py +19 -0
- flyte/extras/__init__.py +5 -0
- flyte/extras/_container.py +286 -0
- flyte/git/__init__.py +3 -0
- flyte/git/_config.py +21 -0
- flyte/io/__init__.py +29 -0
- flyte/io/_dataframe/__init__.py +131 -0
- flyte/io/_dataframe/basic_dfs.py +223 -0
- flyte/io/_dataframe/dataframe.py +1026 -0
- flyte/io/_dir.py +910 -0
- flyte/io/_file.py +914 -0
- flyte/io/_hashing_io.py +342 -0
- flyte/models.py +479 -0
- flyte/py.typed +0 -0
- flyte/remote/__init__.py +35 -0
- flyte/remote/_action.py +738 -0
- flyte/remote/_app.py +57 -0
- flyte/remote/_client/__init__.py +0 -0
- flyte/remote/_client/_protocols.py +189 -0
- flyte/remote/_client/auth/__init__.py +12 -0
- flyte/remote/_client/auth/_auth_utils.py +14 -0
- flyte/remote/_client/auth/_authenticators/__init__.py +0 -0
- flyte/remote/_client/auth/_authenticators/base.py +403 -0
- flyte/remote/_client/auth/_authenticators/client_credentials.py +73 -0
- flyte/remote/_client/auth/_authenticators/device_code.py +117 -0
- flyte/remote/_client/auth/_authenticators/external_command.py +79 -0
- flyte/remote/_client/auth/_authenticators/factory.py +200 -0
- flyte/remote/_client/auth/_authenticators/pkce.py +516 -0
- flyte/remote/_client/auth/_channel.py +213 -0
- flyte/remote/_client/auth/_client_config.py +85 -0
- flyte/remote/_client/auth/_default_html.py +32 -0
- flyte/remote/_client/auth/_grpc_utils/__init__.py +0 -0
- flyte/remote/_client/auth/_grpc_utils/auth_interceptor.py +288 -0
- flyte/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +151 -0
- flyte/remote/_client/auth/_keyring.py +152 -0
- flyte/remote/_client/auth/_token_client.py +260 -0
- flyte/remote/_client/auth/errors.py +16 -0
- flyte/remote/_client/controlplane.py +128 -0
- flyte/remote/_common.py +30 -0
- flyte/remote/_console.py +19 -0
- flyte/remote/_data.py +161 -0
- flyte/remote/_logs.py +185 -0
- flyte/remote/_project.py +88 -0
- flyte/remote/_run.py +386 -0
- flyte/remote/_secret.py +142 -0
- flyte/remote/_task.py +527 -0
- flyte/remote/_trigger.py +306 -0
- flyte/remote/_user.py +33 -0
- flyte/report/__init__.py +3 -0
- flyte/report/_report.py +182 -0
- flyte/report/_template.html +124 -0
- flyte/storage/__init__.py +36 -0
- flyte/storage/_config.py +237 -0
- flyte/storage/_parallel_reader.py +274 -0
- flyte/storage/_remote_fs.py +34 -0
- flyte/storage/_storage.py +456 -0
- flyte/storage/_utils.py +5 -0
- flyte/syncify/__init__.py +56 -0
- flyte/syncify/_api.py +375 -0
- flyte/types/__init__.py +52 -0
- flyte/types/_interface.py +40 -0
- flyte/types/_pickle.py +145 -0
- flyte/types/_renderer.py +162 -0
- flyte/types/_string_literals.py +119 -0
- flyte/types/_type_engine.py +2254 -0
- flyte/types/_utils.py +80 -0
- flyte-2.0.0b32.data/scripts/debug.py +38 -0
- flyte-2.0.0b32.data/scripts/runtime.py +195 -0
- flyte-2.0.0b32.dist-info/METADATA +351 -0
- flyte-2.0.0b32.dist-info/RECORD +204 -0
- flyte-2.0.0b32.dist-info/WHEEL +5 -0
- flyte-2.0.0b32.dist-info/entry_points.txt +7 -0
- flyte-2.0.0b32.dist-info/licenses/LICENSE +201 -0
- flyte-2.0.0b32.dist-info/top_level.txt +1 -0
flyte/remote/_trigger.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from typing import AsyncIterator
|
|
6
|
+
|
|
7
|
+
import grpc.aio
|
|
8
|
+
from flyteidl2.common import identifier_pb2, list_pb2
|
|
9
|
+
from flyteidl2.task import common_pb2, task_definition_pb2
|
|
10
|
+
from flyteidl2.trigger import trigger_definition_pb2, trigger_service_pb2
|
|
11
|
+
|
|
12
|
+
import flyte
|
|
13
|
+
from flyte._initialize import ensure_client, get_client, get_init_config
|
|
14
|
+
from flyte._internal.runtime import trigger_serde
|
|
15
|
+
from flyte.syncify import syncify
|
|
16
|
+
|
|
17
|
+
from ._common import ToJSONMixin
|
|
18
|
+
from ._task import Task, TaskDetails
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class TriggerDetails(ToJSONMixin):
|
|
23
|
+
pb2: trigger_definition_pb2.TriggerDetails
|
|
24
|
+
|
|
25
|
+
@syncify
|
|
26
|
+
@classmethod
|
|
27
|
+
async def get(cls, *, name: str, task_name: str) -> TriggerDetails:
|
|
28
|
+
"""
|
|
29
|
+
Retrieve detailed information about a specific trigger by its name.
|
|
30
|
+
"""
|
|
31
|
+
ensure_client()
|
|
32
|
+
cfg = get_init_config()
|
|
33
|
+
resp = await get_client().trigger_service.GetTriggerDetails(
|
|
34
|
+
request=trigger_service_pb2.GetTriggerDetailsRequest(
|
|
35
|
+
name=identifier_pb2.TriggerName(
|
|
36
|
+
task_name=task_name,
|
|
37
|
+
name=name,
|
|
38
|
+
org=cfg.org,
|
|
39
|
+
project=cfg.project,
|
|
40
|
+
domain=cfg.domain,
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
return cls(pb2=resp.trigger)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def name(self) -> str:
|
|
48
|
+
return self.id.name.name
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def id(self) -> identifier_pb2.TriggerIdentifier:
|
|
52
|
+
return self.pb2.id
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def task_name(self) -> str:
|
|
56
|
+
return self.pb2.id.name.task_name
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def automation_spec(self) -> common_pb2.TriggerAutomationSpec:
|
|
60
|
+
return self.pb2.automation_spec
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def metadata(self) -> trigger_definition_pb2.TriggerMetadata:
|
|
64
|
+
return self.pb2.metadata
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def status(self) -> trigger_definition_pb2.TriggerStatus:
|
|
68
|
+
return self.pb2.status
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def is_active(self) -> bool:
|
|
72
|
+
return self.pb2.spec.active
|
|
73
|
+
|
|
74
|
+
@cached_property
|
|
75
|
+
def trigger(self) -> trigger_definition_pb2.Trigger:
|
|
76
|
+
return trigger_definition_pb2.Trigger(
|
|
77
|
+
id=self.pb2.id,
|
|
78
|
+
automation_spec=self.automation_spec,
|
|
79
|
+
metadata=self.metadata,
|
|
80
|
+
status=self.status,
|
|
81
|
+
active=self.is_active,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class Trigger(ToJSONMixin):
|
|
87
|
+
pb2: trigger_definition_pb2.Trigger
|
|
88
|
+
details: TriggerDetails | None = None
|
|
89
|
+
|
|
90
|
+
@syncify
|
|
91
|
+
@classmethod
|
|
92
|
+
async def create(
|
|
93
|
+
cls,
|
|
94
|
+
trigger: flyte.Trigger,
|
|
95
|
+
task_name: str,
|
|
96
|
+
task_version: str | None = None,
|
|
97
|
+
) -> Trigger:
|
|
98
|
+
"""
|
|
99
|
+
Create a new trigger in the Flyte platform.
|
|
100
|
+
|
|
101
|
+
:param trigger: The flyte.Trigger object containing the trigger definition.
|
|
102
|
+
:param task_name: Optional name of the task to associate with the trigger.
|
|
103
|
+
"""
|
|
104
|
+
ensure_client()
|
|
105
|
+
cfg = get_init_config()
|
|
106
|
+
|
|
107
|
+
# Fetch the task to ensure it exists and to get its input definitions
|
|
108
|
+
try:
|
|
109
|
+
lazy = (
|
|
110
|
+
Task.get(name=task_name, version=task_version)
|
|
111
|
+
if task_version
|
|
112
|
+
else Task.get(name=task_name, auto_version="latest")
|
|
113
|
+
)
|
|
114
|
+
task: TaskDetails = await lazy.fetch.aio()
|
|
115
|
+
|
|
116
|
+
task_trigger = await trigger_serde.to_task_trigger(
|
|
117
|
+
t=trigger,
|
|
118
|
+
task_name=task_name,
|
|
119
|
+
task_inputs=task.pb2.spec.task_template.interface.inputs,
|
|
120
|
+
task_default_inputs=list(task.pb2.spec.default_inputs),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
resp = await get_client().trigger_service.DeployTrigger(
|
|
124
|
+
request=trigger_service_pb2.DeployTriggerRequest(
|
|
125
|
+
name=identifier_pb2.TriggerName(
|
|
126
|
+
name=trigger.name,
|
|
127
|
+
task_name=task_name,
|
|
128
|
+
org=cfg.org,
|
|
129
|
+
project=cfg.project,
|
|
130
|
+
domain=cfg.domain,
|
|
131
|
+
),
|
|
132
|
+
spec=trigger_definition_pb2.TriggerSpec(
|
|
133
|
+
active=task_trigger.spec.active,
|
|
134
|
+
inputs=task_trigger.spec.inputs,
|
|
135
|
+
run_spec=task_trigger.spec.run_spec,
|
|
136
|
+
task_version=task.version,
|
|
137
|
+
),
|
|
138
|
+
automation_spec=task_trigger.automation_spec,
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
details = TriggerDetails(pb2=resp.trigger)
|
|
143
|
+
|
|
144
|
+
return cls(pb2=details.trigger, details=details)
|
|
145
|
+
except grpc.aio.AioRpcError as e:
|
|
146
|
+
if e.code() == grpc.StatusCode.NOT_FOUND:
|
|
147
|
+
raise ValueError(f"Task {task_name}:{task_version or 'latest'} not found") from e
|
|
148
|
+
raise
|
|
149
|
+
|
|
150
|
+
@syncify
|
|
151
|
+
@classmethod
|
|
152
|
+
async def get(cls, *, name: str, task_name: str) -> TriggerDetails:
|
|
153
|
+
"""
|
|
154
|
+
Retrieve a trigger by its name and associated task name.
|
|
155
|
+
"""
|
|
156
|
+
return await TriggerDetails.get.aio(name=name, task_name=task_name)
|
|
157
|
+
|
|
158
|
+
@syncify
|
|
159
|
+
@classmethod
|
|
160
|
+
async def listall(
|
|
161
|
+
cls, task_name: str | None = None, task_version: str | None = None, limit: int = 100
|
|
162
|
+
) -> AsyncIterator[Trigger]:
|
|
163
|
+
"""
|
|
164
|
+
List all triggers associated with a specific task or all tasks if no task name is provided.
|
|
165
|
+
"""
|
|
166
|
+
ensure_client()
|
|
167
|
+
cfg = get_init_config()
|
|
168
|
+
token = None
|
|
169
|
+
task_name_id = None
|
|
170
|
+
project_id = None
|
|
171
|
+
task_id = None
|
|
172
|
+
if task_name and task_version:
|
|
173
|
+
task_id = task_definition_pb2.TaskIdentifier(
|
|
174
|
+
name=task_name,
|
|
175
|
+
project=cfg.project,
|
|
176
|
+
domain=cfg.domain,
|
|
177
|
+
org=cfg.org,
|
|
178
|
+
version=task_version,
|
|
179
|
+
)
|
|
180
|
+
elif task_name:
|
|
181
|
+
task_name_id = task_definition_pb2.TaskName(
|
|
182
|
+
name=task_name,
|
|
183
|
+
project=cfg.project,
|
|
184
|
+
domain=cfg.domain,
|
|
185
|
+
org=cfg.org,
|
|
186
|
+
)
|
|
187
|
+
else:
|
|
188
|
+
project_id = identifier_pb2.ProjectIdentifier(
|
|
189
|
+
organization=cfg.org,
|
|
190
|
+
domain=cfg.domain,
|
|
191
|
+
name=cfg.project,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
while True:
|
|
195
|
+
resp = await get_client().trigger_service.ListTriggers(
|
|
196
|
+
request=trigger_service_pb2.ListTriggersRequest(
|
|
197
|
+
project_id=project_id,
|
|
198
|
+
task_id=task_id,
|
|
199
|
+
task_name=task_name_id,
|
|
200
|
+
request=list_pb2.ListRequest(
|
|
201
|
+
limit=limit,
|
|
202
|
+
token=token,
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
token = resp.token
|
|
207
|
+
for r in resp.triggers:
|
|
208
|
+
yield cls(r)
|
|
209
|
+
if not token:
|
|
210
|
+
break
|
|
211
|
+
|
|
212
|
+
@syncify
|
|
213
|
+
@classmethod
|
|
214
|
+
async def update(cls, name: str, task_name: str, active: bool):
|
|
215
|
+
"""
|
|
216
|
+
Pause a trigger by its name and associated task name.
|
|
217
|
+
"""
|
|
218
|
+
ensure_client()
|
|
219
|
+
cfg = get_init_config()
|
|
220
|
+
await get_client().trigger_service.UpdateTriggers(
|
|
221
|
+
request=trigger_service_pb2.UpdateTriggersRequest(
|
|
222
|
+
names=[
|
|
223
|
+
identifier_pb2.TriggerName(
|
|
224
|
+
org=cfg.org,
|
|
225
|
+
project=cfg.project,
|
|
226
|
+
domain=cfg.domain,
|
|
227
|
+
name=name,
|
|
228
|
+
task_name=task_name,
|
|
229
|
+
)
|
|
230
|
+
],
|
|
231
|
+
active=active,
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
@syncify
|
|
236
|
+
@classmethod
|
|
237
|
+
async def delete(cls, name: str, task_name: str):
|
|
238
|
+
"""
|
|
239
|
+
Delete a trigger by its name.
|
|
240
|
+
"""
|
|
241
|
+
ensure_client()
|
|
242
|
+
cfg = get_init_config()
|
|
243
|
+
await get_client().trigger_service.DeleteTriggers(
|
|
244
|
+
request=trigger_service_pb2.DeleteTriggersRequest(
|
|
245
|
+
names=[
|
|
246
|
+
identifier_pb2.TriggerName(
|
|
247
|
+
org=cfg.org,
|
|
248
|
+
project=cfg.project,
|
|
249
|
+
domain=cfg.domain,
|
|
250
|
+
name=name,
|
|
251
|
+
task_name=task_name,
|
|
252
|
+
)
|
|
253
|
+
],
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def id(self) -> identifier_pb2.TriggerIdentifier:
|
|
259
|
+
return self.pb2.id
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def name(self) -> str:
|
|
263
|
+
return self.id.name.name
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def task_name(self) -> str:
|
|
267
|
+
return self.id.name.task_name
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def automation_spec(self) -> common_pb2.TriggerAutomationSpec:
|
|
271
|
+
return self.pb2.automation_spec
|
|
272
|
+
|
|
273
|
+
async def get_details(self) -> TriggerDetails:
|
|
274
|
+
"""
|
|
275
|
+
Get detailed information about this trigger.
|
|
276
|
+
"""
|
|
277
|
+
if not self.details:
|
|
278
|
+
details = await TriggerDetails.get.aio(name=self.pb2.id.name.name)
|
|
279
|
+
self.details = details
|
|
280
|
+
return self.details
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def is_active(self) -> bool:
|
|
284
|
+
return self.pb2.active
|
|
285
|
+
|
|
286
|
+
def _rich_automation(self, automation: common_pb2.TriggerAutomationSpec):
|
|
287
|
+
if automation.type == common_pb2.TriggerAutomationSpec.type.TYPE_NONE:
|
|
288
|
+
yield "none", None
|
|
289
|
+
elif automation.type == common_pb2.TriggerAutomationSpec.type.TYPE_SCHEDULE:
|
|
290
|
+
if automation.schedule.cron is not None:
|
|
291
|
+
yield "cron", automation.schedule.cron
|
|
292
|
+
elif automation.schedule.rate is not None:
|
|
293
|
+
r = automation.schedule.rate
|
|
294
|
+
yield (
|
|
295
|
+
"fixed_rate",
|
|
296
|
+
(
|
|
297
|
+
f"Every [{r.value}] {r.unit} starting at "
|
|
298
|
+
f"{r.start_time.ToDatetime() if automation.HasField('start_time') else 'now'}"
|
|
299
|
+
),
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
def __rich_repr__(self):
|
|
303
|
+
yield "task_name", self.task_name
|
|
304
|
+
yield "name", self.name
|
|
305
|
+
yield from self._rich_automation(self.pb2.automation_spec)
|
|
306
|
+
yield "auto_activate", self.is_active
|
flyte/remote/_user.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from flyteidl.service import identity_pb2
|
|
6
|
+
from flyteidl.service.identity_pb2 import UserInfoResponse
|
|
7
|
+
|
|
8
|
+
from .._initialize import ensure_client, get_client
|
|
9
|
+
from ..syncify import syncify
|
|
10
|
+
from ._common import ToJSONMixin
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class User(ToJSONMixin):
|
|
15
|
+
pb2: UserInfoResponse
|
|
16
|
+
|
|
17
|
+
@syncify
|
|
18
|
+
@classmethod
|
|
19
|
+
async def get(cls) -> User:
|
|
20
|
+
"""
|
|
21
|
+
Fetches information about the currently logged in user.
|
|
22
|
+
Returns: A User object containing details about the user.
|
|
23
|
+
"""
|
|
24
|
+
ensure_client()
|
|
25
|
+
|
|
26
|
+
resp = await get_client().identity_service.UserInfo(identity_pb2.UserInfoRequest())
|
|
27
|
+
return cls(resp)
|
|
28
|
+
|
|
29
|
+
def subject(self) -> str:
|
|
30
|
+
return self.pb2.subject
|
|
31
|
+
|
|
32
|
+
def name(self) -> str:
|
|
33
|
+
return self.pb2.name
|
flyte/report/__init__.py
ADDED
flyte/report/_report.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import html
|
|
2
|
+
import pathlib
|
|
3
|
+
import string
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import TYPE_CHECKING, Dict, List, Union
|
|
6
|
+
|
|
7
|
+
from flyte._logging import logger
|
|
8
|
+
from flyte._tools import ipython_check
|
|
9
|
+
from flyte.syncify import syncify
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from IPython.core.display import HTML
|
|
13
|
+
|
|
14
|
+
_MAIN_TAB_NAME = "main"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class Tab:
|
|
19
|
+
name: str
|
|
20
|
+
content: List[str] = field(default_factory=list, init=False)
|
|
21
|
+
|
|
22
|
+
def log(self, content: str):
|
|
23
|
+
"""
|
|
24
|
+
Add content to the tab.
|
|
25
|
+
The content should be a valid HTML string, but not a complete HTML document, as it will be inserted into a div.
|
|
26
|
+
|
|
27
|
+
:param content: The content to add.
|
|
28
|
+
"""
|
|
29
|
+
self.content.append(content)
|
|
30
|
+
|
|
31
|
+
def replace(self, content: str):
|
|
32
|
+
"""
|
|
33
|
+
Replace the content of the tab.
|
|
34
|
+
The content should be a valid HTML string, but not a complete HTML document, as it will be inserted into a div.
|
|
35
|
+
|
|
36
|
+
:param content: The content to replace.
|
|
37
|
+
"""
|
|
38
|
+
self.content = [content]
|
|
39
|
+
|
|
40
|
+
def get_html(self) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Get the HTML representation of the tab.
|
|
43
|
+
|
|
44
|
+
:return: The HTML representation of the tab.
|
|
45
|
+
"""
|
|
46
|
+
return "\n".join(self.content)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class Report:
|
|
51
|
+
name: str
|
|
52
|
+
tabs: Dict[str, Tab] = field(default_factory=dict)
|
|
53
|
+
template_path: pathlib.Path = field(default_factory=lambda: pathlib.Path(__file__).parent / "_template.html")
|
|
54
|
+
|
|
55
|
+
def __post_init__(self):
|
|
56
|
+
self.tabs[_MAIN_TAB_NAME] = Tab(_MAIN_TAB_NAME)
|
|
57
|
+
|
|
58
|
+
def get_tab(self, name: str, create_if_missing: bool = True) -> Tab:
|
|
59
|
+
"""
|
|
60
|
+
Get a tab by name. If the tab does not exist, create it.
|
|
61
|
+
|
|
62
|
+
:param name: The name of the tab.
|
|
63
|
+
:param create_if_missing: Whether to create the tab if it does not exist.
|
|
64
|
+
:return: The tab.
|
|
65
|
+
"""
|
|
66
|
+
if name not in self.tabs:
|
|
67
|
+
if create_if_missing:
|
|
68
|
+
self.tabs[name] = Tab(name)
|
|
69
|
+
else:
|
|
70
|
+
raise ValueError(f"Tab {name} does not exist.")
|
|
71
|
+
return self.tabs[name]
|
|
72
|
+
|
|
73
|
+
def get_final_report(self) -> Union[str, "HTML"]:
|
|
74
|
+
"""
|
|
75
|
+
Get the final report as a string.
|
|
76
|
+
|
|
77
|
+
:return: The final report.
|
|
78
|
+
"""
|
|
79
|
+
tabs = {n: t.get_html() for n, t in self.tabs.items()}
|
|
80
|
+
nav_htmls = []
|
|
81
|
+
body_htmls = []
|
|
82
|
+
|
|
83
|
+
for key, value in tabs.items():
|
|
84
|
+
nav_htmls.append(f'<li onclick="handleLinkClick(this)">{html.escape(key)}</li>')
|
|
85
|
+
# Can not escape here because this is HTML. Escaping it will present the HTML as text.
|
|
86
|
+
# The renderer must ensure that the HTML is safe.
|
|
87
|
+
body_htmls.append(f"<div>{value}</div>")
|
|
88
|
+
|
|
89
|
+
template = string.Template(self.template_path.open("r").read())
|
|
90
|
+
|
|
91
|
+
raw_html = template.substitute(NAV_HTML="".join(nav_htmls), BODY_HTML="".join(body_htmls))
|
|
92
|
+
if ipython_check():
|
|
93
|
+
try:
|
|
94
|
+
from IPython.core.display import HTML
|
|
95
|
+
|
|
96
|
+
return HTML(raw_html)
|
|
97
|
+
except ImportError:
|
|
98
|
+
...
|
|
99
|
+
return raw_html
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def get_tab(name: str, /, create_if_missing: bool = True) -> Tab:
|
|
103
|
+
"""
|
|
104
|
+
Get a tab by name. If the tab does not exist, create it.
|
|
105
|
+
|
|
106
|
+
:param name: The name of the tab.
|
|
107
|
+
:param create_if_missing: Whether to create the tab if it does not exist.
|
|
108
|
+
:return: The tab.
|
|
109
|
+
"""
|
|
110
|
+
report = current_report()
|
|
111
|
+
return report.get_tab(name, create_if_missing=create_if_missing)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@syncify
|
|
115
|
+
async def log(content: str, do_flush: bool = False):
|
|
116
|
+
"""
|
|
117
|
+
Log content to the main tab. The content should be a valid HTML string, but not a complete HTML document,
|
|
118
|
+
as it will be inserted into a div.
|
|
119
|
+
|
|
120
|
+
:param content: The content to log.
|
|
121
|
+
:param do_flush: flush the report after logging.
|
|
122
|
+
"""
|
|
123
|
+
get_tab(_MAIN_TAB_NAME).log(content)
|
|
124
|
+
if do_flush:
|
|
125
|
+
await flush.aio()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@syncify
|
|
129
|
+
async def flush():
|
|
130
|
+
"""
|
|
131
|
+
Flush the report.
|
|
132
|
+
"""
|
|
133
|
+
import flyte.storage as storage
|
|
134
|
+
from flyte._context import internal_ctx
|
|
135
|
+
from flyte._internal.runtime import io
|
|
136
|
+
|
|
137
|
+
if not internal_ctx().is_task_context():
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
report = internal_ctx().get_report()
|
|
141
|
+
if report is None:
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
report_html = report.get_final_report()
|
|
145
|
+
assert report_html is not None
|
|
146
|
+
assert isinstance(report_html, str)
|
|
147
|
+
report_path = io.report_path(internal_ctx().data.task_context.output_path)
|
|
148
|
+
content_types = {
|
|
149
|
+
"Content-Type": "text/html", # For s3
|
|
150
|
+
"content_type": "text/html", # For gcs
|
|
151
|
+
}
|
|
152
|
+
final_path = await storage.put_stream(report_html.encode("utf-8"), to_path=report_path, attributes=content_types)
|
|
153
|
+
logger.debug(f"Report flushed to {final_path}")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@syncify
|
|
157
|
+
async def replace(content: str, do_flush: bool = False):
|
|
158
|
+
"""
|
|
159
|
+
Get the report. Replaces the content of the main tab.
|
|
160
|
+
|
|
161
|
+
:return: The report.
|
|
162
|
+
"""
|
|
163
|
+
report = current_report()
|
|
164
|
+
if report is None:
|
|
165
|
+
return
|
|
166
|
+
report.get_tab(_MAIN_TAB_NAME).replace(content)
|
|
167
|
+
if do_flush:
|
|
168
|
+
await flush.aio()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def current_report() -> Report:
|
|
172
|
+
"""
|
|
173
|
+
Get the current report. This is a dummy report if not in a task context.
|
|
174
|
+
|
|
175
|
+
:return: The current report.
|
|
176
|
+
"""
|
|
177
|
+
from flyte._context import internal_ctx
|
|
178
|
+
|
|
179
|
+
report = internal_ctx().get_report()
|
|
180
|
+
if report is None:
|
|
181
|
+
report = Report("dummy")
|
|
182
|
+
return report
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>User Content</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,700%7COpen+Sans:400,700" rel="stylesheet">
|
|
8
|
+
<style>
|
|
9
|
+
ol, ul {
|
|
10
|
+
list-style: none;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
table {
|
|
14
|
+
border-collapse: collapse;
|
|
15
|
+
border-spacing: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#flyte-frame-nav {
|
|
19
|
+
display: flex;
|
|
20
|
+
width: 100%;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#flyte-frame-tabs {
|
|
24
|
+
display: flex;
|
|
25
|
+
width: 100%;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
margin-block: 0;
|
|
28
|
+
padding-inline-start: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#flyte-frame-tabs li {
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
padding: 8px;
|
|
34
|
+
margin: 0;
|
|
35
|
+
margin-right: 12px;
|
|
36
|
+
font-size: 14px;
|
|
37
|
+
line-height: 20px;
|
|
38
|
+
font-weight: 700;
|
|
39
|
+
font-style: normal;
|
|
40
|
+
font-family: Open Sans, helvetica, arial, sans-serif;
|
|
41
|
+
color: #666666;
|
|
42
|
+
width: 126px;
|
|
43
|
+
text-align: center;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#flyte-frame-tabs li:last-child {
|
|
47
|
+
margin-right: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#flyte-frame-tabs li.active {
|
|
51
|
+
border-bottom: 4px solid rgb(163, 26, 255);
|
|
52
|
+
color: #333333;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#flyte-frame-container {
|
|
56
|
+
width: auto;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#flyte-frame-container > div {
|
|
60
|
+
display: None;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#flyte-frame-container > div.active {
|
|
64
|
+
display: block;
|
|
65
|
+
padding: 2rem 2rem;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
</style>
|
|
69
|
+
|
|
70
|
+
</head>
|
|
71
|
+
<body>
|
|
72
|
+
<nav id="flyte-frame-nav">
|
|
73
|
+
<ul id="flyte-frame-tabs">
|
|
74
|
+
$NAV_HTML
|
|
75
|
+
</ul>
|
|
76
|
+
</nav>
|
|
77
|
+
<div id="flyte-frame-container">
|
|
78
|
+
$BODY_HTML
|
|
79
|
+
</div>
|
|
80
|
+
</body>
|
|
81
|
+
<script>
|
|
82
|
+
const setTabs = index => {
|
|
83
|
+
const container = document.getElementById('flyte-frame-tabs')
|
|
84
|
+
for (let i = 0; i < container.children.length; i++) {
|
|
85
|
+
const tabIndex = container.children[i].getAttribute('link_index')
|
|
86
|
+
if (tabIndex === index) {
|
|
87
|
+
container.children[i].classList.add('active')
|
|
88
|
+
} else {
|
|
89
|
+
container.children[i].className = ''
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const setContent = index => {
|
|
94
|
+
const container = document.getElementById('flyte-frame-container')
|
|
95
|
+
for (let i = 0; i < container.children.length; i++) {
|
|
96
|
+
const tabIndex = container.children[i].getAttribute('link_index')
|
|
97
|
+
if (tabIndex === index) {
|
|
98
|
+
container.children[i].classList.add('active')
|
|
99
|
+
} else {
|
|
100
|
+
container.children[i].className = ''
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const setLinkIndex = index => {
|
|
105
|
+
setTabs(index)
|
|
106
|
+
setContent(index)
|
|
107
|
+
}
|
|
108
|
+
const handleLinkClick = e => {
|
|
109
|
+
const linkIndex = e.getAttribute('link_index');
|
|
110
|
+
setLinkIndex(linkIndex)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const tabs = document.getElementById('flyte-frame-tabs');
|
|
114
|
+
const containers = document.getElementById('flyte-frame-container');
|
|
115
|
+
for(var i = 0; i < tabs.children.length; i++) {
|
|
116
|
+
if (i === 0) {
|
|
117
|
+
tabs.children[i].classList.add('active')
|
|
118
|
+
containers.children[i].classList.add('active')
|
|
119
|
+
}
|
|
120
|
+
tabs.children[i].setAttribute("link_index", i+1)
|
|
121
|
+
containers.children[i].setAttribute("link_index", i+1)
|
|
122
|
+
}
|
|
123
|
+
</script>
|
|
124
|
+
</html>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"ABFS",
|
|
3
|
+
"GCS",
|
|
4
|
+
"S3",
|
|
5
|
+
"Storage",
|
|
6
|
+
"exists",
|
|
7
|
+
"exists_sync",
|
|
8
|
+
"get",
|
|
9
|
+
"get_configured_fsspec_kwargs",
|
|
10
|
+
"get_random_local_directory",
|
|
11
|
+
"get_random_local_path",
|
|
12
|
+
"get_stream",
|
|
13
|
+
"get_underlying_filesystem",
|
|
14
|
+
"is_remote",
|
|
15
|
+
"join",
|
|
16
|
+
"open",
|
|
17
|
+
"put",
|
|
18
|
+
"put_stream",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
from ._config import ABFS, GCS, S3, Storage
|
|
22
|
+
from ._storage import (
|
|
23
|
+
exists,
|
|
24
|
+
exists_sync,
|
|
25
|
+
get,
|
|
26
|
+
get_configured_fsspec_kwargs,
|
|
27
|
+
get_random_local_directory,
|
|
28
|
+
get_random_local_path,
|
|
29
|
+
get_stream,
|
|
30
|
+
get_underlying_filesystem,
|
|
31
|
+
is_remote,
|
|
32
|
+
join,
|
|
33
|
+
open,
|
|
34
|
+
put,
|
|
35
|
+
put_stream,
|
|
36
|
+
)
|