flyte 0.1.0__py3-none-any.whl → 0.2.0a0__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 +78 -2
- flyte/_bin/__init__.py +0 -0
- flyte/_bin/runtime.py +152 -0
- flyte/_build.py +26 -0
- flyte/_cache/__init__.py +12 -0
- flyte/_cache/cache.py +145 -0
- flyte/_cache/defaults.py +9 -0
- flyte/_cache/policy_function_body.py +42 -0
- flyte/_code_bundle/__init__.py +8 -0
- flyte/_code_bundle/_ignore.py +113 -0
- flyte/_code_bundle/_packaging.py +187 -0
- flyte/_code_bundle/_utils.py +323 -0
- flyte/_code_bundle/bundle.py +209 -0
- flyte/_context.py +152 -0
- flyte/_deploy.py +243 -0
- flyte/_doc.py +29 -0
- flyte/_docstring.py +32 -0
- flyte/_environment.py +84 -0
- flyte/_excepthook.py +37 -0
- flyte/_group.py +32 -0
- flyte/_hash.py +23 -0
- flyte/_image.py +762 -0
- flyte/_initialize.py +492 -0
- flyte/_interface.py +84 -0
- flyte/_internal/__init__.py +3 -0
- flyte/_internal/controllers/__init__.py +128 -0
- flyte/_internal/controllers/_local_controller.py +193 -0
- flyte/_internal/controllers/_trace.py +41 -0
- flyte/_internal/controllers/remote/__init__.py +60 -0
- flyte/_internal/controllers/remote/_action.py +146 -0
- flyte/_internal/controllers/remote/_client.py +47 -0
- flyte/_internal/controllers/remote/_controller.py +494 -0
- flyte/_internal/controllers/remote/_core.py +410 -0
- flyte/_internal/controllers/remote/_informer.py +361 -0
- flyte/_internal/controllers/remote/_service_protocol.py +50 -0
- flyte/_internal/imagebuild/__init__.py +11 -0
- flyte/_internal/imagebuild/docker_builder.py +427 -0
- flyte/_internal/imagebuild/image_builder.py +246 -0
- flyte/_internal/imagebuild/remote_builder.py +0 -0
- flyte/_internal/resolvers/__init__.py +0 -0
- flyte/_internal/resolvers/_task_module.py +54 -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 +342 -0
- flyte/_internal/runtime/entrypoints.py +135 -0
- flyte/_internal/runtime/io.py +136 -0
- flyte/_internal/runtime/resources_serde.py +138 -0
- flyte/_internal/runtime/task_serde.py +330 -0
- flyte/_internal/runtime/taskrunner.py +191 -0
- flyte/_internal/runtime/types_serde.py +54 -0
- flyte/_logging.py +135 -0
- flyte/_map.py +215 -0
- flyte/_pod.py +19 -0
- flyte/_protos/__init__.py +0 -0
- flyte/_protos/common/authorization_pb2.py +66 -0
- flyte/_protos/common/authorization_pb2.pyi +108 -0
- flyte/_protos/common/authorization_pb2_grpc.py +4 -0
- flyte/_protos/common/identifier_pb2.py +71 -0
- flyte/_protos/common/identifier_pb2.pyi +82 -0
- flyte/_protos/common/identifier_pb2_grpc.py +4 -0
- flyte/_protos/common/identity_pb2.py +48 -0
- flyte/_protos/common/identity_pb2.pyi +72 -0
- flyte/_protos/common/identity_pb2_grpc.py +4 -0
- flyte/_protos/common/list_pb2.py +36 -0
- flyte/_protos/common/list_pb2.pyi +71 -0
- flyte/_protos/common/list_pb2_grpc.py +4 -0
- flyte/_protos/common/policy_pb2.py +37 -0
- flyte/_protos/common/policy_pb2.pyi +27 -0
- flyte/_protos/common/policy_pb2_grpc.py +4 -0
- flyte/_protos/common/role_pb2.py +37 -0
- flyte/_protos/common/role_pb2.pyi +53 -0
- flyte/_protos/common/role_pb2_grpc.py +4 -0
- flyte/_protos/common/runtime_version_pb2.py +28 -0
- flyte/_protos/common/runtime_version_pb2.pyi +24 -0
- flyte/_protos/common/runtime_version_pb2_grpc.py +4 -0
- flyte/_protos/logs/dataplane/payload_pb2.py +100 -0
- flyte/_protos/logs/dataplane/payload_pb2.pyi +177 -0
- flyte/_protos/logs/dataplane/payload_pb2_grpc.py +4 -0
- flyte/_protos/secret/definition_pb2.py +49 -0
- flyte/_protos/secret/definition_pb2.pyi +93 -0
- flyte/_protos/secret/definition_pb2_grpc.py +4 -0
- flyte/_protos/secret/payload_pb2.py +62 -0
- flyte/_protos/secret/payload_pb2.pyi +94 -0
- flyte/_protos/secret/payload_pb2_grpc.py +4 -0
- flyte/_protos/secret/secret_pb2.py +38 -0
- flyte/_protos/secret/secret_pb2.pyi +6 -0
- flyte/_protos/secret/secret_pb2_grpc.py +198 -0
- flyte/_protos/secret/secret_pb2_grpc_grpc.py +198 -0
- flyte/_protos/validate/validate/validate_pb2.py +76 -0
- flyte/_protos/workflow/common_pb2.py +27 -0
- flyte/_protos/workflow/common_pb2.pyi +14 -0
- flyte/_protos/workflow/common_pb2_grpc.py +4 -0
- flyte/_protos/workflow/environment_pb2.py +29 -0
- flyte/_protos/workflow/environment_pb2.pyi +12 -0
- flyte/_protos/workflow/environment_pb2_grpc.py +4 -0
- flyte/_protos/workflow/node_execution_service_pb2.py +26 -0
- flyte/_protos/workflow/node_execution_service_pb2.pyi +4 -0
- flyte/_protos/workflow/node_execution_service_pb2_grpc.py +32 -0
- flyte/_protos/workflow/queue_service_pb2.py +105 -0
- flyte/_protos/workflow/queue_service_pb2.pyi +146 -0
- flyte/_protos/workflow/queue_service_pb2_grpc.py +172 -0
- flyte/_protos/workflow/run_definition_pb2.py +128 -0
- flyte/_protos/workflow/run_definition_pb2.pyi +314 -0
- flyte/_protos/workflow/run_definition_pb2_grpc.py +4 -0
- flyte/_protos/workflow/run_logs_service_pb2.py +41 -0
- flyte/_protos/workflow/run_logs_service_pb2.pyi +28 -0
- flyte/_protos/workflow/run_logs_service_pb2_grpc.py +69 -0
- flyte/_protos/workflow/run_service_pb2.py +129 -0
- flyte/_protos/workflow/run_service_pb2.pyi +171 -0
- flyte/_protos/workflow/run_service_pb2_grpc.py +412 -0
- flyte/_protos/workflow/state_service_pb2.py +66 -0
- flyte/_protos/workflow/state_service_pb2.pyi +75 -0
- flyte/_protos/workflow/state_service_pb2_grpc.py +138 -0
- flyte/_protos/workflow/task_definition_pb2.py +79 -0
- flyte/_protos/workflow/task_definition_pb2.pyi +81 -0
- flyte/_protos/workflow/task_definition_pb2_grpc.py +4 -0
- flyte/_protos/workflow/task_service_pb2.py +60 -0
- flyte/_protos/workflow/task_service_pb2.pyi +59 -0
- flyte/_protos/workflow/task_service_pb2_grpc.py +138 -0
- flyte/_resources.py +226 -0
- flyte/_retry.py +32 -0
- flyte/_reusable_environment.py +25 -0
- flyte/_run.py +482 -0
- flyte/_secret.py +61 -0
- flyte/_task.py +449 -0
- flyte/_task_environment.py +183 -0
- flyte/_timeout.py +47 -0
- flyte/_tools.py +27 -0
- flyte/_trace.py +120 -0
- flyte/_utils/__init__.py +26 -0
- flyte/_utils/asyn.py +119 -0
- flyte/_utils/async_cache.py +139 -0
- flyte/_utils/coro_management.py +23 -0
- flyte/_utils/file_handling.py +72 -0
- flyte/_utils/helpers.py +134 -0
- flyte/_utils/lazy_module.py +54 -0
- flyte/_utils/org_discovery.py +57 -0
- flyte/_utils/uv_script_parser.py +49 -0
- flyte/_version.py +21 -0
- flyte/cli/__init__.py +3 -0
- flyte/cli/_abort.py +28 -0
- flyte/cli/_common.py +337 -0
- flyte/cli/_create.py +145 -0
- flyte/cli/_delete.py +23 -0
- flyte/cli/_deploy.py +152 -0
- flyte/cli/_gen.py +163 -0
- flyte/cli/_get.py +310 -0
- flyte/cli/_params.py +538 -0
- flyte/cli/_run.py +231 -0
- flyte/cli/main.py +166 -0
- flyte/config/__init__.py +3 -0
- flyte/config/_config.py +216 -0
- flyte/config/_internal.py +64 -0
- flyte/config/_reader.py +207 -0
- flyte/connectors/__init__.py +0 -0
- flyte/errors.py +172 -0
- flyte/extras/__init__.py +5 -0
- flyte/extras/_container.py +263 -0
- flyte/io/__init__.py +27 -0
- flyte/io/_dir.py +448 -0
- flyte/io/_file.py +467 -0
- flyte/io/_structured_dataset/__init__.py +129 -0
- flyte/io/_structured_dataset/basic_dfs.py +219 -0
- flyte/io/_structured_dataset/structured_dataset.py +1061 -0
- flyte/models.py +391 -0
- flyte/remote/__init__.py +26 -0
- flyte/remote/_client/__init__.py +0 -0
- flyte/remote/_client/_protocols.py +133 -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 +397 -0
- flyte/remote/_client/auth/_authenticators/client_credentials.py +73 -0
- flyte/remote/_client/auth/_authenticators/device_code.py +118 -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 +215 -0
- flyte/remote/_client/auth/_client_config.py +83 -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 +143 -0
- flyte/remote/_client/auth/_token_client.py +260 -0
- flyte/remote/_client/auth/errors.py +16 -0
- flyte/remote/_client/controlplane.py +95 -0
- flyte/remote/_console.py +18 -0
- flyte/remote/_data.py +159 -0
- flyte/remote/_logs.py +176 -0
- flyte/remote/_project.py +85 -0
- flyte/remote/_run.py +970 -0
- flyte/remote/_secret.py +132 -0
- flyte/remote/_task.py +391 -0
- flyte/report/__init__.py +3 -0
- flyte/report/_report.py +178 -0
- flyte/report/_template.html +124 -0
- flyte/storage/__init__.py +29 -0
- flyte/storage/_config.py +233 -0
- flyte/storage/_remote_fs.py +34 -0
- flyte/storage/_storage.py +271 -0
- flyte/storage/_utils.py +5 -0
- flyte/syncify/__init__.py +56 -0
- flyte/syncify/_api.py +371 -0
- flyte/types/__init__.py +36 -0
- flyte/types/_interface.py +40 -0
- flyte/types/_pickle.py +118 -0
- flyte/types/_renderer.py +162 -0
- flyte/types/_string_literals.py +120 -0
- flyte/types/_type_engine.py +2287 -0
- flyte/types/_utils.py +80 -0
- flyte-0.2.0a0.dist-info/METADATA +249 -0
- flyte-0.2.0a0.dist-info/RECORD +218 -0
- {flyte-0.1.0.dist-info → flyte-0.2.0a0.dist-info}/WHEEL +2 -1
- flyte-0.2.0a0.dist-info/entry_points.txt +3 -0
- flyte-0.2.0a0.dist-info/top_level.txt +1 -0
- flyte-0.1.0.dist-info/METADATA +0 -6
- flyte-0.1.0.dist-info/RECORD +0 -5
flyte/remote/_secret.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import AsyncIterator, Literal, Union
|
|
5
|
+
|
|
6
|
+
import rich.repr
|
|
7
|
+
|
|
8
|
+
from flyte._initialize import ensure_client, get_client, get_common_config
|
|
9
|
+
from flyte._protos.secret import definition_pb2, payload_pb2
|
|
10
|
+
from flyte.syncify import syncify
|
|
11
|
+
|
|
12
|
+
SecretTypes = Literal["regular", "image_pull"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Secret:
|
|
17
|
+
pb2: definition_pb2.Secret
|
|
18
|
+
|
|
19
|
+
@syncify
|
|
20
|
+
@classmethod
|
|
21
|
+
async def create(cls, name: str, value: Union[str, bytes], type: SecretTypes = "regular"):
|
|
22
|
+
ensure_client()
|
|
23
|
+
cfg = get_common_config()
|
|
24
|
+
secret_type = (
|
|
25
|
+
definition_pb2.SecretType.SECRET_TYPE_GENERIC
|
|
26
|
+
if type == "regular"
|
|
27
|
+
else definition_pb2.SecretType.SECRET_TYPE_IMAGE_PULL_SECRET
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if isinstance(value, str):
|
|
31
|
+
secret = definition_pb2.SecretSpec(
|
|
32
|
+
type=secret_type,
|
|
33
|
+
string_value=value,
|
|
34
|
+
)
|
|
35
|
+
else:
|
|
36
|
+
secret = definition_pb2.SecretSpec(
|
|
37
|
+
type=secret_type,
|
|
38
|
+
binary_value=value,
|
|
39
|
+
)
|
|
40
|
+
await get_client().secrets_service.CreateSecret( # type: ignore
|
|
41
|
+
request=payload_pb2.CreateSecretRequest(
|
|
42
|
+
id=definition_pb2.SecretIdentifier(
|
|
43
|
+
organization=cfg.org,
|
|
44
|
+
project=cfg.project,
|
|
45
|
+
domain=cfg.domain,
|
|
46
|
+
name=name,
|
|
47
|
+
),
|
|
48
|
+
secret_spec=secret,
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
@syncify
|
|
53
|
+
@classmethod
|
|
54
|
+
async def get(cls, name: str) -> Secret:
|
|
55
|
+
ensure_client()
|
|
56
|
+
cfg = get_common_config()
|
|
57
|
+
resp = await get_client().secrets_service.GetSecret(
|
|
58
|
+
request=payload_pb2.GetSecretRequest(
|
|
59
|
+
id=definition_pb2.SecretIdentifier(
|
|
60
|
+
organization=cfg.org,
|
|
61
|
+
project=cfg.project,
|
|
62
|
+
domain=cfg.domain,
|
|
63
|
+
name=name,
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
return Secret(pb2=resp.secret)
|
|
68
|
+
|
|
69
|
+
@syncify
|
|
70
|
+
@classmethod
|
|
71
|
+
async def listall(cls, limit: int = 100) -> AsyncIterator[Secret]:
|
|
72
|
+
ensure_client()
|
|
73
|
+
cfg = get_common_config()
|
|
74
|
+
token = None
|
|
75
|
+
while True:
|
|
76
|
+
resp = await get_client().secrets_service.ListSecrets( # type: ignore
|
|
77
|
+
request=payload_pb2.ListSecretsRequest(
|
|
78
|
+
organization=cfg.org,
|
|
79
|
+
project=cfg.project,
|
|
80
|
+
domain=cfg.domain,
|
|
81
|
+
token=token,
|
|
82
|
+
limit=limit,
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
token = resp.token
|
|
86
|
+
for r in resp.secrets:
|
|
87
|
+
yield cls(r)
|
|
88
|
+
if not token:
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
@syncify
|
|
92
|
+
@classmethod
|
|
93
|
+
async def delete(cls, name):
|
|
94
|
+
ensure_client()
|
|
95
|
+
cfg = get_common_config()
|
|
96
|
+
await get_client().secrets_service.DeleteSecret( # type: ignore
|
|
97
|
+
request=payload_pb2.DeleteSecretRequest(
|
|
98
|
+
id=definition_pb2.SecretIdentifier(
|
|
99
|
+
organization=cfg.org,
|
|
100
|
+
project=cfg.project,
|
|
101
|
+
domain=cfg.domain,
|
|
102
|
+
name=name,
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def name(self) -> str:
|
|
109
|
+
return self.pb2.id.name
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def type(self) -> str:
|
|
113
|
+
if self.pb2.secret_metadata.type == definition_pb2.SecretType.SECRET_TYPE_GENERIC:
|
|
114
|
+
return "regular"
|
|
115
|
+
elif self.pb2.secret_metadata.type == definition_pb2.SecretType.SECRET_TYPE_IMAGE_PULL_SECRET:
|
|
116
|
+
return "image_pull"
|
|
117
|
+
raise ValueError("unknown type")
|
|
118
|
+
|
|
119
|
+
def __rich_repr__(self) -> rich.repr.Result:
|
|
120
|
+
yield "project", self.pb2.id.project or "-"
|
|
121
|
+
yield "domain", self.pb2.id.domain or "-"
|
|
122
|
+
yield "name", self.name
|
|
123
|
+
yield "type", self.type
|
|
124
|
+
yield "created_time", self.pb2.secret_metadata.created_time.ToDatetime().isoformat()
|
|
125
|
+
yield "status", definition_pb2.OverallStatus.Name(self.pb2.secret_metadata.secret_status.overall_status)
|
|
126
|
+
yield (
|
|
127
|
+
"cluster_status",
|
|
128
|
+
{
|
|
129
|
+
s.cluster.name: definition_pb2.SecretPresenceStatus.Name(s.presence_status)
|
|
130
|
+
for s in self.pb2.secret_metadata.secret_status.cluster_status
|
|
131
|
+
},
|
|
132
|
+
)
|
flyte/remote/_task.py
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from threading import Lock
|
|
6
|
+
from typing import Any, AsyncIterator, Callable, Coroutine, Dict, Iterator, Literal, Optional, Tuple, Union
|
|
7
|
+
|
|
8
|
+
import rich.repr
|
|
9
|
+
from google.protobuf import timestamp
|
|
10
|
+
|
|
11
|
+
import flyte
|
|
12
|
+
import flyte.errors
|
|
13
|
+
from flyte._context import internal_ctx
|
|
14
|
+
from flyte._initialize import ensure_client, get_client, get_common_config
|
|
15
|
+
from flyte._logging import logger
|
|
16
|
+
from flyte._protos.common import list_pb2
|
|
17
|
+
from flyte._protos.workflow import task_definition_pb2, task_service_pb2
|
|
18
|
+
from flyte.models import NativeInterface
|
|
19
|
+
from flyte.syncify import syncify
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _repr_task_metadata(metadata: task_definition_pb2.TaskMetadata) -> rich.repr.Result:
|
|
23
|
+
"""
|
|
24
|
+
Rich representation of the task metadata.
|
|
25
|
+
"""
|
|
26
|
+
if metadata.deployed_by:
|
|
27
|
+
if metadata.deployed_by.user:
|
|
28
|
+
yield "deployed_by", f"User: {metadata.deployed_by.user.spec.email}"
|
|
29
|
+
else:
|
|
30
|
+
yield "deployed_by", f"App: {metadata.deployed_by.application.spec.name}"
|
|
31
|
+
yield "short_name", metadata.short_name
|
|
32
|
+
yield "deployed_at", timestamp.to_datetime(metadata.deployed_at)
|
|
33
|
+
yield "environment_name", metadata.environment_name
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class LazyEntity:
|
|
37
|
+
"""
|
|
38
|
+
Fetches the entity when the entity is called or when the entity is retrieved.
|
|
39
|
+
The entity is derived from RemoteEntity so that it behaves exactly like the mimicked entity.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, name: str, getter: Callable[..., Coroutine[Any, Any, TaskDetails]], *args, **kwargs):
|
|
43
|
+
self._task: Optional[TaskDetails] = None
|
|
44
|
+
self._getter = getter
|
|
45
|
+
self._name = name
|
|
46
|
+
self._mutex = Lock()
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def name(self) -> str:
|
|
50
|
+
return self._name
|
|
51
|
+
|
|
52
|
+
@syncify
|
|
53
|
+
async def fetch(self) -> TaskDetails:
|
|
54
|
+
"""
|
|
55
|
+
Forwards all other attributes to task, causing the task to be fetched!
|
|
56
|
+
"""
|
|
57
|
+
with self._mutex:
|
|
58
|
+
if self._task is None:
|
|
59
|
+
self._task = await self._getter()
|
|
60
|
+
if self._task is None:
|
|
61
|
+
raise RuntimeError(f"Error downloading the task {self._name}, (check original exception...)")
|
|
62
|
+
return self._task
|
|
63
|
+
|
|
64
|
+
async def __call__(self, *args, **kwargs):
|
|
65
|
+
"""
|
|
66
|
+
Forwards the call to the underlying task. The entity will be fetched if not already present
|
|
67
|
+
"""
|
|
68
|
+
tk = await self.fetch.aio()
|
|
69
|
+
return await tk(*args, **kwargs)
|
|
70
|
+
|
|
71
|
+
def __repr__(self) -> str:
|
|
72
|
+
return str(self)
|
|
73
|
+
|
|
74
|
+
def __str__(self) -> str:
|
|
75
|
+
return f"Future for task with name {self._name}"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
AutoVersioning = Literal["latest", "current"]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class TaskDetails:
|
|
83
|
+
pb2: task_definition_pb2.TaskDetails
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def get(cls, name: str, version: str | None = None, auto_version: AutoVersioning | None = None) -> LazyEntity:
|
|
87
|
+
"""
|
|
88
|
+
Get a task by its ID or name. If both are provided, the ID will take precedence.
|
|
89
|
+
|
|
90
|
+
Either version or auto_version are required parameters.
|
|
91
|
+
|
|
92
|
+
:param uri: The URI of the task. If provided, do not provide the rest of the parameters.
|
|
93
|
+
:param name: The name of the task.
|
|
94
|
+
:param version: The version of the task.
|
|
95
|
+
:param auto_version: If set to "latest", the latest-by-time ordered from now, version of the task will be used.
|
|
96
|
+
If set to "current", the version will be derived from the callee tasks context. This is useful if you are
|
|
97
|
+
deploying all environments with the same version. If auto_version is current, you can only access the task from
|
|
98
|
+
within a task context.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
if version is None and auto_version is None:
|
|
102
|
+
raise ValueError("Either version or auto_version must be provided.")
|
|
103
|
+
|
|
104
|
+
if version is None and auto_version not in ["latest", "current"]:
|
|
105
|
+
raise ValueError("auto_version must be either 'latest' or 'current'.")
|
|
106
|
+
|
|
107
|
+
async def deferred_get(_version: str | None, _auto_version: AutoVersioning | None) -> TaskDetails:
|
|
108
|
+
if _version is None:
|
|
109
|
+
if _auto_version == "latest":
|
|
110
|
+
tasks = []
|
|
111
|
+
async for x in Task.listall.aio(
|
|
112
|
+
by_task_name=name,
|
|
113
|
+
sort_by=("created_at", "desc"),
|
|
114
|
+
limit=1,
|
|
115
|
+
):
|
|
116
|
+
tasks.append(x)
|
|
117
|
+
if not tasks:
|
|
118
|
+
raise flyte.errors.ReferenceTaskError(f"Task {name} not found.")
|
|
119
|
+
_version = tasks[0].version
|
|
120
|
+
elif _auto_version == "current":
|
|
121
|
+
ctx = flyte.ctx()
|
|
122
|
+
if ctx is None:
|
|
123
|
+
raise ValueError("auto_version=current can only be used within a task context.")
|
|
124
|
+
_version = ctx.version
|
|
125
|
+
cfg = get_common_config()
|
|
126
|
+
task_id = task_definition_pb2.TaskIdentifier(
|
|
127
|
+
org=cfg.org,
|
|
128
|
+
project=cfg.project,
|
|
129
|
+
domain=cfg.domain,
|
|
130
|
+
name=name,
|
|
131
|
+
version=_version,
|
|
132
|
+
)
|
|
133
|
+
resp = await get_client().task_service.GetTaskDetails(
|
|
134
|
+
task_service_pb2.GetTaskDetailsRequest(
|
|
135
|
+
task_id=task_id,
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
return cls(resp.details)
|
|
139
|
+
|
|
140
|
+
return LazyEntity(
|
|
141
|
+
name=name, getter=functools.partial(deferred_get, _version=version, _auto_version=auto_version)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def name(self) -> str:
|
|
146
|
+
"""
|
|
147
|
+
The name of the task.
|
|
148
|
+
"""
|
|
149
|
+
return self.pb2.task_id.name
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def version(self) -> str:
|
|
153
|
+
"""
|
|
154
|
+
The version of the task.
|
|
155
|
+
"""
|
|
156
|
+
return self.pb2.task_id.version
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def task_type(self) -> str:
|
|
160
|
+
"""
|
|
161
|
+
The type of the task.
|
|
162
|
+
"""
|
|
163
|
+
return self.pb2.spec.task_template.type
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def default_input_args(self) -> Tuple[str, ...]:
|
|
167
|
+
"""
|
|
168
|
+
The default input arguments of the task.
|
|
169
|
+
"""
|
|
170
|
+
return tuple(x.name for x in self.pb2.spec.default_inputs)
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def required_args(self) -> Tuple[str, ...]:
|
|
174
|
+
"""
|
|
175
|
+
The required input arguments of the task.
|
|
176
|
+
"""
|
|
177
|
+
return tuple(x for x, _ in self.interface.inputs.items() if x not in self.default_input_args)
|
|
178
|
+
|
|
179
|
+
@functools.cached_property
|
|
180
|
+
def interface(self) -> NativeInterface:
|
|
181
|
+
"""
|
|
182
|
+
The interface of the task.
|
|
183
|
+
"""
|
|
184
|
+
import flyte.types as types
|
|
185
|
+
|
|
186
|
+
return types.guess_interface(self.pb2.spec.task_template.interface, default_inputs=self.pb2.spec.default_inputs)
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def cache(self) -> flyte.Cache:
|
|
190
|
+
"""
|
|
191
|
+
The cache policy of the task.
|
|
192
|
+
"""
|
|
193
|
+
return flyte.Cache(
|
|
194
|
+
behavior="enabled" if self.pb2.spec.task_template.metadata.discoverable else "disable",
|
|
195
|
+
version_override=self.pb2.spec.task_template.metadata.discovery_version,
|
|
196
|
+
serialize=self.pb2.spec.task_template.metadata.cache_serializable,
|
|
197
|
+
ignored_inputs=tuple(self.pb2.spec.task_template.metadata.cache_ignore_input_vars),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def secrets(self):
|
|
202
|
+
"""
|
|
203
|
+
The secrets of the task.
|
|
204
|
+
"""
|
|
205
|
+
return [s.key for s in self.pb2.spec.task_template.security_context.secrets]
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def resources(self):
|
|
209
|
+
"""
|
|
210
|
+
The resources of the task.
|
|
211
|
+
"""
|
|
212
|
+
if self.pb2.spec.task_template.container is None:
|
|
213
|
+
return ()
|
|
214
|
+
return (
|
|
215
|
+
self.pb2.spec.task_template.container.resources.requests,
|
|
216
|
+
self.pb2.spec.task_template.container.resources.limits,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
async def __call__(self, *args, **kwargs):
|
|
220
|
+
"""
|
|
221
|
+
Forwards the call to the underlying task. The entity will be fetched if not already present
|
|
222
|
+
"""
|
|
223
|
+
# TODO support kwargs, for this we need ordered inputs to be stored in the task spec.
|
|
224
|
+
if len(args) > 0:
|
|
225
|
+
raise flyte.errors.ReferenceTaskError(
|
|
226
|
+
f"Reference task {self.name} does not support positional arguments"
|
|
227
|
+
f"currently. Please use keyword arguments."
|
|
228
|
+
)
|
|
229
|
+
if len(self.required_args) > 0:
|
|
230
|
+
if len(args) + len(kwargs) < len(self.required_args):
|
|
231
|
+
raise ValueError(
|
|
232
|
+
f"Task {self.name} requires at least {self.required_args} arguments, "
|
|
233
|
+
f"but only received args:{args} kwargs{kwargs}."
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
ctx = internal_ctx()
|
|
237
|
+
if ctx.is_task_context():
|
|
238
|
+
# If we are in a task context, that implies we are executing a Run.
|
|
239
|
+
# In this scenario, we should submit the task to the controller.
|
|
240
|
+
# We will also check if we are not initialized, It is not expected to be not initialized
|
|
241
|
+
from flyte._internal.controllers import get_controller
|
|
242
|
+
|
|
243
|
+
controller = get_controller()
|
|
244
|
+
if controller:
|
|
245
|
+
return await controller.submit_task_ref(self.pb2, *args, **kwargs)
|
|
246
|
+
raise flyte.errors
|
|
247
|
+
|
|
248
|
+
def override(
|
|
249
|
+
self,
|
|
250
|
+
*,
|
|
251
|
+
local: Optional[bool] = None,
|
|
252
|
+
ref: Optional[bool] = None,
|
|
253
|
+
resources: Optional[flyte.Resources] = None,
|
|
254
|
+
cache: flyte.CacheRequest = "auto",
|
|
255
|
+
retries: Union[int, flyte.RetryStrategy] = 0,
|
|
256
|
+
timeout: Optional[flyte.TimeoutType] = None,
|
|
257
|
+
reusable: Union[flyte.ReusePolicy, Literal["auto"], None] = None,
|
|
258
|
+
env: Optional[Dict[str, str]] = None,
|
|
259
|
+
secrets: Optional[flyte.SecretRequest] = None,
|
|
260
|
+
**kwargs: Any,
|
|
261
|
+
) -> TaskDetails:
|
|
262
|
+
raise NotImplementedError
|
|
263
|
+
|
|
264
|
+
def __rich_repr__(self) -> rich.repr.Result:
|
|
265
|
+
"""
|
|
266
|
+
Rich representation of the task.
|
|
267
|
+
"""
|
|
268
|
+
yield "friendly_name", self.pb2.spec.short_name
|
|
269
|
+
yield "environment", self.pb2.spec.environment
|
|
270
|
+
yield "default_inputs_keys", self.default_input_args
|
|
271
|
+
yield "required_args", self.required_args
|
|
272
|
+
yield "raw_default_inputs", [str(x) for x in self.pb2.spec.default_inputs]
|
|
273
|
+
yield "project", self.pb2.task_id.project
|
|
274
|
+
yield "domain", self.pb2.task_id.domain
|
|
275
|
+
yield "name", self.name
|
|
276
|
+
yield "version", self.version
|
|
277
|
+
yield "task_type", self.task_type
|
|
278
|
+
yield "cache", self.cache
|
|
279
|
+
yield "interface", self.name + str(self.interface)
|
|
280
|
+
yield "secrets", self.secrets
|
|
281
|
+
yield "resources", self.resources
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@dataclass
|
|
285
|
+
class Task:
|
|
286
|
+
pb2: task_definition_pb2.Task
|
|
287
|
+
|
|
288
|
+
def __init__(self, pb2: task_definition_pb2.Task):
|
|
289
|
+
self.pb2 = pb2
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def name(self) -> str:
|
|
293
|
+
"""
|
|
294
|
+
The name of the task.
|
|
295
|
+
"""
|
|
296
|
+
return self.pb2.task_id.name
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def version(self) -> str:
|
|
300
|
+
"""
|
|
301
|
+
The version of the task.
|
|
302
|
+
"""
|
|
303
|
+
return self.pb2.task_id.version
|
|
304
|
+
|
|
305
|
+
@classmethod
|
|
306
|
+
def get(cls, name: str, version: str | None = None, auto_version: AutoVersioning | None = None) -> LazyEntity:
|
|
307
|
+
"""
|
|
308
|
+
Get a task by its ID or name. If both are provided, the ID will take precedence.
|
|
309
|
+
|
|
310
|
+
Either version or auto_version are required parameters.
|
|
311
|
+
|
|
312
|
+
:param name: The name of the task.
|
|
313
|
+
:param version: The version of the task.
|
|
314
|
+
:param auto_version: If set to "latest", the latest-by-time ordered from now, version of the task will be used.
|
|
315
|
+
If set to "current", the version will be derived from the callee tasks context. This is useful if you are
|
|
316
|
+
deploying all environments with the same version. If auto_version is current, you can only access the task from
|
|
317
|
+
within a task context.
|
|
318
|
+
"""
|
|
319
|
+
return TaskDetails.get(name, version=version, auto_version=auto_version)
|
|
320
|
+
|
|
321
|
+
@syncify
|
|
322
|
+
@classmethod
|
|
323
|
+
async def listall(
|
|
324
|
+
cls,
|
|
325
|
+
by_task_name: str | None = None,
|
|
326
|
+
sort_by: Tuple[str, Literal["asc", "desc"]] | None = None,
|
|
327
|
+
limit: int = 100,
|
|
328
|
+
) -> Union[AsyncIterator[Task], Iterator[Task]]:
|
|
329
|
+
"""
|
|
330
|
+
Get all runs for the current project and domain.
|
|
331
|
+
|
|
332
|
+
:param by_task_name: If provided, only tasks with this name will be returned.
|
|
333
|
+
:param sort_by: The sorting criteria for the project list, in the format (field, order).
|
|
334
|
+
:param limit: The maximum number of tasks to return.
|
|
335
|
+
:return: An iterator of runs.
|
|
336
|
+
"""
|
|
337
|
+
ensure_client()
|
|
338
|
+
token = None
|
|
339
|
+
sort_by = sort_by or ("created_at", "asc")
|
|
340
|
+
sort_pb2 = list_pb2.Sort(
|
|
341
|
+
key=sort_by[0], direction=list_pb2.Sort.ASCENDING if sort_by[1] == "asc" else list_pb2.Sort.DESCENDING
|
|
342
|
+
)
|
|
343
|
+
cfg = get_common_config()
|
|
344
|
+
filters = []
|
|
345
|
+
if by_task_name:
|
|
346
|
+
filters.append(
|
|
347
|
+
list_pb2.Filter(
|
|
348
|
+
function=list_pb2.Filter.Function.EQUAL,
|
|
349
|
+
field="name",
|
|
350
|
+
values=[by_task_name],
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
original_limit = limit
|
|
354
|
+
if limit > cfg.batch_size:
|
|
355
|
+
limit = cfg.batch_size
|
|
356
|
+
retrieved = 0
|
|
357
|
+
while True:
|
|
358
|
+
resp = await get_client().task_service.ListTasks(
|
|
359
|
+
task_service_pb2.ListTasksRequest(
|
|
360
|
+
org=cfg.org,
|
|
361
|
+
request=list_pb2.ListRequest(
|
|
362
|
+
sort_by=sort_pb2,
|
|
363
|
+
filters=filters,
|
|
364
|
+
limit=limit,
|
|
365
|
+
token=token,
|
|
366
|
+
),
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
token = resp.token
|
|
370
|
+
for t in resp.tasks:
|
|
371
|
+
retrieved += 1
|
|
372
|
+
yield cls(t)
|
|
373
|
+
if not token or retrieved >= original_limit:
|
|
374
|
+
logger.debug(f"Retrieved {retrieved} tasks, stopping iteration.")
|
|
375
|
+
break
|
|
376
|
+
|
|
377
|
+
def __rich_repr__(self) -> rich.repr.Result:
|
|
378
|
+
"""
|
|
379
|
+
Rich representation of the task.
|
|
380
|
+
"""
|
|
381
|
+
yield "project", self.pb2.task_id.project
|
|
382
|
+
yield "domain", self.pb2.task_id.domain
|
|
383
|
+
yield "name", self.pb2.task_id.name
|
|
384
|
+
yield "version", self.pb2.task_id.version
|
|
385
|
+
yield "short_name", self.pb2.metadata.short_name
|
|
386
|
+
for t in _repr_task_metadata(self.pb2.metadata):
|
|
387
|
+
yield t
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
if __name__ == "__main__":
|
|
391
|
+
tk = Task.get(name="example_task")
|
flyte/report/__init__.py
ADDED
flyte/report/_report.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
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._internal.runtime import io
|
|
8
|
+
from flyte._logging import logger
|
|
9
|
+
from flyte._tools import ipython_check
|
|
10
|
+
from flyte.syncify import syncify
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from IPython.core.display import HTML
|
|
14
|
+
|
|
15
|
+
_MAIN_TAB_NAME = "main"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class Tab:
|
|
20
|
+
name: str
|
|
21
|
+
content: List[str] = field(default_factory=list, init=False)
|
|
22
|
+
|
|
23
|
+
def log(self, content: str):
|
|
24
|
+
"""
|
|
25
|
+
Add content to the tab.
|
|
26
|
+
The content should be a valid HTML string, but not a complete HTML document, as it will be inserted into a div.
|
|
27
|
+
|
|
28
|
+
:param content: The content to add.
|
|
29
|
+
"""
|
|
30
|
+
self.content.append(content)
|
|
31
|
+
|
|
32
|
+
def replace(self, content: str):
|
|
33
|
+
"""
|
|
34
|
+
Replace the content of the tab.
|
|
35
|
+
The content should be a valid HTML string, but not a complete HTML document, as it will be inserted into a div.
|
|
36
|
+
|
|
37
|
+
:param content: The content to replace.
|
|
38
|
+
"""
|
|
39
|
+
self.content = [content]
|
|
40
|
+
|
|
41
|
+
def get_html(self) -> str:
|
|
42
|
+
"""
|
|
43
|
+
Get the HTML representation of the tab.
|
|
44
|
+
|
|
45
|
+
:return: The HTML representation of the tab.
|
|
46
|
+
"""
|
|
47
|
+
return "\n".join(self.content)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class Report:
|
|
52
|
+
name: str
|
|
53
|
+
tabs: Dict[str, Tab] = field(default_factory=dict)
|
|
54
|
+
template_path: pathlib.Path = field(default_factory=lambda: pathlib.Path(__file__).parent / "_template.html")
|
|
55
|
+
|
|
56
|
+
def __post_init__(self):
|
|
57
|
+
self.tabs[_MAIN_TAB_NAME] = Tab(_MAIN_TAB_NAME)
|
|
58
|
+
|
|
59
|
+
def get_tab(self, name: str, create_if_missing: bool = True) -> Tab:
|
|
60
|
+
"""
|
|
61
|
+
Get a tab by name. If the tab does not exist, create it.
|
|
62
|
+
|
|
63
|
+
:param name: The name of the tab.
|
|
64
|
+
:param create_if_missing: Whether to create the tab if it does not exist.
|
|
65
|
+
:return: The tab.
|
|
66
|
+
"""
|
|
67
|
+
if name not in self.tabs:
|
|
68
|
+
if create_if_missing:
|
|
69
|
+
self.tabs[name] = Tab(name)
|
|
70
|
+
else:
|
|
71
|
+
raise ValueError(f"Tab {name} does not exist.")
|
|
72
|
+
return self.tabs[name]
|
|
73
|
+
|
|
74
|
+
def get_final_report(self) -> Union[str, "HTML"]:
|
|
75
|
+
"""
|
|
76
|
+
Get the final report as a string.
|
|
77
|
+
|
|
78
|
+
:return: The final report.
|
|
79
|
+
"""
|
|
80
|
+
tabs = {n: t.get_html() for n, t in self.tabs.items()}
|
|
81
|
+
nav_htmls = []
|
|
82
|
+
body_htmls = []
|
|
83
|
+
|
|
84
|
+
for key, value in tabs.items():
|
|
85
|
+
nav_htmls.append(f'<li onclick="handleLinkClick(this)">{html.escape(key)}</li>')
|
|
86
|
+
# Can not escape here because this is HTML. Escaping it will present the HTML as text.
|
|
87
|
+
# The renderer must ensure that the HTML is safe.
|
|
88
|
+
body_htmls.append(f"<div>{value}</div>")
|
|
89
|
+
|
|
90
|
+
template = string.Template(self.template_path.open("r").read())
|
|
91
|
+
|
|
92
|
+
raw_html = template.substitute(NAV_HTML="".join(nav_htmls), BODY_HTML="".join(body_htmls))
|
|
93
|
+
if ipython_check():
|
|
94
|
+
try:
|
|
95
|
+
from IPython.core.display import HTML
|
|
96
|
+
|
|
97
|
+
return HTML(raw_html)
|
|
98
|
+
except ImportError:
|
|
99
|
+
...
|
|
100
|
+
return raw_html
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_tab(name: str, /, create_if_missing: bool = True) -> Tab:
|
|
104
|
+
"""
|
|
105
|
+
Get a tab by name. If the tab does not exist, create it.
|
|
106
|
+
|
|
107
|
+
:param name: The name of the tab.
|
|
108
|
+
:param create_if_missing: Whether to create the tab if it does not exist.
|
|
109
|
+
:return: The tab.
|
|
110
|
+
"""
|
|
111
|
+
report = current_report()
|
|
112
|
+
return report.get_tab(name, create_if_missing=create_if_missing)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@syncify
|
|
116
|
+
async def log(content: str, do_flush: bool = False):
|
|
117
|
+
"""
|
|
118
|
+
Log content to the main tab. The content should be a valid HTML string, but not a complete HTML document,
|
|
119
|
+
as it will be inserted into a div.
|
|
120
|
+
|
|
121
|
+
:param content: The content to log.
|
|
122
|
+
:param do_flush: flush the report after logging.
|
|
123
|
+
"""
|
|
124
|
+
get_tab(_MAIN_TAB_NAME).log(content)
|
|
125
|
+
if do_flush:
|
|
126
|
+
await flush.aio()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@syncify
|
|
130
|
+
async def flush():
|
|
131
|
+
"""
|
|
132
|
+
Flush the report.
|
|
133
|
+
"""
|
|
134
|
+
import flyte.storage as storage
|
|
135
|
+
from flyte._context import internal_ctx
|
|
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
|
+
final_path = await storage.put_stream(report_html.encode("utf-8"), to_path=report_path)
|
|
149
|
+
logger.debug(f"Report flushed to {final_path}")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@syncify
|
|
153
|
+
async def replace(content: str, do_flush: bool = False):
|
|
154
|
+
"""
|
|
155
|
+
Get the report. Replaces the content of the main tab.
|
|
156
|
+
|
|
157
|
+
:return: The report.
|
|
158
|
+
"""
|
|
159
|
+
report = current_report()
|
|
160
|
+
if report is None:
|
|
161
|
+
return
|
|
162
|
+
report.get_tab(_MAIN_TAB_NAME).replace(content)
|
|
163
|
+
if do_flush:
|
|
164
|
+
await flush.aio()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def current_report() -> Report:
|
|
168
|
+
"""
|
|
169
|
+
Get the current report. This is a dummy report if not in a task context.
|
|
170
|
+
|
|
171
|
+
:return: The current report.
|
|
172
|
+
"""
|
|
173
|
+
from flyte._context import internal_ctx
|
|
174
|
+
|
|
175
|
+
report = internal_ctx().get_report()
|
|
176
|
+
if report is None:
|
|
177
|
+
report = Report("dummy")
|
|
178
|
+
return report
|