flyte 2.0.0b32__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 (204) hide show
  1. flyte/__init__.py +108 -0
  2. flyte/_bin/__init__.py +0 -0
  3. flyte/_bin/debug.py +38 -0
  4. flyte/_bin/runtime.py +195 -0
  5. flyte/_bin/serve.py +178 -0
  6. flyte/_build.py +26 -0
  7. flyte/_cache/__init__.py +12 -0
  8. flyte/_cache/cache.py +147 -0
  9. flyte/_cache/defaults.py +9 -0
  10. flyte/_cache/local_cache.py +216 -0
  11. flyte/_cache/policy_function_body.py +42 -0
  12. flyte/_code_bundle/__init__.py +8 -0
  13. flyte/_code_bundle/_ignore.py +121 -0
  14. flyte/_code_bundle/_packaging.py +218 -0
  15. flyte/_code_bundle/_utils.py +347 -0
  16. flyte/_code_bundle/bundle.py +266 -0
  17. flyte/_constants.py +1 -0
  18. flyte/_context.py +155 -0
  19. flyte/_custom_context.py +73 -0
  20. flyte/_debug/__init__.py +0 -0
  21. flyte/_debug/constants.py +38 -0
  22. flyte/_debug/utils.py +17 -0
  23. flyte/_debug/vscode.py +307 -0
  24. flyte/_deploy.py +408 -0
  25. flyte/_deployer.py +109 -0
  26. flyte/_doc.py +29 -0
  27. flyte/_docstring.py +32 -0
  28. flyte/_environment.py +122 -0
  29. flyte/_excepthook.py +37 -0
  30. flyte/_group.py +32 -0
  31. flyte/_hash.py +8 -0
  32. flyte/_image.py +1055 -0
  33. flyte/_initialize.py +628 -0
  34. flyte/_interface.py +119 -0
  35. flyte/_internal/__init__.py +3 -0
  36. flyte/_internal/controllers/__init__.py +129 -0
  37. flyte/_internal/controllers/_local_controller.py +239 -0
  38. flyte/_internal/controllers/_trace.py +48 -0
  39. flyte/_internal/controllers/remote/__init__.py +58 -0
  40. flyte/_internal/controllers/remote/_action.py +211 -0
  41. flyte/_internal/controllers/remote/_client.py +47 -0
  42. flyte/_internal/controllers/remote/_controller.py +583 -0
  43. flyte/_internal/controllers/remote/_core.py +465 -0
  44. flyte/_internal/controllers/remote/_informer.py +381 -0
  45. flyte/_internal/controllers/remote/_service_protocol.py +50 -0
  46. flyte/_internal/imagebuild/__init__.py +3 -0
  47. flyte/_internal/imagebuild/docker_builder.py +706 -0
  48. flyte/_internal/imagebuild/image_builder.py +277 -0
  49. flyte/_internal/imagebuild/remote_builder.py +386 -0
  50. flyte/_internal/imagebuild/utils.py +78 -0
  51. flyte/_internal/resolvers/__init__.py +0 -0
  52. flyte/_internal/resolvers/_task_module.py +21 -0
  53. flyte/_internal/resolvers/common.py +31 -0
  54. flyte/_internal/resolvers/default.py +28 -0
  55. flyte/_internal/runtime/__init__.py +0 -0
  56. flyte/_internal/runtime/convert.py +486 -0
  57. flyte/_internal/runtime/entrypoints.py +204 -0
  58. flyte/_internal/runtime/io.py +188 -0
  59. flyte/_internal/runtime/resources_serde.py +152 -0
  60. flyte/_internal/runtime/reuse.py +125 -0
  61. flyte/_internal/runtime/rusty.py +193 -0
  62. flyte/_internal/runtime/task_serde.py +362 -0
  63. flyte/_internal/runtime/taskrunner.py +209 -0
  64. flyte/_internal/runtime/trigger_serde.py +160 -0
  65. flyte/_internal/runtime/types_serde.py +54 -0
  66. flyte/_keyring/__init__.py +0 -0
  67. flyte/_keyring/file.py +115 -0
  68. flyte/_logging.py +300 -0
  69. flyte/_map.py +312 -0
  70. flyte/_module.py +72 -0
  71. flyte/_pod.py +30 -0
  72. flyte/_resources.py +473 -0
  73. flyte/_retry.py +32 -0
  74. flyte/_reusable_environment.py +102 -0
  75. flyte/_run.py +724 -0
  76. flyte/_secret.py +96 -0
  77. flyte/_task.py +550 -0
  78. flyte/_task_environment.py +316 -0
  79. flyte/_task_plugins.py +47 -0
  80. flyte/_timeout.py +47 -0
  81. flyte/_tools.py +27 -0
  82. flyte/_trace.py +119 -0
  83. flyte/_trigger.py +1000 -0
  84. flyte/_utils/__init__.py +30 -0
  85. flyte/_utils/asyn.py +121 -0
  86. flyte/_utils/async_cache.py +139 -0
  87. flyte/_utils/coro_management.py +27 -0
  88. flyte/_utils/docker_credentials.py +173 -0
  89. flyte/_utils/file_handling.py +72 -0
  90. flyte/_utils/helpers.py +134 -0
  91. flyte/_utils/lazy_module.py +54 -0
  92. flyte/_utils/module_loader.py +104 -0
  93. flyte/_utils/org_discovery.py +57 -0
  94. flyte/_utils/uv_script_parser.py +49 -0
  95. flyte/_version.py +34 -0
  96. flyte/app/__init__.py +22 -0
  97. flyte/app/_app_environment.py +157 -0
  98. flyte/app/_deploy.py +125 -0
  99. flyte/app/_input.py +160 -0
  100. flyte/app/_runtime/__init__.py +3 -0
  101. flyte/app/_runtime/app_serde.py +347 -0
  102. flyte/app/_types.py +101 -0
  103. flyte/app/extras/__init__.py +3 -0
  104. flyte/app/extras/_fastapi.py +151 -0
  105. flyte/cli/__init__.py +12 -0
  106. flyte/cli/_abort.py +28 -0
  107. flyte/cli/_build.py +114 -0
  108. flyte/cli/_common.py +468 -0
  109. flyte/cli/_create.py +371 -0
  110. flyte/cli/_delete.py +45 -0
  111. flyte/cli/_deploy.py +293 -0
  112. flyte/cli/_gen.py +176 -0
  113. flyte/cli/_get.py +370 -0
  114. flyte/cli/_option.py +33 -0
  115. flyte/cli/_params.py +554 -0
  116. flyte/cli/_plugins.py +209 -0
  117. flyte/cli/_run.py +597 -0
  118. flyte/cli/_serve.py +64 -0
  119. flyte/cli/_update.py +37 -0
  120. flyte/cli/_user.py +17 -0
  121. flyte/cli/main.py +221 -0
  122. flyte/config/__init__.py +3 -0
  123. flyte/config/_config.py +248 -0
  124. flyte/config/_internal.py +73 -0
  125. flyte/config/_reader.py +225 -0
  126. flyte/connectors/__init__.py +11 -0
  127. flyte/connectors/_connector.py +270 -0
  128. flyte/connectors/_server.py +197 -0
  129. flyte/connectors/utils.py +135 -0
  130. flyte/errors.py +243 -0
  131. flyte/extend.py +19 -0
  132. flyte/extras/__init__.py +5 -0
  133. flyte/extras/_container.py +286 -0
  134. flyte/git/__init__.py +3 -0
  135. flyte/git/_config.py +21 -0
  136. flyte/io/__init__.py +29 -0
  137. flyte/io/_dataframe/__init__.py +131 -0
  138. flyte/io/_dataframe/basic_dfs.py +223 -0
  139. flyte/io/_dataframe/dataframe.py +1026 -0
  140. flyte/io/_dir.py +910 -0
  141. flyte/io/_file.py +914 -0
  142. flyte/io/_hashing_io.py +342 -0
  143. flyte/models.py +479 -0
  144. flyte/py.typed +0 -0
  145. flyte/remote/__init__.py +35 -0
  146. flyte/remote/_action.py +738 -0
  147. flyte/remote/_app.py +57 -0
  148. flyte/remote/_client/__init__.py +0 -0
  149. flyte/remote/_client/_protocols.py +189 -0
  150. flyte/remote/_client/auth/__init__.py +12 -0
  151. flyte/remote/_client/auth/_auth_utils.py +14 -0
  152. flyte/remote/_client/auth/_authenticators/__init__.py +0 -0
  153. flyte/remote/_client/auth/_authenticators/base.py +403 -0
  154. flyte/remote/_client/auth/_authenticators/client_credentials.py +73 -0
  155. flyte/remote/_client/auth/_authenticators/device_code.py +117 -0
  156. flyte/remote/_client/auth/_authenticators/external_command.py +79 -0
  157. flyte/remote/_client/auth/_authenticators/factory.py +200 -0
  158. flyte/remote/_client/auth/_authenticators/pkce.py +516 -0
  159. flyte/remote/_client/auth/_channel.py +213 -0
  160. flyte/remote/_client/auth/_client_config.py +85 -0
  161. flyte/remote/_client/auth/_default_html.py +32 -0
  162. flyte/remote/_client/auth/_grpc_utils/__init__.py +0 -0
  163. flyte/remote/_client/auth/_grpc_utils/auth_interceptor.py +288 -0
  164. flyte/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +151 -0
  165. flyte/remote/_client/auth/_keyring.py +152 -0
  166. flyte/remote/_client/auth/_token_client.py +260 -0
  167. flyte/remote/_client/auth/errors.py +16 -0
  168. flyte/remote/_client/controlplane.py +128 -0
  169. flyte/remote/_common.py +30 -0
  170. flyte/remote/_console.py +19 -0
  171. flyte/remote/_data.py +161 -0
  172. flyte/remote/_logs.py +185 -0
  173. flyte/remote/_project.py +88 -0
  174. flyte/remote/_run.py +386 -0
  175. flyte/remote/_secret.py +142 -0
  176. flyte/remote/_task.py +527 -0
  177. flyte/remote/_trigger.py +306 -0
  178. flyte/remote/_user.py +33 -0
  179. flyte/report/__init__.py +3 -0
  180. flyte/report/_report.py +182 -0
  181. flyte/report/_template.html +124 -0
  182. flyte/storage/__init__.py +36 -0
  183. flyte/storage/_config.py +237 -0
  184. flyte/storage/_parallel_reader.py +274 -0
  185. flyte/storage/_remote_fs.py +34 -0
  186. flyte/storage/_storage.py +456 -0
  187. flyte/storage/_utils.py +5 -0
  188. flyte/syncify/__init__.py +56 -0
  189. flyte/syncify/_api.py +375 -0
  190. flyte/types/__init__.py +52 -0
  191. flyte/types/_interface.py +40 -0
  192. flyte/types/_pickle.py +145 -0
  193. flyte/types/_renderer.py +162 -0
  194. flyte/types/_string_literals.py +119 -0
  195. flyte/types/_type_engine.py +2254 -0
  196. flyte/types/_utils.py +80 -0
  197. flyte-2.0.0b32.data/scripts/debug.py +38 -0
  198. flyte-2.0.0b32.data/scripts/runtime.py +195 -0
  199. flyte-2.0.0b32.dist-info/METADATA +351 -0
  200. flyte-2.0.0b32.dist-info/RECORD +204 -0
  201. flyte-2.0.0b32.dist-info/WHEEL +5 -0
  202. flyte-2.0.0b32.dist-info/entry_points.txt +7 -0
  203. flyte-2.0.0b32.dist-info/licenses/LICENSE +201 -0
  204. flyte-2.0.0b32.dist-info/top_level.txt +1 -0
