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,56 @@
1
+ """
2
+ # Syncify Module
3
+ This module provides the `syncify` decorator and the `Syncify` class.
4
+ The decorator can be used to convert asynchronous functions or methods into synchronous ones.
5
+ This is useful for integrating async code into synchronous contexts.
6
+
7
+ Every asynchronous function or method wrapped with `syncify` can be called synchronously using the
8
+ parenthesis `()` operator, or asynchronously using the `.aio()` method.
9
+
10
+ Example::
11
+
12
+ ```python
13
+ from flyte.syncify import syncify
14
+
15
+ @syncify
16
+ async def async_function(x: str) -> str:
17
+ return f"Hello, Async World {x}!"
18
+
19
+
20
+ # now you can call it synchronously
21
+ result = async_function("Async World") # Note: no .aio() needed for sync calls
22
+ print(result)
23
+ # Output: Hello, Async World Async World!
24
+
25
+ # or call it asynchronously
26
+ async def main():
27
+ result = await async_function.aio("World") # Note the use of .aio() for async calls
28
+ print(result)
29
+ ```
30
+
31
+ ## Creating a Syncify Instance
32
+ ```python
33
+ from flyte.syncify. import Syncify
34
+
35
+ syncer = Syncify("my_syncer")
36
+
37
+ # Now you can use `syncer` to decorate your async functions or methods
38
+
39
+ ```
40
+
41
+ ## How does it work?
42
+ The Syncify class wraps asynchronous functions, classmethods, instance methods, and static methods to
43
+ provide a synchronous interface. The wrapped methods are always executed in the context of a background loop,
44
+ whether they are called synchronously or asynchronously. This allows for seamless integration of async code, as
45
+ certain async libraries capture the event loop. An example is grpc.aio, which captures the event loop.
46
+ In such a case, the Syncify class ensures that the async function is executed in the context of the background loop.
47
+
48
+ To use it correctly with grpc.aio, you should wrap every grpc.aio channel creation, and client invocation
49
+ with the same `Syncify` instance. This ensures that the async code runs in the correct event loop context.
50
+ """
51
+
52
+ from flyte.syncify._api import Syncify
53
+
54
+ syncify = Syncify()
55
+
56
+ __all__ = ["Syncify", "syncify"]
flyte/syncify/_api.py ADDED
@@ -0,0 +1,371 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import atexit
5
+ import concurrent.futures
6
+ import functools
7
+ import inspect
8
+ import logging
9
+ import threading
10
+ from typing import (
11
+ Any,
12
+ AsyncIterator,
13
+ Awaitable,
14
+ Callable,
15
+ Coroutine,
16
+ Iterator,
17
+ ParamSpec,
18
+ Protocol,
19
+ TypeVar,
20
+ Union,
21
+ cast,
22
+ overload,
23
+ )
24
+
25
+ from flyte._logging import logger
26
+
27
+ P = ParamSpec("P")
28
+ R_co = TypeVar("R_co", covariant=True)
29
+ T = TypeVar("T")
30
+
31
+
32
+ class SyncFunction(Protocol[P, R_co]):
33
+ """
34
+ A protocol that defines the interface for synchronous functions or methods that can be converted from asynchronous
35
+ ones.
36
+ """
37
+
38
+ def __call__(self, *args: Any, **kwargs: Any) -> R_co: ...
39
+
40
+ def aio(self, *args: Any, **kwargs: Any) -> Awaitable[R_co]: ...
41
+
42
+
43
+ class SyncGenFunction(Protocol[P, R_co]):
44
+ """
45
+ A protocol that defines the interface for synchronous functions or methods that can be converted from asynchronous
46
+ ones.
47
+ """
48
+
49
+ def __call__(self, *args: Any, **kwargs: Any) -> Iterator[R_co]: ...
50
+
51
+ def aio(self, *args: Any, **kwargs: Any) -> AsyncIterator[R_co]: ...
52
+
53
+
54
+ class _BackgroundLoop:
55
+ """
56
+ A background event loop that runs in a separate thread and used the `Syncify` decorator to run asynchronous
57
+ functions or methods synchronously.
58
+ """
59
+
60
+ def __init__(self, name: str):
61
+ self.loop = asyncio.new_event_loop()
62
+ self.thread = threading.Thread(name=name, target=self._run, daemon=True)
63
+ self.thread.start()
64
+ atexit.register(self.stop)
65
+
66
+ def _run(self):
67
+ asyncio.set_event_loop(self.loop)
68
+ self.loop.run_forever()
69
+
70
+ def stop(self):
71
+ # stop the loop and wait briefly for thread to exit
72
+ self.loop.call_soon_threadsafe(self.loop.stop)
73
+ self.thread.join(timeout=1)
74
+
75
+ def is_in_loop(self) -> bool:
76
+ """
77
+ Check if the current thread is the background loop thread.
78
+ """
79
+ # If the current thread is not the background loop thread, return False
80
+ if threading.current_thread() != self.thread:
81
+ return False
82
+
83
+ if not self.thread.is_alive():
84
+ # If the thread is not alive, we cannot be in the loop
85
+ return False
86
+
87
+ # Lets get the current event loop and check if it matches the background loop
88
+ loop = None
89
+ try:
90
+ loop = asyncio.get_running_loop()
91
+ except RuntimeError:
92
+ pass
93
+
94
+ return loop == self.loop
95
+
96
+ def iterate_in_loop_sync(self, async_gen: AsyncIterator[R_co]) -> Iterator[R_co]:
97
+ # Create an iterator that pulls items from the async generator
98
+ assert self.thread.name != threading.current_thread().name, (
99
+ f"Cannot run coroutine in the same thread {self.thread.name}"
100
+ )
101
+ while True:
102
+ try:
103
+ # use __anext__() and cast to Coroutine so mypy is happy
104
+ future: concurrent.futures.Future[R_co] = asyncio.run_coroutine_threadsafe(
105
+ cast(Coroutine[Any, Any, R_co], async_gen.__anext__()),
106
+ self.loop,
107
+ )
108
+ yield future.result()
109
+ except (StopAsyncIteration, StopIteration):
110
+ break
111
+ except Exception as e:
112
+ if logger.getEffectiveLevel() > logging.DEBUG:
113
+ # If the log level is not DEBUG, we will remove the extra stack frames to avoid confusion for the
114
+ # user
115
+ # This is because the stack trace will include the Syncify wrapper and the background loop thread
116
+ tb = e.__traceback__
117
+ while tb and tb.tb_next:
118
+ if tb.tb_frame.f_code.co_name == "":
119
+ break
120
+ tb = tb.tb_next
121
+ raise e.with_traceback(tb)
122
+ # If the log level is DEBUG, we will keep the extra stack frames to help with debugging
123
+ raise e
124
+
125
+ def call_in_loop_sync(self, coro: Coroutine[Any, Any, R_co]) -> R_co | Iterator[R_co]:
126
+ """
127
+ Run the given coroutine in the background loop and return its result.
128
+ """
129
+ future: concurrent.futures.Future[R_co | AsyncIterator[R_co]] = asyncio.run_coroutine_threadsafe(
130
+ coro, self.loop
131
+ )
132
+ result = future.result()
133
+ if result is not None and hasattr(result, "__aiter__"):
134
+ # If the result is an async iterator, we need to convert it to a sync iterator
135
+ return cast(Iterator[R_co], self.iterate_in_loop_sync(cast(AsyncIterator[R_co], result)))
136
+ # Otherwise, just return the result
137
+ return result
138
+
139
+ async def iterate_in_loop(self, async_gen: AsyncIterator[R_co]) -> AsyncIterator[R_co]:
140
+ """
141
+ Run the given async iterator in the background loop and yield its results.
142
+ """
143
+ if self.is_in_loop():
144
+ # If we are already in the background loop, just return the async iterator
145
+ async for r in async_gen:
146
+ yield r
147
+ return
148
+
149
+ while True:
150
+ try:
151
+ # same replacement here for the async path
152
+ future: concurrent.futures.Future[R_co] = asyncio.run_coroutine_threadsafe(
153
+ cast(Coroutine[Any, Any, R_co], async_gen.__anext__()),
154
+ self.loop,
155
+ )
156
+ # Wrap the future in an asyncio Future to yield it in an async context
157
+ aio_future: asyncio.Future[R_co] = asyncio.wrap_future(future)
158
+ # await for the future to complete and yield its result
159
+ v = await aio_future
160
+ yield v
161
+ except StopAsyncIteration:
162
+ break
163
+ except Exception as e:
164
+ if logger.getEffectiveLevel() > logging.DEBUG:
165
+ # If the log level is not DEBUG, we will remove the extra stack frames to avoid confusion for the
166
+ # user.
167
+ # This is because the stack trace will include the Syncify wrapper and the background loop thread
168
+ tb = e.__traceback__
169
+ while tb and tb.tb_next:
170
+ if tb.tb_frame.f_code.co_name == "":
171
+ break
172
+ tb = tb.tb_next
173
+ raise e.with_traceback(tb)
174
+ # If the log level is DEBUG, we will keep the extra stack frames to help with debugging
175
+ raise e
176
+
177
+ async def aio(self, coro: Coroutine[Any, Any, R_co]) -> R_co:
178
+ """
179
+ Run the given coroutine in the background loop and return its result.
180
+ """
181
+ if self.is_in_loop():
182
+ # If we are already in the background loop, just run the coroutine
183
+ return await coro
184
+ try:
185
+ # Otherwise, run it in the background loop and wait for the result
186
+ future: concurrent.futures.Future[R_co] = asyncio.run_coroutine_threadsafe(coro, self.loop)
187
+ # Wrap the future in an asyncio Future to await it in an async context
188
+ aio_future: asyncio.Future[R_co] = asyncio.wrap_future(future)
189
+ # await for the future to complete and return its result
190
+ return await aio_future
191
+ except Exception as e:
192
+ if logger.getEffectiveLevel() > logging.DEBUG:
193
+ # If the log level is not DEBUG, we will remove the extra stack frames to avoid confusion for the user
194
+ # This is because the stack trace will include the Syncify wrapper and the background loop thread
195
+ tb = e.__traceback__
196
+ while tb and tb.tb_next:
197
+ if tb.tb_frame.f_code.co_name == "":
198
+ break
199
+ tb = tb.tb_next
200
+ raise e.with_traceback(tb)
201
+ # If the log level is DEBUG, we will keep the extra stack frames to help with debugging
202
+ raise e
203
+
204
+
205
+ class _SyncWrapper:
206
+ """
207
+ A wrapper class that the Syncify decorator uses to convert asynchronous functions or methods into synchronous ones.
208
+ """
209
+
210
+ def __init__(
211
+ self,
212
+ fn: Any,
213
+ bg_loop: _BackgroundLoop,
214
+ underlying_obj: Any = None,
215
+ ):
216
+ self.fn = fn
217
+ self._bg_loop = bg_loop
218
+ self._underlying_obj = underlying_obj
219
+
220
+ def __get__(self, instance: Any, owner: Any) -> Any:
221
+ """
222
+ This method is called when the wrapper is accessed as a method of a class instance.
223
+ :param instance:
224
+ :param owner:
225
+ :return:
226
+ """
227
+ fn: Any = self.fn
228
+ if instance is not None:
229
+ # If we have an instance, we need to bind the method to the instance (for instance methods)
230
+ fn = self.fn.__get__(instance, owner)
231
+
232
+ if instance is None and owner is not None and self._underlying_obj is not None:
233
+ # If we have an owner, we need to bind the method to the owner (for classmethods or staticmethods)
234
+ fn = self._underlying_obj.__get__(None, owner)
235
+
236
+ wrapper = _SyncWrapper(fn, bg_loop=self._bg_loop, underlying_obj=self._underlying_obj)
237
+ functools.update_wrapper(wrapper, self.fn)
238
+ return wrapper
239
+
240
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
241
+ if threading.current_thread().name == self._bg_loop.thread.name:
242
+ # If we are already in the background loop thread, we can call the function directly
243
+ raise AssertionError(
244
+ f"Deadlock detected: blocking call used in syncify thread {self._bg_loop.thread.name} "
245
+ f"when calling function {self.fn}, use .aio() if in an async call."
246
+ )
247
+ try:
248
+ # bind method if needed
249
+ coro_fn = self.fn
250
+
251
+ if inspect.isasyncgenfunction(coro_fn):
252
+ # Handle async iterator by converting to sync iterator
253
+ async_gen = coro_fn(*args, **kwargs)
254
+ return self._bg_loop.iterate_in_loop_sync(async_gen)
255
+ else:
256
+ return self._bg_loop.call_in_loop_sync(coro_fn(*args, **kwargs))
257
+ except Exception as e:
258
+ if logger.getEffectiveLevel() > logging.DEBUG:
259
+ # If the log level is not DEBUG, we will remove the extra stack frames to avoid confusion for the user
260
+ # This is because the stack trace will include the Syncify wrapper and the background loop thread
261
+ tb = e.__traceback__
262
+ while tb and tb.tb_next:
263
+ if tb.tb_frame.f_code.co_name == self.fn.__name__:
264
+ break
265
+ tb = tb.tb_next
266
+ raise e.with_traceback(tb)
267
+ # If the log level is DEBUG, we will keep the extra stack frames to help with debugging
268
+ raise e
269
+
270
+ def aio(self, *args: Any, **kwargs: Any) -> Any:
271
+ fn = self.fn
272
+
273
+ try:
274
+ if inspect.isasyncgenfunction(fn):
275
+ # If the function is an async generator, we need to handle it differently
276
+ async_iter = fn(*args, **kwargs)
277
+ return self._bg_loop.iterate_in_loop(async_iter)
278
+ else:
279
+ # If we are already in the background loop, just return the coroutine
280
+ coro = fn(*args, **kwargs)
281
+ if hasattr(coro, "__aiter__"):
282
+ # If the coroutine is an async iterator, we need to handle it differently
283
+ return self._bg_loop.iterate_in_loop(coro)
284
+ return self._bg_loop.aio(coro)
285
+ except Exception as e:
286
+ if logger.getEffectiveLevel() > logging.DEBUG:
287
+ # If the log level is not DEBUG, we will remove the extra stack frames to avoid confusion for the user
288
+ # This is because the stack trace will include the Syncify wrapper and the background loop thread
289
+ tb = e.__traceback__
290
+ while tb and tb.tb_next:
291
+ if tb.tb_frame.f_code.co_name == self.fn.__name__:
292
+ break
293
+ tb = tb.tb_next
294
+ raise e.with_traceback(tb)
295
+ # If the log level is DEBUG, we will keep the extra stack frames to help with debugging
296
+ raise e
297
+
298
+
299
+ class Syncify:
300
+ """
301
+ A decorator to convert asynchronous functions or methods into synchronous ones.
302
+
303
+ This is useful for integrating async code into synchronous contexts.
304
+
305
+ Example::
306
+
307
+ ```python
308
+ syncer = Syncify()
309
+
310
+ @syncer
311
+ async def async_function(x: str) -> str:
312
+ return f"Hello, Async World {x}!"
313
+
314
+
315
+ # now you can call it synchronously
316
+ result = async_function("Async World")
317
+ print(result)
318
+ # Output: Hello, Async World Async World!
319
+
320
+ # or call it asynchronously
321
+ async def main():
322
+ result = await async_function.aio("World")
323
+ print(result)
324
+ ```
325
+
326
+ """
327
+
328
+ def __init__(self, name: str = "flyte_syncify"):
329
+ self._bg_loop = _BackgroundLoop(name=name)
330
+
331
+ @overload
332
+ def __call__(self, func: Callable[P, Awaitable[R_co]]) -> Any: ...
333
+
334
+ # def __call__(self, func: Callable[P, Awaitable[R_co]]) -> SyncFunction[P, R_co]: ...
335
+
336
+ @overload
337
+ def __call__(self, func: Callable[P, Iterator[R_co] | AsyncIterator[R_co]]) -> SyncGenFunction[P, R_co]: ...
338
+
339
+ # def __call__(self, func: Callable[[Type[T], *P.args, *P.kwargs], Awaitable[R_co]])
340
+ # -> SyncFunction[[Type[T], *P.args, *P.kwargs], R_co]: ...
341
+ @overload
342
+ def __call__(self, func: classmethod) -> Union[SyncFunction[P, R_co], SyncGenFunction[P, R_co]]: ...
343
+
344
+ @overload
345
+ def __call__(self, func: staticmethod) -> staticmethod: ...
346
+
347
+ def __call__(self, obj):
348
+ if isinstance(obj, classmethod):
349
+ wrapper = _SyncWrapper(obj.__func__, bg_loop=self._bg_loop, underlying_obj=obj)
350
+ functools.update_wrapper(wrapper, obj.__func__)
351
+ return wrapper
352
+
353
+ if isinstance(obj, staticmethod):
354
+ fn = obj.__func__
355
+ wrapper = _SyncWrapper(fn, bg_loop=self._bg_loop)
356
+ functools.update_wrapper(wrapper, fn)
357
+ return staticmethod(wrapper)
358
+
359
+ if inspect.isasyncgenfunction(obj):
360
+ wrapper = _SyncWrapper(obj, bg_loop=self._bg_loop)
361
+ functools.update_wrapper(wrapper, obj)
362
+ return cast(Callable[P, Iterator[R_co]], wrapper)
363
+
364
+ if inspect.iscoroutinefunction(obj):
365
+ wrapper = _SyncWrapper(obj, bg_loop=self._bg_loop)
366
+ functools.update_wrapper(wrapper, obj)
367
+ return wrapper
368
+
369
+ raise TypeError(
370
+ "Syncify can only be applied to async functions, async generators, async classmethods or staticmethods."
371
+ )
flyte/types/__init__.py CHANGED
@@ -1,9 +1,32 @@
1
+ """
2
+ # Flyte Type System
3
+
4
+ The Flyte type system provides a way to define, transform, and manipulate types in Flyte workflows.
5
+ Since the data flowing through Flyte has to often cross process, container and langauge boundaries, the type system
6
+ is designed to be serializable to a universal format that can be understood across different environments. This
7
+ universal format is based on Protocol Buffers. The types are called LiteralTypes and the runtime
8
+ representation of data is called Literals.
9
+
10
+ The type system includes:
11
+ - **TypeEngine**: The core engine that manages type transformations and serialization. This is the main entry point for
12
+ for all the internal type transformations and serialization logic.
13
+ - **TypeTransformer**: A class that defines how to transform one type to another. This is extensible
14
+ allowing users to define custom types and transformations.
15
+ - **Renderable**: An interface for types that can be rendered as HTML, that can be outputted to a flyte.report.
16
+
17
+ It is always possible to bypass the type system and use the `FlytePickle` type to serialize any python object
18
+ into a pickle format. The pickle format is not human-readable, but can be passed between flyte tasks that are
19
+ written in python. The Pickled objects cannot be represented in the UI, and may be in-efficient for large datasets.
20
+ """
21
+
1
22
  from ._interface import guess_interface
