flyte 2.0.0b19__py3-none-any.whl → 2.0.0b21__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/_bin/runtime.py +2 -2
- flyte/_code_bundle/_ignore.py +11 -3
- flyte/_code_bundle/_packaging.py +9 -5
- flyte/_code_bundle/_utils.py +2 -2
- flyte/_initialize.py +14 -7
- flyte/_interface.py +35 -2
- flyte/_internal/controllers/remote/__init__.py +0 -2
- flyte/_internal/controllers/remote/_controller.py +3 -3
- flyte/_internal/controllers/remote/_core.py +120 -92
- flyte/_internal/controllers/remote/_informer.py +15 -6
- flyte/_internal/imagebuild/docker_builder.py +58 -8
- flyte/_internal/imagebuild/remote_builder.py +9 -26
- flyte/_keyring/__init__.py +0 -0
- flyte/_keyring/file.py +85 -0
- flyte/_logging.py +19 -8
- flyte/_utils/coro_management.py +2 -1
- flyte/_version.py +3 -3
- flyte/config/_config.py +6 -4
- flyte/config/_reader.py +19 -4
- flyte/errors.py +9 -0
- flyte/git/_config.py +2 -0
- flyte/io/_dataframe/dataframe.py +3 -2
- flyte/io/_dir.py +72 -72
- flyte/models.py +6 -2
- flyte/remote/_client/auth/_authenticators/device_code.py +3 -4
- flyte/remote/_data.py +2 -3
- flyte/remote/_run.py +17 -1
- flyte/storage/_config.py +5 -1
- flyte/types/_type_engine.py +7 -0
- {flyte-2.0.0b19.data → flyte-2.0.0b21.data}/scripts/runtime.py +2 -2
- {flyte-2.0.0b19.dist-info → flyte-2.0.0b21.dist-info}/METADATA +2 -1
- {flyte-2.0.0b19.dist-info → flyte-2.0.0b21.dist-info}/RECORD +37 -35
- {flyte-2.0.0b19.dist-info → flyte-2.0.0b21.dist-info}/entry_points.txt +3 -0
- {flyte-2.0.0b19.data → flyte-2.0.0b21.data}/scripts/debug.py +0 -0
- {flyte-2.0.0b19.dist-info → flyte-2.0.0b21.dist-info}/WHEEL +0 -0
- {flyte-2.0.0b19.dist-info → flyte-2.0.0b21.dist-info}/licenses/LICENSE +0 -0
- {flyte-2.0.0b19.dist-info → flyte-2.0.0b21.dist-info}/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ import tempfile
|
|
|
6
6
|
import typing
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from string import Template
|
|
9
|
-
from typing import ClassVar, Optional, Protocol, cast
|
|
9
|
+
from typing import ClassVar, List, Optional, Protocol, cast
|
|
10
10
|
|
|
11
11
|
import aiofiles
|
|
12
12
|
import click
|
|
@@ -263,7 +263,37 @@ class DockerIgnoreHandler:
|
|
|
263
263
|
|
|
264
264
|
class CopyConfigHandler:
|
|
265
265
|
@staticmethod
|
|
266
|
-
|
|
266
|
+
def list_dockerignore(root_path: Optional[Path], docker_ignore_file_path: Optional[Path]) -> List[str]:
|
|
267
|
+
patterns: List[str] = []
|
|
268
|
+
dockerignore_path: Optional[Path] = None
|
|
269
|
+
if root_path:
|
|
270
|
+
dockerignore_path = root_path / ".dockerignore"
|
|
271
|
+
# DockerIgnore layer should be first priority
|
|
272
|
+
if docker_ignore_file_path:
|
|
273
|
+
dockerignore_path = docker_ignore_file_path
|
|
274
|
+
|
|
275
|
+
# Return empty list if no .dockerignore file found
|
|
276
|
+
if not dockerignore_path or not dockerignore_path.exists() or not dockerignore_path.is_file():
|
|
277
|
+
logger.info(f".dockerignore file not found at path: {dockerignore_path}")
|
|
278
|
+
return patterns
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
with open(dockerignore_path, "r", encoding="utf-8") as f:
|
|
282
|
+
for line in f:
|
|
283
|
+
stripped_line = line.strip()
|
|
284
|
+
# Skip empty lines, whitespace-only lines, and comments
|
|
285
|
+
if not stripped_line or stripped_line.startswith("#"):
|
|
286
|
+
continue
|
|
287
|
+
patterns.append(stripped_line)
|
|
288
|
+
except Exception as e:
|
|
289
|
+
logger.error(f"Failed to read .dockerignore file at {dockerignore_path}: {e}")
|
|
290
|
+
return []
|
|
291
|
+
return patterns
|
|
292
|
+
|
|
293
|
+
@staticmethod
|
|
294
|
+
async def handle(
|
|
295
|
+
layer: CopyConfig, context_path: Path, dockerfile: str, docker_ignore_file_path: Optional[Path]
|
|
296
|
+
) -> str:
|
|
267
297
|
# Copy the source config file or directory to the context path
|
|
268
298
|
if layer.src.is_absolute() or ".." in str(layer.src):
|
|
269
299
|
dst_path = context_path / str(layer.src.absolute()).replace("/", "./_flyte_abs_context/", 1)
|
|
@@ -272,18 +302,26 @@ class CopyConfigHandler:
|
|
|
272
302
|
|
|
273
303
|
dst_path.parent.mkdir(parents=True, exist_ok=True)
|
|
274
304
|
abs_path = layer.src.absolute()
|
|
305
|
+
|
|
275
306
|
if layer.src.is_file():
|
|
276
307
|
# Copy the file
|
|
277
308
|
shutil.copy(abs_path, dst_path)
|
|
278
309
|
elif layer.src.is_dir():
|
|
279
310
|
# Copy the entire directory
|
|
280
|
-
|
|
311
|
+
from flyte._initialize import _get_init_config
|
|
312
|
+
|
|
313
|
+
init_config = _get_init_config()
|
|
314
|
+
root_path = init_config.root_dir if init_config else None
|
|
315
|
+
docker_ignore_patterns = CopyConfigHandler.list_dockerignore(root_path, docker_ignore_file_path)
|
|
316
|
+
shutil.copytree(
|
|
317
|
+
abs_path, dst_path, dirs_exist_ok=True, ignore=shutil.ignore_patterns(*docker_ignore_patterns)
|
|
318
|
+
)
|
|
281
319
|
else:
|
|
282
|
-
|
|
320
|
+
logger.error(f"Source path not exists: {layer.src}")
|
|
321
|
+
return dockerfile
|
|
283
322
|
|
|
284
323
|
# Add a copy command to the dockerfile
|
|
285
324
|
dockerfile += f"\nCOPY {dst_path.relative_to(context_path)} {layer.dst}\n"
|
|
286
|
-
|
|
287
325
|
return dockerfile
|
|
288
326
|
|
|
289
327
|
|
|
@@ -349,7 +387,9 @@ def _get_secret_mounts_layer(secrets: typing.Tuple[str | Secret, ...] | None) ->
|
|
|
349
387
|
return secret_mounts_layer
|
|
350
388
|
|
|
351
389
|
|
|
352
|
-
async def _process_layer(
|
|
390
|
+
async def _process_layer(
|
|
391
|
+
layer: Layer, context_path: Path, dockerfile: str, docker_ignore_file_path: Optional[Path]
|
|
392
|
+
) -> str:
|
|
353
393
|
match layer:
|
|
354
394
|
case PythonWheels():
|
|
355
395
|
# Handle Python wheels
|
|
@@ -385,7 +425,7 @@ async def _process_layer(layer: Layer, context_path: Path, dockerfile: str) -> s
|
|
|
385
425
|
|
|
386
426
|
case CopyConfig():
|
|
387
427
|
# Handle local files and folders
|
|
388
|
-
dockerfile = await CopyConfigHandler.handle(layer, context_path, dockerfile)
|
|
428
|
+
dockerfile = await CopyConfigHandler.handle(layer, context_path, dockerfile, docker_ignore_file_path)
|
|
389
429
|
|
|
390
430
|
case Commands():
|
|
391
431
|
# Handle commands
|
|
@@ -512,6 +552,15 @@ class DockerImageBuilder(ImageBuilder):
|
|
|
512
552
|
else:
|
|
513
553
|
logger.info("Buildx builder already exists.")
|
|
514
554
|
|
|
555
|
+
def get_docker_ignore(self, image: Image) -> Optional[Path]:
|
|
556
|
+
"""Get the .dockerignore file path from the image layers."""
|
|
557
|
+
# Look for DockerIgnore layer in the image layers
|
|
558
|
+
for layer in image._layers:
|
|
559
|
+
if isinstance(layer, DockerIgnore) and layer.path.strip():
|
|
560
|
+
return Path(layer.path)
|
|
561
|
+
|
|
562
|
+
return None
|
|
563
|
+
|
|
515
564
|
async def _build_image(self, image: Image, *, push: bool = True, dry_run: bool = False) -> str:
|
|
516
565
|
"""
|
|
517
566
|
if default image (only base image and locked), raise an error, don't have a dockerfile
|
|
@@ -541,8 +590,9 @@ class DockerImageBuilder(ImageBuilder):
|
|
|
541
590
|
PYTHON_VERSION=f"{image.python_version[0]}.{image.python_version[1]}",
|
|
542
591
|
)
|
|
543
592
|
|
|
593
|
+
docker_ignore_file_path = self.get_docker_ignore(image)
|
|
544
594
|
for layer in image._layers:
|
|
545
|
-
dockerfile = await _process_layer(layer, tmp_path, dockerfile)
|
|
595
|
+
dockerfile = await _process_layer(layer, tmp_path, dockerfile, docker_ignore_file_path)
|
|
546
596
|
|
|
547
597
|
dockerfile += DOCKER_FILE_BASE_FOOTER.substitute(F_IMG_ID=image.uri)
|
|
548
598
|
|
|
@@ -10,7 +10,6 @@ from typing import TYPE_CHECKING, Optional, Tuple, cast
|
|
|
10
10
|
from uuid import uuid4
|
|
11
11
|
|
|
12
12
|
import aiofiles
|
|
13
|
-
import click
|
|
14
13
|
|
|
15
14
|
import flyte
|
|
16
15
|
import flyte.errors
|
|
@@ -42,7 +41,6 @@ if TYPE_CHECKING:
|
|
|
42
41
|
from flyte._protos.imagebuilder import definition_pb2 as image_definition_pb2
|
|
43
42
|
|
|
44
43
|
IMAGE_TASK_NAME = "build-image"
|
|
45
|
-
OPTIMIZE_TASK_NAME = "optimize-task"
|
|
46
44
|
IMAGE_TASK_PROJECT = "system"
|
|
47
45
|
IMAGE_TASK_DOMAIN = "production"
|
|
48
46
|
|
|
@@ -85,10 +83,10 @@ class RemoteImageChecker(ImageChecker):
|
|
|
85
83
|
raise ValueError("remote client should not be None")
|
|
86
84
|
cls._images_client = image_service_pb2_grpc.ImageServiceStub(cfg.client._channel)
|
|
87
85
|
resp = await cls._images_client.GetImage(req)
|
|
88
|
-
logger.warning(
|
|
86
|
+
logger.warning(f"[blue]Image {resp.image.fqin} found. Skip building.[/blue]")
|
|
89
87
|
return resp.image.fqin
|
|
90
88
|
except Exception:
|
|
91
|
-
logger.warning(
|
|
89
|
+
logger.warning(f"[blue]Image {image_name} was not found or has expired.[/blue]", extra={"highlight": False})
|
|
92
90
|
return None
|
|
93
91
|
|
|
94
92
|
|
|
@@ -110,37 +108,25 @@ class RemoteImageBuilder(ImageBuilder):
|
|
|
110
108
|
domain=IMAGE_TASK_DOMAIN,
|
|
111
109
|
auto_version="latest",
|
|
112
110
|
).override.aio(secrets=_get_build_secrets_from_image(image))
|
|
111
|
+
|
|
112
|
+
logger.warning("[bold blue]🐳 Submitting a new build...[/bold blue]")
|
|
113
113
|
run = cast(
|
|
114
114
|
Run,
|
|
115
115
|
await flyte.with_runcontext(project=IMAGE_TASK_PROJECT, domain=IMAGE_TASK_DOMAIN).run.aio(
|
|
116
116
|
entity, spec=spec, context=context, target_image=image_name
|
|
117
117
|
),
|
|
118
118
|
)
|
|
119
|
-
logger.warning(
|
|
119
|
+
logger.warning(f"⏳ Waiting for build to finish at: [bold cyan link={run.url}]{run.url}[/bold cyan link]")
|
|
120
120
|
|
|
121
|
-
logger.warning(click.style("⏳ Waiting for build to finish at: " + click.style(run.url, fg="cyan"), bold=True))
|
|
122
121
|
await run.wait.aio(quiet=True)
|
|
123
122
|
run_details = await run.details.aio()
|
|
124
123
|
|
|
125
124
|
elapsed = str(datetime.now(timezone.utc) - start).split(".")[0]
|
|
126
125
|
|
|
127
126
|
if run_details.action_details.raw_phase == run_definition_pb2.PHASE_SUCCEEDED:
|
|
128
|
-
logger.warning(
|
|
129
|
-
try:
|
|
130
|
-
entity = remote.Task.get(
|
|
131
|
-
name=OPTIMIZE_TASK_NAME,
|
|
132
|
-
project=IMAGE_TASK_PROJECT,
|
|
133
|
-
domain=IMAGE_TASK_DOMAIN,
|
|
134
|
-
auto_version="latest",
|
|
135
|
-
)
|
|
136
|
-
await flyte.with_runcontext(project=IMAGE_TASK_PROJECT, domain=IMAGE_TASK_DOMAIN).run.aio(
|
|
137
|
-
entity, target_image=image_name
|
|
138
|
-
)
|
|
139
|
-
except Exception as e:
|
|
140
|
-
# Ignore the error if optimize is not enabled in the backend.
|
|
141
|
-
logger.warning(f"Failed to run optimize task with error: {e}")
|
|
127
|
+
logger.warning(f"[bold green]✅ Build completed in {elapsed}![/bold green]")
|
|
142
128
|
else:
|
|
143
|
-
raise flyte.errors.ImageBuildError(f"❌ Build failed in {elapsed} at {
|
|
129
|
+
raise flyte.errors.ImageBuildError(f"❌ Build failed in {elapsed} at [cyan]{run.url}[/cyan]")
|
|
144
130
|
|
|
145
131
|
outputs = await run_details.outputs()
|
|
146
132
|
return _get_fully_qualified_image_name(outputs)
|
|
@@ -183,11 +169,8 @@ async def _validate_configuration(image: Image) -> Tuple[str, Optional[str]]:
|
|
|
183
169
|
context_size = tar_path.stat().st_size
|
|
184
170
|
if context_size > 5 * 1024 * 1024:
|
|
185
171
|
logger.warning(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"Upload and build speed will be impacted.",
|
|
189
|
-
fg="yellow",
|
|
190
|
-
)
|
|
172
|
+
f"[yellow]Context size is {context_size / (1024 * 1024):.2f} MB, which is larger than 5 MB. "
|
|
173
|
+
"Upload and build speed will be impacted.[/yellow]",
|
|
191
174
|
)
|
|
192
175
|
_, context_url = await remote.upload_file.aio(context_dst)
|
|
193
176
|
else:
|
|
File without changes
|
flyte/_keyring/file.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from base64 import decodebytes, encodebytes
|
|
2
|
+
from configparser import ConfigParser, NoOptionError, NoSectionError
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from keyring.backend import KeyringBackend
|
|
7
|
+
from keyring.errors import PasswordDeleteError
|
|
8
|
+
|
|
9
|
+
_FLYTE_KEYRING_PATH: Path = Path.home() / ".flyte" / "keyring.cfg"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SimplePlainTextKeyring(KeyringBackend):
|
|
13
|
+
"""Simple plain text keyring"""
|
|
14
|
+
|
|
15
|
+
priority = 0.5
|
|
16
|
+
|
|
17
|
+
def get_password(self, service: str, username: str) -> Optional[str]:
|
|
18
|
+
"""Get password."""
|
|
19
|
+
if not self.file_path.exists():
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
config = ConfigParser(interpolation=None)
|
|
23
|
+
config.read(self.file_path, encoding="utf-8")
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
password_base64 = config.get(service, username).encode("utf-8")
|
|
27
|
+
return decodebytes(password_base64).decode("utf-8")
|
|
28
|
+
except (NoOptionError, NoSectionError):
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
def delete_password(self, service: str, username: str) -> None:
|
|
32
|
+
"""Delete password."""
|
|
33
|
+
if not self.file_path.exists():
|
|
34
|
+
raise PasswordDeleteError("Config file does not exist")
|
|
35
|
+
|
|
36
|
+
config = ConfigParser(interpolation=None)
|
|
37
|
+
config.read(self.file_path, encoding="utf-8")
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
if not config.remove_option(service, username):
|
|
41
|
+
raise PasswordDeleteError("Password not found")
|
|
42
|
+
except NoSectionError:
|
|
43
|
+
raise PasswordDeleteError("Password not found")
|
|
44
|
+
|
|
45
|
+
with self.file_path.open("w", encoding="utf-8") as config_file:
|
|
46
|
+
config.write(config_file)
|
|
47
|
+
|
|
48
|
+
def set_password(self, service: str, username: str, password: str) -> None:
|
|
49
|
+
"""Set password."""
|
|
50
|
+
if not username:
|
|
51
|
+
raise ValueError("Username must be provided")
|
|
52
|
+
|
|
53
|
+
file_path = self._ensure_file_path()
|
|
54
|
+
value = encodebytes(password.encode("utf-8")).decode("utf-8")
|
|
55
|
+
|
|
56
|
+
config = ConfigParser(interpolation=None)
|
|
57
|
+
config.read(file_path, encoding="utf-8")
|
|
58
|
+
|
|
59
|
+
if not config.has_section(service):
|
|
60
|
+
config.add_section(service)
|
|
61
|
+
|
|
62
|
+
config.set(service, username, value)
|
|
63
|
+
|
|
64
|
+
with file_path.open("w", encoding="utf-8") as config_file:
|
|
65
|
+
config.write(config_file)
|
|
66
|
+
|
|
67
|
+
def _ensure_file_path(self):
|
|
68
|
+
self.file_path.parent.mkdir(exist_ok=True, parents=True)
|
|
69
|
+
if not self.file_path.is_file():
|
|
70
|
+
self.file_path.touch(0o600)
|
|
71
|
+
return self.file_path
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def file_path(self) -> Path:
|
|
75
|
+
from flyte.config._reader import resolve_config_path
|
|
76
|
+
|
|
77
|
+
config_path = resolve_config_path()
|
|
78
|
+
if config_path and str(config_path.parent) == ".flyte":
|
|
79
|
+
# if the config is in a .flyte directory, use that as the path
|
|
80
|
+
return config_path.parent / "keyring.cfg"
|
|
81
|
+
# otherwise use the default path
|
|
82
|
+
return _FLYTE_KEYRING_PATH
|
|
83
|
+
|
|
84
|
+
def __repr__(self):
|
|
85
|
+
return f"<{self.__class__.__name__}> at {self.file_path}>"
|
flyte/_logging.py
CHANGED
|
@@ -40,6 +40,21 @@ def log_format_from_env() -> str:
|
|
|
40
40
|
return os.environ.get("LOG_FORMAT", "json")
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
def _get_console():
|
|
44
|
+
"""
|
|
45
|
+
Get the console.
|
|
46
|
+
"""
|
|
47
|
+
from rich.console import Console
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
width = os.get_terminal_size().columns
|
|
51
|
+
except Exception as e:
|
|
52
|
+
logger.debug(f"Failed to get terminal size: {e}")
|
|
53
|
+
width = 160
|
|
54
|
+
|
|
55
|
+
return Console(width=width)
|
|
56
|
+
|
|
57
|
+
|
|
43
58
|
def get_rich_handler(log_level: int) -> Optional[logging.Handler]:
|
|
44
59
|
"""
|
|
45
60
|
Upgrades the global loggers to use Rich logging.
|
|
@@ -51,23 +66,19 @@ def get_rich_handler(log_level: int) -> Optional[logging.Handler]:
|
|
|
51
66
|
return None
|
|
52
67
|
|
|
53
68
|
import click
|
|
54
|
-
from rich.
|
|
69
|
+
from rich.highlighter import NullHighlighter
|
|
55
70
|
from rich.logging import RichHandler
|
|
56
71
|
|
|
57
|
-
try:
|
|
58
|
-
width = os.get_terminal_size().columns
|
|
59
|
-
except Exception as e:
|
|
60
|
-
logger.debug(f"Failed to get terminal size: {e}")
|
|
61
|
-
width = 160
|
|
62
|
-
|
|
63
72
|
handler = RichHandler(
|
|
64
73
|
tracebacks_suppress=[click],
|
|
65
74
|
rich_tracebacks=True,
|
|
66
75
|
omit_repeated_times=False,
|
|
67
76
|
show_path=False,
|
|
68
77
|
log_time_format="%H:%M:%S.%f",
|
|
69
|
-
console=
|
|
78
|
+
console=_get_console(),
|
|
70
79
|
level=log_level,
|
|
80
|
+
highlighter=NullHighlighter(),
|
|
81
|
+
markup=True,
|
|
71
82
|
)
|
|
72
83
|
|
|
73
84
|
formatter = logging.Formatter(fmt="%(filename)s:%(lineno)d - %(message)s")
|
flyte/_utils/coro_management.py
CHANGED
|
@@ -11,7 +11,8 @@ async def run_coros(*coros: typing.Coroutine, return_when: str = asyncio.FIRST_C
|
|
|
11
11
|
:param return_when:
|
|
12
12
|
:return:
|
|
13
13
|
"""
|
|
14
|
-
tasks: typing.List[asyncio.Task[typing.Never]] = [asyncio.create_task(c) for c in coros]
|
|
14
|
+
# tasks: typing.List[asyncio.Task[typing.Never]] = [asyncio.create_task(c) for c in coros] # Python 3.11+
|
|
15
|
+
tasks: typing.List[asyncio.Task] = [asyncio.create_task(c) for c in coros]
|
|
15
16
|
done, pending = await asyncio.wait(tasks, return_when=return_when)
|
|
16
17
|
# TODO we might want to handle asyncio.CancelledError here, for cases when the `action` is cancelled
|
|
17
18
|
# and we want to propagate it to all tasks. Though the backend will handle it anyway,
|
flyte/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '2.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (2, 0, 0, '
|
|
31
|
+
__version__ = version = '2.0.0b21'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 0, 0, 'b21')
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g7f82f6ddd'
|
flyte/config/_config.py
CHANGED
|
@@ -231,10 +231,12 @@ def auto(config_file: typing.Union[str, pathlib.Path, ConfigFile, None] = None)
|
|
|
231
231
|
1. If specified, read the config from the provided file path.
|
|
232
232
|
2. If not specified, the config file is searched in the default locations.
|
|
233
233
|
a. ./config.yaml if it exists (current working directory)
|
|
234
|
-
b.
|
|
235
|
-
c.
|
|
236
|
-
d.
|
|
237
|
-
e.
|
|
234
|
+
b. ./.flyte/config.yaml if it exists (current working directory)
|
|
235
|
+
c. <git_root>/.flyte/config.yaml if it exists
|
|
236
|
+
d. `UCTL_CONFIG` environment variable
|
|
237
|
+
e. `FLYTECTL_CONFIG` environment variable
|
|
238
|
+
f. ~/.union/config.yaml if it exists
|
|
239
|
+
g. ~/.flyte/config.yaml if it exists
|
|
238
240
|
3. If any value is not found in the config file, the default value is used.
|
|
239
241
|
4. For any value there are environment variables that match the config variable names, those will override
|
|
240
242
|
|
flyte/config/_reader.py
CHANGED
|
@@ -135,15 +135,25 @@ class ConfigFile(object):
|
|
|
135
135
|
return self._yaml_config
|
|
136
136
|
|
|
137
137
|
|
|
138
|
+
def _config_path_from_git_root() -> pathlib.Path | None:
|
|
139
|
+
from flyte.git import config_from_root
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
return config_from_root().source
|
|
143
|
+
except RuntimeError:
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
|
|
138
147
|
def resolve_config_path() -> pathlib.Path | None:
|
|
139
148
|
"""
|
|
140
149
|
Config is read from the following locations in order of precedence:
|
|
141
150
|
1. ./config.yaml if it exists
|
|
142
151
|
2. ./.flyte/config.yaml if it exists
|
|
143
|
-
3.
|
|
144
|
-
4. `
|
|
145
|
-
5.
|
|
146
|
-
6. ~/.
|
|
152
|
+
3. <git_root>/.flyte/config.yaml if it exists
|
|
153
|
+
4. `UCTL_CONFIG` environment variable
|
|
154
|
+
5. `FLYTECTL_CONFIG` environment variable
|
|
155
|
+
6. ~/.union/config.yaml if it exists
|
|
156
|
+
7. ~/.flyte/config.yaml if it exists
|
|
147
157
|
"""
|
|
148
158
|
current_location_config = Path("config.yaml")
|
|
149
159
|
if current_location_config.exists():
|
|
@@ -155,6 +165,11 @@ def resolve_config_path() -> pathlib.Path | None:
|
|
|
155
165
|
return dot_flyte_config
|
|
156
166
|
logger.debug("No ./.flyte/config.yaml found")
|
|
157
167
|
|
|
168
|
+
git_root_config = _config_path_from_git_root()
|
|
169
|
+
if git_root_config:
|
|
170
|
+
return git_root_config
|
|
171
|
+
logger.debug("No .flyte/config.yaml found in git repo root")
|
|
172
|
+
|
|
158
173
|
uctl_path_from_env = getenv(UCTL_CONFIG_ENV_VAR, None)
|
|
159
174
|
if uctl_path_from_env:
|
|
160
175
|
return pathlib.Path(uctl_path_from_env)
|
flyte/errors.py
CHANGED
|
@@ -223,3 +223,12 @@ class RunAbortedError(RuntimeUserError):
|
|
|
223
223
|
|
|
224
224
|
def __init__(self, message: str):
|
|
225
225
|
super().__init__("RunAbortedError", message, "user")
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class SlowDownError(RuntimeUserError):
|
|
229
|
+
"""
|
|
230
|
+
This error is raised when the user tries to access a resource that does not exist or is invalid.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def __init__(self, message: str):
|
|
234
|
+
super().__init__("SlowDownError", message, "user")
|
flyte/git/_config.py
CHANGED
|
@@ -14,4 +14,6 @@ def config_from_root(path: pathlib.Path | str = ".flyte/config.yaml") -> flyte.c
|
|
|
14
14
|
if result.returncode != 0:
|
|
15
15
|
raise RuntimeError(f"Failed to get git root directory: {result.stderr}")
|
|
16
16
|
root = pathlib.Path(result.stdout.strip())
|
|
17
|
+
if not (root / path).exists():
|
|
18
|
+
raise RuntimeError(f"Config file {root / path} does not exist")
|
|
17
19
|
return flyte.config.auto(root / path)
|
flyte/io/_dataframe/dataframe.py
CHANGED
|
@@ -904,7 +904,8 @@ class DataFrameTransformerEngine(TypeTransformer[DataFrame]):
|
|
|
904
904
|
# t1(input_a: DataFrame) # or
|
|
905
905
|
# t1(input_a: Annotated[DataFrame, my_cols])
|
|
906
906
|
if issubclass(expected_python_type, DataFrame):
|
|
907
|
-
fdf = DataFrame(format=metad.structured_dataset_type.format)
|
|
907
|
+
fdf = DataFrame(format=metad.structured_dataset_type.format, uri=lv.scalar.structured_dataset.uri)
|
|
908
|
+
fdf._already_uploaded = True
|
|
908
909
|
fdf._literal_sd = lv.scalar.structured_dataset
|
|
909
910
|
fdf._metadata = metad
|
|
910
911
|
return fdf
|
|
@@ -1012,7 +1013,7 @@ class DataFrameTransformerEngine(TypeTransformer[DataFrame]):
|
|
|
1012
1013
|
def guess_python_type(self, literal_type: types_pb2.LiteralType) -> Type[DataFrame]:
|
|
1013
1014
|
# todo: technically we should return the dataframe type specified in the constructor, but to do that,
|
|
1014
1015
|
# we'd have to store that, which we don't do today. See possibly #1363
|
|
1015
|
-
if literal_type.HasField("
|
|
1016
|
+
if literal_type.HasField("structured_dataset_type"):
|
|
1016
1017
|
return DataFrame
|
|
1017
1018
|
raise ValueError(f"DataFrameTransformerEngine cannot reverse {literal_type}")
|
|
1018
1019
|
|