flyte 2.0.0b13__py3-none-any.whl → 2.0.0b30__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.
Files changed (211) hide show
  1. flyte/__init__.py +18 -2
  2. flyte/_bin/debug.py +38 -0
  3. flyte/_bin/runtime.py +62 -8
  4. flyte/_cache/cache.py +4 -2
  5. flyte/_cache/local_cache.py +216 -0
  6. flyte/_code_bundle/_ignore.py +12 -4
  7. flyte/_code_bundle/_packaging.py +13 -9
  8. flyte/_code_bundle/_utils.py +18 -10
  9. flyte/_code_bundle/bundle.py +17 -9
  10. flyte/_constants.py +1 -0
  11. flyte/_context.py +4 -1
  12. flyte/_custom_context.py +73 -0
  13. flyte/_debug/constants.py +38 -0
  14. flyte/_debug/utils.py +17 -0
  15. flyte/_debug/vscode.py +307 -0
  16. flyte/_deploy.py +235 -61
  17. flyte/_environment.py +20 -6
  18. flyte/_excepthook.py +1 -1
  19. flyte/_hash.py +1 -16
  20. flyte/_image.py +178 -81
  21. flyte/_initialize.py +132 -51
  22. flyte/_interface.py +39 -2
  23. flyte/_internal/controllers/__init__.py +4 -5
  24. flyte/_internal/controllers/_local_controller.py +70 -29
  25. flyte/_internal/controllers/_trace.py +1 -1
  26. flyte/_internal/controllers/remote/__init__.py +0 -2
  27. flyte/_internal/controllers/remote/_action.py +14 -16
  28. flyte/_internal/controllers/remote/_client.py +1 -1
  29. flyte/_internal/controllers/remote/_controller.py +68 -70
  30. flyte/_internal/controllers/remote/_core.py +127 -99
  31. flyte/_internal/controllers/remote/_informer.py +19 -10
  32. flyte/_internal/controllers/remote/_service_protocol.py +7 -7
  33. flyte/_internal/imagebuild/docker_builder.py +181 -69
  34. flyte/_internal/imagebuild/image_builder.py +0 -5
  35. flyte/_internal/imagebuild/remote_builder.py +155 -64
  36. flyte/_internal/imagebuild/utils.py +51 -2
  37. flyte/_internal/resolvers/_task_module.py +5 -38
  38. flyte/_internal/resolvers/default.py +2 -2
  39. flyte/_internal/runtime/convert.py +110 -21
  40. flyte/_internal/runtime/entrypoints.py +27 -1
  41. flyte/_internal/runtime/io.py +21 -8
  42. flyte/_internal/runtime/resources_serde.py +20 -6
  43. flyte/_internal/runtime/reuse.py +1 -1
  44. flyte/_internal/runtime/rusty.py +20 -5
  45. flyte/_internal/runtime/task_serde.py +34 -19
  46. flyte/_internal/runtime/taskrunner.py +22 -4
  47. flyte/_internal/runtime/trigger_serde.py +160 -0
  48. flyte/_internal/runtime/types_serde.py +1 -1
  49. flyte/_keyring/__init__.py +0 -0
  50. flyte/_keyring/file.py +115 -0
  51. flyte/_logging.py +201 -39
  52. flyte/_map.py +111 -14
  53. flyte/_module.py +70 -0
  54. flyte/_pod.py +4 -3
  55. flyte/_resources.py +213 -31
  56. flyte/_run.py +110 -39
  57. flyte/_task.py +75 -16
  58. flyte/_task_environment.py +105 -29
  59. flyte/_task_plugins.py +4 -2
  60. flyte/_trace.py +5 -0
  61. flyte/_trigger.py +1000 -0
  62. flyte/_utils/__init__.py +2 -1
  63. flyte/_utils/asyn.py +3 -1
  64. flyte/_utils/coro_management.py +2 -1
  65. flyte/_utils/docker_credentials.py +173 -0
  66. flyte/_utils/module_loader.py +17 -2
  67. flyte/_version.py +3 -3
  68. flyte/cli/_abort.py +3 -3
  69. flyte/cli/_build.py +3 -6
  70. flyte/cli/_common.py +78 -7
  71. flyte/cli/_create.py +182 -4
  72. flyte/cli/_delete.py +23 -1
  73. flyte/cli/_deploy.py +63 -16
  74. flyte/cli/_get.py +79 -34
  75. flyte/cli/_params.py +26 -10
  76. flyte/cli/_plugins.py +209 -0
  77. flyte/cli/_run.py +151 -26
  78. flyte/cli/_serve.py +64 -0
  79. flyte/cli/_update.py +37 -0
  80. flyte/cli/_user.py +17 -0
  81. flyte/cli/main.py +30 -4
  82. flyte/config/_config.py +10 -6
  83. flyte/config/_internal.py +1 -0
  84. flyte/config/_reader.py +29 -8
  85. flyte/connectors/__init__.py +11 -0
  86. flyte/connectors/_connector.py +270 -0
  87. flyte/connectors/_server.py +197 -0
  88. flyte/connectors/utils.py +135 -0
  89. flyte/errors.py +22 -2
  90. flyte/extend.py +8 -1
  91. flyte/extras/_container.py +6 -1
  92. flyte/git/__init__.py +3 -0
  93. flyte/git/_config.py +21 -0
  94. flyte/io/__init__.py +2 -0
  95. flyte/io/_dataframe/__init__.py +2 -0
  96. flyte/io/_dataframe/basic_dfs.py +17 -8
  97. flyte/io/_dataframe/dataframe.py +98 -132
  98. flyte/io/_dir.py +575 -113
  99. flyte/io/_file.py +582 -139
  100. flyte/io/_hashing_io.py +342 -0
  101. flyte/models.py +74 -15
  102. flyte/remote/__init__.py +6 -1
  103. flyte/remote/_action.py +34 -26
  104. flyte/remote/_client/_protocols.py +39 -4
  105. flyte/remote/_client/auth/_authenticators/device_code.py +4 -5
  106. flyte/remote/_client/auth/_authenticators/pkce.py +1 -1
  107. flyte/remote/_client/auth/_channel.py +10 -6
  108. flyte/remote/_client/controlplane.py +17 -5
  109. flyte/remote/_console.py +3 -2
  110. flyte/remote/_data.py +6 -6
  111. flyte/remote/_logs.py +3 -3
  112. flyte/remote/_run.py +64 -8
  113. flyte/remote/_secret.py +26 -17
  114. flyte/remote/_task.py +75 -33
  115. flyte/remote/_trigger.py +306 -0
  116. flyte/remote/_user.py +33 -0
  117. flyte/report/_report.py +1 -1
  118. flyte/storage/__init__.py +6 -1
  119. flyte/storage/_config.py +5 -1
  120. flyte/storage/_parallel_reader.py +274 -0
  121. flyte/storage/_storage.py +200 -103
  122. flyte/types/__init__.py +16 -0
  123. flyte/types/_interface.py +2 -2
  124. flyte/types/_pickle.py +35 -8
  125. flyte/types/_string_literals.py +8 -9
  126. flyte/types/_type_engine.py +40 -70
  127. flyte/types/_utils.py +1 -1
  128. flyte-2.0.0b30.data/scripts/debug.py +38 -0
  129. {flyte-2.0.0b13.data → flyte-2.0.0b30.data}/scripts/runtime.py +62 -8
  130. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/METADATA +11 -3
  131. flyte-2.0.0b30.dist-info/RECORD +192 -0
  132. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/entry_points.txt +3 -0
  133. flyte/_protos/common/authorization_pb2.py +0 -66
  134. flyte/_protos/common/authorization_pb2.pyi +0 -108
  135. flyte/_protos/common/authorization_pb2_grpc.py +0 -4
  136. flyte/_protos/common/identifier_pb2.py +0 -93
  137. flyte/_protos/common/identifier_pb2.pyi +0 -110
  138. flyte/_protos/common/identifier_pb2_grpc.py +0 -4
  139. flyte/_protos/common/identity_pb2.py +0 -48
  140. flyte/_protos/common/identity_pb2.pyi +0 -72
  141. flyte/_protos/common/identity_pb2_grpc.py +0 -4
  142. flyte/_protos/common/list_pb2.py +0 -36
  143. flyte/_protos/common/list_pb2.pyi +0 -71
  144. flyte/_protos/common/list_pb2_grpc.py +0 -4
  145. flyte/_protos/common/policy_pb2.py +0 -37
  146. flyte/_protos/common/policy_pb2.pyi +0 -27
  147. flyte/_protos/common/policy_pb2_grpc.py +0 -4
  148. flyte/_protos/common/role_pb2.py +0 -37
  149. flyte/_protos/common/role_pb2.pyi +0 -53
  150. flyte/_protos/common/role_pb2_grpc.py +0 -4
  151. flyte/_protos/common/runtime_version_pb2.py +0 -28
  152. flyte/_protos/common/runtime_version_pb2.pyi +0 -24
  153. flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
  154. flyte/_protos/imagebuilder/definition_pb2.py +0 -59
  155. flyte/_protos/imagebuilder/definition_pb2.pyi +0 -140
  156. flyte/_protos/imagebuilder/definition_pb2_grpc.py +0 -4
  157. flyte/_protos/imagebuilder/payload_pb2.py +0 -32
  158. flyte/_protos/imagebuilder/payload_pb2.pyi +0 -21
  159. flyte/_protos/imagebuilder/payload_pb2_grpc.py +0 -4
  160. flyte/_protos/imagebuilder/service_pb2.py +0 -29
  161. flyte/_protos/imagebuilder/service_pb2.pyi +0 -5
  162. flyte/_protos/imagebuilder/service_pb2_grpc.py +0 -66
  163. flyte/_protos/logs/dataplane/payload_pb2.py +0 -100
  164. flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -177
  165. flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
  166. flyte/_protos/secret/definition_pb2.py +0 -49
  167. flyte/_protos/secret/definition_pb2.pyi +0 -93
  168. flyte/_protos/secret/definition_pb2_grpc.py +0 -4
  169. flyte/_protos/secret/payload_pb2.py +0 -62
  170. flyte/_protos/secret/payload_pb2.pyi +0 -94
  171. flyte/_protos/secret/payload_pb2_grpc.py +0 -4
  172. flyte/_protos/secret/secret_pb2.py +0 -38
  173. flyte/_protos/secret/secret_pb2.pyi +0 -6
  174. flyte/_protos/secret/secret_pb2_grpc.py +0 -198
  175. flyte/_protos/secret/secret_pb2_grpc_grpc.py +0 -198
  176. flyte/_protos/validate/validate/validate_pb2.py +0 -76
  177. flyte/_protos/workflow/common_pb2.py +0 -27
  178. flyte/_protos/workflow/common_pb2.pyi +0 -14
  179. flyte/_protos/workflow/common_pb2_grpc.py +0 -4
  180. flyte/_protos/workflow/environment_pb2.py +0 -29
  181. flyte/_protos/workflow/environment_pb2.pyi +0 -12
  182. flyte/_protos/workflow/environment_pb2_grpc.py +0 -4
  183. flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
  184. flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
  185. flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
  186. flyte/_protos/workflow/queue_service_pb2.py +0 -109
  187. flyte/_protos/workflow/queue_service_pb2.pyi +0 -166
  188. flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
  189. flyte/_protos/workflow/run_definition_pb2.py +0 -121
  190. flyte/_protos/workflow/run_definition_pb2.pyi +0 -327
  191. flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
  192. flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
  193. flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
  194. flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
  195. flyte/_protos/workflow/run_service_pb2.py +0 -137
  196. flyte/_protos/workflow/run_service_pb2.pyi +0 -185
  197. flyte/_protos/workflow/run_service_pb2_grpc.py +0 -446
  198. flyte/_protos/workflow/state_service_pb2.py +0 -67
  199. flyte/_protos/workflow/state_service_pb2.pyi +0 -76
  200. flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
  201. flyte/_protos/workflow/task_definition_pb2.py +0 -79
  202. flyte/_protos/workflow/task_definition_pb2.pyi +0 -81
  203. flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
  204. flyte/_protos/workflow/task_service_pb2.py +0 -60
  205. flyte/_protos/workflow/task_service_pb2.pyi +0 -59
  206. flyte/_protos/workflow/task_service_pb2_grpc.py +0 -138
  207. flyte-2.0.0b13.dist-info/RECORD +0 -239
  208. /flyte/{_protos → _debug}/__init__.py +0 -0
  209. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/WHEEL +0 -0
  210. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/licenses/LICENSE +0 -0
  211. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/top_level.txt +0 -0