23
+ from ._pickle import FlytePickle
2
24
  from ._renderer import Renderable
3
25
  from ._string_literals import literal_string_repr
4
26
  from ._type_engine import TypeEngine, TypeTransformer, TypeTransformerFailedError
5
27
 
6
28
  __all__ = [
29
+ "FlytePickle",
7
30
  "Renderable",
8
31
  "TypeEngine",
9
32
  "TypeTransformer",
flyte/types/_interface.py CHANGED
@@ -1,11 +1,15 @@
1
- from typing import Any, Dict, Type, cast
1
+ import inspect
2
+ from typing import Any, Dict, Iterable, Tuple, Type, cast
2
3
 
3
- from flyteidl.core import interface_pb2
4
+ from flyteidl.core import interface_pb2, literals_pb2
4
5
 
5
- from flyte._datastructures import NativeInterface
6
+ from flyte._protos.workflow import common_pb2
7
+ from flyte.models import NativeInterface
6
8
 
7
9
 
8
- def guess_interface(interface: interface_pb2.TypedInterface) -> NativeInterface:
10
+ def guess_interface(
11
+ interface: interface_pb2.TypedInterface, default_inputs: Iterable[common_pb2.NamedParameter] | None = None
12
+ ) -> NativeInterface:
9
13
  """
10
14
  Returns the interface of the task with guessed types, as types may not be present in current env.
11
15
  """
@@ -14,12 +18,23 @@ def guess_interface(interface: interface_pb2.TypedInterface) -> NativeInterface:
14
18
  if interface is None:
15
19
  return NativeInterface({}, {})
16
20
 
17
- guessed_inputs: Dict[str, Type[Any]] = {}
21
+ default_input_literals: Dict[str, literals_pb2.Literal] = {}
22
+ if default_inputs is not None:
23
+ for param in default_inputs:
24
+ if param.parameter.HasField("default"):
25
+ default_input_literals[param.name] = param.parameter.default
26
+
27
+ guessed_inputs: Dict[str, Tuple[Type[Any], Any] | Any] = {}
18
28
  if interface.inputs is not None and len(interface.inputs.variables) > 0:
19
- guessed_inputs = flyte.types.TypeEngine.guess_python_types(cast(dict, interface.inputs.variables))
29
+ input_types = flyte.types.TypeEngine.guess_python_types(cast(dict, interface.inputs.variables))
30
+ for name, t in input_types.items():
31
+ if name not in default_input_literals:
32
+ guessed_inputs[name] = (t, inspect.Parameter.empty)
33
+ else:
34
+ guessed_inputs[name] = (t, NativeInterface.has_default)
20
35
 
21
36
  guessed_outputs: Dict[str, Type[Any]] = {}
22
37
  if interface.outputs is not None and len(interface.outputs.variables) > 0:
23
38
  guessed_outputs = flyte.types.TypeEngine.guess_python_types(cast(dict, interface.outputs.variables))
24
39
 
25
- return NativeInterface.from_types(guessed_inputs, guessed_outputs)
40
+ return NativeInterface.from_types(guessed_inputs, guessed_outputs, default_input_literals)
@@ -8,7 +8,8 @@ import cloudpickle
8
8
  from flyteidl.core import literals_pb2, types_pb2
9
9
 
10
10
  import flyte.storage as storage
11
- from flyte.types import TypeEngine, TypeTransformer
11
+
12
+ from ._type_engine import TypeEngine, TypeTransformer
12
13
 
13
14
  T = typing.TypeVar("T")
14
15