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.
- flyte/__init__.py +108 -0
- flyte/_bin/__init__.py +0 -0
- flyte/_bin/debug.py +38 -0
- flyte/_bin/runtime.py +195 -0
- flyte/_bin/serve.py +178 -0
- flyte/_build.py +26 -0
- flyte/_cache/__init__.py +12 -0
- flyte/_cache/cache.py +147 -0
- flyte/_cache/defaults.py +9 -0
- flyte/_cache/local_cache.py +216 -0
- flyte/_cache/policy_function_body.py +42 -0
- flyte/_code_bundle/__init__.py +8 -0
- flyte/_code_bundle/_ignore.py +121 -0
- flyte/_code_bundle/_packaging.py +218 -0
- flyte/_code_bundle/_utils.py +347 -0
- flyte/_code_bundle/bundle.py +266 -0
- flyte/_constants.py +1 -0
- flyte/_context.py +155 -0
- flyte/_custom_context.py +73 -0
- flyte/_debug/__init__.py +0 -0
- flyte/_debug/constants.py +38 -0
- flyte/_debug/utils.py +17 -0
- flyte/_debug/vscode.py +307 -0
- flyte/_deploy.py +408 -0
- flyte/_deployer.py +109 -0
- flyte/_doc.py +29 -0
- flyte/_docstring.py +32 -0
- flyte/_environment.py +122 -0
- flyte/_excepthook.py +37 -0
- flyte/_group.py +32 -0
- flyte/_hash.py +8 -0
- flyte/_image.py +1055 -0
- flyte/_initialize.py +628 -0
- flyte/_interface.py +119 -0
- flyte/_internal/__init__.py +3 -0
- flyte/_internal/controllers/__init__.py +129 -0
- flyte/_internal/controllers/_local_controller.py +239 -0
- flyte/_internal/controllers/_trace.py +48 -0
- flyte/_internal/controllers/remote/__init__.py +58 -0
- flyte/_internal/controllers/remote/_action.py +211 -0
- flyte/_internal/controllers/remote/_client.py +47 -0
- flyte/_internal/controllers/remote/_controller.py +583 -0
- flyte/_internal/controllers/remote/_core.py +465 -0
- flyte/_internal/controllers/remote/_informer.py +381 -0
- flyte/_internal/controllers/remote/_service_protocol.py +50 -0
- flyte/_internal/imagebuild/__init__.py +3 -0
- flyte/_internal/imagebuild/docker_builder.py +706 -0
- flyte/_internal/imagebuild/image_builder.py +277 -0
- flyte/_internal/imagebuild/remote_builder.py +386 -0
- flyte/_internal/imagebuild/utils.py +78 -0
- flyte/_internal/resolvers/__init__.py +0 -0
- flyte/_internal/resolvers/_task_module.py +21 -0
- flyte/_internal/resolvers/common.py +31 -0
- flyte/_internal/resolvers/default.py +28 -0
- flyte/_internal/runtime/__init__.py +0 -0
- flyte/_internal/runtime/convert.py +486 -0
- flyte/_internal/runtime/entrypoints.py +204 -0
- flyte/_internal/runtime/io.py +188 -0
- flyte/_internal/runtime/resources_serde.py +152 -0
- flyte/_internal/runtime/reuse.py +125 -0
- flyte/_internal/runtime/rusty.py +193 -0
- flyte/_internal/runtime/task_serde.py +362 -0
- flyte/_internal/runtime/taskrunner.py +209 -0
- flyte/_internal/runtime/trigger_serde.py +160 -0
- flyte/_internal/runtime/types_serde.py +54 -0
- flyte/_keyring/__init__.py +0 -0
- flyte/_keyring/file.py +115 -0
- flyte/_logging.py +300 -0
- flyte/_map.py +312 -0
- flyte/_module.py +72 -0
- flyte/_pod.py +30 -0
- flyte/_resources.py +473 -0
- flyte/_retry.py +32 -0
- flyte/_reusable_environment.py +102 -0
- flyte/_run.py +724 -0
- flyte/_secret.py +96 -0
- flyte/_task.py +550 -0
- flyte/_task_environment.py +316 -0
- flyte/_task_plugins.py +47 -0
- flyte/_timeout.py +47 -0
- flyte/_tools.py +27 -0
- flyte/_trace.py +119 -0
- flyte/_trigger.py +1000 -0
- flyte/_utils/__init__.py +30 -0
- flyte/_utils/asyn.py +121 -0
- flyte/_utils/async_cache.py +139 -0
- flyte/_utils/coro_management.py +27 -0
- flyte/_utils/docker_credentials.py +173 -0
- flyte/_utils/file_handling.py +72 -0
- flyte/_utils/helpers.py +134 -0
- flyte/_utils/lazy_module.py +54 -0
- flyte/_utils/module_loader.py +104 -0
- flyte/_utils/org_discovery.py +57 -0
- flyte/_utils/uv_script_parser.py +49 -0
- flyte/_version.py +34 -0
- flyte/app/__init__.py +22 -0
- flyte/app/_app_environment.py +157 -0
- flyte/app/_deploy.py +125 -0
- flyte/app/_input.py +160 -0
- flyte/app/_runtime/__init__.py +3 -0
- flyte/app/_runtime/app_serde.py +347 -0
- flyte/app/_types.py +101 -0
- flyte/app/extras/__init__.py +3 -0
- flyte/app/extras/_fastapi.py +151 -0
- flyte/cli/__init__.py +12 -0
- flyte/cli/_abort.py +28 -0
- flyte/cli/_build.py +114 -0
- flyte/cli/_common.py +468 -0
- flyte/cli/_create.py +371 -0
- flyte/cli/_delete.py +45 -0
- flyte/cli/_deploy.py +293 -0
- flyte/cli/_gen.py +176 -0
- flyte/cli/_get.py +370 -0
- flyte/cli/_option.py +33 -0
- flyte/cli/_params.py +554 -0
- flyte/cli/_plugins.py +209 -0
- flyte/cli/_run.py +597 -0
- flyte/cli/_serve.py +64 -0
- flyte/cli/_update.py +37 -0
- flyte/cli/_user.py +17 -0
- flyte/cli/main.py +221 -0
- flyte/config/__init__.py +3 -0
- flyte/config/_config.py +248 -0
- flyte/config/_internal.py +73 -0
- flyte/config/_reader.py +225 -0
- flyte/connectors/__init__.py +11 -0
- flyte/connectors/_connector.py +270 -0
- flyte/connectors/_server.py +197 -0
- flyte/connectors/utils.py +135 -0
- flyte/errors.py +243 -0
- flyte/extend.py +19 -0
- flyte/extras/__init__.py +5 -0
- flyte/extras/_container.py +286 -0
- flyte/git/__init__.py +3 -0
- flyte/git/_config.py +21 -0
- flyte/io/__init__.py +29 -0
- flyte/io/_dataframe/__init__.py +131 -0
- flyte/io/_dataframe/basic_dfs.py +223 -0
- flyte/io/_dataframe/dataframe.py +1026 -0
- flyte/io/_dir.py +910 -0
- flyte/io/_file.py +914 -0
- flyte/io/_hashing_io.py +342 -0
- flyte/models.py +479 -0
- flyte/py.typed +0 -0
- flyte/remote/__init__.py +35 -0
- flyte/remote/_action.py +738 -0
- flyte/remote/_app.py +57 -0
- flyte/remote/_client/__init__.py +0 -0
- flyte/remote/_client/_protocols.py +189 -0
- flyte/remote/_client/auth/__init__.py +12 -0
- flyte/remote/_client/auth/_auth_utils.py +14 -0
- flyte/remote/_client/auth/_authenticators/__init__.py +0 -0
- flyte/remote/_client/auth/_authenticators/base.py +403 -0
- flyte/remote/_client/auth/_authenticators/client_credentials.py +73 -0
- flyte/remote/_client/auth/_authenticators/device_code.py +117 -0
- flyte/remote/_client/auth/_authenticators/external_command.py +79 -0
- flyte/remote/_client/auth/_authenticators/factory.py +200 -0
- flyte/remote/_client/auth/_authenticators/pkce.py +516 -0
- flyte/remote/_client/auth/_channel.py +213 -0
- flyte/remote/_client/auth/_client_config.py +85 -0
- flyte/remote/_client/auth/_default_html.py +32 -0
- flyte/remote/_client/auth/_grpc_utils/__init__.py +0 -0
- flyte/remote/_client/auth/_grpc_utils/auth_interceptor.py +288 -0
- flyte/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +151 -0
- flyte/remote/_client/auth/_keyring.py +152 -0
- flyte/remote/_client/auth/_token_client.py +260 -0
- flyte/remote/_client/auth/errors.py +16 -0
- flyte/remote/_client/controlplane.py +128 -0
- flyte/remote/_common.py +30 -0
- flyte/remote/_console.py +19 -0
- flyte/remote/_data.py +161 -0
- flyte/remote/_logs.py +185 -0
- flyte/remote/_project.py +88 -0
- flyte/remote/_run.py +386 -0
- flyte/remote/_secret.py +142 -0
- flyte/remote/_task.py +527 -0
- flyte/remote/_trigger.py +306 -0
- flyte/remote/_user.py +33 -0
- flyte/report/__init__.py +3 -0
- flyte/report/_report.py +182 -0
- flyte/report/_template.html +124 -0
- flyte/storage/__init__.py +36 -0
- flyte/storage/_config.py +237 -0
- flyte/storage/_parallel_reader.py +274 -0
- flyte/storage/_remote_fs.py +34 -0
- flyte/storage/_storage.py +456 -0
- flyte/storage/_utils.py +5 -0
- flyte/syncify/__init__.py +56 -0
- flyte/syncify/_api.py +375 -0
- flyte/types/__init__.py +52 -0
- flyte/types/_interface.py +40 -0
- flyte/types/_pickle.py +145 -0
- flyte/types/_renderer.py +162 -0
- flyte/types/_string_literals.py +119 -0
- flyte/types/_type_engine.py +2254 -0
- flyte/types/_utils.py +80 -0
- flyte-2.0.0b32.data/scripts/debug.py +38 -0
- flyte-2.0.0b32.data/scripts/runtime.py +195 -0
- flyte-2.0.0b32.dist-info/METADATA +351 -0
- flyte-2.0.0b32.dist-info/RECORD +204 -0
- flyte-2.0.0b32.dist-info/WHEEL +5 -0
- flyte-2.0.0b32.dist-info/entry_points.txt +7 -0
- flyte-2.0.0b32.dist-info/licenses/LICENSE +201 -0
- 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)
|
flyte/_cache/defaults.py
ADDED
|
@@ -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
|