flyte/__init__.py CHANGED
@@ -9,15 +9,17 @@ import sys
9
9
  from ._build import build
10
10
  from ._cache import Cache, CachePolicy, CacheRequest
11
11
  from ._context import ctx
12
+ from ._custom_context import custom_context, get_custom_context
12
13
  from ._deploy import build_images, deploy
13
14
  from ._environment import Environment
14
15
  from ._excepthook import custom_excepthook
15
16
  from ._group import group
16
17
  from ._image import Image
17
- from ._initialize import init, init_from_config
18
+ from ._initialize import current_domain, init, init_from_config
19
+ from ._logging import logger
18
20
  from ._map import map
19
21
  from ._pod import PodTemplate
20
- from ._resources import GPU, TPU, Device, Resources
22
+ from ._resources import AMD_GPU, GPU, HABANA_GAUDI, TPU, Device, DeviceClass, Neuron, Resources
21
23
  from ._retry import RetryStrategy
22
24
  from ._reusable_environment import ReusePolicy
23
25
  from ._run import run, with_runcontext
@@ -25,6 +27,7 @@ from ._secret import Secret, SecretRequest
25
27
  from ._task_environment import TaskEnvironment
26
28
  from ._timeout import Timeout, TimeoutType
27
29
  from ._trace import trace
