ob-metaflow 2.11.13.1__py2.py3-none-any.whl → 2.19.7.1rc0__py2.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.
- metaflow/R.py +10 -7
- metaflow/__init__.py +40 -25
- metaflow/_vendor/imghdr/__init__.py +186 -0
- metaflow/_vendor/importlib_metadata/__init__.py +1063 -0
- metaflow/_vendor/importlib_metadata/_adapters.py +68 -0
- metaflow/_vendor/importlib_metadata/_collections.py +30 -0
- metaflow/_vendor/importlib_metadata/_compat.py +71 -0
- metaflow/_vendor/importlib_metadata/_functools.py +104 -0
- metaflow/_vendor/importlib_metadata/_itertools.py +73 -0
- metaflow/_vendor/importlib_metadata/_meta.py +48 -0
- metaflow/_vendor/importlib_metadata/_text.py +99 -0
- metaflow/_vendor/importlib_metadata/py.typed +0 -0
- metaflow/_vendor/typeguard/__init__.py +48 -0
- metaflow/_vendor/typeguard/_checkers.py +1070 -0
- metaflow/_vendor/typeguard/_config.py +108 -0
- metaflow/_vendor/typeguard/_decorators.py +233 -0
- metaflow/_vendor/typeguard/_exceptions.py +42 -0
- metaflow/_vendor/typeguard/_functions.py +308 -0
- metaflow/_vendor/typeguard/_importhook.py +213 -0
- metaflow/_vendor/typeguard/_memo.py +48 -0
- metaflow/_vendor/typeguard/_pytest_plugin.py +127 -0
- metaflow/_vendor/typeguard/_suppression.py +86 -0
- metaflow/_vendor/typeguard/_transformer.py +1229 -0
- metaflow/_vendor/typeguard/_union_transformer.py +55 -0
- metaflow/_vendor/typeguard/_utils.py +173 -0
- metaflow/_vendor/typeguard/py.typed +0 -0
- metaflow/_vendor/typing_extensions.py +3641 -0
- metaflow/_vendor/v3_7/importlib_metadata/__init__.py +1063 -0
- metaflow/_vendor/v3_7/importlib_metadata/_adapters.py +68 -0
- metaflow/_vendor/v3_7/importlib_metadata/_collections.py +30 -0
- metaflow/_vendor/v3_7/importlib_metadata/_compat.py +71 -0
- metaflow/_vendor/v3_7/importlib_metadata/_functools.py +104 -0
- metaflow/_vendor/v3_7/importlib_metadata/_itertools.py +73 -0
- metaflow/_vendor/v3_7/importlib_metadata/_meta.py +48 -0
- metaflow/_vendor/v3_7/importlib_metadata/_text.py +99 -0
- metaflow/_vendor/v3_7/importlib_metadata/py.typed +0 -0
- metaflow/_vendor/v3_7/typeguard/__init__.py +48 -0
- metaflow/_vendor/v3_7/typeguard/_checkers.py +906 -0
- metaflow/_vendor/v3_7/typeguard/_config.py +108 -0
- metaflow/_vendor/v3_7/typeguard/_decorators.py +237 -0
- metaflow/_vendor/v3_7/typeguard/_exceptions.py +42 -0
- metaflow/_vendor/v3_7/typeguard/_functions.py +310 -0
- metaflow/_vendor/v3_7/typeguard/_importhook.py +213 -0
- metaflow/_vendor/v3_7/typeguard/_memo.py +48 -0
- metaflow/_vendor/v3_7/typeguard/_pytest_plugin.py +100 -0
- metaflow/_vendor/v3_7/typeguard/_suppression.py +88 -0
- metaflow/_vendor/v3_7/typeguard/_transformer.py +1207 -0
- metaflow/_vendor/v3_7/typeguard/_union_transformer.py +54 -0
- metaflow/_vendor/v3_7/typeguard/_utils.py +169 -0
- metaflow/_vendor/v3_7/typeguard/py.typed +0 -0
- metaflow/_vendor/v3_7/typing_extensions.py +3072 -0
- metaflow/_vendor/yaml/__init__.py +427 -0
- metaflow/_vendor/yaml/composer.py +139 -0
- metaflow/_vendor/yaml/constructor.py +748 -0
- metaflow/_vendor/yaml/cyaml.py +101 -0
- metaflow/_vendor/yaml/dumper.py +62 -0
- metaflow/_vendor/yaml/emitter.py +1137 -0
- metaflow/_vendor/yaml/error.py +75 -0
- metaflow/_vendor/yaml/events.py +86 -0
- metaflow/_vendor/yaml/loader.py +63 -0
- metaflow/_vendor/yaml/nodes.py +49 -0
- metaflow/_vendor/yaml/parser.py +589 -0
- metaflow/_vendor/yaml/reader.py +185 -0
- metaflow/_vendor/yaml/representer.py +389 -0
- metaflow/_vendor/yaml/resolver.py +227 -0
- metaflow/_vendor/yaml/scanner.py +1435 -0
- metaflow/_vendor/yaml/serializer.py +111 -0
- metaflow/_vendor/yaml/tokens.py +104 -0
- metaflow/cards.py +5 -0
- metaflow/cli.py +331 -785
- metaflow/cli_args.py +17 -0
- metaflow/cli_components/__init__.py +0 -0
- metaflow/cli_components/dump_cmd.py +96 -0
- metaflow/cli_components/init_cmd.py +52 -0
- metaflow/cli_components/run_cmds.py +546 -0
- metaflow/cli_components/step_cmd.py +334 -0
- metaflow/cli_components/utils.py +140 -0
- metaflow/client/__init__.py +1 -0
- metaflow/client/core.py +467 -73
- metaflow/client/filecache.py +75 -35
- metaflow/clone_util.py +7 -1
- metaflow/cmd/code/__init__.py +231 -0
- metaflow/cmd/develop/stub_generator.py +756 -288
- metaflow/cmd/develop/stubs.py +12 -28
- metaflow/cmd/main_cli.py +6 -4
- metaflow/cmd/make_wrapper.py +78 -0
- metaflow/datastore/__init__.py +1 -0
- metaflow/datastore/content_addressed_store.py +41 -10
- metaflow/datastore/datastore_set.py +11 -2
- metaflow/datastore/flow_datastore.py +156 -10
- metaflow/datastore/spin_datastore.py +91 -0
- metaflow/datastore/task_datastore.py +154 -39
- metaflow/debug.py +5 -0
- metaflow/decorators.py +404 -78
- metaflow/exception.py +8 -2
- metaflow/extension_support/__init__.py +527 -376
- metaflow/extension_support/_empty_file.py +2 -2
- metaflow/extension_support/plugins.py +49 -31
- metaflow/flowspec.py +482 -33
- metaflow/graph.py +210 -42
- metaflow/includefile.py +84 -40
- metaflow/lint.py +141 -22
- metaflow/meta_files.py +13 -0
- metaflow/{metadata → metadata_provider}/heartbeat.py +24 -8
- metaflow/{metadata → metadata_provider}/metadata.py +86 -1
- metaflow/metaflow_config.py +175 -28
- metaflow/metaflow_config_funcs.py +51 -3
- metaflow/metaflow_current.py +4 -10
- metaflow/metaflow_environment.py +139 -53
- metaflow/metaflow_git.py +115 -0
- metaflow/metaflow_profile.py +18 -0
- metaflow/metaflow_version.py +150 -66
- metaflow/mflog/__init__.py +4 -3
- metaflow/mflog/save_logs.py +2 -2
- metaflow/multicore_utils.py +31 -14
- metaflow/package/__init__.py +673 -0
- metaflow/packaging_sys/__init__.py +880 -0
- metaflow/packaging_sys/backend.py +128 -0
- metaflow/packaging_sys/distribution_support.py +153 -0
- metaflow/packaging_sys/tar_backend.py +99 -0
- metaflow/packaging_sys/utils.py +54 -0
- metaflow/packaging_sys/v1.py +527 -0
- metaflow/parameters.py +149 -28
- metaflow/plugins/__init__.py +74 -5
- metaflow/plugins/airflow/airflow.py +40 -25
- metaflow/plugins/airflow/airflow_cli.py +22 -5
- metaflow/plugins/airflow/airflow_decorator.py +1 -1
- metaflow/plugins/airflow/airflow_utils.py +5 -3
- metaflow/plugins/airflow/sensors/base_sensor.py +4 -4
- metaflow/plugins/airflow/sensors/external_task_sensor.py +2 -2
- metaflow/plugins/airflow/sensors/s3_sensor.py +2 -2
- metaflow/plugins/argo/argo_client.py +78 -33
- metaflow/plugins/argo/argo_events.py +6 -6
- metaflow/plugins/argo/argo_workflows.py +2410 -527
- metaflow/plugins/argo/argo_workflows_cli.py +571 -121
- metaflow/plugins/argo/argo_workflows_decorator.py +43 -12
- metaflow/plugins/argo/argo_workflows_deployer.py +106 -0
- metaflow/plugins/argo/argo_workflows_deployer_objects.py +453 -0
- metaflow/plugins/argo/capture_error.py +73 -0
- metaflow/plugins/argo/conditional_input_paths.py +35 -0
- metaflow/plugins/argo/exit_hooks.py +209 -0
- metaflow/plugins/argo/jobset_input_paths.py +15 -0
- metaflow/plugins/argo/param_val.py +19 -0
- metaflow/plugins/aws/aws_client.py +10 -3
- metaflow/plugins/aws/aws_utils.py +55 -2
- metaflow/plugins/aws/batch/batch.py +72 -5
- metaflow/plugins/aws/batch/batch_cli.py +33 -10
- metaflow/plugins/aws/batch/batch_client.py +4 -3
- metaflow/plugins/aws/batch/batch_decorator.py +102 -35
- metaflow/plugins/aws/secrets_manager/aws_secrets_manager_secrets_provider.py +13 -10
- metaflow/plugins/aws/step_functions/dynamo_db_client.py +0 -3
- metaflow/plugins/aws/step_functions/production_token.py +1 -1
- metaflow/plugins/aws/step_functions/step_functions.py +65 -8
- metaflow/plugins/aws/step_functions/step_functions_cli.py +101 -7
- metaflow/plugins/aws/step_functions/step_functions_decorator.py +1 -2
- metaflow/plugins/aws/step_functions/step_functions_deployer.py +97 -0
- metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +264 -0
- metaflow/plugins/azure/azure_exceptions.py +1 -1
- metaflow/plugins/azure/azure_secret_manager_secrets_provider.py +240 -0
- metaflow/plugins/azure/azure_tail.py +1 -1
- metaflow/plugins/azure/includefile_support.py +2 -0
- metaflow/plugins/cards/card_cli.py +66 -30
- metaflow/plugins/cards/card_creator.py +25 -1
- metaflow/plugins/cards/card_datastore.py +21 -49
- metaflow/plugins/cards/card_decorator.py +132 -8
- metaflow/plugins/cards/card_modules/basic.py +112 -17
- metaflow/plugins/cards/card_modules/bundle.css +1 -1
- metaflow/plugins/cards/card_modules/card.py +16 -1
- metaflow/plugins/cards/card_modules/chevron/renderer.py +1 -1
- metaflow/plugins/cards/card_modules/components.py +665 -28
- metaflow/plugins/cards/card_modules/convert_to_native_type.py +36 -7
- metaflow/plugins/cards/card_modules/json_viewer.py +232 -0
- metaflow/plugins/cards/card_modules/main.css +1 -0
- metaflow/plugins/cards/card_modules/main.js +68 -49
- metaflow/plugins/cards/card_modules/renderer_tools.py +1 -0
- metaflow/plugins/cards/card_modules/test_cards.py +26 -12
- metaflow/plugins/cards/card_server.py +39 -14
- metaflow/plugins/cards/component_serializer.py +2 -9
- metaflow/plugins/cards/metadata.py +22 -0
- metaflow/plugins/catch_decorator.py +9 -0
- metaflow/plugins/datastores/azure_storage.py +10 -1
- metaflow/plugins/datastores/gs_storage.py +6 -2
- metaflow/plugins/datastores/local_storage.py +12 -6
- metaflow/plugins/datastores/spin_storage.py +12 -0
- metaflow/plugins/datatools/local.py +2 -0
- metaflow/plugins/datatools/s3/s3.py +126 -75
- metaflow/plugins/datatools/s3/s3op.py +254 -121
- metaflow/plugins/env_escape/__init__.py +3 -3
- metaflow/plugins/env_escape/client_modules.py +102 -72
- metaflow/plugins/env_escape/server.py +7 -0
- metaflow/plugins/env_escape/stub.py +24 -5
- metaflow/plugins/events_decorator.py +343 -185
- metaflow/plugins/exit_hook/__init__.py +0 -0
- metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
- metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
- metaflow/plugins/gcp/__init__.py +1 -1
- metaflow/plugins/gcp/gcp_secret_manager_secrets_provider.py +11 -6
- metaflow/plugins/gcp/gs_tail.py +10 -6
- metaflow/plugins/gcp/includefile_support.py +3 -0
- metaflow/plugins/kubernetes/kube_utils.py +108 -0
- metaflow/plugins/kubernetes/kubernetes.py +411 -130
- metaflow/plugins/kubernetes/kubernetes_cli.py +168 -36
- metaflow/plugins/kubernetes/kubernetes_client.py +104 -2
- metaflow/plugins/kubernetes/kubernetes_decorator.py +246 -88
- metaflow/plugins/kubernetes/kubernetes_job.py +253 -581
- metaflow/plugins/kubernetes/kubernetes_jobsets.py +1071 -0
- metaflow/plugins/kubernetes/spot_metadata_cli.py +69 -0
- metaflow/plugins/kubernetes/spot_monitor_sidecar.py +109 -0
- metaflow/plugins/logs_cli.py +359 -0
- metaflow/plugins/{metadata → metadata_providers}/local.py +144 -84
- metaflow/plugins/{metadata → metadata_providers}/service.py +103 -26
- metaflow/plugins/metadata_providers/spin.py +16 -0
- metaflow/plugins/package_cli.py +36 -24
- metaflow/plugins/parallel_decorator.py +128 -11
- metaflow/plugins/parsers.py +16 -0
- metaflow/plugins/project_decorator.py +51 -5
- metaflow/plugins/pypi/bootstrap.py +357 -105
- metaflow/plugins/pypi/conda_decorator.py +82 -81
- metaflow/plugins/pypi/conda_environment.py +187 -52
- metaflow/plugins/pypi/micromamba.py +157 -47
- metaflow/plugins/pypi/parsers.py +268 -0
- metaflow/plugins/pypi/pip.py +88 -13
- metaflow/plugins/pypi/pypi_decorator.py +37 -1
- metaflow/plugins/pypi/utils.py +48 -2
- metaflow/plugins/resources_decorator.py +2 -2
- metaflow/plugins/secrets/__init__.py +3 -0
- metaflow/plugins/secrets/secrets_decorator.py +26 -181
- metaflow/plugins/secrets/secrets_func.py +49 -0
- metaflow/plugins/secrets/secrets_spec.py +101 -0
- metaflow/plugins/secrets/utils.py +74 -0
- metaflow/plugins/tag_cli.py +4 -7
- metaflow/plugins/test_unbounded_foreach_decorator.py +41 -6
- metaflow/plugins/timeout_decorator.py +3 -3
- metaflow/plugins/uv/__init__.py +0 -0
- metaflow/plugins/uv/bootstrap.py +128 -0
- metaflow/plugins/uv/uv_environment.py +72 -0
- metaflow/procpoll.py +1 -1
- metaflow/pylint_wrapper.py +5 -1
- metaflow/runner/__init__.py +0 -0
- metaflow/runner/click_api.py +717 -0
- metaflow/runner/deployer.py +470 -0
- metaflow/runner/deployer_impl.py +201 -0
- metaflow/runner/metaflow_runner.py +714 -0
- metaflow/runner/nbdeploy.py +132 -0
- metaflow/runner/nbrun.py +225 -0
- metaflow/runner/subprocess_manager.py +650 -0
- metaflow/runner/utils.py +335 -0
- metaflow/runtime.py +1078 -260
- metaflow/sidecar/sidecar_worker.py +1 -1
- metaflow/system/__init__.py +5 -0
- metaflow/system/system_logger.py +85 -0
- metaflow/system/system_monitor.py +108 -0
- metaflow/system/system_utils.py +19 -0
- metaflow/task.py +521 -225
- metaflow/tracing/__init__.py +7 -7
- metaflow/tracing/span_exporter.py +31 -38
- metaflow/tracing/tracing_modules.py +38 -43
- metaflow/tuple_util.py +27 -0
- metaflow/user_configs/__init__.py +0 -0
- metaflow/user_configs/config_options.py +563 -0
- metaflow/user_configs/config_parameters.py +598 -0
- metaflow/user_decorators/__init__.py +0 -0
- metaflow/user_decorators/common.py +144 -0
- metaflow/user_decorators/mutable_flow.py +512 -0
- metaflow/user_decorators/mutable_step.py +424 -0
- metaflow/user_decorators/user_flow_decorator.py +264 -0
- metaflow/user_decorators/user_step_decorator.py +749 -0
- metaflow/util.py +243 -27
- metaflow/vendor.py +23 -7
- metaflow/version.py +1 -1
- ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/Makefile +355 -0
- ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/Tiltfile +726 -0
- ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/pick_services.sh +105 -0
- ob_metaflow-2.19.7.1rc0.dist-info/METADATA +87 -0
- ob_metaflow-2.19.7.1rc0.dist-info/RECORD +445 -0
- {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/WHEEL +1 -1
- {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/entry_points.txt +1 -0
- metaflow/_vendor/v3_5/__init__.py +0 -1
- metaflow/_vendor/v3_5/importlib_metadata/__init__.py +0 -644
- metaflow/_vendor/v3_5/importlib_metadata/_compat.py +0 -152
- metaflow/package.py +0 -188
- ob_metaflow-2.11.13.1.dist-info/METADATA +0 -85
- ob_metaflow-2.11.13.1.dist-info/RECORD +0 -308
- /metaflow/_vendor/{v3_5/zipp.py → zipp.py} +0 -0
- /metaflow/{metadata → metadata_provider}/__init__.py +0 -0
- /metaflow/{metadata → metadata_provider}/util.py +0 -0
- /metaflow/plugins/{metadata → metadata_providers}/__init__.py +0 -0
- {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info/licenses}/LICENSE +0 -0
- {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/top_level.txt +0 -0
metaflow/plugins/pypi/pip.py
CHANGED
|
@@ -8,11 +8,11 @@ from concurrent.futures import ThreadPoolExecutor
|
|
|
8
8
|
from itertools import chain, product
|
|
9
9
|
from urllib.parse import unquote
|
|
10
10
|
|
|
11
|
+
from metaflow.debug import debug
|
|
11
12
|
from metaflow.exception import MetaflowException
|
|
12
|
-
from metaflow.util import which
|
|
13
13
|
|
|
14
14
|
from .micromamba import Micromamba
|
|
15
|
-
from .utils import pip_tags, wheel_tags
|
|
15
|
+
from .utils import pip_tags, wheel_tags, markers_from_platform, conda_platform
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class PipException(MetaflowException):
|
|
@@ -25,6 +25,23 @@ class PipException(MetaflowException):
|
|
|
25
25
|
super(PipException, self).__init__(msg)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
class PipPackageNotFound(Exception):
|
|
29
|
+
"Wrapper for pip package resolve errors."
|
|
30
|
+
|
|
31
|
+
def __init__(self, error):
|
|
32
|
+
self.error = error
|
|
33
|
+
try:
|
|
34
|
+
# Parse the package spec from error message:
|
|
35
|
+
# ERROR: ERROR: Could not find a version that satisfies the requirement pkg==0.0.1 (from versions: none)
|
|
36
|
+
# ERROR: No matching distribution found for pkg==0.0.1
|
|
37
|
+
self.package_spec = re.search(
|
|
38
|
+
"ERROR: No matching distribution found for (.*)", self.error
|
|
39
|
+
)[1]
|
|
40
|
+
self.package_name = re.match("\w*", self.package_spec)[0]
|
|
41
|
+
except Exception:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
28
45
|
METADATA_FILE = "{prefix}/.pip/metadata"
|
|
29
46
|
INSTALLATION_MARKER = "{prefix}/.pip/id"
|
|
30
47
|
|
|
@@ -34,10 +51,23 @@ INSTALLATION_MARKER = "{prefix}/.pip/id"
|
|
|
34
51
|
|
|
35
52
|
|
|
36
53
|
class Pip(object):
|
|
37
|
-
def __init__(self, micromamba=None):
|
|
54
|
+
def __init__(self, micromamba=None, logger=None):
|
|
38
55
|
# pip is assumed to be installed inside a conda environment managed by
|
|
39
56
|
# micromamba. pip commands are executed using `micromamba run --prefix`
|
|
40
|
-
self.micromamba = micromamba or Micromamba()
|
|
57
|
+
self.micromamba = micromamba or Micromamba(logger)
|
|
58
|
+
if logger:
|
|
59
|
+
self.logger = logger
|
|
60
|
+
else:
|
|
61
|
+
self.logger = lambda *args, **kwargs: None # No-op logger if not provided
|
|
62
|
+
|
|
63
|
+
def _get_resolved_python_version(self, prefix):
|
|
64
|
+
try:
|
|
65
|
+
result = self.micromamba._call(["list", "--prefix", prefix, "--json"])
|
|
66
|
+
for package in result:
|
|
67
|
+
if package.get("name") == "python":
|
|
68
|
+
return package["version"]
|
|
69
|
+
except Exception:
|
|
70
|
+
return None
|
|
41
71
|
|
|
42
72
|
def solve(self, id_, packages, python, platform):
|
|
43
73
|
prefix = self.micromamba.path_to_environment(id_)
|
|
@@ -46,12 +76,19 @@ class Pip(object):
|
|
|
46
76
|
msg += "for id {id}".format(id=id_)
|
|
47
77
|
raise PipException(msg)
|
|
48
78
|
|
|
79
|
+
resolved_python = self._get_resolved_python_version(prefix)
|
|
80
|
+
if not resolved_python:
|
|
81
|
+
raise PipException(
|
|
82
|
+
"Could not determine Python version from conda environment"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
debug.conda_exec("Solving packages for PyPI environment %s" % id_)
|
|
49
86
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
50
87
|
report = "{tmp_dir}/report.json".format(tmp_dir=tmp_dir)
|
|
51
88
|
implementations, platforms, abis = zip(
|
|
52
89
|
*[
|
|
53
90
|
(tag.interpreter, tag.platform, tag.abi)
|
|
54
|
-
for tag in pip_tags(
|
|
91
|
+
for tag in pip_tags(resolved_python, platform)
|
|
55
92
|
]
|
|
56
93
|
)
|
|
57
94
|
custom_index_url, extra_index_urls = self.indices(prefix)
|
|
@@ -81,7 +118,25 @@ class Pip(object):
|
|
|
81
118
|
cmd.append(f"{package}{version}")
|
|
82
119
|
else:
|
|
83
120
|
cmd.append(f"{package}=={version}")
|
|
84
|
-
|
|
121
|
+
try:
|
|
122
|
+
env = {}
|
|
123
|
+
if conda_platform() != platform:
|
|
124
|
+
# cross-platform resolving requires patching the machine and system info for pip to pick up all the relevant packages.
|
|
125
|
+
marker_overrides = markers_from_platform(platform)
|
|
126
|
+
env = {
|
|
127
|
+
"PIP_CUSTOMIZE_OVERRIDES": json.dumps(marker_overrides),
|
|
128
|
+
"PYTHONPATH": os.path.join(
|
|
129
|
+
os.path.dirname(__file__), "pip_patcher"
|
|
130
|
+
),
|
|
131
|
+
}
|
|
132
|
+
self._call(prefix, cmd, env)
|
|
133
|
+
except PipPackageNotFound as ex:
|
|
134
|
+
# pretty print package errors
|
|
135
|
+
raise PipException(
|
|
136
|
+
"Unable to find a binary distribution compatible with %s for %s.\n\n"
|
|
137
|
+
"Note: ***@pypi*** does not currently support source distributions"
|
|
138
|
+
% (ex.package_spec, platform)
|
|
139
|
+
)
|
|
85
140
|
|
|
86
141
|
def _format(dl_info):
|
|
87
142
|
res = {k: v for k, v in dl_info.items() if k in ["url"]}
|
|
@@ -96,9 +151,9 @@ class Pip(object):
|
|
|
96
151
|
res["url"] = "{vcs}+{url}@{commit_id}{subdir_str}".format(
|
|
97
152
|
**vcs_info,
|
|
98
153
|
**res,
|
|
99
|
-
subdir_str=
|
|
100
|
-
|
|
101
|
-
|
|
154
|
+
subdir_str=(
|
|
155
|
+
"#subdirectory=%s" % subdirectory if subdirectory else ""
|
|
156
|
+
),
|
|
102
157
|
)
|
|
103
158
|
# used to deduplicate the storage location in case wheel does not
|
|
104
159
|
# build with enough unique identifiers.
|
|
@@ -112,15 +167,26 @@ class Pip(object):
|
|
|
112
167
|
|
|
113
168
|
def download(self, id_, packages, python, platform):
|
|
114
169
|
prefix = self.micromamba.path_to_environment(id_)
|
|
170
|
+
|
|
171
|
+
resolved_python = self._get_resolved_python_version(prefix)
|
|
172
|
+
if not resolved_python:
|
|
173
|
+
raise PipException(
|
|
174
|
+
"Could not determine Python version from conda environment"
|
|
175
|
+
)
|
|
176
|
+
|
|
115
177
|
metadata_file = METADATA_FILE.format(prefix=prefix)
|
|
116
178
|
# download packages only if they haven't ever been downloaded before
|
|
117
179
|
if os.path.isfile(metadata_file):
|
|
118
|
-
|
|
180
|
+
with open(metadata_file, "r") as file:
|
|
181
|
+
metadata = json.load(file)
|
|
182
|
+
if all(package["url"] in metadata for package in packages):
|
|
183
|
+
return
|
|
119
184
|
|
|
120
185
|
metadata = {}
|
|
121
186
|
custom_index_url, extra_index_urls = self.indices(prefix)
|
|
122
187
|
|
|
123
188
|
# build wheels if needed
|
|
189
|
+
debug.conda_exec("Building wheels for PyPI environment %s if necessary" % id_)
|
|
124
190
|
with ThreadPoolExecutor() as executor:
|
|
125
191
|
|
|
126
192
|
def _build(key, package):
|
|
@@ -158,7 +224,11 @@ class Pip(object):
|
|
|
158
224
|
if os.path.isfile(os.path.join(path, f)) and f.endswith(".whl")
|
|
159
225
|
]
|
|
160
226
|
if (
|
|
161
|
-
len(
|
|
227
|
+
len(
|
|
228
|
+
set(pip_tags(resolved_python, platform)).intersection(
|
|
229
|
+
wheel_tags(wheel)
|
|
230
|
+
)
|
|
231
|
+
)
|
|
162
232
|
== 0
|
|
163
233
|
):
|
|
164
234
|
raise PipException(
|
|
@@ -177,10 +247,11 @@ class Pip(object):
|
|
|
177
247
|
implementations, platforms, abis = zip(
|
|
178
248
|
*[
|
|
179
249
|
(tag.interpreter, tag.platform, tag.abi)
|
|
180
|
-
for tag in pip_tags(
|
|
250
|
+
for tag in pip_tags(resolved_python, platform)
|
|
181
251
|
]
|
|
182
252
|
)
|
|
183
253
|
|
|
254
|
+
debug.conda_exec("Downloading packages for PyPI environment %s" % id_)
|
|
184
255
|
cmd = [
|
|
185
256
|
"download",
|
|
186
257
|
"--no-deps",
|
|
@@ -220,6 +291,7 @@ class Pip(object):
|
|
|
220
291
|
# Pip can't install packages if the underlying virtual environment doesn't
|
|
221
292
|
# share the same platform
|
|
222
293
|
if self.micromamba.platform() == platform:
|
|
294
|
+
debug.conda_exec("Installing packages for local PyPI environment %s" % id_)
|
|
223
295
|
cmd = [
|
|
224
296
|
"install",
|
|
225
297
|
"--no-compile",
|
|
@@ -302,11 +374,14 @@ class Pip(object):
|
|
|
302
374
|
.strip()
|
|
303
375
|
)
|
|
304
376
|
except subprocess.CalledProcessError as e:
|
|
377
|
+
errors = e.stderr.decode()
|
|
378
|
+
if "No matching distribution" in errors:
|
|
379
|
+
raise PipPackageNotFound(errors)
|
|
305
380
|
raise PipException(
|
|
306
381
|
"command '{cmd}' returned error ({code}) {output}\n{stderr}".format(
|
|
307
382
|
cmd=" ".join(e.cmd),
|
|
308
383
|
code=e.returncode,
|
|
309
384
|
output=e.output.decode(),
|
|
310
|
-
stderr=
|
|
385
|
+
stderr=errors,
|
|
311
386
|
)
|
|
312
387
|
)
|
|
@@ -24,6 +24,13 @@ class PyPIStepDecorator(StepDecorator):
|
|
|
24
24
|
name = "pypi"
|
|
25
25
|
defaults = {"packages": {}, "python": None, "disabled": None} # wheels
|
|
26
26
|
|
|
27
|
+
def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
|
|
28
|
+
self._attributes_with_user_values = (
|
|
29
|
+
set(attributes.keys()) if attributes is not None else set()
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
super().__init__(attributes, statically_defined, inserted_by)
|
|
33
|
+
|
|
27
34
|
def step_init(self, flow, graph, step, decos, environment, flow_datastore, logger):
|
|
28
35
|
# The init_environment hook for Environment creates the relevant virtual
|
|
29
36
|
# environments. The step_init hook sets up the relevant state for that hook to
|
|
@@ -34,7 +41,11 @@ class PyPIStepDecorator(StepDecorator):
|
|
|
34
41
|
|
|
35
42
|
# Support flow-level decorator
|
|
36
43
|
if "pypi_base" in self.flow._flow_decorators:
|
|
37
|
-
|
|
44
|
+
pypi_base = self.flow._flow_decorators["pypi_base"][0]
|
|
45
|
+
super_attributes = pypi_base.attributes
|
|
46
|
+
self._attributes_with_user_values.update(
|
|
47
|
+
pypi_base._attributes_with_user_values
|
|
48
|
+
)
|
|
38
49
|
self.attributes["packages"] = {
|
|
39
50
|
**super_attributes["packages"],
|
|
40
51
|
**self.attributes["packages"],
|
|
@@ -70,6 +81,10 @@ class PyPIStepDecorator(StepDecorator):
|
|
|
70
81
|
# --environment=pypi to --environment=conda
|
|
71
82
|
_supported_virtual_envs.extend(["pypi"])
|
|
72
83
|
|
|
84
|
+
# TODO: Hardcoded for now to support the fast bakery environment.
|
|
85
|
+
# We should introduce a more robust mechanism for appending supported environments, for example from within extensions.
|
|
86
|
+
_supported_virtual_envs.extend(["fast-bakery"])
|
|
87
|
+
|
|
73
88
|
# The --environment= requirement ensures that valid virtual environments are
|
|
74
89
|
# created for every step to execute it, greatly simplifying the @pypi
|
|
75
90
|
# implementation.
|
|
@@ -83,6 +98,15 @@ class PyPIStepDecorator(StepDecorator):
|
|
|
83
98
|
),
|
|
84
99
|
)
|
|
85
100
|
)
|
|
101
|
+
# TODO: This code snippet can be done away with by altering the constructor of
|
|
102
|
+
# MetaflowEnvironment. A good first-task exercise.
|
|
103
|
+
# Avoid circular import
|
|
104
|
+
from metaflow.plugins.datastores.local_storage import LocalStorage
|
|
105
|
+
|
|
106
|
+
environment.set_local_root(LocalStorage.get_datastore_root_from_config(logger))
|
|
107
|
+
|
|
108
|
+
def is_attribute_user_defined(self, name):
|
|
109
|
+
return name in self._attributes_with_user_values
|
|
86
110
|
|
|
87
111
|
|
|
88
112
|
class PyPIFlowDecorator(FlowDecorator):
|
|
@@ -104,12 +128,20 @@ class PyPIFlowDecorator(FlowDecorator):
|
|
|
104
128
|
name = "pypi_base"
|
|
105
129
|
defaults = {"packages": {}, "python": None, "disabled": None}
|
|
106
130
|
|
|
131
|
+
def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
|
|
132
|
+
self._attributes_with_user_values = (
|
|
133
|
+
set(attributes.keys()) if attributes is not None else set()
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
super().__init__(attributes, statically_defined, inserted_by)
|
|
137
|
+
|
|
107
138
|
def flow_init(
|
|
108
139
|
self, flow, graph, environment, flow_datastore, metadata, logger, echo, options
|
|
109
140
|
):
|
|
110
141
|
from metaflow import decorators
|
|
111
142
|
|
|
112
143
|
decorators._attach_decorators(flow, ["pypi"])
|
|
144
|
+
decorators._init(flow)
|
|
113
145
|
|
|
114
146
|
# @pypi uses a conda environment to create a virtual environment.
|
|
115
147
|
# The conda environment can be created through micromamba.
|
|
@@ -119,6 +151,10 @@ class PyPIFlowDecorator(FlowDecorator):
|
|
|
119
151
|
# --environment=pypi to --environment=conda
|
|
120
152
|
_supported_virtual_envs.extend(["pypi"])
|
|
121
153
|
|
|
154
|
+
# TODO: Hardcoded for now to support the fast bakery environment.
|
|
155
|
+
# We should introduce a more robust mechanism for appending supported environments, for example from within extensions.
|
|
156
|
+
_supported_virtual_envs.extend(["fast-bakery"])
|
|
157
|
+
|
|
122
158
|
# The --environment= requirement ensures that valid virtual environments are
|
|
123
159
|
# created for every step to execute it, greatly simplifying the @conda
|
|
124
160
|
# implementation.
|
metaflow/plugins/pypi/utils.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import platform
|
|
3
2
|
import sys
|
|
4
3
|
|
|
@@ -17,10 +16,13 @@ else:
|
|
|
17
16
|
from metaflow._vendor.packaging import tags
|
|
18
17
|
from metaflow._vendor.packaging.utils import parse_wheel_filename
|
|
19
18
|
|
|
20
|
-
from urllib.parse import unquote
|
|
19
|
+
from urllib.parse import unquote
|
|
21
20
|
|
|
22
21
|
from metaflow.exception import MetaflowException
|
|
23
22
|
|
|
23
|
+
MICROMAMBA_URL = "https://micro.mamba.pm/api/micromamba/{platform}/{version}"
|
|
24
|
+
MICROMAMBA_MIRROR_URL = "https://micromamba.outerbounds.sh/{platform}/{version}.tar.bz2"
|
|
25
|
+
|
|
24
26
|
|
|
25
27
|
def conda_platform():
|
|
26
28
|
# Returns the conda platform for the Python interpreter
|
|
@@ -39,6 +41,28 @@ def conda_platform():
|
|
|
39
41
|
return "osx-64"
|
|
40
42
|
|
|
41
43
|
|
|
44
|
+
def markers_from_platform(platform):
|
|
45
|
+
plat, mach = platform.split("-")
|
|
46
|
+
|
|
47
|
+
platform_system = {"osx": "Darwin", "linux": "Linux"}.get(plat)
|
|
48
|
+
platform_machine = {
|
|
49
|
+
"32": "x86",
|
|
50
|
+
"64": "x86_64",
|
|
51
|
+
"arm64": "aarch64",
|
|
52
|
+
"aarch64": "aarch64",
|
|
53
|
+
}.get(mach)
|
|
54
|
+
|
|
55
|
+
markers = {
|
|
56
|
+
k: v
|
|
57
|
+
for k, v in {
|
|
58
|
+
"platform_system": platform_system,
|
|
59
|
+
"platform_machine": platform_machine,
|
|
60
|
+
}.items()
|
|
61
|
+
if v is not None
|
|
62
|
+
}
|
|
63
|
+
return markers
|
|
64
|
+
|
|
65
|
+
|
|
42
66
|
def wheel_tags(wheel):
|
|
43
67
|
_, _, _, tags = parse_wheel_filename(wheel)
|
|
44
68
|
return list(tags)
|
|
@@ -67,9 +91,31 @@ def pip_tags(python_version, mamba_platform):
|
|
|
67
91
|
"_2_25",
|
|
68
92
|
"_2_26",
|
|
69
93
|
"_2_27",
|
|
94
|
+
"_2_28",
|
|
95
|
+
"_2_29",
|
|
70
96
|
)
|
|
71
97
|
]
|
|
72
98
|
platforms.append("linux_x86_64")
|
|
99
|
+
elif mamba_platform == "linux-aarch64":
|
|
100
|
+
platforms = [
|
|
101
|
+
"manylinux%s_aarch64" % s
|
|
102
|
+
for s in (
|
|
103
|
+
"2014",
|
|
104
|
+
"_2_17",
|
|
105
|
+
"_2_18",
|
|
106
|
+
"_2_19",
|
|
107
|
+
"_2_20",
|
|
108
|
+
"_2_21",
|
|
109
|
+
"_2_23",
|
|
110
|
+
"_2_24",
|
|
111
|
+
"_2_25",
|
|
112
|
+
"_2_26",
|
|
113
|
+
"_2_27",
|
|
114
|
+
"_2_28",
|
|
115
|
+
"_2_29",
|
|
116
|
+
)
|
|
117
|
+
]
|
|
118
|
+
platforms.append("linux_aarch64")
|
|
73
119
|
elif mamba_platform == "osx-64":
|
|
74
120
|
platforms = tags.mac_platforms(arch="x86_64")
|
|
75
121
|
elif mamba_platform == "osx-arm64":
|
|
@@ -23,7 +23,7 @@ class ResourcesDecorator(StepDecorator):
|
|
|
23
23
|
----------
|
|
24
24
|
cpu : int, default 1
|
|
25
25
|
Number of CPUs required for this step.
|
|
26
|
-
gpu : int, default
|
|
26
|
+
gpu : int, optional, default None
|
|
27
27
|
Number of GPUs required for this step.
|
|
28
28
|
disk : int, optional, default None
|
|
29
29
|
Disk size (in MB) required for this step. Only applies on Kubernetes.
|
|
@@ -37,7 +37,7 @@ class ResourcesDecorator(StepDecorator):
|
|
|
37
37
|
name = "resources"
|
|
38
38
|
defaults = {
|
|
39
39
|
"cpu": "1",
|
|
40
|
-
"gpu":
|
|
40
|
+
"gpu": None,
|
|
41
41
|
"disk": None,
|
|
42
42
|
"memory": "4096",
|
|
43
43
|
"shared_memory": None,
|
|
@@ -1,182 +1,16 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import re
|
|
3
2
|
|
|
4
3
|
from metaflow.exception import MetaflowException
|
|
5
4
|
from metaflow.decorators import StepDecorator
|
|
6
5
|
from metaflow.metaflow_config import DEFAULT_SECRETS_ROLE
|
|
7
|
-
from metaflow.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
from metaflow.metaflow_config import DEFAULT_SECRETS_BACKEND_TYPE
|
|
16
|
-
|
|
17
|
-
if DEFAULT_SECRETS_BACKEND_TYPE is None:
|
|
18
|
-
raise MetaflowException(
|
|
19
|
-
"No default secrets backend type configured, but needed by @secrets. "
|
|
20
|
-
"Set METAFLOW_DEFAULT_SECRETS_BACKEND_TYPE."
|
|
21
|
-
)
|
|
22
|
-
return DEFAULT_SECRETS_BACKEND_TYPE
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class SecretSpec:
|
|
26
|
-
def __init__(self, secrets_backend_type, secret_id, options={}, role=None):
|
|
27
|
-
self._secrets_backend_type = secrets_backend_type
|
|
28
|
-
self._secret_id = secret_id
|
|
29
|
-
self._options = options
|
|
30
|
-
self._role = role
|
|
31
|
-
|
|
32
|
-
@property
|
|
33
|
-
def secrets_backend_type(self):
|
|
34
|
-
return self._secrets_backend_type
|
|
35
|
-
|
|
36
|
-
@property
|
|
37
|
-
def secret_id(self):
|
|
38
|
-
return self._secret_id
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def options(self):
|
|
42
|
-
return self._options
|
|
43
|
-
|
|
44
|
-
@property
|
|
45
|
-
def role(self):
|
|
46
|
-
return self._role
|
|
47
|
-
|
|
48
|
-
def to_json(self):
|
|
49
|
-
"""Mainly used for testing... not the same as the input dict in secret_spec_from_dict()!"""
|
|
50
|
-
return {
|
|
51
|
-
"secrets_backend_type": self.secrets_backend_type,
|
|
52
|
-
"secret_id": self.secret_id,
|
|
53
|
-
"options": self.options,
|
|
54
|
-
"role": self.role,
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
def __str__(self):
|
|
58
|
-
return "%s (%s)" % (self._secret_id, self._secrets_backend_type)
|
|
59
|
-
|
|
60
|
-
@staticmethod
|
|
61
|
-
def secret_spec_from_str(secret_spec_str, role):
|
|
62
|
-
# "." may be used in secret_id one day (provider specific). HOWEVER, it provides the best UX for
|
|
63
|
-
# non-conflicting cases (i.e. for secret ids that don't contain "."). This is true for all AWS
|
|
64
|
-
# Secrets Manager secrets.
|
|
65
|
-
#
|
|
66
|
-
# So we skew heavily optimize for best upfront UX for the present (1/2023).
|
|
67
|
-
#
|
|
68
|
-
# If/when a certain secret backend supports "." secret names, we can figure out a solution at that time.
|
|
69
|
-
# At a minimum, dictionary style secret spec may be used with no code changes (see secret_spec_from_dict()).
|
|
70
|
-
# Other options could be:
|
|
71
|
-
# - accept and document that "." secret_ids don't work in Metaflow (across all possible providers)
|
|
72
|
-
# - add a Metaflow config variable that specifies the separator (default ".")
|
|
73
|
-
# - smarter spec parsing, that errors on secrets that look ambiguous. "aws-secrets-manager.XYZ" could mean:
|
|
74
|
-
# + secret_id "XYZ" in aws-secrets-manager backend, OR
|
|
75
|
-
# + secret_id "aws-secrets-manager.XYZ" default backend (if it is defined).
|
|
76
|
-
# + in this case, user can simply set "azure-key-vault.aws-secrets-manager.XYZ" instead!
|
|
77
|
-
parts = secret_spec_str.split(".", maxsplit=1)
|
|
78
|
-
if len(parts) == 1:
|
|
79
|
-
secrets_backend_type = get_default_secrets_backend_type()
|
|
80
|
-
secret_id = parts[0]
|
|
81
|
-
else:
|
|
82
|
-
secrets_backend_type = parts[0]
|
|
83
|
-
secret_id = parts[1]
|
|
84
|
-
return SecretSpec(
|
|
85
|
-
secrets_backend_type, secret_id=secret_id, options={}, role=role
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
@staticmethod
|
|
89
|
-
def secret_spec_from_dict(secret_spec_dict, role):
|
|
90
|
-
if "type" not in secret_spec_dict:
|
|
91
|
-
secrets_backend_type = get_default_secrets_backend_type()
|
|
92
|
-
else:
|
|
93
|
-
secrets_backend_type = secret_spec_dict["type"]
|
|
94
|
-
if not isinstance(secrets_backend_type, str):
|
|
95
|
-
raise MetaflowException(
|
|
96
|
-
"Bad @secrets specification - 'type' must be a string - found %s"
|
|
97
|
-
% type(secrets_backend_type)
|
|
98
|
-
)
|
|
99
|
-
secret_id = secret_spec_dict.get("id")
|
|
100
|
-
if not isinstance(secret_id, str):
|
|
101
|
-
raise MetaflowException(
|
|
102
|
-
"Bad @secrets specification - 'id' must be a string - found %s"
|
|
103
|
-
% type(secret_id)
|
|
104
|
-
)
|
|
105
|
-
options = secret_spec_dict.get("options", {})
|
|
106
|
-
if not isinstance(options, dict):
|
|
107
|
-
raise MetaflowException(
|
|
108
|
-
"Bad @secrets specification - 'option' must be a dict - found %s"
|
|
109
|
-
% type(options)
|
|
110
|
-
)
|
|
111
|
-
role_for_source = secret_spec_dict.get("role", None)
|
|
112
|
-
if role_for_source is not None:
|
|
113
|
-
if not isinstance(role_for_source, str):
|
|
114
|
-
raise MetaflowException(
|
|
115
|
-
"Bad @secrets specification - 'role' must be a str - found %s"
|
|
116
|
-
% type(role_for_source)
|
|
117
|
-
)
|
|
118
|
-
role = role_for_source
|
|
119
|
-
return SecretSpec(
|
|
120
|
-
secrets_backend_type, secret_id=secret_id, options=options, role=role
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def validate_env_vars_across_secrets(all_secrets_env_vars):
|
|
125
|
-
vars_injected_by = {}
|
|
126
|
-
for secret_spec, env_vars in all_secrets_env_vars:
|
|
127
|
-
for k in env_vars:
|
|
128
|
-
if k in vars_injected_by:
|
|
129
|
-
raise MetaflowException(
|
|
130
|
-
"Secret '%s' will inject '%s' as env var, and it is also added by '%s'"
|
|
131
|
-
% (secret_spec, k, vars_injected_by[k])
|
|
132
|
-
)
|
|
133
|
-
vars_injected_by[k] = secret_spec
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def validate_env_vars_vs_existing_env(all_secrets_env_vars):
|
|
137
|
-
for secret_spec, env_vars in all_secrets_env_vars:
|
|
138
|
-
for k in env_vars:
|
|
139
|
-
if k in os.environ:
|
|
140
|
-
raise MetaflowException(
|
|
141
|
-
"Secret '%s' will inject '%s' as env var, but it already exists in env"
|
|
142
|
-
% (secret_spec, k)
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def validate_env_vars(env_vars):
|
|
147
|
-
for k, v in env_vars.items():
|
|
148
|
-
if not isinstance(k, str):
|
|
149
|
-
raise MetaflowException("Found non string key %s (%s)" % (str(k), type(k)))
|
|
150
|
-
if not isinstance(v, str):
|
|
151
|
-
raise MetaflowException(
|
|
152
|
-
"Found non string value %s (%s)" % (str(v), type(v))
|
|
153
|
-
)
|
|
154
|
-
if not re.fullmatch("[a-zA-Z_][a-zA-Z0-9_]*", k):
|
|
155
|
-
raise MetaflowException("Found invalid env var name '%s'." % k)
|
|
156
|
-
for disallowed_prefix in DISALLOWED_SECRETS_ENV_VAR_PREFIXES:
|
|
157
|
-
if k.startswith(disallowed_prefix):
|
|
158
|
-
raise MetaflowException(
|
|
159
|
-
"Found disallowed env var name '%s' (starts with '%s')."
|
|
160
|
-
% (k, disallowed_prefix)
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def get_secrets_backend_provider(secrets_backend_type):
|
|
165
|
-
from metaflow.plugins import SECRETS_PROVIDERS
|
|
166
|
-
|
|
167
|
-
try:
|
|
168
|
-
provider_cls = [
|
|
169
|
-
pc for pc in SECRETS_PROVIDERS if pc.TYPE == secrets_backend_type
|
|
170
|
-
][0]
|
|
171
|
-
return provider_cls()
|
|
172
|
-
except IndexError:
|
|
173
|
-
raise MetaflowException(
|
|
174
|
-
"Unknown secrets backend type %s (available types: %s)"
|
|
175
|
-
% (
|
|
176
|
-
secrets_backend_type,
|
|
177
|
-
", ".join(pc.TYPE for pc in SECRETS_PROVIDERS if pc.TYPE != "inline"),
|
|
178
|
-
)
|
|
179
|
-
)
|
|
6
|
+
from metaflow.plugins.secrets.secrets_spec import SecretSpec
|
|
7
|
+
from metaflow.plugins.secrets.utils import (
|
|
8
|
+
get_secrets_backend_provider,
|
|
9
|
+
validate_env_vars,
|
|
10
|
+
validate_env_vars_across_secrets,
|
|
11
|
+
validate_env_vars_vs_existing_env,
|
|
12
|
+
)
|
|
13
|
+
from metaflow.unbounded_foreach import UBF_TASK
|
|
180
14
|
|
|
181
15
|
|
|
182
16
|
class SecretsDecorator(StepDecorator):
|
|
@@ -188,13 +22,14 @@ class SecretsDecorator(StepDecorator):
|
|
|
188
22
|
----------
|
|
189
23
|
sources : List[Union[str, Dict[str, Any]]], default: []
|
|
190
24
|
List of secret specs, defining how the secrets are to be retrieved
|
|
25
|
+
role : str, optional, default: None
|
|
26
|
+
Role to use for fetching secrets
|
|
27
|
+
allow_override : bool, optional, default: False
|
|
28
|
+
Toggle whether secrets can replace existing environment variables.
|
|
191
29
|
"""
|
|
192
30
|
|
|
193
31
|
name = "secrets"
|
|
194
|
-
defaults = {
|
|
195
|
-
"sources": [],
|
|
196
|
-
"role": None,
|
|
197
|
-
}
|
|
32
|
+
defaults = {"sources": [], "role": None, "allow_override": False}
|
|
198
33
|
|
|
199
34
|
def task_pre_step(
|
|
200
35
|
self,
|
|
@@ -210,8 +45,17 @@ class SecretsDecorator(StepDecorator):
|
|
|
210
45
|
ubf_context,
|
|
211
46
|
inputs,
|
|
212
47
|
):
|
|
213
|
-
if
|
|
214
|
-
|
|
48
|
+
if (
|
|
49
|
+
ubf_context
|
|
50
|
+
and ubf_context == UBF_TASK
|
|
51
|
+
and os.environ.get("METAFLOW_RUNTIME_ENVIRONMENT", "local") == "local"
|
|
52
|
+
):
|
|
53
|
+
# We will skip the secret injection for "locally" launched UBF_TASK (worker) tasks
|
|
54
|
+
# When we "locally" run @parallel tasks, the control task will create the worker tasks and the environment variables
|
|
55
|
+
# of the control task are inherited by the worker tasks. If we don't skip setting secrets in the worker task then the
|
|
56
|
+
# worker tasks will try to set the environment variables again which will cause a clash with the control task's env vars,
|
|
57
|
+
# causing the @secrets' `task_pre_step` to fail. In remote settings, (e.g. AWS Batch/Kubernetes), the worker task and
|
|
58
|
+
# control task are independently created so there is no chances of an env var clash.
|
|
215
59
|
return
|
|
216
60
|
# List of pairs (secret_spec, env_vars_from_this_spec)
|
|
217
61
|
all_secrets_env_vars = []
|
|
@@ -267,7 +111,8 @@ class SecretsDecorator(StepDecorator):
|
|
|
267
111
|
all_secrets_env_vars.append((secret_spec, env_vars_for_secret))
|
|
268
112
|
|
|
269
113
|
validate_env_vars_across_secrets(all_secrets_env_vars)
|
|
270
|
-
|
|
114
|
+
if not self.attributes["allow_override"]:
|
|
115
|
+
validate_env_vars_vs_existing_env(all_secrets_env_vars)
|
|
271
116
|
|
|
272
117
|
# By this point
|
|
273
118
|
# all_secrets_env_vars contains a list of dictionaries... env maps.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional, Union
|
|
2
|
+
|
|
3
|
+
from metaflow.metaflow_config import DEFAULT_SECRETS_ROLE
|
|
4
|
+
from metaflow.exception import MetaflowException
|
|
5
|
+
from metaflow.plugins.secrets.secrets_spec import SecretSpec
|
|
6
|
+
from metaflow.plugins.secrets.utils import get_secrets_backend_provider
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_secret(
|
|
10
|
+
source: Union[str, Dict[str, Any]], role: Optional[str] = None
|
|
11
|
+
) -> Dict[str, str]:
|
|
12
|
+
"""
|
|
13
|
+
Get secret from source
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
source : Union[str, Dict[str, Any]]
|
|
18
|
+
Secret spec, defining how the secret is to be retrieved
|
|
19
|
+
role : str, optional
|
|
20
|
+
Role to use for fetching secrets
|
|
21
|
+
"""
|
|
22
|
+
if role is None:
|
|
23
|
+
role = DEFAULT_SECRETS_ROLE
|
|
24
|
+
|
|
25
|
+
secret_spec = None
|
|
26
|
+
|
|
27
|
+
if isinstance(source, str):
|
|
28
|
+
secret_spec = SecretSpec.secret_spec_from_str(source, role=role)
|
|
29
|
+
elif isinstance(source, dict):
|
|
30
|
+
secret_spec = SecretSpec.secret_spec_from_dict(source, role=role)
|
|
31
|
+
else:
|
|
32
|
+
raise MetaflowException(
|
|
33
|
+
"get_secrets sources items must be either a string or a dict"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
secrets_backend_provider = get_secrets_backend_provider(
|
|
37
|
+
secret_spec.secrets_backend_type
|
|
38
|
+
)
|
|
39
|
+
try:
|
|
40
|
+
dict_for_secret = secrets_backend_provider.get_secret_as_dict(
|
|
41
|
+
secret_spec.secret_id,
|
|
42
|
+
options=secret_spec.options,
|
|
43
|
+
role=secret_spec.role,
|
|
44
|
+
)
|
|
45
|
+
return dict_for_secret
|
|
46
|
+
except Exception as e:
|
|
47
|
+
raise MetaflowException(
|
|
48
|
+
"Failed to retrieve secret '%s': %s" % (secret_spec.secret_id, e)
|
|
49
|
+
)
|