flyte 2.0.0b4__py3-none-any.whl → 2.0.0b5__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/_code_bundle/bundle.py +2 -0
- flyte/_deploy.py +71 -50
- flyte/_environment.py +14 -2
- flyte/_image.py +49 -38
- flyte/_internal/imagebuild/docker_builder.py +18 -0
- flyte/_internal/imagebuild/remote_builder.py +12 -2
- flyte/_internal/runtime/convert.py +3 -5
- flyte/_run.py +48 -14
- flyte/_task.py +13 -0
- flyte/_utils/__init__.py +2 -0
- flyte/_utils/module_loader.py +89 -0
- flyte/_version.py +2 -2
- flyte/cli/_common.py +5 -1
- flyte/cli/_deploy.py +120 -12
- flyte/cli/_run.py +1 -1
- flyte/errors.py +10 -0
- flyte/models.py +9 -1
- {flyte-2.0.0b4.dist-info → flyte-2.0.0b5.dist-info}/METADATA +1 -1
- {flyte-2.0.0b4.dist-info → flyte-2.0.0b5.dist-info}/RECORD +24 -23
- {flyte-2.0.0b4.data → flyte-2.0.0b5.data}/scripts/runtime.py +0 -0
- {flyte-2.0.0b4.dist-info → flyte-2.0.0b5.dist-info}/WHEEL +0 -0
- {flyte-2.0.0b4.dist-info → flyte-2.0.0b5.dist-info}/entry_points.txt +0 -0
- {flyte-2.0.0b4.dist-info → flyte-2.0.0b5.dist-info}/licenses/LICENSE +0 -0
- {flyte-2.0.0b4.dist-info → flyte-2.0.0b5.dist-info}/top_level.txt +0 -0
flyte/_code_bundle/bundle.py
CHANGED
|
@@ -7,6 +7,7 @@ import tempfile
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import ClassVar, Type
|
|
9
9
|
|
|
10
|
+
from async_lru import alru_cache
|
|
10
11
|
from flyteidl.core.tasks_pb2 import TaskTemplate
|
|
11
12
|
|
|
12
13
|
from flyte._logging import log, logger
|
|
@@ -109,6 +110,7 @@ async def build_pkl_bundle(
|
|
|
109
110
|
return CodeBundle(pkl=str(dest), computed_version=str_digest)
|
|
110
111
|
|
|
111
112
|
|
|
113
|
+
@alru_cache
|
|
112
114
|
async def build_code_bundle(
|
|
113
115
|
from_dir: Path,
|
|
114
116
|
*ignore: Type[Ignore],
|
flyte/_deploy.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import typing
|
|
4
5
|
from dataclasses import dataclass
|
|
5
6
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
|
6
7
|
|
|
8
|
+
import grpc.aio
|
|
7
9
|
import rich.repr
|
|
8
10
|
|
|
9
11
|
import flyte.errors
|
|
@@ -90,27 +92,39 @@ async def _deploy_task(
|
|
|
90
92
|
|
|
91
93
|
image_uri = task.image.uri if isinstance(task.image, Image) else task.image
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
95
|
+
try:
|
|
96
|
+
if dryrun:
|
|
97
|
+
return translate_task_to_wire(task, serialization_context)
|
|
98
|
+
|
|
99
|
+
default_inputs = await convert_upload_default_inputs(task.interface)
|
|
100
|
+
spec = translate_task_to_wire(task, serialization_context, default_inputs=default_inputs)
|
|
101
|
+
|
|
102
|
+
msg = f"Deploying task {task.name}, with image {image_uri} version {serialization_context.version}"
|
|
103
|
+
if spec.task_template.HasField("container") and spec.task_template.container.args:
|
|
104
|
+
msg += f" from {spec.task_template.container.args[-3]}.{spec.task_template.container.args[-1]}"
|
|
105
|
+
logger.info(msg)
|
|
106
|
+
task_id = task_definition_pb2.TaskIdentifier(
|
|
107
|
+
org=spec.task_template.id.org,
|
|
108
|
+
project=spec.task_template.id.project,
|
|
109
|
+
domain=spec.task_template.id.domain,
|
|
110
|
+
version=spec.task_template.id.version,
|
|
111
|
+
name=spec.task_template.id.name,
|
|
112
|
+
)
|
|
110
113
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
try:
|
|
115
|
+
await get_client().task_service.DeployTask(task_service_pb2.DeployTaskRequest(task_id=task_id, spec=spec))
|
|
116
|
+
logger.info(f"Deployed task {task.name} with version {task_id.version}")
|
|
117
|
+
except grpc.aio.AioRpcError as e:
|
|
118
|
+
if e.code() == grpc.StatusCode.ALREADY_EXISTS:
|
|
119
|
+
logger.info(f"Task {task.name} with image {image_uri} already exists, skipping deployment.")
|
|
120
|
+
return spec
|
|
121
|
+
raise
|
|
122
|
+
return spec
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.error(f"Failed to deploy task {task.name} with image {image_uri}: {e}")
|
|
125
|
+
raise flyte.errors.DeploymentError(
|
|
126
|
+
f"Failed to deploy task {task.name} file{task.source_file} with image {image_uri}, Error: {e!s}"
|
|
127
|
+
) from e
|
|
114
128
|
|
|
115
129
|
|
|
116
130
|
async def _build_image_bg(env_name: str, image: Image) -> Tuple[str, str]:
|
|
@@ -151,13 +165,14 @@ async def _build_images(deployment: DeploymentPlan) -> ImageCache:
|
|
|
151
165
|
|
|
152
166
|
|
|
153
167
|
@requires_initialization
|
|
154
|
-
async def apply(
|
|
168
|
+
async def apply(deployment_plan: DeploymentPlan, copy_style: CopyFiles, dryrun: bool = False) -> Deployment:
|
|
155
169
|
from ._code_bundle import build_code_bundle
|
|
156
170
|
|
|
157
171
|
cfg = get_common_config()
|
|
158
|
-
image_cache = await _build_images(deployment)
|
|
159
172
|
|
|
160
|
-
|
|
173
|
+
image_cache = await _build_images(deployment_plan)
|
|
174
|
+
|
|
175
|
+
version = deployment_plan.version
|
|
161
176
|
if copy_style == "none" and not version:
|
|
162
177
|
raise flyte.errors.DeploymentError("Version must be set when copy_style is none")
|
|
163
178
|
else:
|
|
@@ -178,41 +193,44 @@ async def apply(deployment: DeploymentPlan, copy_style: CopyFiles, dryrun: bool
|
|
|
178
193
|
)
|
|
179
194
|
|
|
180
195
|
tasks = []
|
|
181
|
-
|
|
196
|
+
|
|
197
|
+
for env_name, env in deployment_plan.envs.items():
|
|
182
198
|
logger.info(f"Deploying environment {env_name}")
|
|
183
199
|
# TODO Make this pluggable based on the environment type
|
|
184
200
|
if isinstance(env, TaskEnvironment):
|
|
185
201
|
for task in env.tasks.values():
|
|
186
202
|
tasks.append(_deploy_task(task, dryrun=dryrun, serialization_context=sc))
|
|
187
|
-
return Deployment(envs=
|
|
203
|
+
return Deployment(envs=deployment_plan.envs, deployed_tasks=await asyncio.gather(*tasks))
|
|
188
204
|
|
|
189
205
|
|
|
190
|
-
def _recursive_discover(
|
|
191
|
-
planned_envs: Dict[str, Environment], envs: Environment | List[Environment]
|
|
192
|
-
) -> Dict[str, Environment]:
|
|
206
|
+
def _recursive_discover(planned_envs: Dict[str, Environment], env: Environment) -> Dict[str, Environment]:
|
|
193
207
|
"""
|
|
194
208
|
Recursively deploy the environment and its dependencies, if not already deployed (present in env_tasks) and
|
|
195
209
|
return the updated env_tasks.
|
|
196
210
|
"""
|
|
197
|
-
if
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
_recursive_discover(planned_envs, dependent_env)
|
|
206
|
-
# Add the environment to the existing envs
|
|
207
|
-
planned_envs[env.name] = env
|
|
211
|
+
# Skip if the environment is already planned
|
|
212
|
+
if env.name in planned_envs:
|
|
213
|
+
return planned_envs
|
|
214
|
+
# Recursively discover dependent environments
|
|
215
|
+
for dependent_env in env.depends_on:
|
|
216
|
+
_recursive_discover(planned_envs, dependent_env)
|
|
217
|
+
# Add the environment to the existing envs
|
|
218
|
+
planned_envs[env.name] = env
|
|
208
219
|
return planned_envs
|
|
209
220
|
|
|
210
221
|
|
|
211
|
-
def plan_deploy(*envs: Environment, version: Optional[str] = None) -> DeploymentPlan:
|
|
222
|
+
def plan_deploy(*envs: Environment, version: Optional[str] = None) -> List[DeploymentPlan]:
|
|
212
223
|
if envs is None:
|
|
213
|
-
return DeploymentPlan({})
|
|
214
|
-
|
|
215
|
-
|
|
224
|
+
return [DeploymentPlan({})]
|
|
225
|
+
deployment_plans = []
|
|
226
|
+
visited_envs: typing.Set[str] = set()
|
|
227
|
+
for env in envs:
|
|
228
|
+
if env.name in visited_envs:
|
|
229
|
+
continue
|
|
230
|
+
planned_envs = _recursive_discover({}, env)
|
|
231
|
+
deployment_plans.append(DeploymentPlan(planned_envs, version=version))
|
|
232
|
+
visited_envs.update(planned_envs.keys())
|
|
233
|
+
return deployment_plans
|
|
216
234
|
|
|
217
235
|
|
|
218
236
|
@syncify
|
|
@@ -222,7 +240,7 @@ async def deploy(
|
|
|
222
240
|
version: str | None = None,
|
|
223
241
|
interactive_mode: bool | None = None,
|
|
224
242
|
copy_style: CopyFiles = "loaded_modules",
|
|
225
|
-
) -> Deployment:
|
|
243
|
+
) -> List[Deployment]:
|
|
226
244
|
"""
|
|
227
245
|
Deploy the given environment or list of environments.
|
|
228
246
|
:param envs: Environment or list of environments to deploy.
|
|
@@ -238,16 +256,19 @@ async def deploy(
|
|
|
238
256
|
"""
|
|
239
257
|
if interactive_mode:
|
|
240
258
|
raise NotImplementedError("Interactive mode not yet implemented for deployment")
|
|
241
|
-
|
|
242
|
-
|
|
259
|
+
deployment_plans = plan_deploy(*envs, version=version)
|
|
260
|
+
deployments = []
|
|
261
|
+
for deployment_plan in deployment_plans:
|
|
262
|
+
deployments.append(apply(deployment_plan, copy_style=copy_style, dryrun=dryrun))
|
|
263
|
+
return await asyncio.gather(*deployments)
|
|
243
264
|
|
|
244
265
|
|
|
245
266
|
@syncify
|
|
246
|
-
async def build_images(
|
|
267
|
+
async def build_images(envs: Environment) -> ImageCache:
|
|
247
268
|
"""
|
|
248
269
|
Build the images for the given environments.
|
|
249
|
-
:param envs: Environment
|
|
270
|
+
:param envs: Environment to build images for.
|
|
250
271
|
:return: ImageCache containing the built images.
|
|
251
272
|
"""
|
|
252
|
-
deployment = plan_deploy(
|
|
253
|
-
return await _build_images(deployment)
|
|
273
|
+
deployment = plan_deploy(envs)
|
|
274
|
+
return await _build_images(deployment[0])
|
flyte/_environment.py
CHANGED
|
@@ -6,14 +6,24 @@ from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union
|
|
|
6
6
|
|
|
7
7
|
import rich.repr
|
|
8
8
|
|
|
9
|
-
from flyte._secret import SecretRequest
|
|
10
|
-
|
|
11
9
|
from ._image import Image
|
|
12
10
|
from ._resources import Resources
|
|
11
|
+
from ._secret import SecretRequest
|
|
13
12
|
|
|
14
13
|
if TYPE_CHECKING:
|
|
15
14
|
from kubernetes.client import V1PodTemplate
|
|
16
15
|
|
|
16
|
+
# Global registry to track all Environment instances in load order
|
|
17
|
+
_ENVIRONMENT_REGISTRY: List[Environment] = []
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def list_loaded_environments() -> List[Environment]:
|
|
21
|
+
"""
|
|
22
|
+
Return a list of all Environment objects in the order they were loaded.
|
|
23
|
+
This is useful for deploying environments in the order they were defined.
|
|
24
|
+
"""
|
|
25
|
+
return _ENVIRONMENT_REGISTRY
|
|
26
|
+
|
|
17
27
|
|
|
18
28
|
def is_snake_or_kebab_with_numbers(s: str) -> bool:
|
|
19
29
|
return re.fullmatch(r"^[a-z0-9]+([_-][a-z0-9]+)*$", s) is not None
|
|
@@ -44,6 +54,8 @@ class Environment:
|
|
|
44
54
|
def __post_init__(self):
|
|
45
55
|
if not is_snake_or_kebab_with_numbers(self.name):
|
|
46
56
|
raise ValueError(f"Environment name '{self.name}' must be in snake_case or kebab-case format.")
|
|
57
|
+
# Automatically register this environment instance in load order
|
|
58
|
+
_ENVIRONMENT_REGISTRY.append(self)
|
|
47
59
|
|
|
48
60
|
def add_dependency(self, *env: Environment):
|
|
49
61
|
"""
|
flyte/_image.py
CHANGED
|
@@ -165,6 +165,15 @@ class UVProject(PipOption, Layer):
|
|
|
165
165
|
pyproject: Path
|
|
166
166
|
uvlock: Path
|
|
167
167
|
|
|
168
|
+
def validate(self):
|
|
169
|
+
if not self.pyproject.exists():
|
|
170
|
+
raise FileNotFoundError(f"pyproject.toml file {self.pyproject} does not exist")
|
|
171
|
+
if not self.pyproject.is_file():
|
|
172
|
+
raise ValueError(f"Pyproject file {self.pyproject} is not a file")
|
|
173
|
+
if not self.uvlock.exists():
|
|
174
|
+
raise ValueError(f"UVLock file {self.uvlock} does not exist")
|
|
175
|
+
super().validate()
|
|
176
|
+
|
|
168
177
|
def update_hash(self, hasher: hashlib._Hash):
|
|
169
178
|
from ._utils import filehash_update
|
|
170
179
|
|
|
@@ -172,6 +181,27 @@ class UVProject(PipOption, Layer):
|
|
|
172
181
|
filehash_update(self.uvlock, hasher)
|
|
173
182
|
|
|
174
183
|
|
|
184
|
+
@rich.repr.auto
|
|
185
|
+
@dataclass(frozen=True, repr=True)
|
|
186
|
+
class UVScript(PipOption, Layer):
|
|
187
|
+
script: Path
|
|
188
|
+
|
|
189
|
+
def validate(self):
|
|
190
|
+
if not self.script.exists():
|
|
191
|
+
raise FileNotFoundError(f"UV script {self.script} does not exist")
|
|
192
|
+
if not self.script.is_file():
|
|
193
|
+
raise ValueError(f"UV script {self.script} is not a file")
|
|
194
|
+
if not self.script.suffix == ".py":
|
|
195
|
+
raise ValueError(f"UV script {self.script} must have a .py extension")
|
|
196
|
+
super().validate()
|
|
197
|
+
|
|
198
|
+
def update_hash(self, hasher: hashlib._Hash):
|
|
199
|
+
from ._utils import filehash_update
|
|
200
|
+
|
|
201
|
+
super().update_hash(hasher)
|
|
202
|
+
filehash_update(self.script, hasher)
|
|
203
|
+
|
|
204
|
+
|
|
175
205
|
@rich.repr.auto
|
|
176
206
|
@dataclass(frozen=True, repr=True)
|
|
177
207
|
class AptPackages(Layer):
|
|
@@ -404,8 +434,8 @@ class Image:
|
|
|
404
434
|
)
|
|
405
435
|
labels_and_user = _DockerLines(
|
|
406
436
|
(
|
|
407
|
-
"LABEL org.opencontainers.image.authors='Union.AI <
|
|
408
|
-
"LABEL org.opencontainers.image.source=https://github.com/
|
|
437
|
+
"LABEL org.opencontainers.image.authors='Union.AI <info@union.ai>'",
|
|
438
|
+
"LABEL org.opencontainers.image.source=https://github.com/flyteorg/flyte",
|
|
409
439
|
"RUN useradd --create-home --shell /bin/bash flytekit &&"
|
|
410
440
|
" chown -R flytekit /root && chown -R flytekit /home",
|
|
411
441
|
"WORKDIR /root",
|
|
@@ -511,6 +541,7 @@ class Image:
|
|
|
511
541
|
pre: bool = False,
|
|
512
542
|
extra_args: Optional[str] = None,
|
|
513
543
|
platform: Optional[Tuple[Architecture, ...]] = None,
|
|
544
|
+
secret_mounts: Optional[SecretRequest] = None,
|
|
514
545
|
) -> Image:
|
|
515
546
|
"""
|
|
516
547
|
Use this method to create a new image with the specified uv script.
|
|
@@ -544,36 +575,18 @@ class Image:
|
|
|
544
575
|
|
|
545
576
|
:return: Image
|
|
546
577
|
"""
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
if not script.suffix == ".py":
|
|
556
|
-
raise ValueError(f"UV script {script} must have a .py extension")
|
|
557
|
-
header = parse_uv_script_file(script)
|
|
578
|
+
ll = UVScript(
|
|
579
|
+
script=Path(script),
|
|
580
|
+
index_url=index_url,
|
|
581
|
+
extra_index_urls=_ensure_tuple(extra_index_urls) if extra_index_urls else None,
|
|
582
|
+
pre=pre,
|
|
583
|
+
extra_args=extra_args,
|
|
584
|
+
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
585
|
+
)
|
|
558
586
|
|
|
559
|
-
# todo: arch
|
|
560
587
|
img = cls.from_debian_base(registry=registry, name=name, python_version=python_version, platform=platform)
|
|
561
588
|
|
|
562
|
-
|
|
563
|
-
img = img.with_apt_packages("ca-certificates")
|
|
564
|
-
|
|
565
|
-
if header.dependencies:
|
|
566
|
-
return img.with_pip_packages(
|
|
567
|
-
*header.dependencies,
|
|
568
|
-
index_url=index_url,
|
|
569
|
-
extra_index_urls=extra_index_urls,
|
|
570
|
-
pre=pre,
|
|
571
|
-
extra_args=extra_args,
|
|
572
|
-
)
|
|
573
|
-
|
|
574
|
-
# todo: override the _identifier_override to be the script name or a hash of the script contents
|
|
575
|
-
# This is needed because inside the image, the identifier will be computed to be something different.
|
|
576
|
-
return img
|
|
589
|
+
return img.clone(addl_layer=ll)
|
|
577
590
|
|
|
578
591
|
def clone(
|
|
579
592
|
self,
|
|
@@ -809,7 +822,8 @@ class Image:
|
|
|
809
822
|
|
|
810
823
|
def with_uv_project(
|
|
811
824
|
self,
|
|
812
|
-
pyproject_file: Path,
|
|
825
|
+
pyproject_file: str | Path,
|
|
826
|
+
uvlock: Path | None = None,
|
|
813
827
|
index_url: Optional[str] = None,
|
|
814
828
|
extra_index_urls: Union[List[str], Tuple[str, ...], None] = None,
|
|
815
829
|
pre: bool = False,
|
|
@@ -823,6 +837,8 @@ class Image:
|
|
|
823
837
|
In the Union builders, using this will change the virtual env to /root/.venv
|
|
824
838
|
|
|
825
839
|
:param pyproject_file: path to the pyproject.toml file, needs to have a corresponding uv.lock file
|
|
840
|
+
:param uvlock: path to the uv.lock file, if not specified, will use the default uv.lock file in the same
|
|
841
|
+
directory as the pyproject.toml file. (pyproject.parent / uv.lock)
|
|
826
842
|
:param index_url: index url to use for pip install, default is None
|
|
827
843
|
:param extra_index_urls: extra index urls to use for pip install, default is None
|
|
828
844
|
:param pre: whether to allow pre-release versions, default is False
|
|
@@ -830,17 +846,12 @@ class Image:
|
|
|
830
846
|
:param secret_mounts: list of secret mounts to use for the build process.
|
|
831
847
|
:return: Image
|
|
832
848
|
"""
|
|
833
|
-
if
|
|
834
|
-
|
|
835
|
-
if not pyproject_file.is_file():
|
|
836
|
-
raise ValueError(f"UVLock file {pyproject_file} is not a file")
|
|
837
|
-
lock = pyproject_file.parent / "uv.lock"
|
|
838
|
-
if not lock.exists():
|
|
839
|
-
raise ValueError(f"UVLock file {lock} does not exist")
|
|
849
|
+
if isinstance(pyproject_file, str):
|
|
850
|
+
pyproject_file = Path(pyproject_file)
|
|
840
851
|
new_image = self.clone(
|
|
841
852
|
addl_layer=UVProject(
|
|
842
853
|
pyproject=pyproject_file,
|
|
843
|
-
uvlock=lock,
|
|
854
|
+
uvlock=uvlock or (pyproject_file.parent / "uv.lock"),
|
|
844
855
|
index_url=index_url,
|
|
845
856
|
extra_index_urls=extra_index_urls,
|
|
846
857
|
pre=pre,
|
|
@@ -25,8 +25,10 @@ from flyte._image import (
|
|
|
25
25
|
PythonWheels,
|
|
26
26
|
Requirements,
|
|
27
27
|
UVProject,
|
|
28
|
+
UVScript,
|
|
28
29
|
WorkDir,
|
|
29
30
|
_DockerLines,
|
|
31
|
+
_ensure_tuple,
|
|
30
32
|
)
|
|
31
33
|
from flyte._internal.imagebuild.image_builder import (
|
|
32
34
|
DockerAPIImageChecker,
|
|
@@ -325,6 +327,22 @@ async def _process_layer(layer: Layer, context_path: Path, dockerfile: str) -> s
|
|
|
325
327
|
# Handle Python wheels
|
|
326
328
|
dockerfile = await PythonWheelHandler.handle(layer, context_path, dockerfile)
|
|
327
329
|
|
|
330
|
+
case UVScript():
|
|
331
|
+
# Handle UV script
|
|
332
|
+
from flyte._utils import parse_uv_script_file
|
|
333
|
+
|
|
334
|
+
header = parse_uv_script_file(layer.script)
|
|
335
|
+
if header.dependencies:
|
|
336
|
+
pip = PipPackages(
|
|
337
|
+
packages=_ensure_tuple(header.dependencies) if header.dependencies else None,
|
|
338
|
+
secret_mounts=layer.secret_mounts,
|
|
339
|
+
index_url=layer.index_url,
|
|
340
|
+
extra_args=layer.extra_args,
|
|
341
|
+
pre=layer.pre,
|
|
342
|
+
extra_index_urls=layer.extra_index_urls,
|
|
343
|
+
)
|
|
344
|
+
dockerfile = await PipAndRequirementsHandler.handle(pip, context_path, dockerfile)
|
|
345
|
+
|
|
328
346
|
case Requirements() | PipPackages():
|
|
329
347
|
# Handle pip packages and requirements
|
|
330
348
|
dockerfile = await PipAndRequirementsHandler.handle(layer, context_path, dockerfile)
|
|
@@ -23,6 +23,7 @@ from flyte._image import (
|
|
|
23
23
|
PythonWheels,
|
|
24
24
|
Requirements,
|
|
25
25
|
UVProject,
|
|
26
|
+
UVScript,
|
|
26
27
|
)
|
|
27
28
|
from flyte._internal.imagebuild.image_builder import ImageBuilder, ImageChecker
|
|
28
29
|
from flyte._logging import logger
|
|
@@ -196,10 +197,19 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
|
|
|
196
197
|
)
|
|
197
198
|
)
|
|
198
199
|
layers.append(requirements_layer)
|
|
199
|
-
elif isinstance(layer, PipPackages):
|
|
200
|
+
elif isinstance(layer, PipPackages) or isinstance(layer, UVScript):
|
|
201
|
+
if isinstance(layer, UVScript):
|
|
202
|
+
from flyte._utils import parse_uv_script_file
|
|
203
|
+
|
|
204
|
+
header = parse_uv_script_file(layer.script)
|
|
205
|
+
if not header.dependencies:
|
|
206
|
+
continue
|
|
207
|
+
packages: typing.Iterable[str] = header.dependencies
|
|
208
|
+
else:
|
|
209
|
+
packages = layer.packages or []
|
|
200
210
|
pip_layer = image_definition_pb2.Layer(
|
|
201
211
|
pip_packages=image_definition_pb2.PipPackages(
|
|
202
|
-
packages=
|
|
212
|
+
packages=packages,
|
|
203
213
|
options=image_definition_pb2.PipOptions(
|
|
204
214
|
index_url=layer.index_url,
|
|
205
215
|
extra_index_urls=layer.extra_index_urls,
|
|
@@ -105,11 +105,9 @@ def is_optional_type(tp) -> bool:
|
|
|
105
105
|
async def convert_from_native_to_inputs(interface: NativeInterface, *args, **kwargs) -> Inputs:
|
|
106
106
|
kwargs = interface.convert_to_kwargs(*args, **kwargs)
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
f"Please provide all required inputs. Inputs received: {kwargs}, interface: {interface}"
|
|
112
|
-
)
|
|
108
|
+
missing = [key for key in interface.required_inputs() if key not in kwargs]
|
|
109
|
+
if missing:
|
|
110
|
+
raise ValueError(f"Missing required inputs: {', '.join(missing)}")
|
|
113
111
|
|
|
114
112
|
if len(interface.inputs) == 0:
|
|
115
113
|
return Inputs.empty()
|
flyte/_run.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import pathlib
|
|
5
5
|
import uuid
|
|
6
|
+
from dataclasses import dataclass
|
|
6
7
|
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, Union, cast
|
|
7
8
|
|
|
8
9
|
import flyte.errors
|
|
@@ -36,10 +37,26 @@ if TYPE_CHECKING:
|
|
|
36
37
|
from flyte.remote._task import LazyEntity
|
|
37
38
|
|
|
38
39
|
from ._code_bundle import CopyFiles
|
|
40
|
+
from ._internal.imagebuild.image_builder import ImageCache
|
|
39
41
|
|
|
40
42
|
Mode = Literal["local", "remote", "hybrid"]
|
|
41
43
|
|
|
42
44
|
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class _CacheKey:
|
|
47
|
+
obj_id: int
|
|
48
|
+
dry_run: bool
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class _CacheValue:
|
|
53
|
+
code_bundle: CodeBundle | None
|
|
54
|
+
image_cache: Optional[ImageCache]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_RUN_CACHE: Dict[_CacheKey, _CacheValue] = {}
|
|
58
|
+
|
|
59
|
+
|
|
43
60
|
async def _get_code_bundle_for_run(name: str) -> CodeBundle | None:
|
|
44
61
|
"""
|
|
45
62
|
Get the code bundle for the run with the given name.
|
|
@@ -78,6 +95,7 @@ class _Runner:
|
|
|
78
95
|
annotations: Dict[str, str] | None = None,
|
|
79
96
|
interruptible: bool = False,
|
|
80
97
|
log_level: int | None = None,
|
|
98
|
+
disable_run_cache: bool = False,
|
|
81
99
|
):
|
|
82
100
|
init_config = _get_init_config()
|
|
83
101
|
client = init_config.client if init_config else None
|
|
@@ -104,6 +122,7 @@ class _Runner:
|
|
|
104
122
|
self._annotations = annotations
|
|
105
123
|
self._interruptible = interruptible
|
|
106
124
|
self._log_level = log_level
|
|
125
|
+
self._disable_run_cache = disable_run_cache
|
|
107
126
|
|
|
108
127
|
@requires_initialization
|
|
109
128
|
async def _run_remote(self, obj: TaskTemplate[P, R] | LazyEntity, *args: P.args, **kwargs: P.kwargs) -> Run:
|
|
@@ -135,24 +154,36 @@ class _Runner:
|
|
|
135
154
|
if obj.parent_env is None:
|
|
136
155
|
raise ValueError("Task is not attached to an environment. Please attach the task to an environment")
|
|
137
156
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
)
|
|
157
|
+
if (
|
|
158
|
+
not self._disable_run_cache
|
|
159
|
+
and _RUN_CACHE.get(_CacheKey(obj_id=id(obj), dry_run=self._dry_run)) is not None
|
|
160
|
+
):
|
|
161
|
+
cached_value = _RUN_CACHE[_CacheKey(obj_id=id(obj), dry_run=self._dry_run)]
|
|
162
|
+
code_bundle = cached_value.code_bundle
|
|
163
|
+
image_cache = cached_value.image_cache
|
|
146
164
|
else:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
165
|
+
image_cache = await build_images.aio(cast(Environment, obj.parent_env()))
|
|
166
|
+
|
|
167
|
+
if self._interactive_mode:
|
|
168
|
+
code_bundle = await build_pkl_bundle(
|
|
169
|
+
obj,
|
|
170
|
+
upload_to_controlplane=not self._dry_run,
|
|
151
171
|
copy_bundle_to=self._copy_bundle_to,
|
|
152
|
-
copy_style=self._copy_files,
|
|
153
172
|
)
|
|
154
173
|
else:
|
|
155
|
-
|
|
174
|
+
if self._copy_files != "none":
|
|
175
|
+
code_bundle = await build_code_bundle(
|
|
176
|
+
from_dir=cfg.root_dir,
|
|
177
|
+
dryrun=self._dry_run,
|
|
178
|
+
copy_bundle_to=self._copy_bundle_to,
|
|
179
|
+
copy_style=self._copy_files,
|
|
180
|
+
)
|
|
181
|
+
else:
|
|
182
|
+
code_bundle = None
|
|
183
|
+
if not self._disable_run_cache:
|
|
184
|
+
_RUN_CACHE[_CacheKey(obj_id=id(obj), dry_run=self._dry_run)] = _CacheValue(
|
|
185
|
+
code_bundle=code_bundle, image_cache=image_cache
|
|
186
|
+
)
|
|
156
187
|
|
|
157
188
|
version = self._version or (
|
|
158
189
|
code_bundle.computed_version if code_bundle and code_bundle.computed_version else None
|
|
@@ -516,6 +547,7 @@ def with_runcontext(
|
|
|
516
547
|
annotations: Dict[str, str] | None = None,
|
|
517
548
|
interruptible: bool = False,
|
|
518
549
|
log_level: int | None = None,
|
|
550
|
+
disable_run_cache: bool = False,
|
|
519
551
|
) -> _Runner:
|
|
520
552
|
"""
|
|
521
553
|
Launch a new run with the given parameters as the context.
|
|
@@ -556,6 +588,7 @@ def with_runcontext(
|
|
|
556
588
|
:param interruptible: Optional If true, the run can be interrupted by the user.
|
|
557
589
|
:param log_level: Optional Log level to set for the run. If not provided, it will be set to the default log level
|
|
558
590
|
set using `flyte.init()`
|
|
591
|
+
:param disable_run_cache: Optional If true, the run cache will be disabled. This is useful for testing purposes.
|
|
559
592
|
|
|
560
593
|
:return: runner
|
|
561
594
|
"""
|
|
@@ -580,6 +613,7 @@ def with_runcontext(
|
|
|
580
613
|
project=project,
|
|
581
614
|
domain=domain,
|
|
582
615
|
log_level=log_level,
|
|
616
|
+
disable_run_cache=disable_run_cache,
|
|
583
617
|
)
|
|
584
618
|
|
|
585
619
|
|
flyte/_task.py
CHANGED
|
@@ -150,6 +150,10 @@ class TaskTemplate(Generic[P, R]):
|
|
|
150
150
|
self.__dict__.update(state)
|
|
151
151
|
self.parent_env = None
|
|
152
152
|
|
|
153
|
+
@property
|
|
154
|
+
def source_file(self) -> Optional[str]:
|
|
155
|
+
return None
|
|
156
|
+
|
|
153
157
|
async def pre(self, *args, **kwargs) -> Dict[str, Any]:
|
|
154
158
|
"""
|
|
155
159
|
This is the preexecute function that will be
|
|
@@ -395,6 +399,15 @@ class AsyncFunctionTaskTemplate(TaskTemplate[P, R]):
|
|
|
395
399
|
if not iscoroutinefunction(self.func):
|
|
396
400
|
self._call_as_synchronous = True
|
|
397
401
|
|
|
402
|
+
@property
|
|
403
|
+
def source_file(self) -> Optional[str]:
|
|
404
|
+
"""
|
|
405
|
+
Returns the source file of the function, if available. This is useful for debugging and tracing.
|
|
406
|
+
"""
|
|
407
|
+
if hasattr(self.func, "__code__") and self.func.__code__:
|
|
408
|
+
return self.func.__code__.co_filename
|
|
409
|
+
return None
|
|
410
|
+
|
|
398
411
|
def forward(self, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, R] | R:
|
|
399
412
|
# In local execution, we want to just call the function. Note we're not awaiting anything here.
|
|
400
413
|
# If the function was a coroutine function, the coroutine is returned and the await that the caller has
|
flyte/_utils/__init__.py
CHANGED
|
@@ -9,6 +9,7 @@ from .coro_management import run_coros
|
|
|
9
9
|
from .file_handling import filehash_update, update_hasher_for_source
|
|
10
10
|
from .helpers import get_cwd_editable_install
|
|
11
11
|
from .lazy_module import lazy_module
|
|
12
|
+
from .module_loader import load_python_modules
|
|
12
13
|
from .org_discovery import hostname_from_url, org_from_endpoint, sanitize_endpoint
|
|
13
14
|
from .uv_script_parser import parse_uv_script_file
|
|
14
15
|
|
|
@@ -18,6 +19,7 @@ __all__ = [
|
|
|
18
19
|
"get_cwd_editable_install",
|
|
19
20
|
"hostname_from_url",
|
|
20
21
|
"lazy_module",
|
|
22
|
+
"load_python_modules",
|
|
21
23
|
"org_from_endpoint",
|
|
22
24
|
"parse_uv_script_file",
|
|
23
25
|
"run_coros",
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import importlib.util
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Tuple
|
|
7
|
+
|
|
8
|
+
from rich.progress import BarColumn, Progress, TextColumn, TimeElapsedColumn, TimeRemainingColumn
|
|
9
|
+
|
|
10
|
+
import flyte.errors
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_python_modules(path: Path, recursive: bool = False) -> Tuple[List[str], List[Tuple[Path, str]]]:
|
|
14
|
+
"""
|
|
15
|
+
Load all Python modules from a path and return list of loaded module names.
|
|
16
|
+
|
|
17
|
+
:param path: File or directory path
|
|
18
|
+
:param recursive: If True, load modules recursively from subdirectories
|
|
19
|
+
:return: List of loaded module names, and list of file paths that failed to load
|
|
20
|
+
"""
|
|
21
|
+
loaded_modules = []
|
|
22
|
+
failed_paths = []
|
|
23
|
+
|
|
24
|
+
if path.is_file() and path.suffix == ".py":
|
|
25
|
+
# Single file case
|
|
26
|
+
module_name = _load_module_from_file(path)
|
|
27
|
+
if module_name:
|
|
28
|
+
loaded_modules.append(module_name)
|
|
29
|
+
|
|
30
|
+
elif path.is_dir():
|
|
31
|
+
# Directory case
|
|
32
|
+
pattern = "**/*.py" if recursive else "*.py"
|
|
33
|
+
python_files = glob.glob(str(path / pattern), recursive=recursive)
|
|
34
|
+
|
|
35
|
+
with Progress(
|
|
36
|
+
TextColumn("[progress.description]{task.description}"),
|
|
37
|
+
BarColumn(),
|
|
38
|
+
"[progress.percentage]{task.percentage:>3.0f}%",
|
|
39
|
+
TimeElapsedColumn(),
|
|
40
|
+
TimeRemainingColumn(),
|
|
41
|
+
TextColumn("• {task.fields[current_file]}"),
|
|
42
|
+
) as progress:
|
|
43
|
+
task = progress.add_task(f"Loading {len(python_files)} files", total=len(python_files), current_file="")
|
|
44
|
+
for file_path in python_files:
|
|
45
|
+
p = Path(file_path)
|
|
46
|
+
progress.update(task, advance=1, current_file=p.name)
|
|
47
|
+
# Skip __init__.py files
|
|
48
|
+
if p.name == "__init__.py":
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
module_name = _load_module_from_file(p)
|
|
53
|
+
if module_name:
|
|
54
|
+
loaded_modules.append(module_name)
|
|
55
|
+
except flyte.errors.ModuleLoadError as e:
|
|
56
|
+
failed_paths.append((p, str(e)))
|
|
57
|
+
|
|
58
|
+
progress.update(task, advance=1, current_file="[green]Done[/green]")
|
|
59
|
+
|
|
60
|
+
return loaded_modules, failed_paths
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _load_module_from_file(file_path: Path) -> str | None:
|
|
64
|
+
"""
|
|
65
|
+
Load a Python module from a file path.
|
|
66
|
+
|
|
67
|
+
:param file_path: Path to the Python file
|
|
68
|
+
:return: Module name if successfully loaded, None otherwise
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
# Use the file stem as module name
|
|
72
|
+
module_name = file_path.stem
|
|
73
|
+
|
|
74
|
+
# Load the module specification
|
|
75
|
+
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
|
76
|
+
if spec is None or spec.loader is None:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
# Create and execute the module
|
|
80
|
+
module = importlib.util.module_from_spec(spec)
|
|
81
|
+
sys.modules[module_name] = module
|
|
82
|
+
module_path = os.path.dirname(os.path.abspath(file_path))
|
|
83
|
+
sys.path.append(module_path)
|
|
84
|
+
spec.loader.exec_module(module)
|
|
85
|
+
|
|
86
|
+
return module_name
|
|
87
|
+
|
|
88
|
+
except Exception as e:
|
|
89
|
+
raise flyte.errors.ModuleLoadError(f"Failed to load module from {file_path}: {e}") from e
|
flyte/_version.py
CHANGED
|
@@ -17,5 +17,5 @@ __version__: str
|
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
|
18
18
|
version_tuple: VERSION_TUPLE
|
|
19
19
|
|
|
20
|
-
__version__ = version = '2.0.
|
|
21
|
-
__version_tuple__ = version_tuple = (2, 0, 0, '
|
|
20
|
+
__version__ = version = '2.0.0b5'
|
|
21
|
+
__version_tuple__ = version_tuple = (2, 0, 0, 'b5')
|
flyte/cli/_common.py
CHANGED
|
@@ -265,7 +265,7 @@ class ObjectsPerFileGroup(GroupBase):
|
|
|
265
265
|
|
|
266
266
|
spec = importlib.util.spec_from_file_location(module_name, self.filename)
|
|
267
267
|
if spec is None or spec.loader is None:
|
|
268
|
-
raise click.ClickException(f"Could not load module {module_name} from {self.filename}")
|
|
268
|
+
raise click.ClickException(f"Could not load module {module_name} from path [{self.filename}]")
|
|
269
269
|
|
|
270
270
|
module = importlib.util.module_from_spec(spec)
|
|
271
271
|
sys.modules[module_name] = module
|
|
@@ -314,6 +314,10 @@ class FileGroup(GroupBase):
|
|
|
314
314
|
if self._files is None:
|
|
315
315
|
directory = self._dir or Path(".").absolute()
|
|
316
316
|
self._files = [os.fspath(p) for p in directory.glob("*.py") if p.name != "__init__.py"]
|
|
317
|
+
if not self._files:
|
|
318
|
+
self._files = [os.fspath(".")] + [
|
|
319
|
+
os.fspath(p.name) for p in directory.iterdir() if not p.name.startswith(("_", ".")) and p.is_dir()
|
|
320
|
+
]
|
|
317
321
|
return self._files
|
|
318
322
|
|
|
319
323
|
def list_commands(self, ctx):
|
flyte/cli/_deploy.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import pathlib
|
|
1
2
|
from dataclasses import dataclass, field, fields
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from types import ModuleType
|
|
@@ -43,6 +44,36 @@ class DeployArguments:
|
|
|
43
44
|
)
|
|
44
45
|
},
|
|
45
46
|
)
|
|
47
|
+
recursive: bool = field(
|
|
48
|
+
default=False,
|
|
49
|
+
metadata={
|
|
50
|
+
"click.option": click.Option(
|
|
51
|
+
["--recursive", "-r"],
|
|
52
|
+
is_flag=True,
|
|
53
|
+
help="Recursively deploy all environments in the current directory",
|
|
54
|
+
)
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
all: bool = field(
|
|
58
|
+
default=False,
|
|
59
|
+
metadata={
|
|
60
|
+
"click.option": click.Option(
|
|
61
|
+
["--all"],
|
|
62
|
+
is_flag=True,
|
|
63
|
+
help="Deploy all environments in the current directory, ignoring the file name",
|
|
64
|
+
)
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
ignore_load_errors: bool = field(
|
|
68
|
+
default=False,
|
|
69
|
+
metadata={
|
|
70
|
+
"click.option": click.Option(
|
|
71
|
+
["--ignore-load-errors", "-i"],
|
|
72
|
+
is_flag=True,
|
|
73
|
+
help="Ignore errors when loading environments especially when using --recursive or --all.",
|
|
74
|
+
)
|
|
75
|
+
},
|
|
76
|
+
)
|
|
46
77
|
|
|
47
78
|
@classmethod
|
|
48
79
|
def from_dict(cls, d: Dict[str, Any]) -> "DeployArguments":
|
|
@@ -57,9 +88,9 @@ class DeployArguments:
|
|
|
57
88
|
|
|
58
89
|
|
|
59
90
|
class DeployEnvCommand(click.Command):
|
|
60
|
-
def __init__(self,
|
|
61
|
-
self.
|
|
62
|
-
self.
|
|
91
|
+
def __init__(self, env_name: str, env: Any, deploy_args: DeployArguments, *args, **kwargs):
|
|
92
|
+
self.env_name = env_name
|
|
93
|
+
self.env = env
|
|
63
94
|
self.deploy_args = deploy_args
|
|
64
95
|
super().__init__(*args, **kwargs)
|
|
65
96
|
|
|
@@ -67,19 +98,81 @@ class DeployEnvCommand(click.Command):
|
|
|
67
98
|
from rich.console import Console
|
|
68
99
|
|
|
69
100
|
console = Console()
|
|
70
|
-
console.print(f"Deploying root - environment: {self.
|
|
101
|
+
console.print(f"Deploying root - environment: {self.env_name}")
|
|
71
102
|
obj: CLIConfig = ctx.obj
|
|
72
103
|
obj.init(self.deploy_args.project, self.deploy_args.domain)
|
|
73
104
|
with console.status("Deploying...", spinner="dots"):
|
|
74
105
|
deployment = flyte.deploy(
|
|
75
|
-
self.
|
|
106
|
+
self.env,
|
|
76
107
|
dryrun=self.deploy_args.dry_run,
|
|
77
108
|
copy_style=self.deploy_args.copy_style,
|
|
78
109
|
version=self.deploy_args.version,
|
|
79
110
|
)
|
|
80
111
|
|
|
81
|
-
console.print(common.get_table("Environments", deployment.env_repr(), simple=obj.simple))
|
|
82
|
-
console.print(common.get_table("Tasks", deployment.task_repr(), simple=obj.simple))
|
|
112
|
+
console.print(common.get_table("Environments", deployment[0].env_repr(), simple=obj.simple))
|
|
113
|
+
console.print(common.get_table("Tasks", deployment[0].task_repr(), simple=obj.simple))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class DeployEnvRecursiveCommand(click.Command):
|
|
117
|
+
"""
|
|
118
|
+
Command to deploy all loaded environments in a directory or a file, optionally recursively.
|
|
119
|
+
This command will load all python files in the directory, and deploy all environments found in them.
|
|
120
|
+
If the path is a file, it will deploy all environments in that file.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __init__(self, path: pathlib.Path, deploy_args: DeployArguments, *args, **kwargs):
|
|
124
|
+
self.path = path
|
|
125
|
+
self.deploy_args = deploy_args
|
|
126
|
+
super().__init__(*args, **kwargs)
|
|
127
|
+
|
|
128
|
+
def invoke(self, ctx: Context):
|
|
129
|
+
from rich.console import Console
|
|
130
|
+
|
|
131
|
+
from flyte._environment import list_loaded_environments
|
|
132
|
+
from flyte._utils import load_python_modules
|
|
133
|
+
|
|
134
|
+
console = Console()
|
|
135
|
+
obj: CLIConfig = ctx.obj
|
|
136
|
+
|
|
137
|
+
# Load all python modules
|
|
138
|
+
loaded_modules, failed_paths = load_python_modules(self.path, self.deploy_args.recursive)
|
|
139
|
+
if failed_paths:
|
|
140
|
+
console.print(f"Loaded {len(loaded_modules)} modules with, but failed to load {len(failed_paths)} paths:")
|
|
141
|
+
console.print(
|
|
142
|
+
common.get_table("Modules", [[("Path", p), ("Err", e)] for p, e in failed_paths], simple=obj.simple)
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
console.print(f"Loaded {len(loaded_modules)} modules")
|
|
146
|
+
|
|
147
|
+
# Get newly loaded environments
|
|
148
|
+
all_envs = list_loaded_environments()
|
|
149
|
+
if not all_envs:
|
|
150
|
+
console.print("No environments found to deploy")
|
|
151
|
+
return
|
|
152
|
+
console.print(
|
|
153
|
+
common.get_table("Loaded Environments", [[("name", e.name)] for e in all_envs], simple=obj.simple)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if not self.deploy_args.ignore_load_errors and len(failed_paths) > 0:
|
|
157
|
+
raise click.ClickException(
|
|
158
|
+
f"Failed to load {len(failed_paths)} files. Use --ignore-load-errors to ignore these errors."
|
|
159
|
+
)
|
|
160
|
+
# Now start connection and deploy all environments
|
|
161
|
+
obj.init(self.deploy_args.project, self.deploy_args.domain)
|
|
162
|
+
with console.status("Deploying...", spinner="dots"):
|
|
163
|
+
deployments = flyte.deploy(
|
|
164
|
+
*all_envs,
|
|
165
|
+
dryrun=self.deploy_args.dry_run,
|
|
166
|
+
copy_style=self.deploy_args.copy_style,
|
|
167
|
+
version=self.deploy_args.version,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
console.print(
|
|
171
|
+
common.get_table("Environments", [env for d in deployments for env in d.env_repr()], simple=obj.simple)
|
|
172
|
+
)
|
|
173
|
+
console.print(
|
|
174
|
+
common.get_table("Tasks", [task for d in deployments for task in d.task_repr()], simple=obj.simple)
|
|
175
|
+
)
|
|
83
176
|
|
|
84
177
|
|
|
85
178
|
class EnvPerFileGroup(common.ObjectsPerFileGroup):
|
|
@@ -99,8 +192,8 @@ class EnvPerFileGroup(common.ObjectsPerFileGroup):
|
|
|
99
192
|
obj = cast(flyte.Environment, obj)
|
|
100
193
|
return DeployEnvCommand(
|
|
101
194
|
name=obj_name,
|
|
102
|
-
|
|
103
|
-
|
|
195
|
+
env_name=obj_name,
|
|
196
|
+
env=obj,
|
|
104
197
|
help=f"{obj.name}" + (f": {obj.description}" if obj.description else ""),
|
|
105
198
|
deploy_args=self.deploy_args,
|
|
106
199
|
)
|
|
@@ -116,20 +209,35 @@ class EnvFiles(common.FileGroup):
|
|
|
116
209
|
def __init__(
|
|
117
210
|
self,
|
|
118
211
|
*args,
|
|
212
|
+
directory: Path | None = None,
|
|
119
213
|
**kwargs,
|
|
120
214
|
):
|
|
121
215
|
if "params" not in kwargs:
|
|
122
216
|
kwargs["params"] = []
|
|
123
217
|
kwargs["params"].extend(DeployArguments.options())
|
|
124
|
-
super().__init__(*args, **kwargs)
|
|
218
|
+
super().__init__(*args, directory=directory, **kwargs)
|
|
125
219
|
|
|
126
220
|
def get_command(self, ctx, filename):
|
|
127
221
|
deploy_args = DeployArguments.from_dict(ctx.params)
|
|
222
|
+
fp = Path(filename)
|
|
223
|
+
if not fp.exists():
|
|
224
|
+
raise click.BadParameter(f"File {filename} does not exist")
|
|
225
|
+
if deploy_args.recursive or deploy_args.all:
|
|
226
|
+
# If recursive or all, we want to deploy all environments in the current directory
|
|
227
|
+
return DeployEnvRecursiveCommand(
|
|
228
|
+
path=fp,
|
|
229
|
+
deploy_args=deploy_args,
|
|
230
|
+
name=filename,
|
|
231
|
+
help="Deploy all loaded environments from the file, or directory (optional recursively)",
|
|
232
|
+
)
|
|
233
|
+
if fp.is_dir():
|
|
234
|
+
# If the path is a directory, we want to deploy all environments in that directory
|
|
235
|
+
return EnvFiles(directory=fp)
|
|
128
236
|
return EnvPerFileGroup(
|
|
129
|
-
filename=
|
|
237
|
+
filename=fp,
|
|
130
238
|
deploy_args=deploy_args,
|
|
131
239
|
name=filename,
|
|
132
|
-
help=
|
|
240
|
+
help="Deploy a single environment and all its dependencies, from the file.",
|
|
133
241
|
)
|
|
134
242
|
|
|
135
243
|
|
flyte/cli/_run.py
CHANGED
|
@@ -199,7 +199,7 @@ class TaskFiles(common.FileGroup):
|
|
|
199
199
|
if fp.is_dir():
|
|
200
200
|
return TaskFiles(directory=fp)
|
|
201
201
|
return TaskPerFileGroup(
|
|
202
|
-
filename=
|
|
202
|
+
filename=fp,
|
|
203
203
|
run_args=run_args,
|
|
204
204
|
name=filename,
|
|
205
205
|
help=f"Run, functions decorated with `env.task` in {filename}",
|
flyte/errors.py
CHANGED
|
@@ -181,6 +181,16 @@ class ImageBuildError(RuntimeUserError):
|
|
|
181
181
|
super().__init__("ImageBuildError", message, "user")
|
|
182
182
|
|
|
183
183
|
|
|
184
|
+
class ModuleLoadError(RuntimeUserError):
|
|
185
|
+
"""
|
|
186
|
+
This error is raised when the module cannot be loaded, either because it does not exist or because of a
|
|
187
|
+
syntax error.
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
def __init__(self, message: str):
|
|
191
|
+
super().__init__("ModuleLoadError", message, "user")
|
|
192
|
+
|
|
193
|
+
|
|
184
194
|
class InlineIOMaxBytesBreached(RuntimeUserError):
|
|
185
195
|
"""
|
|
186
196
|
This error is raised when the inline IO max bytes limit is breached.
|
flyte/models.py
CHANGED
|
@@ -4,7 +4,7 @@ import inspect
|
|
|
4
4
|
import os
|
|
5
5
|
import pathlib
|
|
6
6
|
from dataclasses import dataclass, field, replace
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, Literal, Optional, Tuple, Type
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Literal, Optional, Tuple, Type
|
|
8
8
|
|
|
9
9
|
import rich.repr
|
|
10
10
|
|
|
@@ -270,6 +270,14 @@ class NativeInterface:
|
|
|
270
270
|
"""
|
|
271
271
|
return self.outputs is not None and len(self.outputs) > 0
|
|
272
272
|
|
|
273
|
+
def required_inputs(self) -> List[str]:
|
|
274
|
+
"""
|
|
275
|
+
Get the names of the required inputs for the task. This is used to determine which inputs are required for the
|
|
276
|
+
task execution.
|
|
277
|
+
:return: A list of required input names.
|
|
278
|
+
"""
|
|
279
|
+
return [k for k, v in self.inputs.items() if v[1] is inspect.Parameter.empty]
|
|
280
|
+
|
|
273
281
|
def num_required_inputs(self) -> int:
|
|
274
282
|
"""
|
|
275
283
|
Get the number of required inputs for the task. This is used to determine how many inputs are required for the
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
flyte/__init__.py,sha256=jWSynBJyJ0WuSjE8HID2MT386ZNlRzx7LzCHW1y_XNw,1468
|
|
2
2
|
flyte/_build.py,sha256=MkgfLAPeL56YeVrGRNZUCZgbwzlEzVP3wLbl5Qru4yk,578
|
|
3
3
|
flyte/_context.py,sha256=K0-TCt-_pHOoE5Xni87_8uIe2vCBOhfNQEtjGT4Hu4k,5239
|
|
4
|
-
flyte/_deploy.py,sha256=
|
|
4
|
+
flyte/_deploy.py,sha256=v4QYa7L9AeFxZh1Ya5Wn8OSPBA3YAX5favQStdD-x-s,10536
|
|
5
5
|
flyte/_doc.py,sha256=_OPCf3t_git6UT7kSJISFaWO9cfNzJhhoe6JjVdyCJo,706
|
|
6
6
|
flyte/_docstring.py,sha256=SsG0Ab_YMAwy2ABJlEo3eBKlyC3kwPdnDJ1FIms-ZBQ,1127
|
|
7
|
-
flyte/_environment.py,sha256=
|
|
7
|
+
flyte/_environment.py,sha256=6ks0lkvGt4oSqM5EFPFlhWC3eoUghxUvCn0wstcAD2E,3713
|
|
8
8
|
flyte/_excepthook.py,sha256=nXts84rzEg6-7RtFarbKzOsRZTQR4plnbWVIFMAEprs,1310
|
|
9
9
|
flyte/_group.py,sha256=7o1j16sZyUmYB50mOiq1ui4TBAKhRpDqLakV8Ya1kw4,803
|
|
10
10
|
flyte/_hash.py,sha256=Of_Zl_DzzzF2jp4ZsLm-3o-xJFCCJ8_GubmLI1htx78,504
|
|
11
|
-
flyte/_image.py,sha256=
|
|
11
|
+
flyte/_image.py,sha256=lPkMW1cqFvFeuIG91CxD9Oif3-EymraSu-2XaLIn2Ng,35691
|
|
12
12
|
flyte/_initialize.py,sha256=xKl_LYMluRt21wWqa6RTKuLo0_DCbSaTfUk27_brtNk,18232
|
|
13
13
|
flyte/_interface.py,sha256=1B9zIwFDjiVp_3l_mk8EpA4g3Re-6DUBEBi9z9vDvPs,3504
|
|
14
14
|
flyte/_logging.py,sha256=QrT4Z30C2tsZ-yIojisQODTuq6Y6zSJYuTrLgF58UYc,3664
|
|
@@ -17,18 +17,18 @@ flyte/_pod.py,sha256=--72b0c6IkOEbBwZPLmgl-ll-j7ECfG-kh75LzBnNN8,1068
|
|
|
17
17
|
flyte/_resources.py,sha256=L2JuvQDlMo1JLJeUmJPRwtWbunhR2xJEhFgQW5yc72c,9690
|
|
18
18
|
flyte/_retry.py,sha256=rfLv0MvWxzPByKESTglEmjPsytEAKiIvvmzlJxXwsfE,941
|
|
19
19
|
flyte/_reusable_environment.py,sha256=f8Y1GilUwGcXH4n2Fckrnx0SrZmhk3nCfoe-TqUKivI,3740
|
|
20
|
-
flyte/_run.py,sha256=
|
|
20
|
+
flyte/_run.py,sha256=SSD35ICaYaqt7Ep4SNAi7BLESIullo68g0g4x_dfrW4,25654
|
|
21
21
|
flyte/_secret.py,sha256=89VIihdXI03irHb217GMfipt7jzXBafm17YYmyv6gHo,3245
|
|
22
|
-
flyte/_task.py,sha256=
|
|
22
|
+
flyte/_task.py,sha256=FUqGDtDmhOVPdv-UVko4h0oecoAcc3JZKu8S__cwUpY,19805
|
|
23
23
|
flyte/_task_environment.py,sha256=Zpfr8gjwXg5KuCfIbT4s3l2mtJCFqDxXwv6ZStHWBuc,9840
|
|
24
24
|
flyte/_task_plugins.py,sha256=9MH3nFPOH_e8_92BT4sFk4oyAnj6GJFvaPYWaraX7yE,1037
|
|
25
25
|
flyte/_timeout.py,sha256=zx5sFcbYmjJAJbZWSGzzX-BpC9HC7Jfs35T7vVhKwkk,1571
|
|
26
26
|
flyte/_tools.py,sha256=tWb0sx3t3mm4jbaQVjCTc9y39oR_Ibo3z_KHToP3Lto,966
|
|
27
27
|
flyte/_trace.py,sha256=SSE1nzUgmVTS2xFNtchEOjEjlRavMOIInasXzY8i9lU,4911
|
|
28
|
-
flyte/_version.py,sha256=
|
|
29
|
-
flyte/errors.py,sha256=
|
|
28
|
+
flyte/_version.py,sha256=KY7k4TTnCC_HdhG4ZUyzH7UsSbByUSTtQDckcSpcmmQ,519
|
|
29
|
+
flyte/errors.py,sha256=DKKw0LVywBNuiWR9WMkODxd_dwWRn-NtyN1TMs6HHd0,5800
|
|
30
30
|
flyte/extend.py,sha256=GB4ZedGzKa30vYWRVPOdxEeK62xnUVFY4z2tD6H9eEw,376
|
|
31
|
-
flyte/models.py,sha256=
|
|
31
|
+
flyte/models.py,sha256=2TgfrkPPgcnnk1P_MO5SEmOYAUbsMKl3gxIDwhW2yEU,15674
|
|
32
32
|
flyte/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
flyte/_bin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
34
|
flyte/_bin/runtime.py,sha256=2jTy3ccvrJ__Xrfdo2t0Fxhsojc5o2zIxDHt98RE_eU,6475
|
|
@@ -40,7 +40,7 @@ flyte/_code_bundle/__init__.py,sha256=G7DJTQ0UN_ETvdh55pYcWsTrZJKXEcyQl9iQQNQOBX
|
|
|
40
40
|
flyte/_code_bundle/_ignore.py,sha256=Tfaoa62CQVTH17kBHD6Xv6xEh1FhcAyvXivl9m-MEE0,3853
|
|
41
41
|
flyte/_code_bundle/_packaging.py,sha256=5QUuea6kg9s-ebBg7gFAHaxOMchxR5MhTQ8KohWsjPk,6909
|
|
42
42
|
flyte/_code_bundle/_utils.py,sha256=qlAVmik9rLasfd1oNrCxhL870w5ntk5ZlNGeaKSKaAU,12028
|
|
43
|
-
flyte/_code_bundle/bundle.py,sha256=
|
|
43
|
+
flyte/_code_bundle/bundle.py,sha256=QbodfyX1RW_V8v0lW8kNwJ8lf4JCL3_uVE43_9ePAzo,8842
|
|
44
44
|
flyte/_internal/__init__.py,sha256=vjXgGzAAjy609YFkAy9_RVPuUlslsHSJBXCLNTVnqOY,136
|
|
45
45
|
flyte/_internal/controllers/__init__.py,sha256=TVAc4ydsldcIFmN3PW9-IX5UkKeD8oOmuIukIgEae9M,4341
|
|
46
46
|
flyte/_internal/controllers/_local_controller.py,sha256=__-eEira0k18DsBu1LBXeEjhFGFcp1Uai9K0YEBbwKM,7300
|
|
@@ -53,15 +53,15 @@ flyte/_internal/controllers/remote/_core.py,sha256=PhqI_qwKieH0abOzXYzUZt3v166Dx
|
|
|
53
53
|
flyte/_internal/controllers/remote/_informer.py,sha256=w4p29_dzS_ns762eNBljvnbJLgCm36d1Ogo2ZkgV1yg,14418
|
|
54
54
|
flyte/_internal/controllers/remote/_service_protocol.py,sha256=B9qbIg6DiGeac-iSccLmX_AL2xUgX4ezNUOiAbSy4V0,1357
|
|
55
55
|
flyte/_internal/imagebuild/__init__.py,sha256=dwXdJ1jMhw9RF8itF7jkPLanvX1yCviSns7hE5eoIts,102
|
|
56
|
-
flyte/_internal/imagebuild/docker_builder.py,sha256=
|
|
56
|
+
flyte/_internal/imagebuild/docker_builder.py,sha256=tTA5Wg479ThCB2qwttrZcAqUWBjzzOVUwVg1wNKY4u4,20508
|
|
57
57
|
flyte/_internal/imagebuild/image_builder.py,sha256=dXBXl62qcPabus6dR3eP8P9mBGNhpZHZ2Xm12AymKkk,11150
|
|
58
|
-
flyte/_internal/imagebuild/remote_builder.py,sha256=
|
|
58
|
+
flyte/_internal/imagebuild/remote_builder.py,sha256=oP8JgnkRgkEMvIdcakxvXjOZiKEYyt5h6CvgEbakSAM,11016
|
|
59
59
|
flyte/_internal/resolvers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
60
|
flyte/_internal/resolvers/_task_module.py,sha256=jwy1QYygUK7xmpCZLt1SPTfJCkfox3Ck3mTlTsm66UI,1973
|
|
61
61
|
flyte/_internal/resolvers/common.py,sha256=ADQLRoyGsJ4vuUkitffMGrMKKjy0vpk6X53g4FuKDLc,993
|
|
62
62
|
flyte/_internal/resolvers/default.py,sha256=nX4DHUYod1nRvEsl_vSgutQVEdExu2xL8pRkyi4VWbY,981
|
|
63
63
|
flyte/_internal/runtime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
64
|
-
flyte/_internal/runtime/convert.py,sha256=
|
|
64
|
+
flyte/_internal/runtime/convert.py,sha256=dX3PfIhuYvuCpRAFGRo-uTRy8Ys3Rgs9cowO6XUjHMo,15951
|
|
65
65
|
flyte/_internal/runtime/entrypoints.py,sha256=9Ng-aQ45M-_MMWeOe9uGmgx69qO9b0xaMRiu542ZI9g,6581
|
|
66
66
|
flyte/_internal/runtime/io.py,sha256=ysL7hMpfVumvsEYWOM-_VPa8MXn5_X_CZorKbOThyv4,5935
|
|
67
67
|
flyte/_internal/runtime/resources_serde.py,sha256=TObMVsSjVcQhcY8-nY81pbvrz7TP-adDD5xV-LqAaxM,4813
|
|
@@ -145,26 +145,27 @@ flyte/_protos/workflow/task_definition_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8g
|
|
|
145
145
|
flyte/_protos/workflow/task_service_pb2.py,sha256=7kCVgR8Is9MzlbdoGd1kVCwz1ot39r2qyY3oZzE_Xuo,5781
|
|
146
146
|
flyte/_protos/workflow/task_service_pb2.pyi,sha256=W0OZWui3TbQANi0GL7lCVmbpJKRwJ0X-8vjXj1qNP5k,3100
|
|
147
147
|
flyte/_protos/workflow/task_service_pb2_grpc.py,sha256=whmfmOTiNhz6_CBsXm8aXUCwtA5bncOikqKYz-bKdok,5992
|
|
148
|
-
flyte/_utils/__init__.py,sha256=
|
|
148
|
+
flyte/_utils/__init__.py,sha256=Lwn9_fLxNF4YR0oCUIKCwM_aXYT5UFjUFInofTHnQTs,831
|
|
149
149
|
flyte/_utils/asyn.py,sha256=KeJKarXNIyD16g6oPM0T9cH7JDmh1KY7JLbwo7i0IlQ,3673
|
|
150
150
|
flyte/_utils/async_cache.py,sha256=JtZJmWO62OowJ0QFNl6wryWqh-kuDi76aAASMie87QY,4596
|
|
151
151
|
flyte/_utils/coro_management.py,sha256=wIsul4XY-tQbH9bjqZ3A0jKluE19xSzLlkMeYu_dk_A,934
|
|
152
152
|
flyte/_utils/file_handling.py,sha256=iU4TxW--fCho_Eg5xTMODn96P03SxzF-V-5f-7bZAZY,2233
|
|
153
153
|
flyte/_utils/helpers.py,sha256=9N70yzfLF4lLGEEdOv5OcweEpYtrCvZqqhtzkjZUXNY,4779
|
|
154
154
|
flyte/_utils/lazy_module.py,sha256=fvXPjvZLzCfcI8Vzs4pKedUDdY0U_RQ1ZVrp9b8qBQY,1994
|
|
155
|
+
flyte/_utils/module_loader.py,sha256=XDmK2qndI2Lx-7JUvPi0LW33_zr5HgCgt-FsyXJzccI,3124
|
|
155
156
|
flyte/_utils/org_discovery.py,sha256=C7aJa0LfnWBkDtSU9M7bE60zp27qEhJC58piqOErZ94,2088
|
|
156
157
|
flyte/_utils/uv_script_parser.py,sha256=PxqD8lSMi6xv0uDd1s8LKB2IPZr4ttZJCUweqlyMTKk,1483
|
|
157
158
|
flyte/cli/__init__.py,sha256=aeCcumeP9xD_5aCmaRYUPCe2QRJSGCaxcUbTZ3co768,341
|
|
158
159
|
flyte/cli/_abort.py,sha256=Ty-63Gtd2PUn6lCuL5AaasfBoPu7TDSU5EQKVbkF4qw,661
|
|
159
160
|
flyte/cli/_build.py,sha256=SBgybTVWOZ22VBHFL8CVFB_oo34lF9wvlwNirYFFyk0,3543
|
|
160
|
-
flyte/cli/_common.py,sha256=
|
|
161
|
+
flyte/cli/_common.py,sha256=SLY3M7ganzLCf2MaybJFRkk677IoZrkZKjIR5Qoc0cE,12542
|
|
161
162
|
flyte/cli/_create.py,sha256=Rv_Ox_OA9TqdSI6zaTzLp9vwiqOanOk-Oasnbgx1Q3M,5081
|
|
162
163
|
flyte/cli/_delete.py,sha256=VTmXv09PBjkdtyl23mbSjIQQlN7Y1AI_bO0GkHP-f9E,546
|
|
163
|
-
flyte/cli/_deploy.py,sha256=
|
|
164
|
+
flyte/cli/_deploy.py,sha256=h-laMIbqNskxPzqtBNxIhgamjt2MxGoNongi9_nTQbo,8939
|
|
164
165
|
flyte/cli/_gen.py,sha256=vlE5l8UR1zz4RSdaRyUfYFvGR0TLxGcTYcP4dhA3Pvg,5458
|
|
165
166
|
flyte/cli/_get.py,sha256=fvoJaBmZuD4sDs33dMo94dvBJVk3MaWPJe24e3cG0Ps,10200
|
|
166
167
|
flyte/cli/_params.py,sha256=8Gj8UYGHwu-SUXGWCTRX5QsVf19NiajhaUMMae6FF9o,19466
|
|
167
|
-
flyte/cli/_run.py,sha256=
|
|
168
|
+
flyte/cli/_run.py,sha256=x1BRMK4M0kUboZVOKNuufi8B0cFjsOE7b36zbHT40Cc,7764
|
|
168
169
|
flyte/cli/main.py,sha256=9sLH-xaGdF9HamUQrTxmGTGNScdCtBfNmyYyPHiW3vU,5180
|
|
169
170
|
flyte/config/__init__.py,sha256=MiwEYK5Iv7MRR22z61nzbsbvZ9Q6MdmAU_g9If1Pmb8,144
|
|
170
171
|
flyte/config/_config.py,sha256=WElU--Kw4MM9zx1v-rLD8qYu2T5Zk0-1QbTpkEc27bc,10779
|
|
@@ -226,10 +227,10 @@ flyte/types/_renderer.py,sha256=ygcCo5l60lHufyQISFddZfWwLlQ8kJAKxUT_XnR_6dY,4818
|
|
|
226
227
|
flyte/types/_string_literals.py,sha256=NlG1xV8RSA-sZ-n-IFQCAsdB6jXJOAKkHWtnopxVVDk,4231
|
|
227
228
|
flyte/types/_type_engine.py,sha256=Tas_OXYddOi0nDuORjqan2SkJ96wKD8937I2l1bo8vk,97916
|
|
228
229
|
flyte/types/_utils.py,sha256=pbts9E1_2LTdLygAY0UYTLYJ8AsN3BZyviSXvrtcutc,2626
|
|
229
|
-
flyte-2.0.
|
|
230
|
-
flyte-2.0.
|
|
231
|
-
flyte-2.0.
|
|
232
|
-
flyte-2.0.
|
|
233
|
-
flyte-2.0.
|
|
234
|
-
flyte-2.0.
|
|
235
|
-
flyte-2.0.
|
|
230
|
+
flyte-2.0.0b5.data/scripts/runtime.py,sha256=2jTy3ccvrJ__Xrfdo2t0Fxhsojc5o2zIxDHt98RE_eU,6475
|
|
231
|
+
flyte-2.0.0b5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
232
|
+
flyte-2.0.0b5.dist-info/METADATA,sha256=qrlVB1zQRgqKWHit2lwbo3gO0sAEGSXdeoUMJKHIc9E,10004
|
|
233
|
+
flyte-2.0.0b5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
234
|
+
flyte-2.0.0b5.dist-info/entry_points.txt,sha256=MIq2z5dBurdCJfpXfMKzgBv7sJOakKRYxr8G0cMiTrg,75
|
|
235
|
+
flyte-2.0.0b5.dist-info/top_level.txt,sha256=7dkyFbikvA12LEZEqawx8oDG1CMod6hTliPj7iWzgYo,6
|
|
236
|
+
flyte-2.0.0b5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|