30
+ from ._trigger import Cron, FixedRate, Trigger, TriggerTime
28
31
  from ._version import __version__
29
32
 
30
33
  sys.excepthook = custom_excepthook
@@ -59,14 +62,20 @@ def version() -> str:
59
62
 
60
63
 
61
64
  __all__ = [
65
+ "AMD_GPU",
62
66
  "GPU",
67
+ "HABANA_GAUDI",
63
68
  "TPU",
64
69
  "Cache",
65
70
  "CachePolicy",
66
71
  "CacheRequest",
72
+ "Cron",
67
73
  "Device",
74
+ "DeviceClass",
68
75
  "Environment",
76
+ "FixedRate",
69
77
  "Image",
78
+ "Neuron",
70
79
  "PodTemplate",
71
80
  "Resources",
72
81
  "RetryStrategy",
@@ -76,16 +85,23 @@ __all__ = [
76
85
  "TaskEnvironment",
77
86
  "Timeout",
78
87
  "TimeoutType",
88
+ "Trigger",
89
+ "TriggerTime",
79
90
  "__version__",
80
91
  "build",
81
92
  "build_images",
82
93
  "ctx",
94
+ "current_domain",
95
+ "custom_context",
83
96
  "deploy",
97
+ "get_custom_context",
84
98
  "group",
85
99
  "init",
86
100
  "init_from_config",
101
+ "logger",
87
102
  "map",
88
103
  "run",
89
104
  "trace",
105
+ "version",
90
106
  "with_runcontext",
91
107
  ]
flyte/_bin/debug.py ADDED
@@ -0,0 +1,38 @@
1
+ import click
2
+
3
+
4
+ @click.group()
5
+ def _debug():
6
+ """Debug commands for Flyte."""
7
+
8
+
9
+ @_debug.command("resume")
10
+ @click.option("--pid", "-m", type=int, required=True, help="PID of the vscode server.")
11
+ def resume(pid):
12
+ """
13
+ Resume a Flyte task for debugging purposes.
14
+
15
+ Args:
16
+ pid (int): PID of the vscode server.
17
+ """
18
+ import os
19
+ import signal
20
+
21
+ print("Terminating server and resuming task.")
22
+ answer = (
23
+ input(
24
+ "This operation will kill the server. All unsaved data will be lost,"
25
+ " and you will no longer be able to connect to it. Do you really want to terminate? (Y/N): "
26
+ )
27
+ .strip()
28
+ .upper()
29
+ )
30
+ if answer == "Y":
31
+ os.kill(pid, signal.SIGTERM)
32
+ print("The server has been terminated and the task has been resumed.")
33
+ else:
34
+ print("Operation canceled.")
35
+
36
+
37
+ if __name__ == "__main__":
38
+ _debug()
flyte/_bin/runtime.py CHANGED
@@ -12,6 +12,9 @@ from typing import Any, List
12
12
 
13
13
  import click
14
14
 
15
+ from flyte._utils.helpers import str2bool
16
+ from flyte.models import PathRewrite
17
+
15
18
  # Todo: work with pvditt to make these the names
16
19
  # ACTION_NAME = "_U_ACTION_NAME"
17
20
  # RUN_NAME = "_U_RUN_NAME"
@@ -21,14 +24,16 @@ import click
21
24
 
22
25
  ACTION_NAME = "ACTION_NAME"
23
26
  RUN_NAME = "RUN_NAME"
24
- PROJECT_NAME = "FLYTE_INTERNAL_TASK_PROJECT"
25
- DOMAIN_NAME = "FLYTE_INTERNAL_TASK_DOMAIN"
27
+ PROJECT_NAME = "FLYTE_INTERNAL_EXECUTION_PROJECT"
28
+ DOMAIN_NAME = "FLYTE_INTERNAL_EXECUTION_DOMAIN"
26
29
  ORG_NAME = "_U_ORG_NAME"
27
30
  ENDPOINT_OVERRIDE = "_U_EP_OVERRIDE"
31
+ INSECURE_SKIP_VERIFY_OVERRIDE = "_U_INSECURE_SKIP_VERIFY"
28
32
  RUN_OUTPUT_BASE_DIR = "_U_RUN_BASE"
33
+ FLYTE_ENABLE_VSCODE_KEY = "_F_E_VS"
29
34
 
30
- # TODO: Remove this after proper auth is implemented
31
35
  _UNION_EAGER_API_KEY_ENV_VAR = "_UNION_EAGER_API_KEY"
36
+ _F_PATH_REWRITE = "_F_PATH_REWRITE"
32
37
 
33
38
 
34
39
  @click.group()
@@ -49,6 +54,8 @@ def _pass_through():
49
54
  @click.option("--project", envvar=PROJECT_NAME, required=False)
50
55
  @click.option("--domain", envvar=DOMAIN_NAME, required=False)
51
56
  @click.option("--org", envvar=ORG_NAME, required=False)
57
+ @click.option("--debug", envvar=FLYTE_ENABLE_VSCODE_KEY, type=click.BOOL, required=False)
58
+ @click.option("--interactive-mode", type=click.BOOL, required=False)
52
59
  @click.option("--image-cache", required=False)
53
60
  @click.option("--tgz", required=False)
54
61
  @click.option("--pkl", required=False)
@@ -59,12 +66,16 @@ def _pass_through():
59
66
  type=click.UNPROCESSED,
60
67
  nargs=-1,
61
68
  )
