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.

Files changed (319) hide show
  1. flyte/__init__.py +20 -4
  2. flyte/_bin/runtime.py +33 -7
  3. flyte/_build.py +3 -2
  4. flyte/_cache/cache.py +1 -2
  5. flyte/_code_bundle/_packaging.py +1 -1
  6. flyte/_code_bundle/_utils.py +0 -16
  7. flyte/_code_bundle/bundle.py +43 -12
  8. flyte/_context.py +8 -2
  9. flyte/_deploy.py +56 -15
  10. flyte/_environment.py +45 -4
  11. flyte/_excepthook.py +37 -0
  12. flyte/_group.py +2 -1
  13. flyte/_image.py +8 -4
  14. flyte/_initialize.py +112 -254
  15. flyte/_interface.py +3 -3
  16. flyte/_internal/controllers/__init__.py +19 -6
  17. flyte/_internal/controllers/_local_controller.py +83 -8
  18. flyte/_internal/controllers/_trace.py +2 -1
  19. flyte/_internal/controllers/remote/__init__.py +27 -7
  20. flyte/_internal/controllers/remote/_action.py +7 -2
  21. flyte/_internal/controllers/remote/_client.py +5 -1
  22. flyte/_internal/controllers/remote/_controller.py +159 -26
  23. flyte/_internal/controllers/remote/_core.py +13 -5
  24. flyte/_internal/controllers/remote/_informer.py +4 -4
  25. flyte/_internal/controllers/remote/_service_protocol.py +6 -6
  26. flyte/_internal/imagebuild/docker_builder.py +12 -1
  27. flyte/_internal/imagebuild/image_builder.py +16 -11
  28. flyte/_internal/runtime/convert.py +164 -21
  29. flyte/_internal/runtime/entrypoints.py +1 -1
  30. flyte/_internal/runtime/io.py +3 -3
  31. flyte/_internal/runtime/task_serde.py +140 -20
  32. flyte/_internal/runtime/taskrunner.py +4 -3
  33. flyte/_internal/runtime/types_serde.py +1 -1
  34. flyte/_logging.py +12 -1
  35. flyte/_map.py +215 -0
  36. flyte/_pod.py +19 -0
  37. flyte/_protos/common/list_pb2.py +3 -3
  38. flyte/_protos/common/list_pb2.pyi +2 -0
  39. flyte/_protos/logs/dataplane/payload_pb2.py +28 -24
  40. flyte/_protos/logs/dataplane/payload_pb2.pyi +11 -2
  41. flyte/_protos/workflow/common_pb2.py +27 -0
  42. flyte/_protos/workflow/common_pb2.pyi +14 -0
  43. flyte/_protos/workflow/environment_pb2.py +29 -0
  44. flyte/_protos/workflow/environment_pb2.pyi +12 -0
  45. flyte/_protos/workflow/queue_service_pb2.py +40 -41
  46. flyte/_protos/workflow/queue_service_pb2.pyi +35 -30
  47. flyte/_protos/workflow/queue_service_pb2_grpc.py +15 -15
  48. flyte/_protos/workflow/run_definition_pb2.py +61 -61
  49. flyte/_protos/workflow/run_definition_pb2.pyi +8 -4
  50. flyte/_protos/workflow/run_service_pb2.py +20 -24
  51. flyte/_protos/workflow/run_service_pb2.pyi +2 -6
  52. flyte/_protos/workflow/state_service_pb2.py +36 -28
  53. flyte/_protos/workflow/state_service_pb2.pyi +19 -15
  54. flyte/_protos/workflow/state_service_pb2_grpc.py +28 -28
  55. flyte/_protos/workflow/task_definition_pb2.py +29 -22
  56. flyte/_protos/workflow/task_definition_pb2.pyi +21 -5
  57. flyte/_protos/workflow/task_service_pb2.py +27 -11
  58. flyte/_protos/workflow/task_service_pb2.pyi +29 -1
  59. flyte/_protos/workflow/task_service_pb2_grpc.py +34 -0
  60. flyte/_run.py +166 -95
  61. flyte/_task.py +110 -28
  62. flyte/_task_environment.py +55 -72
  63. flyte/_trace.py +6 -14
  64. flyte/_utils/__init__.py +6 -0
  65. flyte/_utils/async_cache.py +139 -0
  66. flyte/_utils/coro_management.py +0 -2
  67. flyte/_utils/helpers.py +45 -19
  68. flyte/_utils/org_discovery.py +57 -0
  69. flyte/_version.py +2 -2
  70. flyte/cli/__init__.py +3 -0
  71. flyte/cli/_abort.py +28 -0
  72. flyte/{_cli → cli}/_common.py +73 -23
  73. flyte/cli/_create.py +145 -0
  74. flyte/{_cli → cli}/_delete.py +4 -4
  75. flyte/{_cli → cli}/_deploy.py +26 -14
  76. flyte/cli/_gen.py +163 -0
  77. flyte/{_cli → cli}/_get.py +98 -23
  78. {union/_cli → flyte/cli}/_params.py +106 -147
  79. flyte/{_cli → cli}/_run.py +99 -20
  80. flyte/cli/main.py +166 -0
  81. flyte/config/__init__.py +3 -0
  82. flyte/config/_config.py +216 -0
  83. flyte/config/_internal.py +64 -0
  84. flyte/config/_reader.py +207 -0
  85. flyte/errors.py +29 -0
  86. flyte/extras/_container.py +33 -43
  87. flyte/io/__init__.py +17 -1
  88. flyte/io/_dir.py +2 -2
  89. flyte/io/_file.py +3 -4
  90. flyte/io/{structured_dataset → _structured_dataset}/basic_dfs.py +1 -1
  91. flyte/io/{structured_dataset → _structured_dataset}/structured_dataset.py +1 -1
  92. flyte/{_datastructures.py → models.py} +56 -7
  93. flyte/remote/__init__.py +2 -1
  94. flyte/remote/_client/_protocols.py +2 -0
  95. flyte/remote/_client/auth/_auth_utils.py +14 -0
  96. flyte/remote/_client/auth/_channel.py +34 -3
  97. flyte/remote/_client/auth/_token_client.py +3 -3
  98. flyte/remote/_client/controlplane.py +13 -13
  99. flyte/remote/_console.py +1 -1
  100. flyte/remote/_data.py +10 -6
  101. flyte/remote/_logs.py +89 -29
  102. flyte/remote/_project.py +8 -9
  103. flyte/remote/_run.py +228 -131
  104. flyte/remote/_secret.py +12 -12
  105. flyte/remote/_task.py +179 -15
  106. flyte/report/_report.py +4 -4
  107. flyte/storage/__init__.py +5 -0
  108. flyte/storage/_config.py +233 -0
  109. flyte/storage/_storage.py +23 -3
  110. flyte/syncify/__init__.py +56 -0
  111. flyte/syncify/_api.py +371 -0
  112. flyte/types/__init__.py +23 -0
  113. flyte/types/_interface.py +22 -7
  114. flyte/{io/pickle/transformer.py → types/_pickle.py} +2 -1
  115. flyte/types/_type_engine.py +95 -18
  116. flyte-0.2.0a0.dist-info/METADATA +249 -0
  117. flyte-0.2.0a0.dist-info/RECORD +218 -0
  118. {flyte-0.0.1b3.dist-info → flyte-0.2.0a0.dist-info}/entry_points.txt +1 -1
  119. flyte/_api_commons.py +0 -3
  120. flyte/_cli/__init__.py +0 -0
  121. flyte/_cli/_create.py +0 -42
  122. flyte/_cli/main.py +0 -72
  123. flyte/_internal/controllers/pbhash.py +0 -39
  124. flyte/io/_dataframe.py +0 -0
  125. flyte/io/pickle/__init__.py +0 -0
  126. flyte-0.0.1b3.dist-info/METADATA +0 -179
  127. flyte-0.0.1b3.dist-info/RECORD +0 -390
  128. union/__init__.py +0 -54
  129. union/_api_commons.py +0 -3
  130. union/_bin/__init__.py +0 -0
  131. union/_bin/runtime.py +0 -113
  132. union/_build.py +0 -25
  133. union/_cache/__init__.py +0 -12
  134. union/_cache/cache.py +0 -141
  135. union/_cache/defaults.py +0 -9
  136. union/_cache/policy_function_body.py +0 -42
  137. union/_cli/__init__.py +0 -0
  138. union/_cli/_common.py +0 -263
  139. union/_cli/_create.py +0 -40
  140. union/_cli/_delete.py +0 -23
  141. union/_cli/_deploy.py +0 -120
  142. union/_cli/_get.py +0 -162
  143. union/_cli/_run.py +0 -150
  144. union/_cli/main.py +0 -72
  145. union/_code_bundle/__init__.py +0 -8
  146. union/_code_bundle/_ignore.py +0 -113
  147. union/_code_bundle/_packaging.py +0 -187
  148. union/_code_bundle/_utils.py +0 -342
  149. union/_code_bundle/bundle.py +0 -176
  150. union/_context.py +0 -146
  151. union/_datastructures.py +0 -295
  152. union/_deploy.py +0 -185
  153. union/_doc.py +0 -29
  154. union/_docstring.py +0 -26
  155. union/_environment.py +0 -43
  156. union/_group.py +0 -31
  157. union/_hash.py +0 -23
  158. union/_image.py +0 -760
  159. union/_initialize.py +0 -585
  160. union/_interface.py +0 -84
  161. union/_internal/__init__.py +0 -3
  162. union/_internal/controllers/__init__.py +0 -77
  163. union/_internal/controllers/_local_controller.py +0 -77
  164. union/_internal/controllers/pbhash.py +0 -39
  165. union/_internal/controllers/remote/__init__.py +0 -40
  166. union/_internal/controllers/remote/_action.py +0 -131
  167. union/_internal/controllers/remote/_client.py +0 -43
  168. union/_internal/controllers/remote/_controller.py +0 -169
  169. union/_internal/controllers/remote/_core.py +0 -341
  170. union/_internal/controllers/remote/_informer.py +0 -260
  171. union/_internal/controllers/remote/_service_protocol.py +0 -44
  172. union/_internal/imagebuild/__init__.py +0 -11
  173. union/_internal/imagebuild/docker_builder.py +0 -416
  174. union/_internal/imagebuild/image_builder.py +0 -243
  175. union/_internal/imagebuild/remote_builder.py +0 -0
  176. union/_internal/resolvers/__init__.py +0 -0
  177. union/_internal/resolvers/_task_module.py +0 -31
  178. union/_internal/resolvers/common.py +0 -24
  179. union/_internal/resolvers/default.py +0 -27
  180. union/_internal/runtime/__init__.py +0 -0
  181. union/_internal/runtime/convert.py +0 -163
  182. union/_internal/runtime/entrypoints.py +0 -121
  183. union/_internal/runtime/io.py +0 -136
  184. union/_internal/runtime/resources_serde.py +0 -134
  185. union/_internal/runtime/task_serde.py +0 -202
  186. union/_internal/runtime/taskrunner.py +0 -179
  187. union/_internal/runtime/types_serde.py +0 -53
  188. union/_logging.py +0 -124
  189. union/_protos/__init__.py +0 -0
  190. union/_protos/common/authorization_pb2.py +0 -66
  191. union/_protos/common/authorization_pb2.pyi +0 -106
  192. union/_protos/common/identifier_pb2.py +0 -71
  193. union/_protos/common/identifier_pb2.pyi +0 -82
  194. union/_protos/common/identity_pb2.py +0 -48
  195. union/_protos/common/identity_pb2.pyi +0 -72
  196. union/_protos/common/identity_pb2_grpc.py +0 -4
  197. union/_protos/common/list_pb2.py +0 -36
  198. union/_protos/common/list_pb2.pyi +0 -69
  199. union/_protos/common/list_pb2_grpc.py +0 -4
  200. union/_protos/common/policy_pb2.py +0 -37
  201. union/_protos/common/policy_pb2.pyi +0 -27
  202. union/_protos/common/policy_pb2_grpc.py +0 -4
  203. union/_protos/common/role_pb2.py +0 -37
  204. union/_protos/common/role_pb2.pyi +0 -51
  205. union/_protos/common/role_pb2_grpc.py +0 -4
  206. union/_protos/common/runtime_version_pb2.py +0 -28
  207. union/_protos/common/runtime_version_pb2.pyi +0 -24
  208. union/_protos/common/runtime_version_pb2_grpc.py +0 -4
  209. union/_protos/logs/dataplane/payload_pb2.py +0 -96
  210. union/_protos/logs/dataplane/payload_pb2.pyi +0 -168
  211. union/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
  212. union/_protos/secret/definition_pb2.py +0 -49
  213. union/_protos/secret/definition_pb2.pyi +0 -93
  214. union/_protos/secret/definition_pb2_grpc.py +0 -4
  215. union/_protos/secret/payload_pb2.py +0 -62
  216. union/_protos/secret/payload_pb2.pyi +0 -94
  217. union/_protos/secret/payload_pb2_grpc.py +0 -4
  218. union/_protos/secret/secret_pb2.py +0 -38
  219. union/_protos/secret/secret_pb2.pyi +0 -6
  220. union/_protos/secret/secret_pb2_grpc.py +0 -198
  221. union/_protos/validate/validate/validate_pb2.py +0 -76
  222. union/_protos/workflow/node_execution_service_pb2.py +0 -26
  223. union/_protos/workflow/node_execution_service_pb2.pyi +0 -4
  224. union/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
  225. union/_protos/workflow/queue_service_pb2.py +0 -75
  226. union/_protos/workflow/queue_service_pb2.pyi +0 -103
  227. union/_protos/workflow/queue_service_pb2_grpc.py +0 -172
  228. union/_protos/workflow/run_definition_pb2.py +0 -100
  229. union/_protos/workflow/run_definition_pb2.pyi +0 -256
  230. union/_protos/workflow/run_definition_pb2_grpc.py +0 -4
  231. union/_protos/workflow/run_logs_service_pb2.py +0 -41
  232. union/_protos/workflow/run_logs_service_pb2.pyi +0 -28
  233. union/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
  234. union/_protos/workflow/run_service_pb2.py +0 -133
  235. union/_protos/workflow/run_service_pb2.pyi +0 -173
  236. union/_protos/workflow/run_service_pb2_grpc.py +0 -412
  237. union/_protos/workflow/state_service_pb2.py +0 -58
  238. union/_protos/workflow/state_service_pb2.pyi +0 -69
  239. union/_protos/workflow/state_service_pb2_grpc.py +0 -138
  240. union/_protos/workflow/task_definition_pb2.py +0 -72
  241. union/_protos/workflow/task_definition_pb2.pyi +0 -65
  242. union/_protos/workflow/task_definition_pb2_grpc.py +0 -4
  243. union/_protos/workflow/task_service_pb2.py +0 -44
  244. union/_protos/workflow/task_service_pb2.pyi +0 -31
  245. union/_protos/workflow/task_service_pb2_grpc.py +0 -104
  246. union/_resources.py +0 -226
  247. union/_retry.py +0 -32
  248. union/_reusable_environment.py +0 -25
  249. union/_run.py +0 -374
  250. union/_secret.py +0 -61
  251. union/_task.py +0 -354
  252. union/_task_environment.py +0 -186
  253. union/_timeout.py +0 -47
  254. union/_tools.py +0 -27
  255. union/_utils/__init__.py +0 -11
  256. union/_utils/asyn.py +0 -119
  257. union/_utils/file_handling.py +0 -71
  258. union/_utils/helpers.py +0 -46
  259. union/_utils/lazy_module.py +0 -54
  260. union/_utils/uv_script_parser.py +0 -49
  261. union/_version.py +0 -21
  262. union/connectors/__init__.py +0 -0
  263. union/errors.py +0 -128
  264. union/extras/__init__.py +0 -5
  265. union/extras/_container.py +0 -263
  266. union/io/__init__.py +0 -11
  267. union/io/_dataframe.py +0 -0
  268. union/io/_dir.py +0 -425
  269. union/io/_file.py +0 -418
  270. union/io/pickle/__init__.py +0 -0
  271. union/io/pickle/transformer.py +0 -117
  272. union/io/structured_dataset/__init__.py +0 -122
  273. union/io/structured_dataset/basic_dfs.py +0 -219
  274. union/io/structured_dataset/structured_dataset.py +0 -1057
  275. union/py.typed +0 -0
  276. union/remote/__init__.py +0 -23
  277. union/remote/_client/__init__.py +0 -0
  278. union/remote/_client/_protocols.py +0 -129
  279. union/remote/_client/auth/__init__.py +0 -12
  280. union/remote/_client/auth/_authenticators/__init__.py +0 -0
  281. union/remote/_client/auth/_authenticators/base.py +0 -391
  282. union/remote/_client/auth/_authenticators/client_credentials.py +0 -73
  283. union/remote/_client/auth/_authenticators/device_code.py +0 -120
  284. union/remote/_client/auth/_authenticators/external_command.py +0 -77
  285. union/remote/_client/auth/_authenticators/factory.py +0 -200
  286. union/remote/_client/auth/_authenticators/pkce.py +0 -515
  287. union/remote/_client/auth/_channel.py +0 -184
  288. union/remote/_client/auth/_client_config.py +0 -83
  289. union/remote/_client/auth/_default_html.py +0 -32
  290. union/remote/_client/auth/_grpc_utils/__init__.py +0 -0
  291. union/remote/_client/auth/_grpc_utils/auth_interceptor.py +0 -204
  292. union/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +0 -144
  293. union/remote/_client/auth/_keyring.py +0 -154
  294. union/remote/_client/auth/_token_client.py +0 -258
  295. union/remote/_client/auth/errors.py +0 -16
  296. union/remote/_client/controlplane.py +0 -86
  297. union/remote/_data.py +0 -149
  298. union/remote/_logs.py +0 -74
  299. union/remote/_project.py +0 -86
  300. union/remote/_run.py +0 -820
  301. union/remote/_secret.py +0 -132
  302. union/remote/_task.py +0 -193
  303. union/report/__init__.py +0 -3
  304. union/report/_report.py +0 -178
  305. union/report/_template.html +0 -124
  306. union/storage/__init__.py +0 -24
  307. union/storage/_remote_fs.py +0 -34
  308. union/storage/_storage.py +0 -247
  309. union/storage/_utils.py +0 -5
  310. union/types/__init__.py +0 -11
  311. union/types/_renderer.py +0 -162
  312. union/types/_string_literals.py +0 -120
  313. union/types/_type_engine.py +0 -2131
  314. union/types/_utils.py +0 -80
  315. /union/_protos/common/authorization_pb2_grpc.py → /flyte/_protos/workflow/common_pb2_grpc.py +0 -0
  316. /union/_protos/common/identifier_pb2_grpc.py → /flyte/_protos/workflow/environment_pb2_grpc.py +0 -0
  317. /flyte/io/{structured_dataset → _structured_dataset}/__init__.py +0 -0
  318. {flyte-0.0.1b3.dist-info → flyte-0.2.0a0.dist-info}/WHEEL +0 -0
  319. {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
+ """
@@ -21,5 +21,3 @@ async def run_coros(*coros: typing.Coroutine, return_when: str = asyncio.FIRST_C
21
21
  err = t.exception()
22
22
  if err:
23
23
  raise err
24
- else:
25
- print(f"Task result: {t.result()}")
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
- # does not work at all in the setuptools case. see old flytekit editable installs
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
- egg_links.extend(pth_files)
80
- for file in egg_links:
81
- with open(file, "r") as f:
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.0.1b3'
21
- __version_tuple__ = version_tuple = (0, 0, 1, 'b3')
20
+ __version__ = version = '0.2.0a0'
21
+ __version_tuple__ = version_tuple = (0, 2, 0, 'a0')
flyte/cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from flyte.cli.main import main
2
+
3
+ __all__ = ["main"]
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.")
@@ -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="default",
29
- help="Project to operate on",
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="development",
38
- help="Domain to operate on",
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, do not actually call the backend service.",
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 for that will be added to all commands and groups that inherit from CommandBase or GroupBase.
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
- org_override: str | None = None
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 flyte
82
-
83
- flyte.init(
84
- endpoint=self.endpoint,
85
- insecure=self.insecure,
86
- org=self.org_override,
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 and raise them as
96
- gclick.ClickException.
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("Initialize the CLI with a remote configuration. For example, pass --endpoint")
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 p.__rich_repr__()]
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 p.__rich_repr__()])
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}")