flyte 0.0.1b3__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 +20 -4
- flyte/_bin/runtime.py +33 -7
- flyte/_build.py +3 -2
- flyte/_cache/cache.py +1 -2
- flyte/_code_bundle/_packaging.py +1 -1
- flyte/_code_bundle/_utils.py +0 -16
- flyte/_code_bundle/bundle.py +43 -12
- flyte/_context.py +8 -2
- flyte/_deploy.py +56 -15
- flyte/_environment.py +45 -4
- flyte/_excepthook.py +37 -0
- flyte/_group.py +2 -1
- flyte/_image.py +8 -4
- flyte/_initialize.py +112 -254
- flyte/_interface.py +3 -3
- flyte/_internal/controllers/__init__.py +19 -6
- flyte/_internal/controllers/_local_controller.py +83 -8
- flyte/_internal/controllers/_trace.py +2 -1
- flyte/_internal/controllers/remote/__init__.py +27 -7
- flyte/_internal/controllers/remote/_action.py +7 -2
- flyte/_internal/controllers/remote/_client.py +5 -1
- flyte/_internal/controllers/remote/_controller.py +159 -26
- flyte/_internal/controllers/remote/_core.py +13 -5
- flyte/_internal/controllers/remote/_informer.py +4 -4
- flyte/_internal/controllers/remote/_service_protocol.py +6 -6
- flyte/_internal/imagebuild/docker_builder.py +12 -1
- flyte/_internal/imagebuild/image_builder.py +16 -11
- flyte/_internal/runtime/convert.py +164 -21
- flyte/_internal/runtime/entrypoints.py +1 -1
- flyte/_internal/runtime/io.py +3 -3
- flyte/_internal/runtime/task_serde.py +140 -20
- flyte/_internal/runtime/taskrunner.py +4 -3
- flyte/_internal/runtime/types_serde.py +1 -1
- flyte/_logging.py +12 -1
- flyte/_map.py +215 -0
- flyte/_pod.py +19 -0
- flyte/_protos/common/list_pb2.py +3 -3
- flyte/_protos/common/list_pb2.pyi +2 -0
- flyte/_protos/logs/dataplane/payload_pb2.py +28 -24
- flyte/_protos/logs/dataplane/payload_pb2.pyi +11 -2
- flyte/_protos/workflow/common_pb2.py +27 -0
- flyte/_protos/workflow/common_pb2.pyi +14 -0
- flyte/_protos/workflow/environment_pb2.py +29 -0
- flyte/_protos/workflow/environment_pb2.pyi +12 -0
- flyte/_protos/workflow/queue_service_pb2.py +40 -41
- flyte/_protos/workflow/queue_service_pb2.pyi +35 -30
- flyte/_protos/workflow/queue_service_pb2_grpc.py +15 -15
- flyte/_protos/workflow/run_definition_pb2.py +61 -61
- flyte/_protos/workflow/run_definition_pb2.pyi +8 -4
- flyte/_protos/workflow/run_service_pb2.py +20 -24
- flyte/_protos/workflow/run_service_pb2.pyi +2 -6
- flyte/_protos/workflow/state_service_pb2.py +36 -28
- flyte/_protos/workflow/state_service_pb2.pyi +19 -15
- flyte/_protos/workflow/state_service_pb2_grpc.py +28 -28
- flyte/_protos/workflow/task_definition_pb2.py +29 -22
- flyte/_protos/workflow/task_definition_pb2.pyi +21 -5
- flyte/_protos/workflow/task_service_pb2.py +27 -11
- flyte/_protos/workflow/task_service_pb2.pyi +29 -1
- flyte/_protos/workflow/task_service_pb2_grpc.py +34 -0
- flyte/_run.py +166 -95
- flyte/_task.py +110 -28
- flyte/_task_environment.py +55 -72
- flyte/_trace.py +6 -14
- flyte/_utils/__init__.py +6 -0
- flyte/_utils/async_cache.py +139 -0
- flyte/_utils/coro_management.py +0 -2
- flyte/_utils/helpers.py +45 -19
- flyte/_utils/org_discovery.py +57 -0
- flyte/_version.py +2 -2
- flyte/cli/__init__.py +3 -0
- flyte/cli/_abort.py +28 -0
- flyte/{_cli → cli}/_common.py +73 -23
- flyte/cli/_create.py +145 -0
- flyte/{_cli → cli}/_delete.py +4 -4
- flyte/{_cli → cli}/_deploy.py +26 -14
- flyte/cli/_gen.py +163 -0
- flyte/{_cli → cli}/_get.py +98 -23
- {union/_cli → flyte/cli}/_params.py +106 -147
- flyte/{_cli → cli}/_run.py +99 -20
- 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/errors.py +29 -0
- flyte/extras/_container.py +33 -43
- flyte/io/__init__.py +17 -1
- flyte/io/_dir.py +2 -2
- flyte/io/_file.py +3 -4
- flyte/io/{structured_dataset → _structured_dataset}/basic_dfs.py +1 -1
- flyte/io/{structured_dataset → _structured_dataset}/structured_dataset.py +1 -1
- flyte/{_datastructures.py → models.py} +56 -7
- flyte/remote/__init__.py +2 -1
- flyte/remote/_client/_protocols.py +2 -0
- flyte/remote/_client/auth/_auth_utils.py +14 -0
- flyte/remote/_client/auth/_channel.py +34 -3
- flyte/remote/_client/auth/_token_client.py +3 -3
- flyte/remote/_client/controlplane.py +13 -13
- flyte/remote/_console.py +1 -1
- flyte/remote/_data.py +10 -6
- flyte/remote/_logs.py +89 -29
- flyte/remote/_project.py +8 -9
- flyte/remote/_run.py +228 -131
- flyte/remote/_secret.py +12 -12
- flyte/remote/_task.py +179 -15
- flyte/report/_report.py +4 -4
- flyte/storage/__init__.py +5 -0
- flyte/storage/_config.py +233 -0
- flyte/storage/_storage.py +23 -3
- flyte/syncify/__init__.py +56 -0
- flyte/syncify/_api.py +371 -0
- flyte/types/__init__.py +23 -0
- flyte/types/_interface.py +22 -7
- flyte/{io/pickle/transformer.py → types/_pickle.py} +2 -1
- flyte/types/_type_engine.py +95 -18
- flyte-0.2.0a0.dist-info/METADATA +249 -0
- flyte-0.2.0a0.dist-info/RECORD +218 -0
- {flyte-0.0.1b3.dist-info → flyte-0.2.0a0.dist-info}/entry_points.txt +1 -1
- flyte/_api_commons.py +0 -3
- flyte/_cli/__init__.py +0 -0
- flyte/_cli/_create.py +0 -42
- flyte/_cli/main.py +0 -72
- flyte/_internal/controllers/pbhash.py +0 -39
- flyte/io/_dataframe.py +0 -0
- flyte/io/pickle/__init__.py +0 -0
- flyte-0.0.1b3.dist-info/METADATA +0 -179
- flyte-0.0.1b3.dist-info/RECORD +0 -390
- union/__init__.py +0 -54
- union/_api_commons.py +0 -3
- union/_bin/__init__.py +0 -0
- union/_bin/runtime.py +0 -113
- union/_build.py +0 -25
- union/_cache/__init__.py +0 -12
- union/_cache/cache.py +0 -141
- union/_cache/defaults.py +0 -9
- union/_cache/policy_function_body.py +0 -42
- union/_cli/__init__.py +0 -0
- union/_cli/_common.py +0 -263
- union/_cli/_create.py +0 -40
- union/_cli/_delete.py +0 -23
- union/_cli/_deploy.py +0 -120
- union/_cli/_get.py +0 -162
- union/_cli/_run.py +0 -150
- union/_cli/main.py +0 -72
- union/_code_bundle/__init__.py +0 -8
- union/_code_bundle/_ignore.py +0 -113
- union/_code_bundle/_packaging.py +0 -187
- union/_code_bundle/_utils.py +0 -342
- union/_code_bundle/bundle.py +0 -176
- union/_context.py +0 -146
- union/_datastructures.py +0 -295
- union/_deploy.py +0 -185
- union/_doc.py +0 -29
- union/_docstring.py +0 -26
- union/_environment.py +0 -43
- union/_group.py +0 -31
- union/_hash.py +0 -23
- union/_image.py +0 -760
- union/_initialize.py +0 -585
- union/_interface.py +0 -84
- union/_internal/__init__.py +0 -3
- union/_internal/controllers/__init__.py +0 -77
- union/_internal/controllers/_local_controller.py +0 -77
- union/_internal/controllers/pbhash.py +0 -39
- union/_internal/controllers/remote/__init__.py +0 -40
- union/_internal/controllers/remote/_action.py +0 -131
- union/_internal/controllers/remote/_client.py +0 -43
- union/_internal/controllers/remote/_controller.py +0 -169
- union/_internal/controllers/remote/_core.py +0 -341
- union/_internal/controllers/remote/_informer.py +0 -260
- union/_internal/controllers/remote/_service_protocol.py +0 -44
- union/_internal/imagebuild/__init__.py +0 -11
- union/_internal/imagebuild/docker_builder.py +0 -416
- union/_internal/imagebuild/image_builder.py +0 -243
- union/_internal/imagebuild/remote_builder.py +0 -0
- union/_internal/resolvers/__init__.py +0 -0
- union/_internal/resolvers/_task_module.py +0 -31
- union/_internal/resolvers/common.py +0 -24
- union/_internal/resolvers/default.py +0 -27
- union/_internal/runtime/__init__.py +0 -0
- union/_internal/runtime/convert.py +0 -163
- union/_internal/runtime/entrypoints.py +0 -121
- union/_internal/runtime/io.py +0 -136
- union/_internal/runtime/resources_serde.py +0 -134
- union/_internal/runtime/task_serde.py +0 -202
- union/_internal/runtime/taskrunner.py +0 -179
- union/_internal/runtime/types_serde.py +0 -53
- union/_logging.py +0 -124
- union/_protos/__init__.py +0 -0
- union/_protos/common/authorization_pb2.py +0 -66
- union/_protos/common/authorization_pb2.pyi +0 -106
- union/_protos/common/identifier_pb2.py +0 -71
- union/_protos/common/identifier_pb2.pyi +0 -82
- union/_protos/common/identity_pb2.py +0 -48
- union/_protos/common/identity_pb2.pyi +0 -72
- union/_protos/common/identity_pb2_grpc.py +0 -4
- union/_protos/common/list_pb2.py +0 -36
- union/_protos/common/list_pb2.pyi +0 -69
- union/_protos/common/list_pb2_grpc.py +0 -4
- union/_protos/common/policy_pb2.py +0 -37
- union/_protos/common/policy_pb2.pyi +0 -27
- union/_protos/common/policy_pb2_grpc.py +0 -4
- union/_protos/common/role_pb2.py +0 -37
- union/_protos/common/role_pb2.pyi +0 -51
- union/_protos/common/role_pb2_grpc.py +0 -4
- union/_protos/common/runtime_version_pb2.py +0 -28
- union/_protos/common/runtime_version_pb2.pyi +0 -24
- union/_protos/common/runtime_version_pb2_grpc.py +0 -4
- union/_protos/logs/dataplane/payload_pb2.py +0 -96
- union/_protos/logs/dataplane/payload_pb2.pyi +0 -168
- union/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
- union/_protos/secret/definition_pb2.py +0 -49
- union/_protos/secret/definition_pb2.pyi +0 -93
- union/_protos/secret/definition_pb2_grpc.py +0 -4
- union/_protos/secret/payload_pb2.py +0 -62
- union/_protos/secret/payload_pb2.pyi +0 -94
- union/_protos/secret/payload_pb2_grpc.py +0 -4
- union/_protos/secret/secret_pb2.py +0 -38
- union/_protos/secret/secret_pb2.pyi +0 -6
- union/_protos/secret/secret_pb2_grpc.py +0 -198
- union/_protos/validate/validate/validate_pb2.py +0 -76
- union/_protos/workflow/node_execution_service_pb2.py +0 -26
- union/_protos/workflow/node_execution_service_pb2.pyi +0 -4
- union/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
- union/_protos/workflow/queue_service_pb2.py +0 -75
- union/_protos/workflow/queue_service_pb2.pyi +0 -103
- union/_protos/workflow/queue_service_pb2_grpc.py +0 -172
- union/_protos/workflow/run_definition_pb2.py +0 -100
- union/_protos/workflow/run_definition_pb2.pyi +0 -256
- union/_protos/workflow/run_definition_pb2_grpc.py +0 -4
- union/_protos/workflow/run_logs_service_pb2.py +0 -41
- union/_protos/workflow/run_logs_service_pb2.pyi +0 -28
- union/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
- union/_protos/workflow/run_service_pb2.py +0 -133
- union/_protos/workflow/run_service_pb2.pyi +0 -173
- union/_protos/workflow/run_service_pb2_grpc.py +0 -412
- union/_protos/workflow/state_service_pb2.py +0 -58
- union/_protos/workflow/state_service_pb2.pyi +0 -69
- union/_protos/workflow/state_service_pb2_grpc.py +0 -138
- union/_protos/workflow/task_definition_pb2.py +0 -72
- union/_protos/workflow/task_definition_pb2.pyi +0 -65
- union/_protos/workflow/task_definition_pb2_grpc.py +0 -4
- union/_protos/workflow/task_service_pb2.py +0 -44
- union/_protos/workflow/task_service_pb2.pyi +0 -31
- union/_protos/workflow/task_service_pb2_grpc.py +0 -104
- union/_resources.py +0 -226
- union/_retry.py +0 -32
- union/_reusable_environment.py +0 -25
- union/_run.py +0 -374
- union/_secret.py +0 -61
- union/_task.py +0 -354
- union/_task_environment.py +0 -186
- union/_timeout.py +0 -47
- union/_tools.py +0 -27
- union/_utils/__init__.py +0 -11
- union/_utils/asyn.py +0 -119
- union/_utils/file_handling.py +0 -71
- union/_utils/helpers.py +0 -46
- union/_utils/lazy_module.py +0 -54
- union/_utils/uv_script_parser.py +0 -49
- union/_version.py +0 -21
- union/connectors/__init__.py +0 -0
- union/errors.py +0 -128
- union/extras/__init__.py +0 -5
- union/extras/_container.py +0 -263
- union/io/__init__.py +0 -11
- union/io/_dataframe.py +0 -0
- union/io/_dir.py +0 -425
- union/io/_file.py +0 -418
- union/io/pickle/__init__.py +0 -0
- union/io/pickle/transformer.py +0 -117
- union/io/structured_dataset/__init__.py +0 -122
- union/io/structured_dataset/basic_dfs.py +0 -219
- union/io/structured_dataset/structured_dataset.py +0 -1057
- union/py.typed +0 -0
- union/remote/__init__.py +0 -23
- union/remote/_client/__init__.py +0 -0
- union/remote/_client/_protocols.py +0 -129
- union/remote/_client/auth/__init__.py +0 -12
- union/remote/_client/auth/_authenticators/__init__.py +0 -0
- union/remote/_client/auth/_authenticators/base.py +0 -391
- union/remote/_client/auth/_authenticators/client_credentials.py +0 -73
- union/remote/_client/auth/_authenticators/device_code.py +0 -120
- union/remote/_client/auth/_authenticators/external_command.py +0 -77
- union/remote/_client/auth/_authenticators/factory.py +0 -200
- union/remote/_client/auth/_authenticators/pkce.py +0 -515
- union/remote/_client/auth/_channel.py +0 -184
- union/remote/_client/auth/_client_config.py +0 -83
- union/remote/_client/auth/_default_html.py +0 -32
- union/remote/_client/auth/_grpc_utils/__init__.py +0 -0
- union/remote/_client/auth/_grpc_utils/auth_interceptor.py +0 -204
- union/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +0 -144
- union/remote/_client/auth/_keyring.py +0 -154
- union/remote/_client/auth/_token_client.py +0 -258
- union/remote/_client/auth/errors.py +0 -16
- union/remote/_client/controlplane.py +0 -86
- union/remote/_data.py +0 -149
- union/remote/_logs.py +0 -74
- union/remote/_project.py +0 -86
- union/remote/_run.py +0 -820
- union/remote/_secret.py +0 -132
- union/remote/_task.py +0 -193
- union/report/__init__.py +0 -3
- union/report/_report.py +0 -178
- union/report/_template.html +0 -124
- union/storage/__init__.py +0 -24
- union/storage/_remote_fs.py +0 -34
- union/storage/_storage.py +0 -247
- union/storage/_utils.py +0 -5
- union/types/__init__.py +0 -11
- union/types/_renderer.py +0 -162
- union/types/_string_literals.py +0 -120
- union/types/_type_engine.py +0 -2131
- union/types/_utils.py +0 -80
- /union/_protos/common/authorization_pb2_grpc.py → /flyte/_protos/workflow/common_pb2_grpc.py +0 -0
- /union/_protos/common/identifier_pb2_grpc.py → /flyte/_protos/workflow/environment_pb2_grpc.py +0 -0
- /flyte/io/{structured_dataset → _structured_dataset}/__init__.py +0 -0
- {flyte-0.0.1b3.dist-info → flyte-0.2.0a0.dist-info}/WHEEL +0 -0
- {flyte-0.0.1b3.dist-info → flyte-0.2.0a0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import time
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
from typing import Awaitable, Callable, Dict, Generic, Optional, TypeVar
|
|
5
|
+
|
|
6
|
+
K = TypeVar("K")
|
|
7
|
+
V = TypeVar("V")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AsyncLRUCache(Generic[K, V]):
|
|
11
|
+
"""
|
|
12
|
+
A high-performance async-compatible LRU cache.
|
|
13
|
+
|
|
14
|
+
Examples:
|
|
15
|
+
```python
|
|
16
|
+
# Create a cache instance
|
|
17
|
+
cache = AsyncLRUCache[str, dict](maxsize=100)
|
|
18
|
+
|
|
19
|
+
async def fetch_data(user_id: str) -> dict:
|
|
20
|
+
# Define the expensive operation as a local function
|
|
21
|
+
async def get_user_data():
|
|
22
|
+
await asyncio.sleep(1) # Simulating network/DB delay
|
|
23
|
+
return {"id": user_id, "name": f"User {user_id}"}
|
|
24
|
+
|
|
25
|
+
# Use the cache
|
|
26
|
+
return await cache.get(f"user:{user_id}", get_user_data)
|
|
27
|
+
```
|
|
28
|
+
This cache can be used from async coroutines and handles concurrent access safely.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, maxsize: int = 128, ttl: Optional[float] = None):
|
|
32
|
+
"""
|
|
33
|
+
Initialize the async LRU cache.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
maxsize: Maximum number of items to keep in the cache
|
|
37
|
+
ttl: Time-to-live for cache entries in seconds, or None for no expiration
|
|
38
|
+
"""
|
|
39
|
+
self._cache: OrderedDict[K, tuple[V, float]] = OrderedDict()
|
|
40
|
+
self._maxsize = maxsize
|
|
41
|
+
self._ttl = ttl
|
|
42
|
+
self._locks: Dict[K, asyncio.Lock] = {}
|
|
43
|
+
self._access_lock = asyncio.Lock()
|
|
44
|
+
|
|
45
|
+
async def get(self, key: K, value_func: Callable[[], V | Awaitable[V]]) -> V:
|
|
46
|
+
"""
|
|
47
|
+
Get a value from the cache, computing it if necessary.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
key: The cache key
|
|
51
|
+
value_func: Function or coroutine to compute the value if not cached
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
The cached or computed value
|
|
55
|
+
"""
|
|
56
|
+
# Fast path: check if key exists and is not expired
|
|
57
|
+
if key in self._cache:
|
|
58
|
+
value, timestamp = self._cache[key]
|
|
59
|
+
if self._ttl is None or time.time() - timestamp < self._ttl:
|
|
60
|
+
# Move the accessed item to the end (most recently used)
|
|
61
|
+
async with self._access_lock:
|
|
62
|
+
self._cache.move_to_end(key)
|
|
63
|
+
return value
|
|
64
|
+
|
|
65
|
+
# Slow path: compute the value
|
|
66
|
+
# Get or create a lock for this key to prevent redundant computation
|
|
67
|
+
async with self._access_lock:
|
|
68
|
+
lock = self._locks.get(key)
|
|
69
|
+
if lock is None:
|
|
70
|
+
lock = asyncio.Lock()
|
|
71
|
+
self._locks[key] = lock
|
|
72
|
+
|
|
73
|
+
async with lock:
|
|
74
|
+
# Check again in case another coroutine computed the value while we waited
|
|
75
|
+
if key in self._cache:
|
|
76
|
+
value, timestamp = self._cache[key]
|
|
77
|
+
if self._ttl is None or time.time() - timestamp < self._ttl:
|
|
78
|
+
async with self._access_lock:
|
|
79
|
+
self._cache.move_to_end(key)
|
|
80
|
+
return value
|
|
81
|
+
|
|
82
|
+
# Compute the value
|
|
83
|
+
if asyncio.iscoroutinefunction(value_func):
|
|
84
|
+
value = await value_func()
|
|
85
|
+
else:
|
|
86
|
+
value = value_func() # type: ignore
|
|
87
|
+
|
|
88
|
+
# Store in cache
|
|
89
|
+
async with self._access_lock:
|
|
90
|
+
self._cache[key] = (value, time.time())
|
|
91
|
+
# Evict least recently used items if needed
|
|
92
|
+
while len(self._cache) > self._maxsize:
|
|
93
|
+
self._cache.popitem(last=False)
|
|
94
|
+
# Clean up the lock
|
|
95
|
+
self._locks.pop(key, None)
|
|
96
|
+
|
|
97
|
+
return value
|
|
98
|
+
|
|
99
|
+
async def set(self, key: K, value: V) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Explicitly set a value in the cache.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
key: The cache key
|
|
105
|
+
value: The value to cache
|
|
106
|
+
"""
|
|
107
|
+
async with self._access_lock:
|
|
108
|
+
self._cache[key] = (value, time.time())
|
|
109
|
+
# Evict least recently used items if needed
|
|
110
|
+
while len(self._cache) > self._maxsize:
|
|
111
|
+
self._cache.popitem(last=False)
|
|
112
|
+
|
|
113
|
+
async def invalidate(self, key: K) -> None:
|
|
114
|
+
"""Remove a specific key from the cache."""
|
|
115
|
+
async with self._access_lock:
|
|
116
|
+
self._cache.pop(key, None)
|
|
117
|
+
|
|
118
|
+
async def clear(self) -> None:
|
|
119
|
+
"""Clear the entire cache."""
|
|
120
|
+
async with self._access_lock:
|
|
121
|
+
self._cache.clear()
|
|
122
|
+
self._locks.clear()
|
|
123
|
+
|
|
124
|
+
async def contains(self, key: K) -> bool:
|
|
125
|
+
"""Check if a key exists in the cache and is not expired."""
|
|
126
|
+
if key not in self._cache:
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
if self._ttl is None:
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
_, timestamp = self._cache[key]
|
|
133
|
+
return time.time() - timestamp < self._ttl
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# Example usage:
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
"""
|
flyte/_utils/coro_management.py
CHANGED
flyte/_utils/helpers.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import string
|
|
3
3
|
import typing
|
|
4
|
+
from contextlib import contextmanager
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
|
|
@@ -51,7 +52,33 @@ def base36_encode(byte_data: bytes) -> str:
|
|
|
51
52
|
return "".join(reversed(base36))
|
|
52
53
|
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
def _iter_editable():
|
|
56
|
+
"""
|
|
57
|
+
Yield (project_name, source_path) for every editable distribution
|
|
58
|
+
visible to the current interpreter
|
|
59
|
+
"""
|
|
60
|
+
import json
|
|
61
|
+
import pathlib
|
|
62
|
+
from importlib.metadata import distributions
|
|
63
|
+
|
|
64
|
+
for dist in distributions():
|
|
65
|
+
# PEP-610 / PEP-660 (preferred, wheel-style editables)
|
|
66
|
+
direct = dist.read_text("direct_url.json")
|
|
67
|
+
if direct:
|
|
68
|
+
data = json.loads(direct)
|
|
69
|
+
if data.get("dir_info", {}).get("editable"): # spec key
|
|
70
|
+
# todo: will need testing on windows
|
|
71
|
+
yield dist.metadata["Name"], pathlib.Path(data["url"][7:]) # strip file://
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
# Legacy setuptools-develop / pip-e (egg-link)
|
|
75
|
+
for file in dist.files or (): # importlib.metadata 3.8+
|
|
76
|
+
if file.suffix == ".egg-link":
|
|
77
|
+
with open(dist.locate_file(file), "r") as f:
|
|
78
|
+
line = f.readline()
|
|
79
|
+
yield dist.metadata["Name"], pathlib.Path(line.strip())
|
|
80
|
+
|
|
81
|
+
|
|
55
82
|
def get_cwd_editable_install() -> typing.Optional[Path]:
|
|
56
83
|
"""
|
|
57
84
|
This helper function is incomplete since it hasn't been tested with all the package managers out there,
|
|
@@ -64,28 +91,13 @@ def get_cwd_editable_install() -> typing.Optional[Path]:
|
|
|
64
91
|
|
|
65
92
|
:return:
|
|
66
93
|
"""
|
|
67
|
-
import site
|
|
68
94
|
|
|
69
95
|
from flyte._logging import logger
|
|
70
96
|
|
|
71
|
-
egg_links = [Path(p) for p in Path(site.getsitepackages()[0]).glob("*.egg-link")]
|
|
72
|
-
pth_files = [Path(p) for p in Path(site.getsitepackages()[0]).glob("*.pth")]
|
|
73
|
-
|
|
74
|
-
if not egg_links and not pth_files:
|
|
75
|
-
logger.debug("No editable installs found.")
|
|
76
|
-
return None
|
|
77
|
-
|
|
78
97
|
editable_installs = []
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
line = f.readline()
|
|
83
|
-
if line:
|
|
84
|
-
# Check if the first line is a directory
|
|
85
|
-
p = Path(line)
|
|
86
|
-
if p.is_dir():
|
|
87
|
-
editable_installs.append(p)
|
|
88
|
-
logger.debug(f"Editable installs: {editable_installs}")
|
|
98
|
+
for name, path in _iter_editable():
|
|
99
|
+
logger.debug(f"Detected editable install: {name} at {path}")
|
|
100
|
+
editable_installs.append(path)
|
|
89
101
|
|
|
90
102
|
# check to see if the current working directory is in any of the editable installs
|
|
91
103
|
# including if the current folder is the root folder, one level up from the src and contains
|
|
@@ -106,3 +118,17 @@ def get_cwd_editable_install() -> typing.Optional[Path]:
|
|
|
106
118
|
return install # note we want the install folder, not the parent
|
|
107
119
|
|
|
108
120
|
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@contextmanager
|
|
124
|
+
def _selector_policy():
|
|
125
|
+
import asyncio
|
|
126
|
+
|
|
127
|
+
original_policy = asyncio.get_event_loop_policy()
|
|
128
|
+
try:
|
|
129
|
+
if os.name == "nt" and hasattr(asyncio, "WindowsSelectorEventLoopPolicy"):
|
|
130
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
131
|
+
|
|
132
|
+
yield
|
|
133
|
+
finally:
|
|
134
|
+
asyncio.set_event_loop_policy(original_policy)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
def hostname_from_url(url: str) -> str:
|
|
2
|
+
"""Parse a URL and return the hostname part."""
|
|
3
|
+
|
|
4
|
+
# Handle dns:/// format specifically (gRPC convention)
|
|
5
|
+
if url.startswith("dns:///"):
|
|
6
|
+
return url[7:] # Skip the "dns:///" prefix
|
|
7
|
+
|
|
8
|
+
# Handle standard URL formats
|
|
9
|
+
import urllib.parse
|
|
10
|
+
|
|
11
|
+
parsed = urllib.parse.urlparse(url)
|
|
12
|
+
return parsed.netloc or parsed.path.lstrip("/").rsplit("/")[0]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def org_from_endpoint(endpoint: str | None) -> str | None:
|
|
16
|
+
"""
|
|
17
|
+
Extracts the organization from the endpoint URL. The organization is assumed to be the first part of the domain.
|
|
18
|
+
This is temporary until we have a proper organization discovery mechanism through APIs.
|
|
19
|
+
|
|
20
|
+
:param endpoint: The endpoint URL
|
|
21
|
+
:return: The organization name or None if not found
|
|
22
|
+
"""
|
|
23
|
+
if not endpoint:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
hostname = hostname_from_url(endpoint)
|
|
27
|
+
domain_parts = hostname.split(".")
|
|
28
|
+
if len(domain_parts) > 2:
|
|
29
|
+
# Assuming the organization is the first part of the domain
|
|
30
|
+
return domain_parts[0]
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def sanitize_endpoint(endpoint: str | None) -> str | None:
|
|
35
|
+
"""
|
|
36
|
+
Sanitize the endpoint URL by ensuring it has a valid scheme.
|
|
37
|
+
:param endpoint: The endpoint URL to sanitize
|
|
38
|
+
:return: Sanitized endpoint URL or None if the input was None
|
|
39
|
+
"""
|
|
40
|
+
if not endpoint:
|
|
41
|
+
return None
|
|
42
|
+
if "://" not in endpoint:
|
|
43
|
+
endpoint = f"dns:///{endpoint}"
|
|
44
|
+
else:
|
|
45
|
+
if endpoint.startswith("https://"):
|
|
46
|
+
# If the endpoint starts with dns:///, we assume it's a gRPC endpoint
|
|
47
|
+
endpoint = f"dns:///{endpoint[8:]}"
|
|
48
|
+
elif endpoint.startswith("http://"):
|
|
49
|
+
# If the endpoint starts with http://, we assume it's a REST endpoint
|
|
50
|
+
endpoint = f"dns:///{endpoint[7:]}"
|
|
51
|
+
elif not endpoint.startswith("dns:///"):
|
|
52
|
+
raise RuntimeError(
|
|
53
|
+
f"Invalid endpoint {endpoint}, expected format is "
|
|
54
|
+
f"dns:///<hostname> or https://<hostname> or http://<hostname>"
|
|
55
|
+
)
|
|
56
|
+
endpoint = endpoint.removesuffix("/")
|
|
57
|
+
return endpoint
|
flyte/_version.py
CHANGED
|
@@ -17,5 +17,5 @@ __version__: str
|
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
|
18
18
|
version_tuple: VERSION_TUPLE
|
|
19
19
|
|
|
20
|
-
__version__ = version = '0.
|
|
21
|
-
__version_tuple__ = version_tuple = (0,
|
|
20
|
+
__version__ = version = '0.2.0a0'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 2, 0, 'a0')
|
flyte/cli/__init__.py
ADDED
flyte/cli/_abort.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import rich_click as click
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
|
|
4
|
+
from flyte.cli import _common as common
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.group(name="abort")
|
|
8
|
+
def abort():
|
|
9
|
+
"""
|
|
10
|
+
Abort an ongoing process.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@abort.command(cls=common.CommandBase)
|
|
15
|
+
@click.argument("run-name", type=str, required=True)
|
|
16
|
+
@click.pass_obj
|
|
17
|
+
def run(cfg: common.CLIConfig, run_name: str, project: str | None = None, domain: str | None = None):
|
|
18
|
+
"""
|
|
19
|
+
Abort a run.
|
|
20
|
+
"""
|
|
21
|
+
from flyte.remote import Run
|
|
22
|
+
|
|
23
|
+
cfg.init(project=project, domain=domain)
|
|
24
|
+
r = Run.get(name=run_name)
|
|
25
|
+
if r:
|
|
26
|
+
console = Console()
|
|
27
|
+
r.abort()
|
|
28
|
+
console.print(f"Run '{run_name}' has been aborted.")
|
flyte/{_cli → cli}/_common.py
RENAMED
|
@@ -11,22 +11,28 @@ from types import MappingProxyType, ModuleType
|
|
|
11
11
|
from typing import Any, Dict, Iterable, List, Optional
|
|
12
12
|
|
|
13
13
|
import rich.box
|
|
14
|
+
import rich.repr
|
|
14
15
|
import rich_click as click
|
|
16
|
+
from rich.console import Console
|
|
15
17
|
from rich.panel import Panel
|
|
16
18
|
from rich.table import Table
|
|
19
|
+
from rich.traceback import Traceback
|
|
17
20
|
|
|
18
21
|
import flyte.errors
|
|
22
|
+
from flyte._logging import logger
|
|
23
|
+
from flyte.config import Config
|
|
19
24
|
|
|
20
25
|
PREFERRED_BORDER_COLOR = "dim cyan"
|
|
21
26
|
PREFERRED_ACCENT_COLOR = "bold #FFD700"
|
|
22
27
|
HEADER_STYLE = f"{PREFERRED_ACCENT_COLOR} on black"
|
|
28
|
+
PANELS = False
|
|
23
29
|
|
|
24
30
|
PROJECT_OPTION = click.Option(
|
|
25
31
|
param_decls=["-p", "--project"],
|
|
26
32
|
required=False,
|
|
27
33
|
type=str,
|
|
28
|
-
default=
|
|
29
|
-
help="Project to
|
|
34
|
+
default=None,
|
|
35
|
+
help="Project to which this command applies.",
|
|
30
36
|
show_default=True,
|
|
31
37
|
)
|
|
32
38
|
|
|
@@ -34,8 +40,8 @@ DOMAIN_OPTION = click.Option(
|
|
|
34
40
|
param_decls=["-d", "--domain"],
|
|
35
41
|
required=False,
|
|
36
42
|
type=str,
|
|
37
|
-
default=
|
|
38
|
-
help="Domain to
|
|
43
|
+
default=None,
|
|
44
|
+
help="Domain to which this command applies.",
|
|
39
45
|
show_default=True,
|
|
40
46
|
)
|
|
41
47
|
|
|
@@ -45,14 +51,14 @@ DRY_RUN_OPTION = click.Option(
|
|
|
45
51
|
type=bool,
|
|
46
52
|
is_flag=True,
|
|
47
53
|
default=False,
|
|
48
|
-
help="Dry run
|
|
54
|
+
help="Dry run. Do not actually call the backend service.",
|
|
49
55
|
show_default=True,
|
|
50
56
|
)
|
|
51
57
|
|
|
52
58
|
|
|
53
59
|
def _common_options() -> List[click.Option]:
|
|
54
60
|
"""
|
|
55
|
-
Common options
|
|
61
|
+
Common options that will be added to all commands and groups that inherit from CommandBase or GroupBase.
|
|
56
62
|
"""
|
|
57
63
|
return [PROJECT_OPTION, DOMAIN_OPTION]
|
|
58
64
|
|
|
@@ -60,16 +66,19 @@ def _common_options() -> List[click.Option]:
|
|
|
60
66
|
# This is global state for the CLI, it is manipulated by the main command
|
|
61
67
|
|
|
62
68
|
|
|
69
|
+
@rich.repr.auto
|
|
63
70
|
@dataclass(frozen=True)
|
|
64
71
|
class CLIConfig:
|
|
65
72
|
"""
|
|
66
73
|
This is the global state for the CLI. It is manipulated by the main command.
|
|
67
74
|
"""
|
|
68
75
|
|
|
76
|
+
config: Config
|
|
77
|
+
ctx: click.Context
|
|
69
78
|
log_level: int | None = logging.ERROR
|
|
70
79
|
endpoint: str | None = None
|
|
71
80
|
insecure: bool = False
|
|
72
|
-
|
|
81
|
+
org: str | None = None
|
|
73
82
|
|
|
74
83
|
def replace(self, **kwargs) -> CLIConfig:
|
|
75
84
|
"""
|
|
@@ -78,22 +87,30 @@ class CLIConfig:
|
|
|
78
87
|
return replace(self, **kwargs)
|
|
79
88
|
|
|
80
89
|
def init(self, project: str | None = None, domain: str | None = None):
|
|
81
|
-
import
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
project=project,
|
|
88
|
-
domain=domain,
|
|
89
|
-
log_level=self.log_level,
|
|
90
|
+
from flyte.config._config import TaskConfig
|
|
91
|
+
|
|
92
|
+
task_cfg = TaskConfig(
|
|
93
|
+
org=self.org or self.config.task.org,
|
|
94
|
+
project=project or self.config.task.project,
|
|
95
|
+
domain=domain or self.config.task.domain,
|
|
90
96
|
)
|
|
91
97
|
|
|
98
|
+
kwargs: Dict[str, Any] = {}
|
|
99
|
+
if self.endpoint:
|
|
100
|
+
kwargs["endpoint"] = self.endpoint
|
|
101
|
+
if self.insecure is not None:
|
|
102
|
+
kwargs["insecure"] = self.insecure
|
|
103
|
+
platform_cfg = self.config.platform.replace(**kwargs)
|
|
104
|
+
|
|
105
|
+
updated_config = self.config.with_params(platform_cfg, task_cfg)
|
|
106
|
+
|
|
107
|
+
flyte.init_from_config(updated_config, log_level=self.log_level)
|
|
108
|
+
|
|
92
109
|
|
|
93
110
|
class InvokeBaseMixin:
|
|
94
111
|
"""
|
|
95
|
-
Mixin to catch grpc.RpcError, flyte.RpcError, other errors and other exceptions
|
|
96
|
-
|
|
112
|
+
Mixin to catch grpc.RpcError, flyte.RpcError, other errors and other exceptions
|
|
113
|
+
and raise them as gclick.ClickException.
|
|
97
114
|
"""
|
|
98
115
|
|
|
99
116
|
def invoke(self, ctx):
|
|
@@ -108,10 +125,33 @@ class InvokeBaseMixin:
|
|
|
108
125
|
raise click.ClickException(f"Requested object NOT FOUND. Please check your input. Error: {e.details()}")
|
|
109
126
|
if e.code() == grpc.StatusCode.ALREADY_EXISTS:
|
|
110
127
|
raise click.ClickException("Resource already exists.")
|
|
128
|
+
if e.code() == grpc.StatusCode.INTERNAL:
|
|
129
|
+
raise click.ClickException(f"Internal server error: {e.details()}")
|
|
130
|
+
if e.code() == grpc.StatusCode.UNAVAILABLE:
|
|
131
|
+
raise click.ClickException(
|
|
132
|
+
f"Service is currently unavailable. Please try again later. Error: {e.details()}"
|
|
133
|
+
)
|
|
134
|
+
if e.code() == grpc.StatusCode.PERMISSION_DENIED:
|
|
135
|
+
raise click.ClickException(f"Permission denied. Please check your access rights. Error: {e.details()}")
|
|
136
|
+
if e.code() == grpc.StatusCode.INVALID_ARGUMENT:
|
|
137
|
+
raise click.ClickException(f"Invalid argument provided. Please check your input. Error: {e.details()}")
|
|
111
138
|
raise click.ClickException(f"RPC error invoking command: {e!s}") from e
|
|
112
|
-
except flyte.errors.InitializationError:
|
|
113
|
-
raise click.ClickException("
|
|
139
|
+
except flyte.errors.InitializationError as e:
|
|
140
|
+
raise click.ClickException(f"Initialization failed. Pass remote config for CLI. (Reason: {e})")
|
|
141
|
+
except flyte.errors.BaseRuntimeError as e:
|
|
142
|
+
raise click.ClickException(f"{e.kind} failure, {e.code}. {e}") from e
|
|
143
|
+
except click.exceptions.Exit as e:
|
|
144
|
+
# This is a normal exit, do nothing
|
|
145
|
+
raise e
|
|
146
|
+
except click.exceptions.NoArgsIsHelpError:
|
|
147
|
+
# Do not raise an error if no arguments are passed, just show the help message.
|
|
148
|
+
# https://github.com/pallets/click/pull/1489
|
|
149
|
+
return None
|
|
114
150
|
except Exception as e:
|
|
151
|
+
if ctx.obj and ctx.obj.log_level and ctx.obj.log_level <= logging.DEBUG:
|
|
152
|
+
# If the user has requested verbose output, print the full traceback
|
|
153
|
+
console = Console()
|
|
154
|
+
console.print(Traceback.from_exception(type(e), e, e.__traceback__))
|
|
115
155
|
raise click.ClickException(f"Error invoking command: {e}") from e
|
|
116
156
|
|
|
117
157
|
|
|
@@ -173,8 +213,10 @@ class ObjectsPerFileGroup(GroupBase):
|
|
|
173
213
|
Group that creates a command for each object in a python file.
|
|
174
214
|
"""
|
|
175
215
|
|
|
176
|
-
def __init__(self, filename: Path, *args, **kwargs):
|
|
216
|
+
def __init__(self, filename: Path | None = None, *args, **kwargs):
|
|
177
217
|
super().__init__(*args, **kwargs)
|
|
218
|
+
if filename is None:
|
|
219
|
+
raise ValueError("filename must be provided")
|
|
178
220
|
if not filename.exists():
|
|
179
221
|
raise click.ClickException(f"{filename} does not exists")
|
|
180
222
|
self.filename = filename
|
|
@@ -267,12 +309,18 @@ def get_table(title: str, vals: Iterable[Any]) -> Table:
|
|
|
267
309
|
border_style=PREFERRED_BORDER_COLOR,
|
|
268
310
|
)
|
|
269
311
|
headers = None
|
|
312
|
+
has_rich_repr = False
|
|
270
313
|
for p in vals:
|
|
314
|
+
if hasattr(p, "__rich_repr__"):
|
|
315
|
+
has_rich_repr = True
|
|
316
|
+
elif not isinstance(p, (list, tuple)):
|
|
317
|
+
raise ValueError("Expected a list or tuple of values, or an object with __rich_repr__ method.")
|
|
318
|
+
o = list(p.__rich_repr__()) if has_rich_repr else p
|
|
271
319
|
if headers is None:
|
|
272
|
-
headers = [k for k, _ in
|
|
320
|
+
headers = [k for k, _ in o]
|
|
273
321
|
for h in headers:
|
|
274
322
|
table.add_column(h.capitalize())
|
|
275
|
-
table.add_row(*[str(v) for _, v in
|
|
323
|
+
table.add_row(*[str(v) for _, v in o])
|
|
276
324
|
return table
|
|
277
325
|
|
|
278
326
|
|
|
@@ -280,6 +328,8 @@ def get_panel(title: str, renderable: Any) -> Panel:
|
|
|
280
328
|
"""
|
|
281
329
|
Get a panel from a list of values.
|
|
282
330
|
"""
|
|
331
|
+
if not PANELS:
|
|
332
|
+
return renderable
|
|
283
333
|
return Panel.fit(
|
|
284
334
|
renderable,
|
|
285
335
|
title=f"[{PREFERRED_ACCENT_COLOR}]{title}[/{PREFERRED_ACCENT_COLOR}]",
|
flyte/cli/_create.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, Dict, get_args
|
|
3
|
+
|
|
4
|
+
import rich_click as click
|
|
5
|
+
|
|
6
|
+
import flyte.cli._common as common
|
|
7
|
+
from flyte.remote import SecretTypes
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group(name="create")
|
|
11
|
+
def create():
|
|
12
|
+
"""
|
|
13
|
+
Create resources in a Flyte deployment.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@create.command(cls=common.CommandBase)
|
|
18
|
+
@click.argument("name", type=str, required=True)
|
|
19
|
+
@click.argument("value", type=str, required=False)
|
|
20
|
+
@click.option("--from-file", type=click.Path(exists=True), help="Path to the file with the binary secret.")
|
|
21
|
+
@click.option(
|
|
22
|
+
"--type", type=click.Choice(get_args(SecretTypes)), default="regular", help="Type of the secret.", show_default=True
|
|
23
|
+
)
|
|
24
|
+
@click.pass_obj
|
|
25
|
+
def secret(
|
|
26
|
+
cfg: common.CLIConfig,
|
|
27
|
+
name: str,
|
|
28
|
+
value: str | bytes | None = None,
|
|
29
|
+
from_file: str | None = None,
|
|
30
|
+
type: SecretTypes = "regular",
|
|
31
|
+
project: str | None = None,
|
|
32
|
+
domain: str | None = None,
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Create a new secret. The name of the secret is required. For example:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
$ flyte create secret my_secret --value my_value
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If `--from-file` is specified, the value will be read from the file instead of being provided directly:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
$ flyte create secret my_secret --from-file /path/to/secret_file
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The `--type` option can be used to create specific types of secrets.
|
|
48
|
+
Either `regular` or `image_pull` can be specified.
|
|
49
|
+
Secrets intended to access container images should be specified as `image_pull`.
|
|
50
|
+
Other secrets should be specified as `regular`.
|
|
51
|
+
If no type is specified, `regular` is assumed.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
$ flyte create secret my_secret --type image_pull
|
|
55
|
+
```
|
|
56
|
+
"""
|
|
57
|
+
from flyte.remote import Secret
|
|
58
|
+
|
|
59
|
+
cfg.init(project, domain)
|
|
60
|
+
if from_file:
|
|
61
|
+
with open(from_file, "rb") as f:
|
|
62
|
+
value = f.read()
|
|
63
|
+
Secret.create(name=name, value=value, type=type)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@create.command(cls=common.CommandBase)
|
|
67
|
+
@click.option("--endpoint", type=str, help="Endpoint of the Flyte backend.")
|
|
68
|
+
@click.option("--insecure", is_flag=True, help="Use an insecure connection to the Flyte backend.")
|
|
69
|
+
@click.option(
|
|
70
|
+
"--org",
|
|
71
|
+
type=str,
|
|
72
|
+
required=False,
|
|
73
|
+
help="Organization to use. This will override the organization in the configuration file.",
|
|
74
|
+
)
|
|
75
|
+
@click.option(
|
|
76
|
+
"-o",
|
|
77
|
+
"--output",
|
|
78
|
+
type=click.Path(exists=False, writable=True),
|
|
79
|
+
default=Path.cwd() / "config.yaml",
|
|
80
|
+
help="Path to the output directory where the configuration will be saved. Defaults to current directory.",
|
|
81
|
+
show_default=True,
|
|
82
|
+
)
|
|
83
|
+
@click.option(
|
|
84
|
+
"--force",
|
|
85
|
+
is_flag=True,
|
|
86
|
+
default=False,
|
|
87
|
+
help="Force overwrite of the configuration file if it already exists.",
|
|
88
|
+
show_default=True,
|
|
89
|
+
)
|
|
90
|
+
def config(
|
|
91
|
+
output: str,
|
|
92
|
+
endpoint: str | None = None,
|
|
93
|
+
insecure: bool = False,
|
|
94
|
+
org: str | None = None,
|
|
95
|
+
project: str | None = None,
|
|
96
|
+
domain: str | None = None,
|
|
97
|
+
force: bool = False,
|
|
98
|
+
):
|
|
99
|
+
"""
|
|
100
|
+
Creates a configuration file for Flyte CLI.
|
|
101
|
+
If the `--output` option is not specified, it will create a file named `config.yaml` in the current directory.
|
|
102
|
+
If the file already exists, it will raise an error unless the `--force` option is used.
|
|
103
|
+
"""
|
|
104
|
+
import yaml
|
|
105
|
+
|
|
106
|
+
from flyte._utils import org_from_endpoint, sanitize_endpoint
|
|
107
|
+
|
|
108
|
+
output_path = Path(output)
|
|
109
|
+
|
|
110
|
+
if output_path.exists() and not force:
|
|
111
|
+
force = click.confirm(f"Overwrite [{output_path}]?", default=False)
|
|
112
|
+
if not force:
|
|
113
|
+
click.echo(f"Will not overwrite the existing config file at {output_path}")
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
admin: Dict[str, Any] = {}
|
|
117
|
+
if endpoint:
|
|
118
|
+
endpoint = sanitize_endpoint(endpoint)
|
|
119
|
+
admin["endpoint"] = endpoint
|
|
120
|
+
if insecure:
|
|
121
|
+
admin["insecure"] = insecure
|
|
122
|
+
|
|
123
|
+
if not org and endpoint:
|
|
124
|
+
org = org_from_endpoint(endpoint)
|
|
125
|
+
|
|
126
|
+
task: Dict[str, str] = {}
|
|
127
|
+
if org:
|
|
128
|
+
task["org"] = org
|
|
129
|
+
if project:
|
|
130
|
+
task["project"] = project
|
|
131
|
+
if domain:
|
|
132
|
+
task["domain"] = domain
|
|
133
|
+
|
|
134
|
+
if not admin and not task:
|
|
135
|
+
raise click.BadParameter("At least one of --endpoint or --org must be provided.")
|
|
136
|
+
|
|
137
|
+
with open(output_path, "w") as f:
|
|
138
|
+
d: Dict[str, Any] = {}
|
|
139
|
+
if admin:
|
|
140
|
+
d["admin"] = admin
|
|
141
|
+
if task:
|
|
142
|
+
d["task"] = task
|
|
143
|
+
yaml.dump(d, f)
|
|
144
|
+
|
|
145
|
+
click.echo(f"Config file written to {output_path}")
|