69
+ @click.pass_context
62
70
  def main(
71
+ ctx: click.Context,
63
72
  run_name: str,
64
73
  name: str,
65
74
  project: str,
66
75
  domain: str,
67
76
  org: str,
77
+ debug: bool,
78
+ interactive_mode: bool,
68
79
  image_cache: str,
69
80
  version: str,
70
81
  inputs: str,
@@ -87,6 +98,7 @@ def main(
87
98
  import flyte
88
99
  import flyte._utils as utils
89
100
  import flyte.errors
101
+ import flyte.storage as storage
90
102
  from flyte._initialize import init
91
103
  from flyte._internal.controllers import create_controller
92
104
  from flyte._internal.imagebuild.image_builder import ImageCache
@@ -109,6 +121,13 @@ def main(
109
121
  if name.startswith("{{"):
110
122
  name = os.getenv("ACTION_NAME", "")
111
123
 
124
+ logger.warning(f"Flyte runtime started for action {name} with run name {run_name}")
125
+
126
+ if debug and name == "a0":
127
+ from flyte._debug.vscode import _start_vscode_server
128
+
129
+ asyncio.run(_start_vscode_server(ctx))
130
+
112
131
  # Figure out how to connect
113
132
  # This detection of api key is a hack for now.
114
133
  controller_kwargs: dict[str, Any] = {"insecure": False}
@@ -122,19 +141,40 @@ def main(
122
141
  controller_kwargs["insecure"] = True
123
142
  logger.debug(f"Using controller endpoint: {ep} with kwargs: {controller_kwargs}")
124
143
 
125
- bundle = CodeBundle(tgz=tgz, pkl=pkl, destination=dest, computed_version=version)
126
- init(org=org, project=project, domain=domain, **controller_kwargs)
144
+ # Check for insecure_skip_verify override (e.g. for self-signed certs)
145
+ insecure_skip_verify_str = os.getenv(INSECURE_SKIP_VERIFY_OVERRIDE, "")
146
+ if str2bool(insecure_skip_verify_str):
147
+ controller_kwargs["insecure_skip_verify"] = True
148
+ logger.info("SSL certificate verification disabled (insecure_skip_verify=True)")
149
+
150
+ bundle = None
151
+ if tgz or pkl:
152
+ bundle = CodeBundle(tgz=tgz, pkl=pkl, destination=dest, computed_version=version)
153
+ init(org=org, project=project, domain=domain, image_builder="remote", **controller_kwargs)
127
154
  # Controller is created with the same kwargs as init, so that it can be used to run tasks
128
155
  controller = create_controller(ct="remote", **controller_kwargs)
129
156
 
130
157
  ic = ImageCache.from_transport(image_cache) if image_cache else None
131
158
 
159
+ path_rewrite_cfg = os.getenv(_F_PATH_REWRITE, None)
160
+ path_rewrite = None
161
+ if path_rewrite_cfg:
162
+ potential_path_rewrite = PathRewrite.from_str(path_rewrite_cfg)
163
+ if storage.exists_sync(potential_path_rewrite.new_prefix):
164
+ path_rewrite = potential_path_rewrite
165
+ logger.info(f"Path rewrite configured for {path_rewrite.new_prefix}")
166
+ else:
167
+ logger.error(
168
+ f"Path rewrite failed for path {potential_path_rewrite.new_prefix}, "
169
+ f"not found, reverting to original path {potential_path_rewrite.old_prefix}"
170
+ )
171
+
132
172
  # Create a coroutine to load the task and run it
133
173
  task_coroutine = load_and_run_task(
134
174
  resolver=resolver,
135
175
  resolver_args=resolver_args,
136
176
  action=ActionID(name=name, run_name=run_name, project=project, domain=domain, org=org),
137
- raw_data_path=RawDataPath(path=raw_data_path),
177
+ raw_data_path=RawDataPath(path=raw_data_path, path_rewrite=path_rewrite),
138
178
  checkpoints=Checkpoints(checkpoint_path, prev_checkpoint),
139
179
  code_bundle=bundle,
140
180
  input_path=inputs,
@@ -143,6 +183,7 @@ def main(
143
183
  version=version,
144
184
  controller=controller,
145
185
  image_cache=ic,
186
+ interactive_mode=interactive_mode or debug,
146
187
  )
147
188
  # Create a coroutine to watch for errors
148
189
  controller_failure = controller.watch_for_errors()
@@ -151,10 +192,23 @@ def main(
151
192
  async def _run_and_stop():
152
193
  loop = asyncio.get_event_loop()
153
194
  loop.set_exception_handler(flyte.errors.silence_grpc_polling_error)
154
- await utils.run_coros(controller_failure, task_coroutine)
155
- await controller.stop()
195
+ try:
196
+ await utils.run_coros(controller_failure, task_coroutine)
197
+ await controller.stop()
198
+ except flyte.errors.RuntimeSystemError as e:
199
+ logger.error(f"Runtime system error: {e}")
200
+ from flyte._internal.runtime.convert import convert_from_native_to_error
201
+ from flyte._internal.runtime.io import upload_error
202
+
203
+ logger.error(f"Flyte runtime failed for action {name} with run name {run_name}, error: {e}")
204
+ err = convert_from_native_to_error(e)
205
+ path = await upload_error(err.err, outputs_path)
206
+ logger.error(f"Run {run_name} Action {name} failed with error: {err}. Uploaded error to {path}")
207
+ await controller.stop()
208
+ raise
156
209
 
157
210
  asyncio.run(_run_and_stop())
211
+ logger.warning(f"Flyte runtime completed for action {name} with run name {run_name}")
158
212
 
159
213
 
160
214
  if __name__ == "__main__":
flyte/_cache/cache.py CHANGED
@@ -77,14 +77,16 @@ class Cache:
77
77
  def __post_init__(self):
78
78
  if self.behavior not in get_args(CacheBehavior):
79
79
  raise ValueError(f"Invalid cache behavior: {self.behavior}. Must be one of ['auto', 'override', 'disable']")
80
- if self.behavior == "disable":
81
- return
82
80
 
81
+ # Still setup _ignore_inputs when cache is disabled to prevent _ignored_inputs attribute not found error
83
82
  if isinstance(self.ignored_inputs, str):
84
83
  self._ignored_inputs = (self.ignored_inputs,)
85
84
  else:
86
85
  self._ignored_inputs = self.ignored_inputs
87
86
 
87
+ if self.behavior == "disable":
88
+ return
89
+
88
90
  # Normalize policies so that self._policies is always a list
89
91
  if self.policies is None:
90
92
  from flyte._cache.defaults import get_default_policies
@@ -0,0 +1,216 @@
1
+ import sqlite3
2
+ from pathlib import Path
3
+
4
+ try:
5
+ import aiosqlite
6
+
7
+ HAS_AIOSQLITE = True
8
+ except ImportError:
9
+ HAS_AIOSQLITE = False
10
+
11
+ from flyteidl2.task import common_pb2
12
+
13
+ from flyte._internal.runtime import convert
14
+ from flyte._logging import logger
15
+ from flyte.config import auto
16
+
17
+ DEFAULT_CACHE_DIR = "~/.flyte"
18
+ CACHE_LOCATION = "local-cache/cache.db"
19
+
20
+
21
+ class LocalTaskCache(object):
22
+ """
23
+ This class implements a persistent store able to cache the result of local task executions.
24
+ """
25
+
26
+ _conn: "aiosqlite.Connection | None" = None
27
+ _conn_sync: sqlite3.Connection | None = None
28
+ _initialized: bool = False
29
+
30
+ @staticmethod
31
+ def _get_cache_path() -> str:
32
+ """Get the cache database path, creating directory if needed."""
33
+ config = auto()
34
+ if config.source:
35
+ cache_dir = config.source.parent
36
+ else:
37
+ cache_dir = Path(DEFAULT_CACHE_DIR).expanduser()
38
+
39
+ cache_path = cache_dir / CACHE_LOCATION
40
+ # Ensure the directory exists
41
+ cache_path.parent.mkdir(parents=True, exist_ok=True)
42
+ logger.info(f"Use local cache path: {cache_path}")
43
+ return str(cache_path)
44
+
45
+ @staticmethod
46
+ async def initialize():
47
+ """Initialize the cache with database connection."""
48
+ if not LocalTaskCache._initialized:
49
+ if HAS_AIOSQLITE:
50
+ await LocalTaskCache._initialize_async()
51
+ else:
52
+ LocalTaskCache._initialize_sync()
53
+
54
+ @staticmethod
55
+ async def _initialize_async():
56
+ """Initialize async cache connection."""
57
+ db_path = LocalTaskCache._get_cache_path()
58
+ conn = await aiosqlite.connect(db_path)
59
+ await conn.execute("""
60
+ CREATE TABLE IF NOT EXISTS task_cache (
61
+ key TEXT PRIMARY KEY,
62
+ value BLOB
63
+ )
64
+ """)
65
+ await conn.commit()
66
+ LocalTaskCache._conn = conn
67
+ LocalTaskCache._initialized = True
68
+
69
+ @staticmethod
70
+ def _initialize_sync():
71
+ """Initialize sync cache connection."""
72
+ db_path = LocalTaskCache._get_cache_path()
73
+ conn = sqlite3.connect(db_path)
74
+ conn.execute("""
75
+ CREATE TABLE IF NOT EXISTS task_cache (
76
+ key TEXT PRIMARY KEY,
77
+ value BLOB
78
+ )
79
+ """)
80
+ conn.commit()
81
+ LocalTaskCache._conn_sync = conn
82
+ LocalTaskCache._initialized = True
83
+
84
+ @staticmethod
85
+ async def clear():
86
+ """Clear all cache entries."""
87
+ if not LocalTaskCache._initialized:
88
+ await LocalTaskCache.initialize()
89
+
90
+ if HAS_AIOSQLITE:
91
+ await LocalTaskCache._clear_async()
92
+ else:
93
+ LocalTaskCache._clear_sync()
94
+
95
+ @staticmethod
96
+ async def _clear_async():
97
+ """Clear all cache entries (async)."""
98
+ if LocalTaskCache._conn is None:
99
+ raise RuntimeError("Cache not properly initialized")
100
+ await LocalTaskCache._conn.execute("DELETE FROM task_cache")
101
+ await LocalTaskCache._conn.commit()
102
+
103
+ @staticmethod
104
+ def _clear_sync():
105
+ """Clear all cache entries (sync)."""
106
+ if LocalTaskCache._conn_sync is None:
107
+ raise RuntimeError("Cache not properly initialized")
108
+ LocalTaskCache._conn_sync.execute("DELETE FROM task_cache")
109
+ LocalTaskCache._conn_sync.commit()
110
+
111
+ @staticmethod
112
+ async def get(cache_key: str) -> convert.Outputs | None:
113
+ if not LocalTaskCache._initialized:
114
+ await LocalTaskCache.initialize()
115
+
116
+ if HAS_AIOSQLITE:
117
+ return await LocalTaskCache._get_async(cache_key)
118
+ else:
119
+ return LocalTaskCache._get_sync(cache_key)
120
+
121
+ @staticmethod
122
+ async def _get_async(cache_key: str) -> convert.Outputs | None:
123
+ """Get cache entry (async)."""
124
+ if LocalTaskCache._conn is None:
125
+ raise RuntimeError("Cache not properly initialized")
126
+
127
+ async with LocalTaskCache._conn.execute("SELECT value FROM task_cache WHERE key = ?", (cache_key,)) as cursor:
128
+ row = await cursor.fetchone()
129
+ if row:
130
+ outputs_bytes = row[0]
131
+ outputs = common_pb2.Outputs()
132
+ outputs.ParseFromString(outputs_bytes)
133
+ return convert.Outputs(proto_outputs=outputs)
134
+ return None
135
+
136
+ @staticmethod
137
+ def _get_sync(cache_key: str) -> convert.Outputs | None:
138
+ """Get cache entry (sync)."""
139
+ if LocalTaskCache._conn_sync is None:
140
+ raise RuntimeError("Cache not properly initialized")
141
+
142
+ cursor = LocalTaskCache._conn_sync.execute("SELECT value FROM task_cache WHERE key = ?", (cache_key,))
143
+ row = cursor.fetchone()
144
+ if row:
145
+ outputs_bytes = row[0]
146
+ outputs = common_pb2.Outputs()
147
+ outputs.ParseFromString(outputs_bytes)
148
+ return convert.Outputs(proto_outputs=outputs)
149
+ return None
150
+
151
+ @staticmethod
152
+ async def set(
153
+ cache_key: str,
154
+ value: convert.Outputs,
155
+ ) -> None:
156
+ if not LocalTaskCache._initialized:
157
+ await LocalTaskCache.initialize()
158
+
159
+ if HAS_AIOSQLITE:
160
+ await LocalTaskCache._set_async(cache_key, value)
161
+ else:
162
+ LocalTaskCache._set_sync(cache_key, value)
163
+
164
+ @staticmethod
165
+ async def _set_async(
166
+ cache_key: str,
167
+ value: convert.Outputs,
168
+ ) -> None:
169
+ """Set cache entry (async)."""
170
+ if LocalTaskCache._conn is None:
171
+ raise RuntimeError("Cache not properly initialized")
172
+
173
+ output_bytes = value.proto_outputs.SerializeToString()
174
+ await LocalTaskCache._conn.execute(
175
+ "INSERT OR REPLACE INTO task_cache (key, value) VALUES (?, ?)", (cache_key, output_bytes)
176
+ )
177
+ await LocalTaskCache._conn.commit()
178
+
179
+ @staticmethod
180
+ def _set_sync(
181
+ cache_key: str,
182
+ value: convert.Outputs,
183
+ ) -> None:
184
+ """Set cache entry (sync)."""
185
+ if LocalTaskCache._conn_sync is None:
186
+ raise RuntimeError("Cache not properly initialized")
187
+
188
+ output_bytes = value.proto_outputs.SerializeToString()
189
+ LocalTaskCache._conn_sync.execute(
190
+ "INSERT OR REPLACE INTO task_cache (key, value) VALUES (?, ?)", (cache_key, output_bytes)
191
+ )
192
+ LocalTaskCache._conn_sync.commit()
193
+
194
+ @staticmethod
195
+ async def close():
196
+ """Close the database connection."""
197
+ if HAS_AIOSQLITE:
198
+ await LocalTaskCache._close_async()
199
+ else:
200
+ LocalTaskCache._close_sync()
201
+
202
+ @staticmethod
203
+ async def _close_async():
204
+ """Close async database connection."""
205
+ if LocalTaskCache._conn:
206
+ await LocalTaskCache._conn.close()
207
+ LocalTaskCache._conn = None
208
+ LocalTaskCache._initialized = False
209
+
210
+ @staticmethod
211
+ def _close_sync():
212
+ """Close sync database connection."""
213
+ if LocalTaskCache._conn_sync:
214
+ LocalTaskCache._conn_sync.close()
215
+ LocalTaskCache._conn_sync = None
216
+ LocalTaskCache._initialized = False
@@ -79,12 +79,19 @@ class StandardIgnore(Ignore):
79
79
  by fed with custom ignore patterns from cli."""
80
80
 
81
81
  def __init__(self, root: Path, patterns: Optional[List[str]] = None):
82
- super().__init__(root)
82
+ super().__init__(root.resolve())
83
83
  self.patterns = patterns if patterns else STANDARD_IGNORE_PATTERNS
84
84
 
85
85
  def _is_ignored(self, path: pathlib.Path) -> bool:
86
+ # Convert to relative path for pattern matching
87
+ try:
88
+ rel_path = path.relative_to(self.root)
89
+ except ValueError:
90
+ # If path is not under root, don't ignore it
91
+ return False
92
+
86
93
  for pattern in self.patterns:
87
- if fnmatch(str(path), pattern):
94
+ if fnmatch(str(rel_path), pattern):
88
95
  return True
89
96
  return False
90
97
 
@@ -105,9 +112,10 @@ class IgnoreGroup(Ignore):
105
112
 
106
113
  def list_ignored(self) -> List[str]:
107
114
  ignored = []
108
- for dir, _, files in self.root.walk():
115
+ for dir, _, files in os.walk(self.root):
116
+ dir_path = Path(dir)
109
117
  for file in files:
110
- abs_path = dir / file
118
+ abs_path = dir_path / file
111
119
  if self.is_ignored(abs_path):
112
120
  ignored.append(str(abs_path.relative_to(self.root)))
113
121
  return ignored
@@ -14,10 +14,9 @@ import typing
14
14
  from typing import List, Optional, Tuple, Union
15
15
 
16
16
  import click
17
- from rich import print as rich_print
18
17
  from rich.tree import Tree
19
18
 
20
- from flyte._logging import logger
19
+ from flyte._logging import _get_console, logger
21
20
 
22
21
  from ._ignore import Ignore, IgnoreGroup
23
22
  from ._utils import CopyFiles, _filehash_update, _pathhash_update, ls_files, tar_strip_file_attributes
@@ -27,21 +26,21 @@ FAST_FILEENDING = ".tar.gz"
27
26
 
28
27
 
29
28
  def print_ls_tree(source: os.PathLike, ls: typing.List[str]):
30
- click.secho("Files to be copied for fast registration...", fg="bright_blue")
29
+ logger.info("Files to be copied for fast registration...")
31
30
 
32
31
  tree_root = Tree(
33
- f":open_file_folder: [link file://{source}]{source} (detected source root)",
32
+ f"File structure:\n:open_file_folder: {source}",
34
33
  guide_style="bold bright_blue",
35
34
  )
36
- trees = {pathlib.Path(source): tree_root}
37
-
35
+ source_path = pathlib.Path(source).resolve()
36
+ trees = {source_path: tree_root}
38
37
  for f in ls:
39
38
  fpp = pathlib.Path(f)
40
39
  if fpp.parent not in trees:
41
40
  # add trees for all intermediate folders
42
41
  current = tree_root
43
- current_path = pathlib.Path(source)
44
- for subdir in fpp.parent.relative_to(source).parts:
42
+ current_path = source_path # pathlib.Path(source)
43
+ for subdir in fpp.parent.relative_to(source_path).parts:
45
44
  current_path = current_path / subdir
46
45
  if current_path not in trees:
47
46
  current = current.add(f"{subdir}", guide_style="bold bright_blue")
@@ -49,7 +48,12 @@ def print_ls_tree(source: os.PathLike, ls: typing.List[str]):
49
48
  else:
50
49
  current = trees[current_path]
51
50
  trees[fpp.parent].add(f"{fpp.name}", guide_style="bold bright_blue")
52
- rich_print(tree_root)
51
+
52
+ console = _get_console()
53
+ with console.capture() as capture:
54
+ console.print(tree_root, overflow="ignore", no_wrap=True, crop=False)
55
+ logger.info(f"Root directory: [link=file://{source}]{source}[/link]")
56
+ logger.info(capture.get(), extra={"console": console})
53
57
 
54
58
 
55
59
  def _compress_tarball(source: pathlib.Path, output: pathlib.Path) -> None:
@@ -156,7 +156,7 @@ def list_all_files(source_path: pathlib.Path, deref_symlinks, ignore_group: Opti
156
156
 
157
157
  # This is needed to prevent infinite recursion when walking with followlinks
158
158
  visited_inodes = set()
159
- for root, dirnames, files in source_path.walk(top_down=True, follow_symlinks=deref_symlinks):
159
+ for root, dirnames, files in os.walk(source_path, topdown=True, followlinks=deref_symlinks):
160
160
  dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]
161
161
  if deref_symlinks:
162
162
  inode = os.stat(root).st_ino
@@ -167,7 +167,7 @@ def list_all_files(source_path: pathlib.Path, deref_symlinks, ignore_group: Opti
167
167
  ff = []
168
168
  files.sort()
169
169
  for fname in files:
170
- abspath = (root / fname).absolute()
170
+ abspath = (pathlib.Path(root) / fname).absolute()
171
171
  # Only consider files that exist (e.g. disregard symlinks that point to non-existent files)
172
172
  if not os.path.exists(abspath):
173
173
  logger.info(f"Skipping non-existent file {abspath}")
@@ -193,15 +193,15 @@ def list_all_files(source_path: pathlib.Path, deref_symlinks, ignore_group: Opti
193
193
  def _file_is_in_directory(file: str, directory: str) -> bool:
194
194
  """Return True if file is in directory and in its children."""
195
195
  try:
196
- return os.path.commonpath([file, directory]) == directory
197
- except ValueError as e:
198
- # ValueError is raised by windows if the paths are not from the same drive
199
- logger.debug(f"{file} and {directory} are not in the same drive: {e!s}")
196
+ return pathlib.Path(file).resolve().is_relative_to(pathlib.Path(directory).resolve())
197
+ except OSError as e:
198
+ # OSError can be raised if paths cannot be resolved (permissions, broken symlinks, etc.)
199
+ logger.debug(f"Failed to resolve paths for {file} and {directory}: {e!s}")
200
200
  return False
201
201
 
202
202
 
203
203
  def list_imported_modules_as_files(source_path: str, modules: List[ModuleType]) -> List[str]:
204
- """Copies modules into destination that are in modules. The module files are copied only if:
204
+ """Lists the files of modules that have been loaded. The files are only included if:
205
205
 
206
206
  1. Not a site-packages. These are installed packages and not user files.
207
207
  2. Not in the sys.base_prefix or sys.prefix. These are also installed and not user files.
@@ -211,7 +211,7 @@ def list_imported_modules_as_files(source_path: str, modules: List[ModuleType])
211
211
  import flyte
212
212
  from flyte._utils.lazy_module import is_imported
213
213
 
214
- files = []
214
+ files = set()
215
215
  flyte_root = os.path.dirname(flyte.__file__)
216
216
 
217
217
  # These directories contain installed packages or modules from the Python standard library.
@@ -240,11 +240,19 @@ def list_imported_modules_as_files(source_path: str, modules: List[ModuleType])
240
240
 
241
241
  if not _file_is_in_directory(mod_file, source_path):
242
242
  # Only upload files where the module file in the source directory
243
+ # print log line for files that have common ancestor with source_path, but not in it.
244
+ logger.debug(f"{mod_file} is not in {source_path}")
245
+ continue
246
+
247
+ if not pathlib.Path(mod_file).is_file():
248
+ # Some modules have a __file__ attribute that are relative to the base package. Let's skip these,
249
+ # can add more rigorous logic to really pull out the correct file location if we need to.
250
+ logger.debug(f"Skipping {mod_file} from {mod.__name__} because it is not a file")
243
251
  continue
244
252
 
245
- files.append(mod_file)
253
+ files.add(mod_file)
246
254
 
247
- return files
255
+ return list(files)
248
256
 
249
257
 
250
258
  def add_imported_modules_from_source(source_path: str, destination: str, modules: List[ModuleType]):