flyte 0.0.1b0__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 +62 -0
- flyte/_api_commons.py +3 -0
- flyte/_bin/__init__.py +0 -0
- flyte/_bin/runtime.py +126 -0
- flyte/_build.py +25 -0
- flyte/_cache/__init__.py +12 -0
- flyte/_cache/cache.py +146 -0
- flyte/_cache/defaults.py +9 -0
- flyte/_cache/policy_function_body.py +42 -0
- flyte/_cli/__init__.py +0 -0
- flyte/_cli/_common.py +287 -0
- flyte/_cli/_create.py +42 -0
- flyte/_cli/_delete.py +23 -0
- flyte/_cli/_deploy.py +140 -0
- flyte/_cli/_get.py +235 -0
- flyte/_cli/_run.py +152 -0
- flyte/_cli/main.py +72 -0
- flyte/_code_bundle/__init__.py +8 -0
- flyte/_code_bundle/_ignore.py +113 -0
- flyte/_code_bundle/_packaging.py +187 -0
- flyte/_code_bundle/_utils.py +339 -0
- flyte/_code_bundle/bundle.py +178 -0
- flyte/_context.py +146 -0
- flyte/_datastructures.py +342 -0
- flyte/_deploy.py +202 -0
- flyte/_doc.py +29 -0
- flyte/_docstring.py +32 -0
- flyte/_environment.py +43 -0
- flyte/_group.py +31 -0
- flyte/_hash.py +23 -0
- flyte/_image.py +760 -0
- flyte/_initialize.py +634 -0
- flyte/_interface.py +84 -0
- flyte/_internal/__init__.py +3 -0
- flyte/_internal/controllers/__init__.py +115 -0
- flyte/_internal/controllers/_local_controller.py +118 -0
- flyte/_internal/controllers/_trace.py +40 -0
- flyte/_internal/controllers/pbhash.py +39 -0
- flyte/_internal/controllers/remote/__init__.py +40 -0
- flyte/_internal/controllers/remote/_action.py +141 -0
- flyte/_internal/controllers/remote/_client.py +43 -0
- flyte/_internal/controllers/remote/_controller.py +361 -0
- flyte/_internal/controllers/remote/_core.py +402 -0
- flyte/_internal/controllers/remote/_informer.py +361 -0
- flyte/_internal/controllers/remote/_service_protocol.py +50 -0
- flyte/_internal/imagebuild/__init__.py +11 -0
- flyte/_internal/imagebuild/docker_builder.py +416 -0
- flyte/_internal/imagebuild/image_builder.py +241 -0
- flyte/_internal/imagebuild/remote_builder.py +0 -0
- flyte/_internal/resolvers/__init__.py +0 -0
- flyte/_internal/resolvers/_task_module.py +54 -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 +199 -0
- flyte/_internal/runtime/entrypoints.py +135 -0
- flyte/_internal/runtime/io.py +136 -0
- flyte/_internal/runtime/resources_serde.py +138 -0
- flyte/_internal/runtime/task_serde.py +210 -0
- flyte/_internal/runtime/taskrunner.py +190 -0
- flyte/_internal/runtime/types_serde.py +54 -0
- flyte/_logging.py +124 -0
- flyte/_protos/__init__.py +0 -0
- flyte/_protos/common/authorization_pb2.py +66 -0
- flyte/_protos/common/authorization_pb2.pyi +108 -0
- flyte/_protos/common/authorization_pb2_grpc.py +4 -0
- flyte/_protos/common/identifier_pb2.py +71 -0
- flyte/_protos/common/identifier_pb2.pyi +82 -0
- flyte/_protos/common/identifier_pb2_grpc.py +4 -0
- flyte/_protos/common/identity_pb2.py +48 -0
- flyte/_protos/common/identity_pb2.pyi +72 -0
- flyte/_protos/common/identity_pb2_grpc.py +4 -0
- flyte/_protos/common/list_pb2.py +36 -0
- flyte/_protos/common/list_pb2.pyi +69 -0
- flyte/_protos/common/list_pb2_grpc.py +4 -0
- flyte/_protos/common/policy_pb2.py +37 -0
- flyte/_protos/common/policy_pb2.pyi +27 -0
- flyte/_protos/common/policy_pb2_grpc.py +4 -0
- flyte/_protos/common/role_pb2.py +37 -0
- flyte/_protos/common/role_pb2.pyi +53 -0
- flyte/_protos/common/role_pb2_grpc.py +4 -0
- flyte/_protos/common/runtime_version_pb2.py +28 -0
- flyte/_protos/common/runtime_version_pb2.pyi +24 -0
- flyte/_protos/common/runtime_version_pb2_grpc.py +4 -0
- flyte/_protos/logs/dataplane/payload_pb2.py +96 -0
- flyte/_protos/logs/dataplane/payload_pb2.pyi +168 -0
- flyte/_protos/logs/dataplane/payload_pb2_grpc.py +4 -0
- flyte/_protos/secret/definition_pb2.py +49 -0
- flyte/_protos/secret/definition_pb2.pyi +93 -0
- flyte/_protos/secret/definition_pb2_grpc.py +4 -0
- flyte/_protos/secret/payload_pb2.py +62 -0
- flyte/_protos/secret/payload_pb2.pyi +94 -0
- flyte/_protos/secret/payload_pb2_grpc.py +4 -0
- flyte/_protos/secret/secret_pb2.py +38 -0
- flyte/_protos/secret/secret_pb2.pyi +6 -0
- flyte/_protos/secret/secret_pb2_grpc.py +198 -0
- flyte/_protos/secret/secret_pb2_grpc_grpc.py +198 -0
- flyte/_protos/validate/validate/validate_pb2.py +76 -0
- flyte/_protos/workflow/node_execution_service_pb2.py +26 -0
- flyte/_protos/workflow/node_execution_service_pb2.pyi +4 -0
- flyte/_protos/workflow/node_execution_service_pb2_grpc.py +32 -0
- flyte/_protos/workflow/queue_service_pb2.py +106 -0
- flyte/_protos/workflow/queue_service_pb2.pyi +141 -0
- flyte/_protos/workflow/queue_service_pb2_grpc.py +172 -0
- flyte/_protos/workflow/run_definition_pb2.py +128 -0
- flyte/_protos/workflow/run_definition_pb2.pyi +310 -0
- flyte/_protos/workflow/run_definition_pb2_grpc.py +4 -0
- flyte/_protos/workflow/run_logs_service_pb2.py +41 -0
- flyte/_protos/workflow/run_logs_service_pb2.pyi +28 -0
- flyte/_protos/workflow/run_logs_service_pb2_grpc.py +69 -0
- flyte/_protos/workflow/run_service_pb2.py +133 -0
- flyte/_protos/workflow/run_service_pb2.pyi +175 -0
- flyte/_protos/workflow/run_service_pb2_grpc.py +412 -0
- flyte/_protos/workflow/state_service_pb2.py +58 -0
- flyte/_protos/workflow/state_service_pb2.pyi +71 -0
- flyte/_protos/workflow/state_service_pb2_grpc.py +138 -0
- flyte/_protos/workflow/task_definition_pb2.py +72 -0
- flyte/_protos/workflow/task_definition_pb2.pyi +65 -0
- flyte/_protos/workflow/task_definition_pb2_grpc.py +4 -0
- flyte/_protos/workflow/task_service_pb2.py +44 -0
- flyte/_protos/workflow/task_service_pb2.pyi +31 -0
- flyte/_protos/workflow/task_service_pb2_grpc.py +104 -0
- flyte/_resources.py +226 -0
- flyte/_retry.py +32 -0
- flyte/_reusable_environment.py +25 -0
- flyte/_run.py +411 -0
- flyte/_secret.py +61 -0
- flyte/_task.py +367 -0
- flyte/_task_environment.py +200 -0
- flyte/_timeout.py +47 -0
- flyte/_tools.py +27 -0
- flyte/_trace.py +128 -0
- flyte/_utils/__init__.py +20 -0
- flyte/_utils/asyn.py +119 -0
- flyte/_utils/coro_management.py +25 -0
- flyte/_utils/file_handling.py +72 -0
- flyte/_utils/helpers.py +108 -0
- flyte/_utils/lazy_module.py +54 -0
- flyte/_utils/uv_script_parser.py +49 -0
- flyte/_version.py +21 -0
- flyte/connectors/__init__.py +0 -0
- flyte/errors.py +143 -0
- flyte/extras/__init__.py +5 -0
- flyte/extras/_container.py +273 -0
- flyte/io/__init__.py +11 -0
- flyte/io/_dataframe.py +0 -0
- flyte/io/_dir.py +448 -0
- flyte/io/_file.py +468 -0
- flyte/io/pickle/__init__.py +0 -0
- flyte/io/pickle/transformer.py +117 -0
- flyte/io/structured_dataset/__init__.py +129 -0
- flyte/io/structured_dataset/basic_dfs.py +219 -0
- flyte/io/structured_dataset/structured_dataset.py +1061 -0
- flyte/py.typed +0 -0
- flyte/remote/__init__.py +25 -0
- flyte/remote/_client/__init__.py +0 -0
- flyte/remote/_client/_protocols.py +131 -0
- flyte/remote/_client/auth/__init__.py +12 -0
- flyte/remote/_client/auth/_authenticators/__init__.py +0 -0
- flyte/remote/_client/auth/_authenticators/base.py +397 -0
- flyte/remote/_client/auth/_authenticators/client_credentials.py +73 -0
- flyte/remote/_client/auth/_authenticators/device_code.py +118 -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 +184 -0
- flyte/remote/_client/auth/_client_config.py +83 -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 +143 -0
- flyte/remote/_client/auth/_token_client.py +260 -0
- flyte/remote/_client/auth/errors.py +16 -0
- flyte/remote/_client/controlplane.py +95 -0
- flyte/remote/_console.py +18 -0
- flyte/remote/_data.py +155 -0
- flyte/remote/_logs.py +116 -0
- flyte/remote/_project.py +86 -0
- flyte/remote/_run.py +873 -0
- flyte/remote/_secret.py +132 -0
- flyte/remote/_task.py +227 -0
- flyte/report/__init__.py +3 -0
- flyte/report/_report.py +178 -0
- flyte/report/_template.html +124 -0
- flyte/storage/__init__.py +24 -0
- flyte/storage/_remote_fs.py +34 -0
- flyte/storage/_storage.py +251 -0
- flyte/storage/_utils.py +5 -0
- flyte/types/__init__.py +13 -0
- flyte/types/_interface.py +25 -0
- flyte/types/_renderer.py +162 -0
- flyte/types/_string_literals.py +120 -0
- flyte/types/_type_engine.py +2210 -0
- flyte/types/_utils.py +80 -0
- flyte-0.0.1b0.dist-info/METADATA +179 -0
- flyte-0.0.1b0.dist-info/RECORD +390 -0
- flyte-0.0.1b0.dist-info/WHEEL +5 -0
- flyte-0.0.1b0.dist-info/entry_points.txt +3 -0
- flyte-0.0.1b0.dist-info/top_level.txt +1 -0
- union/__init__.py +54 -0
- union/_api_commons.py +3 -0
- union/_bin/__init__.py +0 -0
- union/_bin/runtime.py +113 -0
- union/_build.py +25 -0
- union/_cache/__init__.py +12 -0
- union/_cache/cache.py +141 -0
- union/_cache/defaults.py +9 -0
- union/_cache/policy_function_body.py +42 -0
- union/_cli/__init__.py +0 -0
- union/_cli/_common.py +263 -0
- union/_cli/_create.py +40 -0
- union/_cli/_delete.py +23 -0
- union/_cli/_deploy.py +120 -0
- union/_cli/_get.py +162 -0
- union/_cli/_params.py +579 -0
- union/_cli/_run.py +150 -0
- union/_cli/main.py +72 -0
- union/_code_bundle/__init__.py +8 -0
- union/_code_bundle/_ignore.py +113 -0
- union/_code_bundle/_packaging.py +187 -0
- union/_code_bundle/_utils.py +342 -0
- union/_code_bundle/bundle.py +176 -0
- union/_context.py +146 -0
- union/_datastructures.py +295 -0
- union/_deploy.py +185 -0
- union/_doc.py +29 -0
- union/_docstring.py +26 -0
- union/_environment.py +43 -0
- union/_group.py +31 -0
- union/_hash.py +23 -0
- union/_image.py +760 -0
- union/_initialize.py +585 -0
- union/_interface.py +84 -0
- union/_internal/__init__.py +3 -0
- union/_internal/controllers/__init__.py +77 -0
- union/_internal/controllers/_local_controller.py +77 -0
- union/_internal/controllers/pbhash.py +39 -0
- union/_internal/controllers/remote/__init__.py +40 -0
- union/_internal/controllers/remote/_action.py +131 -0
- union/_internal/controllers/remote/_client.py +43 -0
- union/_internal/controllers/remote/_controller.py +169 -0
- union/_internal/controllers/remote/_core.py +341 -0
- union/_internal/controllers/remote/_informer.py +260 -0
- union/_internal/controllers/remote/_service_protocol.py +44 -0
- union/_internal/imagebuild/__init__.py +11 -0
- union/_internal/imagebuild/docker_builder.py +416 -0
- union/_internal/imagebuild/image_builder.py +243 -0
- union/_internal/imagebuild/remote_builder.py +0 -0
- union/_internal/resolvers/__init__.py +0 -0
- union/_internal/resolvers/_task_module.py +31 -0
- union/_internal/resolvers/common.py +24 -0
- union/_internal/resolvers/default.py +27 -0
- union/_internal/runtime/__init__.py +0 -0
- union/_internal/runtime/convert.py +163 -0
- union/_internal/runtime/entrypoints.py +121 -0
- union/_internal/runtime/io.py +136 -0
- union/_internal/runtime/resources_serde.py +134 -0
- union/_internal/runtime/task_serde.py +202 -0
- union/_internal/runtime/taskrunner.py +179 -0
- union/_internal/runtime/types_serde.py +53 -0
- union/_logging.py +124 -0
- union/_protos/__init__.py +0 -0
- union/_protos/common/authorization_pb2.py +66 -0
- union/_protos/common/authorization_pb2.pyi +106 -0
- union/_protos/common/authorization_pb2_grpc.py +4 -0
- union/_protos/common/identifier_pb2.py +71 -0
- union/_protos/common/identifier_pb2.pyi +82 -0
- union/_protos/common/identifier_pb2_grpc.py +4 -0
- union/_protos/common/identity_pb2.py +48 -0
- union/_protos/common/identity_pb2.pyi +72 -0
- union/_protos/common/identity_pb2_grpc.py +4 -0
- union/_protos/common/list_pb2.py +36 -0
- union/_protos/common/list_pb2.pyi +69 -0
- union/_protos/common/list_pb2_grpc.py +4 -0
- union/_protos/common/policy_pb2.py +37 -0
- union/_protos/common/policy_pb2.pyi +27 -0
- union/_protos/common/policy_pb2_grpc.py +4 -0
- union/_protos/common/role_pb2.py +37 -0
- union/_protos/common/role_pb2.pyi +51 -0
- union/_protos/common/role_pb2_grpc.py +4 -0
- union/_protos/common/runtime_version_pb2.py +28 -0
- union/_protos/common/runtime_version_pb2.pyi +24 -0
- union/_protos/common/runtime_version_pb2_grpc.py +4 -0
- union/_protos/logs/dataplane/payload_pb2.py +96 -0
- union/_protos/logs/dataplane/payload_pb2.pyi +168 -0
- union/_protos/logs/dataplane/payload_pb2_grpc.py +4 -0
- union/_protos/secret/definition_pb2.py +49 -0
- union/_protos/secret/definition_pb2.pyi +93 -0
- union/_protos/secret/definition_pb2_grpc.py +4 -0
- union/_protos/secret/payload_pb2.py +62 -0
- union/_protos/secret/payload_pb2.pyi +94 -0
- union/_protos/secret/payload_pb2_grpc.py +4 -0
- union/_protos/secret/secret_pb2.py +38 -0
- union/_protos/secret/secret_pb2.pyi +6 -0
- union/_protos/secret/secret_pb2_grpc.py +198 -0
- union/_protos/validate/validate/validate_pb2.py +76 -0
- union/_protos/workflow/node_execution_service_pb2.py +26 -0
- union/_protos/workflow/node_execution_service_pb2.pyi +4 -0
- union/_protos/workflow/node_execution_service_pb2_grpc.py +32 -0
- union/_protos/workflow/queue_service_pb2.py +75 -0
- union/_protos/workflow/queue_service_pb2.pyi +103 -0
- union/_protos/workflow/queue_service_pb2_grpc.py +172 -0
- union/_protos/workflow/run_definition_pb2.py +100 -0
- union/_protos/workflow/run_definition_pb2.pyi +256 -0
- union/_protos/workflow/run_definition_pb2_grpc.py +4 -0
- union/_protos/workflow/run_logs_service_pb2.py +41 -0
- union/_protos/workflow/run_logs_service_pb2.pyi +28 -0
- union/_protos/workflow/run_logs_service_pb2_grpc.py +69 -0
- union/_protos/workflow/run_service_pb2.py +133 -0
- union/_protos/workflow/run_service_pb2.pyi +173 -0
- union/_protos/workflow/run_service_pb2_grpc.py +412 -0
- union/_protos/workflow/state_service_pb2.py +58 -0
- union/_protos/workflow/state_service_pb2.pyi +69 -0
- union/_protos/workflow/state_service_pb2_grpc.py +138 -0
- union/_protos/workflow/task_definition_pb2.py +72 -0
- union/_protos/workflow/task_definition_pb2.pyi +65 -0
- union/_protos/workflow/task_definition_pb2_grpc.py +4 -0
- union/_protos/workflow/task_service_pb2.py +44 -0
- union/_protos/workflow/task_service_pb2.pyi +31 -0
- union/_protos/workflow/task_service_pb2_grpc.py +104 -0
- union/_resources.py +226 -0
- union/_retry.py +32 -0
- union/_reusable_environment.py +25 -0
- union/_run.py +374 -0
- union/_secret.py +61 -0
- union/_task.py +354 -0
- union/_task_environment.py +186 -0
- union/_timeout.py +47 -0
- union/_tools.py +27 -0
- union/_utils/__init__.py +11 -0
- union/_utils/asyn.py +119 -0
- union/_utils/file_handling.py +71 -0
- union/_utils/helpers.py +46 -0
- union/_utils/lazy_module.py +54 -0
- union/_utils/uv_script_parser.py +49 -0
- union/_version.py +21 -0
- union/connectors/__init__.py +0 -0
- union/errors.py +128 -0
- union/extras/__init__.py +5 -0
- union/extras/_container.py +263 -0
- union/io/__init__.py +11 -0
- union/io/_dataframe.py +0 -0
- union/io/_dir.py +425 -0
- union/io/_file.py +418 -0
- union/io/pickle/__init__.py +0 -0
- union/io/pickle/transformer.py +117 -0
- union/io/structured_dataset/__init__.py +122 -0
- union/io/structured_dataset/basic_dfs.py +219 -0
- union/io/structured_dataset/structured_dataset.py +1057 -0
- union/py.typed +0 -0
- union/remote/__init__.py +23 -0
- union/remote/_client/__init__.py +0 -0
- union/remote/_client/_protocols.py +129 -0
- union/remote/_client/auth/__init__.py +12 -0
- union/remote/_client/auth/_authenticators/__init__.py +0 -0
- union/remote/_client/auth/_authenticators/base.py +391 -0
- union/remote/_client/auth/_authenticators/client_credentials.py +73 -0
- union/remote/_client/auth/_authenticators/device_code.py +120 -0
- union/remote/_client/auth/_authenticators/external_command.py +77 -0
- union/remote/_client/auth/_authenticators/factory.py +200 -0
- union/remote/_client/auth/_authenticators/pkce.py +515 -0
- union/remote/_client/auth/_channel.py +184 -0
- union/remote/_client/auth/_client_config.py +83 -0
- union/remote/_client/auth/_default_html.py +32 -0
- union/remote/_client/auth/_grpc_utils/__init__.py +0 -0
- union/remote/_client/auth/_grpc_utils/auth_interceptor.py +204 -0
- union/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +144 -0
- union/remote/_client/auth/_keyring.py +154 -0
- union/remote/_client/auth/_token_client.py +258 -0
- union/remote/_client/auth/errors.py +16 -0
- union/remote/_client/controlplane.py +86 -0
- union/remote/_data.py +149 -0
- union/remote/_logs.py +74 -0
- union/remote/_project.py +86 -0
- union/remote/_run.py +820 -0
- union/remote/_secret.py +132 -0
- union/remote/_task.py +193 -0
- union/report/__init__.py +3 -0
- union/report/_report.py +178 -0
- union/report/_template.html +124 -0
- union/storage/__init__.py +24 -0
- union/storage/_remote_fs.py +34 -0
- union/storage/_storage.py +247 -0
- union/storage/_utils.py +5 -0
- union/types/__init__.py +11 -0
- union/types/_renderer.py +162 -0
- union/types/_string_literals.py +120 -0
- union/types/_type_engine.py +2131 -0
- union/types/_utils.py +80 -0
flyte/_tools.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def ipython_check() -> bool:
|
|
5
|
+
"""
|
|
6
|
+
Check if interface is launching from iPython (not colab)
|
|
7
|
+
:return is_ipython (bool): True or False
|
|
8
|
+
"""
|
|
9
|
+
is_ipython = False
|
|
10
|
+
try: # Check if running interactively using ipython.
|
|
11
|
+
from IPython import get_ipython
|
|
12
|
+
|
|
13
|
+
if get_ipython() is not None:
|
|
14
|
+
is_ipython = True
|
|
15
|
+
except (ImportError, NameError):
|
|
16
|
+
pass
|
|
17
|
+
return is_ipython
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def is_in_cluster() -> bool:
|
|
21
|
+
"""
|
|
22
|
+
Check if the task is running in a cluster
|
|
23
|
+
:return is_in_cluster (bool): True or False
|
|
24
|
+
"""
|
|
25
|
+
if os.getenv("_UN_CLS"):
|
|
26
|
+
return True
|
|
27
|
+
return False
|
flyte/_trace.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import inspect
|
|
3
|
+
import time
|
|
4
|
+
from datetime import timedelta
|
|
5
|
+
from typing import Any, AsyncGenerator, AsyncIterator, Awaitable, Callable, TypeGuard, TypeVar, Union, cast
|
|
6
|
+
|
|
7
|
+
from flyte._datastructures import NativeInterface
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def trace(func: Callable[..., T]) -> Callable[..., T]:
|
|
13
|
+
"""
|
|
14
|
+
A decorator that traces function execution with timing information.
|
|
15
|
+
Works with regular functions, async functions, and async generators/iterators.
|
|
16
|
+
"""
|
|
17
|
+
func_name = func.__name__
|
|
18
|
+
|
|
19
|
+
@functools.wraps(func)
|
|
20
|
+
def wrapper_sync(*args: Any, **kwargs: Any) -> Any:
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
@functools.wraps(func)
|
|
24
|
+
async def wrapper_async(*args: Any, **kwargs: Any) -> Any:
|
|
25
|
+
from flyte._context import internal_ctx
|
|
26
|
+
|
|
27
|
+
ctx = internal_ctx()
|
|
28
|
+
if ctx.is_task_context():
|
|
29
|
+
# If we are in a task context, that implies we are executing a Run.
|
|
30
|
+
# In this scenario, we should submit the task to the controller.
|
|
31
|
+
# We will also check if we are not initialized, It is not expected to be not initialized
|
|
32
|
+
from ._internal.controllers import get_controller
|
|
33
|
+
|
|
34
|
+
controller = await get_controller()
|
|
35
|
+
iface = NativeInterface.from_callable(func)
|
|
36
|
+
info, ok = await controller.get_action_outputs(iface, func_name, *args, **kwargs)
|
|
37
|
+
if ok:
|
|
38
|
+
if info.output:
|
|
39
|
+
return info.output
|
|
40
|
+
elif info.error:
|
|
41
|
+
raise info.error
|
|
42
|
+
start_time = time.time()
|
|
43
|
+
try:
|
|
44
|
+
# Cast to Awaitable to satisfy mypy
|
|
45
|
+
coroutine_result = cast(Awaitable[Any], func(*args, **kwargs))
|
|
46
|
+
results = await coroutine_result
|
|
47
|
+
duration = time.time() - start_time
|
|
48
|
+
info.add_outputs(results, timedelta(seconds=duration))
|
|
49
|
+
await controller.record_trace(info)
|
|
50
|
+
return results
|
|
51
|
+
except Exception as e:
|
|
52
|
+
# If there is an error, we need to record it
|
|
53
|
+
duration = time.time() - start_time
|
|
54
|
+
info.add_error(e, timedelta(seconds=duration))
|
|
55
|
+
await controller.record_trace(info)
|
|
56
|
+
raise e
|
|
57
|
+
else:
|
|
58
|
+
# If we are not in a task context, we can just call the function normally
|
|
59
|
+
# Cast to Awaitable to satisfy mypy
|
|
60
|
+
coroutine_result = cast(Awaitable[Any], func(*args, **kwargs))
|
|
61
|
+
return await coroutine_result
|
|
62
|
+
|
|
63
|
+
def is_async_iterable(obj: Any) -> TypeGuard[Union[AsyncGenerator, AsyncIterator]]:
|
|
64
|
+
return hasattr(obj, "__aiter__")
|
|
65
|
+
|
|
66
|
+
@functools.wraps(func)
|
|
67
|
+
async def wrapper_async_iterator(*args: Any, **kwargs: Any) -> AsyncIterator[Any]:
|
|
68
|
+
from flyte._context import internal_ctx
|
|
69
|
+
|
|
70
|
+
ctx = internal_ctx()
|
|
71
|
+
if ctx.is_task_context():
|
|
72
|
+
# If we are in a task context, that implies we are executing a Run.
|
|
73
|
+
# In this scenario, we should submit the task to the controller.
|
|
74
|
+
# We will also check if we are not initialized, It is not expected to be not initialized
|
|
75
|
+
from ._internal.controllers import get_controller
|
|
76
|
+
|
|
77
|
+
controller = await get_controller()
|
|
78
|
+
iface = NativeInterface.from_callable(func)
|
|
79
|
+
info, ok = await controller.get_action_outputs(iface, func_name, *args, **kwargs)
|
|
80
|
+
if ok:
|
|
81
|
+
if info.output:
|
|
82
|
+
for item in info.output:
|
|
83
|
+
yield item
|
|
84
|
+
elif info.error:
|
|
85
|
+
raise info.error
|
|
86
|
+
start_time = time.time()
|
|
87
|
+
try:
|
|
88
|
+
items = []
|
|
89
|
+
result = func(*args, **kwargs)
|
|
90
|
+
# TODO ideally we should use streaming into the type-engine so that it stream uploads large blocks
|
|
91
|
+
if inspect.isasyncgen(result):
|
|
92
|
+
# If it's directly an async generator
|
|
93
|
+
async_iter = result
|
|
94
|
+
async for item in async_iter:
|
|
95
|
+
items.append(item)
|
|
96
|
+
yield item
|
|
97
|
+
elif is_async_iterable(result):
|
|
98
|
+
# If it's an async iterable (has __aiter__)
|
|
99
|
+
async for item in result:
|
|
100
|
+
items.append(item)
|
|
101
|
+
yield item
|
|
102
|
+
duration = time.time() - start_time
|
|
103
|
+
info.add_outputs(items, timedelta(seconds=duration))
|
|
104
|
+
await controller.record_trace(info)
|
|
105
|
+
return
|
|
106
|
+
except Exception as e:
|
|
107
|
+
end_time = time.time()
|
|
108
|
+
duration = end_time - start_time
|
|
109
|
+
info.add_error(e, timedelta(seconds=duration))
|
|
110
|
+
await controller.record_trace(info)
|
|
111
|
+
raise e
|
|
112
|
+
else:
|
|
113
|
+
result = func(*args, **kwargs)
|
|
114
|
+
if is_async_iterable(result):
|
|
115
|
+
async for item in result:
|
|
116
|
+
yield item
|
|
117
|
+
|
|
118
|
+
# Choose the appropriate wrapper based on the function type
|
|
119
|
+
if inspect.iscoroutinefunction(func):
|
|
120
|
+
# This handles async functions that return normal values
|
|
121
|
+
print(f"Coroutine function {func.__name__}")
|
|
122
|
+
return cast(Callable[..., T], wrapper_async)
|
|
123
|
+
elif inspect.isasyncgenfunction(func):
|
|
124
|
+
print(f"Async generator function {func.__name__}")
|
|
125
|
+
return cast(Callable[..., T], wrapper_async_iterator)
|
|
126
|
+
else:
|
|
127
|
+
# For regular sync functions
|
|
128
|
+
return cast(Callable[..., T], wrapper_sync)
|
flyte/_utils/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Internal utility functions.
|
|
3
|
+
|
|
4
|
+
Except for logging, modules in this package should not depend on any other part of the repo.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .coro_management import run_coros
|
|
8
|
+
from .file_handling import filehash_update, update_hasher_for_source
|
|
9
|
+
from .helpers import get_cwd_editable_install
|
|
10
|
+
from .lazy_module import lazy_module
|
|
11
|
+
from .uv_script_parser import parse_uv_script_file
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"filehash_update",
|
|
15
|
+
"get_cwd_editable_install",
|
|
16
|
+
"lazy_module",
|
|
17
|
+
"parse_uv_script_file",
|
|
18
|
+
"run_coros",
|
|
19
|
+
"update_hasher_for_source",
|
|
20
|
+
]
|
flyte/_utils/asyn.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Manages an async event loop on another thread. Developers should only require to call
|
|
2
|
+
sync to use the managed loop:
|
|
3
|
+
|
|
4
|
+
from flytekit.tools.asyn import run_sync
|
|
5
|
+
|
|
6
|
+
async def async_add(a: int, b: int) -> int:
|
|
7
|
+
return a + b
|
|
8
|
+
|
|
9
|
+
result = run_sync(async_add, a=10, b=12)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import atexit
|
|
14
|
+
import functools
|
|
15
|
+
import os
|
|
16
|
+
import threading
|
|
17
|
+
from contextlib import contextmanager
|
|
18
|
+
from typing import Any, Awaitable, Callable, TypeVar
|
|
19
|
+
|
|
20
|
+
from typing_extensions import ParamSpec
|
|
21
|
+
|
|
22
|
+
from flyte._logging import logger
|
|
23
|
+
|
|
24
|
+
T = TypeVar("T")
|
|
25
|
+
|
|
26
|
+
P = ParamSpec("P")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@contextmanager
|
|
30
|
+
def _selector_policy():
|
|
31
|
+
original_policy = asyncio.get_event_loop_policy()
|
|
32
|
+
try:
|
|
33
|
+
if os.name == "nt" and hasattr(asyncio, "WindowsSelectorEventLoopPolicy"):
|
|
34
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
35
|
+
|
|
36
|
+
yield
|
|
37
|
+
finally:
|
|
38
|
+
asyncio.set_event_loop_policy(original_policy)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class _TaskRunner:
|
|
42
|
+
"""A task runner that runs an asyncio event loop on a background thread."""
|
|
43
|
+
|
|
44
|
+
def __init__(self) -> None:
|
|
45
|
+
self.__loop: asyncio.AbstractEventLoop | None = None
|
|
46
|
+
self.__runner_thread: threading.Thread | None = None
|
|
47
|
+
self.__lock = threading.Lock()
|
|
48
|
+
atexit.register(self._close)
|
|
49
|
+
|
|
50
|
+
def _close(self) -> None:
|
|
51
|
+
if self.__loop:
|
|
52
|
+
self.__loop.stop()
|
|
53
|
+
|
|
54
|
+
def _execute(self) -> None:
|
|
55
|
+
loop = self.__loop
|
|
56
|
+
assert loop is not None
|
|
57
|
+
try:
|
|
58
|
+
loop.run_forever()
|
|
59
|
+
finally:
|
|
60
|
+
loop.close()
|
|
61
|
+
|
|
62
|
+
def get_exc_handler(self):
|
|
63
|
+
def exc_handler(loop, context):
|
|
64
|
+
logger.error(
|
|
65
|
+
f"Taskrunner for {self.__runner_thread.name if self.__runner_thread else 'no thread'} caught"
|
|
66
|
+
f" exception in {loop}: {context}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return exc_handler
|
|
70
|
+
|
|
71
|
+
def run(self, coro: Any) -> Any:
|
|
72
|
+
"""Synchronously run a coroutine on a background thread."""
|
|
73
|
+
name = f"{threading.current_thread().name} : loop-runner"
|
|
74
|
+
with self.__lock:
|
|
75
|
+
if self.__loop is None:
|
|
76
|
+
with _selector_policy():
|
|
77
|
+
self.__loop = asyncio.new_event_loop()
|
|
78
|
+
|
|
79
|
+
exc_handler = self.get_exc_handler()
|
|
80
|
+
self.__loop.set_exception_handler(exc_handler)
|
|
81
|
+
self.__runner_thread = threading.Thread(target=self._execute, daemon=True, name=name)
|
|
82
|
+
self.__runner_thread.start()
|
|
83
|
+
fut = asyncio.run_coroutine_threadsafe(coro, self.__loop)
|
|
84
|
+
|
|
85
|
+
res = fut.result(None)
|
|
86
|
+
|
|
87
|
+
return res
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class _AsyncLoopManager:
|
|
91
|
+
def __init__(self):
|
|
92
|
+
self._runner_map: dict[str, _TaskRunner] = {}
|
|
93
|
+
|
|
94
|
+
def run_sync(self, coro_func: Callable[..., Awaitable[T]], *args, **kwargs) -> T:
|
|
95
|
+
"""
|
|
96
|
+
This should be called from synchronous functions to run an async function.
|
|
97
|
+
"""
|
|
98
|
+
name = threading.current_thread().name + f"PID:{os.getpid()}"
|
|
99
|
+
coro = coro_func(*args, **kwargs)
|
|
100
|
+
if name not in self._runner_map:
|
|
101
|
+
if len(self._runner_map) > 500:
|
|
102
|
+
logger.warning(
|
|
103
|
+
"More than 500 event loop runners created!!! This could be a case of runaway recursion..."
|
|
104
|
+
)
|
|
105
|
+
self._runner_map[name] = _TaskRunner()
|
|
106
|
+
return self._runner_map[name].run(coro)
|
|
107
|
+
|
|
108
|
+
def synced(self, coro_func: Callable[P, Awaitable[T]]) -> Callable[P, T]:
|
|
109
|
+
"""Make loop run coroutine until it returns. Runs in other thread"""
|
|
110
|
+
|
|
111
|
+
@functools.wraps(coro_func)
|
|
112
|
+
def wrapped(*args: Any, **kwargs: Any) -> T:
|
|
113
|
+
return self.run_sync(coro_func, *args, **kwargs)
|
|
114
|
+
|
|
115
|
+
return wrapped
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
loop_manager = _AsyncLoopManager()
|
|
119
|
+
run_sync = loop_manager.run_sync
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def run_coros(*coros: typing.Coroutine, return_when: str = asyncio.FIRST_COMPLETED):
|
|
6
|
+
"""
|
|
7
|
+
Run a list of coroutines concurrently and wait for the first one to finish or exit.
|
|
8
|
+
When the first one finishes, cancel all other tasks.
|
|
9
|
+
|
|
10
|
+
:param coros:
|
|
11
|
+
:param return_when:
|
|
12
|
+
:return:
|
|
13
|
+
"""
|
|
14
|
+
tasks: typing.List[asyncio.Task[typing.Never]] = [asyncio.create_task(c) for c in coros]
|
|
15
|
+
done, pending = await asyncio.wait(tasks, return_when=return_when)
|
|
16
|
+
|
|
17
|
+
for t in pending: # type: asyncio.Task
|
|
18
|
+
t.cancel() # Cancel all tasks that didn't finish first
|
|
19
|
+
|
|
20
|
+
for t in done:
|
|
21
|
+
err = t.exception()
|
|
22
|
+
if err:
|
|
23
|
+
raise err
|
|
24
|
+
else:
|
|
25
|
+
print(f"Task result: {t.result()}")
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import os
|
|
5
|
+
import pathlib
|
|
6
|
+
import stat
|
|
7
|
+
import typing
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List, Optional, Union
|
|
10
|
+
|
|
11
|
+
from flyte._logging import logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def filehash_update(path: pathlib.Path, hasher: hashlib._Hash) -> None:
|
|
15
|
+
blocksize = 65536
|
|
16
|
+
with open(path, "rb") as f:
|
|
17
|
+
bytes = f.read(blocksize)
|
|
18
|
+
while bytes:
|
|
19
|
+
hasher.update(bytes)
|
|
20
|
+
bytes = f.read(blocksize)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _pathhash_update(path: Union[os.PathLike, str], hasher: hashlib._Hash) -> None:
|
|
24
|
+
path_list = str(path).split(os.sep)
|
|
25
|
+
hasher.update("".join(path_list).encode("utf-8"))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def update_hasher_for_source(
|
|
29
|
+
source: Union[os.PathLike, List[os.PathLike]], hasher: hashlib._Hash, filter: Optional[typing.Callable] = None
|
|
30
|
+
):
|
|
31
|
+
"""
|
|
32
|
+
Walks the entirety of the source dir to compute a deterministic md5 hex digest of the dir contents.
|
|
33
|
+
:param os.PathLike source:
|
|
34
|
+
:param callable filter:
|
|
35
|
+
:return Text:
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def compute_digest_for_file(path: os.PathLike, rel_path: os.PathLike) -> None:
|
|
39
|
+
# Only consider files that exist (e.g. disregard symlinks that point to non-existent files)
|
|
40
|
+
if not os.path.exists(path):
|
|
41
|
+
logger.info(f"Skipping non-existent file {path}")
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
# Skip socket files
|
|
45
|
+
if stat.S_ISSOCK(os.stat(path).st_mode):
|
|
46
|
+
logger.info(f"Skip socket file {path}")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if filter:
|
|
50
|
+
if filter(rel_path):
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
filehash_update(Path(path), hasher)
|
|
54
|
+
_pathhash_update(rel_path, hasher)
|
|
55
|
+
|
|
56
|
+
def compute_digest_for_dir(source: os.PathLike):
|
|
57
|
+
for root, _, files in os.walk(str(source), topdown=True):
|
|
58
|
+
files.sort()
|
|
59
|
+
|
|
60
|
+
for fname in files:
|
|
61
|
+
abspath = os.path.join(root, fname)
|
|
62
|
+
relpath = os.path.relpath(abspath, source)
|
|
63
|
+
compute_digest_for_file(Path(abspath), Path(relpath))
|
|
64
|
+
|
|
65
|
+
if isinstance(source, list):
|
|
66
|
+
for src in source:
|
|
67
|
+
if os.path.isdir(src):
|
|
68
|
+
compute_digest_for_dir(src)
|
|
69
|
+
else:
|
|
70
|
+
compute_digest_for_file(src, os.path.basename(src))
|
|
71
|
+
else:
|
|
72
|
+
compute_digest_for_dir(source)
|
flyte/_utils/helpers.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import string
|
|
3
|
+
import typing
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def load_proto_from_file(pb2_type, path):
|
|
8
|
+
with open(path, "rb") as reader:
|
|
9
|
+
out = pb2_type()
|
|
10
|
+
out.ParseFromString(reader.read())
|
|
11
|
+
return out
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def write_proto_to_file(proto, path):
|
|
15
|
+
Path(os.path.dirname(path)).mkdir(parents=True, exist_ok=True)
|
|
16
|
+
with open(path, "wb") as writer:
|
|
17
|
+
writer.write(proto.SerializeToString())
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def str2bool(value: typing.Optional[str]) -> bool:
|
|
21
|
+
"""
|
|
22
|
+
Convert a string to a boolean. This is useful for parsing environment variables.
|
|
23
|
+
:param value: The string to convert to a boolean
|
|
24
|
+
:return: the boolean value
|
|
25
|
+
"""
|
|
26
|
+
if value is None:
|
|
27
|
+
return False
|
|
28
|
+
return value.lower() in ("true", "t", "1")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
BASE36_ALPHABET = string.digits + string.ascii_lowercase # 0-9 + a-z (36 characters)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def base36_encode(byte_data: bytes) -> str:
|
|
35
|
+
"""
|
|
36
|
+
This function expects to encode bytes coming from an hd5 hash function into a base36 encoded string.
|
|
37
|
+
md5 shas are limited to 128 bits, so the maximum byte value should easily fit into a 30 character long string.
|
|
38
|
+
If the input is too large howeer
|
|
39
|
+
"""
|
|
40
|
+
# Convert bytes to a big integer
|
|
41
|
+
num = int.from_bytes(byte_data, byteorder="big")
|
|
42
|
+
|
|
43
|
+
# Convert integer to base36 string
|
|
44
|
+
if num == 0:
|
|
45
|
+
return BASE36_ALPHABET[0]
|
|
46
|
+
|
|
47
|
+
base36 = []
|
|
48
|
+
while num:
|
|
49
|
+
num, rem = divmod(num, 36)
|
|
50
|
+
base36.append(BASE36_ALPHABET[rem])
|
|
51
|
+
return "".join(reversed(base36))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# does not work at all in the setuptools case. see old flytekit editable installs
|
|
55
|
+
def get_cwd_editable_install() -> typing.Optional[Path]:
|
|
56
|
+
"""
|
|
57
|
+
This helper function is incomplete since it hasn't been tested with all the package managers out there,
|
|
58
|
+
but the intention is that it returns the source folder for an editable install if the current working directory
|
|
59
|
+
is inside the editable install project - if the code is inside an src/ folder, and the cwd is a level above,
|
|
60
|
+
it should still work, returning the src/ folder. If cwd is the src/ folder, this should return the same.
|
|
61
|
+
|
|
62
|
+
The idea is that the return path will be used to determine the relative path for imported modules when building
|
|
63
|
+
the code bundle.
|
|
64
|
+
|
|
65
|
+
:return:
|
|
66
|
+
"""
|
|
67
|
+
import site
|
|
68
|
+
|
|
69
|
+
from flyte._logging import logger
|
|
70
|
+
|
|
71
|
+
egg_links = [Path(p) for p in Path(site.getsitepackages()[0]).glob("*.egg-link")]
|
|
72
|
+
pth_files = [Path(p) for p in Path(site.getsitepackages()[0]).glob("*.pth")]
|
|
73
|
+
|
|
74
|
+
if not egg_links and not pth_files:
|
|
75
|
+
logger.debug("No editable installs found.")
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
editable_installs = []
|
|
79
|
+
egg_links.extend(pth_files)
|
|
80
|
+
for file in egg_links:
|
|
81
|
+
with open(file, "r") as f:
|
|
82
|
+
line = f.readline()
|
|
83
|
+
if line:
|
|
84
|
+
# Check if the first line is a directory
|
|
85
|
+
p = Path(line)
|
|
86
|
+
if p.is_dir():
|
|
87
|
+
editable_installs.append(p)
|
|
88
|
+
logger.debug(f"Editable installs: {editable_installs}")
|
|
89
|
+
|
|
90
|
+
# check to see if the current working directory is in any of the editable installs
|
|
91
|
+
# including if the current folder is the root folder, one level up from the src and contains
|
|
92
|
+
# the pyproject.toml file.
|
|
93
|
+
# Two scenarios to consider
|
|
94
|
+
# - if cwd is nested inside the editable install folder.
|
|
95
|
+
# - if the cwd is exactly one level above the editable install folder.
|
|
96
|
+
cwd = Path.cwd()
|
|
97
|
+
for install in editable_installs:
|
|
98
|
+
# child.is_relative_to(parent) is True if child is inside parent
|
|
99
|
+
if cwd.is_relative_to(install):
|
|
100
|
+
return install
|
|
101
|
+
else:
|
|
102
|
+
# check if the cwd is one level above the install folder
|
|
103
|
+
if install.parent == cwd:
|
|
104
|
+
# check if the install folder contains a pyproject.toml file
|
|
105
|
+
if (cwd / "pyproject.toml").exists() or (cwd / "setup.py").exists():
|
|
106
|
+
return install # note we want the install folder, not the parent
|
|
107
|
+
|
|
108
|
+
return None
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import sys
|
|
3
|
+
import types
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class _LazyModule(types.ModuleType):
|
|
7
|
+
"""
|
|
8
|
+
`lazy_module` returns an instance of this class if the module is not found in the python environment.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, module_name: str):
|
|
12
|
+
super().__init__(module_name)
|
|
13
|
+
self._module_name = module_name
|
|
14
|
+
|
|
15
|
+
def __getattribute__(self, attr):
|
|
16
|
+
raise ImportError(f"Module {object.__getattribute__(self, '_module_name')} is not yet installed.")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def is_imported(module_name):
|
|
20
|
+
"""
|
|
21
|
+
This function is used to check if a module has been imported by the regular import.
|
|
22
|
+
Return false if module is lazy imported and not used yet.
|
|
23
|
+
"""
|
|
24
|
+
return (
|
|
25
|
+
module_name in sys.modules
|
|
26
|
+
and object.__getattribute__(lazy_module(module_name), "__class__").__name__ != "_LazyModule"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def lazy_module(fullname):
|
|
31
|
+
"""
|
|
32
|
+
This function is used to lazily import modules. It is used in the following way:
|
|
33
|
+
.. code-block:: python
|
|
34
|
+
from flytekit.lazy_import import lazy_module
|
|
35
|
+
sklearn = lazy_module("sklearn")
|
|
36
|
+
sklearn.svm.SVC()
|
|
37
|
+
:param Text fullname: The full name of the module to import
|
|
38
|
+
"""
|
|
39
|
+
if fullname in sys.modules:
|
|
40
|
+
return sys.modules[fullname]
|
|
41
|
+
# https://docs.python.org/3/library/importlib.html#implementing-lazy-imports
|
|
42
|
+
spec = importlib.util.find_spec(fullname)
|
|
43
|
+
if spec is None or spec.loader is None:
|
|
44
|
+
# Return a lazy module if the module is not found in the python environment,
|
|
45
|
+
# so that we can raise a proper error when the user tries to access an attribute in the module.
|
|
46
|
+
# The reason to do this is because importlib.util.LazyLoader still requires
|
|
47
|
+
# the module to be installed even if you don't use it.
|
|
48
|
+
return _LazyModule(fullname)
|
|
49
|
+
loader = importlib.util.LazyLoader(spec.loader)
|
|
50
|
+
spec.loader = loader
|
|
51
|
+
module = importlib.util.module_from_spec(spec)
|
|
52
|
+
sys.modules[fullname] = module
|
|
53
|
+
loader.exec_module(module)
|
|
54
|
+
return module
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import re
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
import toml
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class ToolUVConfig:
|
|
11
|
+
exclude_newer: Optional[str] = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class UVScriptMetadata:
|
|
16
|
+
requires_python: Optional[str] = None
|
|
17
|
+
dependencies: List[str] = field(default_factory=list)
|
|
18
|
+
tool: Optional[Dict[str, ToolUVConfig]] = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _extract_uv_metadata_block(text: str) -> str | None:
|
|
22
|
+
pattern = re.compile(r"# /// script\s*(.*?)# ///", re.DOTALL)
|
|
23
|
+
match = pattern.search(text)
|
|
24
|
+
if not match:
|
|
25
|
+
return None
|
|
26
|
+
lines = [line.lstrip("# ").rstrip() for line in match.group(1).splitlines()]
|
|
27
|
+
return "\n".join(lines)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def parse_uv_script_file(path: pathlib.Path) -> UVScriptMetadata:
|
|
31
|
+
if not path.exists() or not path.is_file():
|
|
32
|
+
raise FileNotFoundError(f"File not found: {path}")
|
|
33
|
+
|
|
34
|
+
text = path.read_text(encoding="utf-8")
|
|
35
|
+
raw_header = _extract_uv_metadata_block(text)
|
|
36
|
+
if raw_header is None:
|
|
37
|
+
raise ValueError("No uv metadata block found")
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
data = toml.loads(raw_header)
|
|
41
|
+
except toml.TomlDecodeError as e:
|
|
42
|
+
raise ValueError(f"Invalid TOML in metadata block: {e}")
|
|
43
|
+
|
|
44
|
+
tool_data = data.get("tool", {}).get("uv", {})
|
|
45
|
+
return UVScriptMetadata(
|
|
46
|
+
requires_python=data.get("requires-python"),
|
|
47
|
+
dependencies=data.get("dependencies", []),
|
|
48
|
+
tool={"uv": ToolUVConfig(exclude_newer=tool_data.get("exclude-newer"))} if tool_data else None,
|
|
49
|
+
)
|
flyte/_version.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '0.0.1b0'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 0, 1, 'b0')
|
|
File without changes
|