flyte/_cache/cache.py ADDED
@@ -0,0 +1,147 @@
1
+ import hashlib
2
+ from dataclasses import dataclass, field
3
+ from typing import (
4
+ Callable,
5
+ Generic,
6
+ List,
7
+ Optional,
8
+ Protocol,
9
+ Tuple,
10
+ Union,
11
+ runtime_checkable,
12
+ )
13
+
14
+ import rich.repr
15
+ from typing_extensions import Literal, ParamSpec, TypeVar, get_args
16
+
17
+ # if TYPE_CHECKING:
18
+ from flyte._image import Image
19
+ from flyte.models import CodeBundle
20
+
21
+ P = ParamSpec("P")
22
+ FuncOut = TypeVar("FuncOut")
23
+
24
+ CacheBehavior = Literal["auto", "override", "disable"]
25
+
26
+
27
+ @dataclass
28
+ class VersionParameters(Generic[P, FuncOut]):
29
+ """
30
+ Parameters used for cache version hash generation.
31
+
32
+ :param func: The function to generate a version for. This is a required parameter but can be any callable
33
+ :type func: Callable[P, FuncOut]
34
+ :param image: The container image to generate a version for. This can be a string representing the
35
+ image name or an Image object.
36
+ :type image: Optional[Union[str, Image]]
37
+ """
38
+
39
+ func: Callable[P, FuncOut] | None
40
+ image: Optional[Union[str, Image]] = None
41
+ code_bundle: Optional[CodeBundle] = None
42
+
43
+
44
+ @runtime_checkable
45
+ class CachePolicy(Protocol):
46
+ def get_version(self, salt: str, params: VersionParameters) -> str: ...
47
+
48
+
49
+ @rich.repr.auto
50
+ @dataclass
51
+ class Cache:
52
+ """
53
+ Cache configuration for a task.
54
+ :param behavior: The behavior of the cache. Can be "auto", "override" or "disable".
55
+ :param version_override: The version of the cache. If not provided, the version will be
56
+ generated based on the cache policies
57
+ :type version_override: Optional[str]
58
+ :param serialize: Boolean that indicates if identical (ie. same inputs) instances of this task should be executed in
59
+ serial when caching is enabled. This means that given multiple concurrent executions over identical inputs,
60
+ only a single instance executes and the rest wait to reuse the cached results.
61
+ :type serialize: bool
62
+ :param ignored_inputs: A tuple of input names to ignore when generating the version hash.
63
+ :type ignored_inputs: Union[Tuple[str, ...], str]
64
+ :param salt: A salt used in the hash generation.
65
+ :type salt: str
66
+ :param policies: A list of cache policies to generate the version hash.
67
+ :type policies: Optional[Union[List[CachePolicy], CachePolicy]]
68
+ """
69
+
70
+ behavior: CacheBehavior
71
+ version_override: Optional[str] = None
72
+ serialize: bool = False
73
+ ignored_inputs: Union[Tuple[str, ...], str] = field(default_factory=tuple)
74
+ salt: str = ""
75
+ policies: Optional[Union[List[CachePolicy], CachePolicy]] = None
76
+
77
+ def __post_init__(self):
78
+ if self.behavior not in get_args(CacheBehavior):
79
+ raise ValueError(f"Invalid cache behavior: {self.behavior}. Must be one of ['auto', 'override', 'disable']")
80
+
81
+ # Still setup _ignore_inputs when cache is disabled to prevent _ignored_inputs attribute not found error
82
+ if isinstance(self.ignored_inputs, str):
83
+ self._ignored_inputs = (self.ignored_inputs,)
84
+ else:
85
+ self._ignored_inputs = self.ignored_inputs
86
+
87
+ if self.behavior == "disable":
88
+ return
89
+
90
+ # Normalize policies so that self._policies is always a list
91
+ if self.policies is None:
92
+ from flyte._cache.defaults import get_default_policies
93
+
94
+ self.policies = get_default_policies()
95
+ elif isinstance(self.policies, CachePolicy):
96
+ self.policies = [self.policies]
97
+
98
+ if self.version_override is None and not self.policies:
99
+ raise ValueError("If version is not defined then at least one cache policy needs to be set")
100
+
101
+ def is_enabled(self) -> bool:
102
+ """
103
+ Check if the cache policy is enabled.
104
+ """
105
+ return self.behavior in ["auto", "override"]
106
+
107
+ def get_ignored_inputs(self) -> Tuple[str, ...]:
108
+ return self._ignored_inputs
109
+
110
+ def get_version(self, params: Optional[VersionParameters] = None) -> str:
111
+ if not self.is_enabled():
112
+ return ""
113
+
114
+ if self.version_override is not None:
115
+ return self.version_override
116
+
117
+ if params is None:
118
+ raise ValueError("Version parameters must be provided when version_override is not set.")
119
+
120
+ if params.code_bundle is not None:
121
+ if params.code_bundle.pkl is not None:
122
+ return params.code_bundle.computed_version
123
+
124
+ task_hash = ""
125
+ if self.policies is None:
126
+ raise ValueError("Cache policies are not set.")
127
+ policies = self.policies if isinstance(self.policies, list) else [self.policies]
128
+ for policy in policies:
129
+ try:
130
+ task_hash += policy.get_version(self.salt, params)
131
+ except Exception as e:
132
+ raise ValueError(f"Failed to generate version for cache policy {policy}.") from e
133
+
134
+ hash_obj = hashlib.sha256(task_hash.encode())
135
+ return hash_obj.hexdigest()
136
+
137
+
138
+ CacheRequest = CacheBehavior | Cache
139
+
140
+
141
+ def cache_from_request(cache: CacheRequest) -> Cache:
142
+ """
143
+ Coerce user input into a cache object.
144
+ """
145
+ if isinstance(cache, Cache):
146
+ return cache
147
+ return Cache(behavior=cache)
@@ -0,0 +1,9 @@
1
+ from .cache import CachePolicy
2
+ from .policy_function_body import FunctionBodyPolicy
3
+
4
+
5
+ def get_default_policies() -> list[CachePolicy]:
6
+ """
7
+ Get default cache policies.
8
+ """
9
+ return [FunctionBodyPolicy()]
@@ -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
@@ -0,0 +1,42 @@
1
+ import ast
2
+ import hashlib
3
+ import inspect
4
+ import textwrap
5
+
6
+ from .cache import CachePolicy, VersionParameters
7
+
8
+
9
+ class FunctionBodyPolicy(CachePolicy):
10
+ """
11
+ A class that implements a versioning mechanism for functions by generating
12
+ a SHA-256 hash of the function's source code combined with a salt.
13
+ """
14
+
15
+ def get_version(self, salt: str, params: VersionParameters) -> str:
16
+ """
17
+ This method generates a version string for a function by hashing the function's source code
18
+ combined with a salt.
19
+
20
+ :param salt: A string that is used to salt the hash.
21
+ :param params: VersionParameters object that contains the parameters (e.g. function, ImageSpec, etc.) that are
22
+ used to generate the version.
23
+
24
+ :return: A string that represents the version of the function.
25
+ """
26
+ if params.func is None:
27
+ return ""
28
+
29
+ source = inspect.getsource(params.func)
30
+ dedented_source = textwrap.dedent(source)
31
+
32
+ # Parse the source code into an Abstract Syntax Tree (AST)
33
+ parsed_ast = ast.parse(dedented_source)
34
+
35
+ # Convert the AST into a string representation
36
+ ast_bytes = ast.dump(parsed_ast, include_attributes=False).encode("utf-8")
37
+
38
+ # Combine the AST bytes with the salt (encoded into bytes)
39
+ combined_data = ast_bytes + salt.encode("utf-8")
40
+
41
+ # Return the SHA-256 hash of the combined data (AST + salt)
42
+ return hashlib.sha256(combined_data).hexdigest()
@@ -0,0 +1,8 @@
1
+ from ._ignore import GitIgnore, IgnoreGroup, StandardIgnore
2
+ from ._utils import CopyFiles
3
+ from .bundle import build_code_bundle, build_pkl_bundle, download_bundle
4
+
5
+ __all__ = ["CopyFiles", "build_code_bundle", "build_pkl_bundle", "default_ignores", "download_bundle"]
6
+
7
+
8
+ default_ignores = [GitIgnore, StandardIgnore, IgnoreGroup]
@@ -0,0 +1,121 @@
1
+ import os
2
+ import pathlib
3
+ import subprocess
4
+ import tarfile as _tarfile
5
+ from abc import ABC, abstractmethod
6
+ from fnmatch import fnmatch
7
+ from pathlib import Path
8
+ from shutil import which
9
+ from typing import List, Optional, Type
10
+
11
+ from flyte._logging import logger
12
+
13
+
14
+ class Ignore(ABC):
15
+ """Base for Ignores, implements core logic. Children have to implement _is_ignored"""
16
+
17
+ def __init__(self, root: Path):
18
+ self.root = root
19
+
20
+ def is_ignored(self, path: pathlib.Path) -> bool:
21
+ return self._is_ignored(path)
22
+
23
+ def tar_filter(self, tarinfo: _tarfile.TarInfo) -> Optional[_tarfile.TarInfo]:
24
+ if self.is_ignored(pathlib.Path(tarinfo.name)):
25
+ return None
26
+ return tarinfo
27
+
28
+ @abstractmethod
29
+ def _is_ignored(self, path: pathlib.Path) -> bool:
30
+ pass
31
+
32
+
33
+ class GitIgnore(Ignore):
34
+ """Uses git cli (if available) to list all ignored files and compare with those."""
35
+
36
+ def __init__(self, root: Path):
37
+ super().__init__(root)
38
+ self.has_git = which("git") is not None
39
+ self.ignored_files = self._list_ignored_files()
40
+ self.ignored_dirs = self._list_ignored_dirs()
41
+
42
+ def _git_wrapper(self, extra_args: List[str]) -> set[str]:
43
+ if self.has_git:
44
+ out = subprocess.run(
45
+ ["git", "ls-files", "-io", "--exclude-standard", *extra_args],
46
+ cwd=self.root,
47
+ capture_output=True,
48
+ check=False,
49
+ )
50
+ if out.returncode == 0:
51
+ return set(out.stdout.decode("utf-8").split("\n")[:-1])
52
+ logger.info(f"Could not determine ignored paths due to:\n{out.stderr!r}\nNot applying any filters")
53
+ return set()
54
+ logger.info("No git executable found, not applying any filters")
55
+ return set()
56
+
57
+ def _list_ignored_files(self) -> set[str]:
58
+ return self._git_wrapper([])
59
+
60
+ def _list_ignored_dirs(self) -> set[str]:
61
+ return self._git_wrapper(["--directory"])
62
+
63
+ def _is_ignored(self, path: pathlib.Path) -> bool:
64
+ if self.ignored_files:
65
+ # git-ls-files uses POSIX paths
66
+ if Path(path).as_posix() in self.ignored_files:
67
+ return True
68
+ # Ignore empty directories
69
+ if os.path.isdir(os.path.join(self.root, path)) and self.ignored_dirs:
70
+ return Path(path).as_posix() + "/" in self.ignored_dirs
71
+ return False
72
+
73
+
74
+ STANDARD_IGNORE_PATTERNS = ["*.pyc", ".cache", ".cache/*", "__pycache__", "**/__pycache__"]
75
+
76
+
77
+ class StandardIgnore(Ignore):
78
+ """Retains the standard ignore functionality that previously existed. Could in theory
79
+ by fed with custom ignore patterns from cli."""
80
+
81
+ def __init__(self, root: Path, patterns: Optional[List[str]] = None):
82
+ super().__init__(root.resolve())
83
+ self.patterns = patterns if patterns else STANDARD_IGNORE_PATTERNS
84
+
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
+
93
+ for pattern in self.patterns:
94
+ if fnmatch(str(rel_path), pattern):
95
+ return True
96
+ return False
97
+
98
+
99
+ class IgnoreGroup(Ignore):
100
+ """Groups multiple Ignores and checks a path against them. A file is ignored if any
101
+ Ignore considers it ignored."""
102
+
103
+ def __init__(self, root: Path, *ignores: Type[Ignore]):
104
+ super().__init__(root)
105
+ self.ignores = [ignore(root) for ignore in ignores]
106
+
107
+ def _is_ignored(self, path: pathlib.Path) -> bool:
108
+ for ignore in self.ignores:
109
+ if ignore.is_ignored(path):
110
+ return True
111
+ return False
112
+
113
+ def list_ignored(self) -> List[str]:
114
+ ignored = []
115
+ for dir, _, files in os.walk(self.root):
116
+ dir_path = Path(dir)
117
+ for file in files:
118
+ abs_path = dir_path / file
119
+ if self.is_ignored(abs_path):
120
+ ignored.append(str(abs_path.relative_to(self.root)))
121
+ return ignored