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
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
6
|
+
|
|
7
|
+
from metaflow._vendor import click
|
|
8
|
+
from metaflow.debug import debug
|
|
9
|
+
|
|
10
|
+
from .config_parameters import ConfigValue
|
|
11
|
+
from ..exception import MetaflowException, MetaflowInternalError
|
|
12
|
+
from ..packaging_sys import MetaflowCodeContent
|
|
13
|
+
from ..parameters import DeployTimeField, ParameterContext, current_flow
|
|
14
|
+
from ..util import get_username
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_CONVERT_PREFIX = "@!c!@:"
|
|
18
|
+
_DEFAULT_PREFIX = "@!d!@:"
|
|
19
|
+
_NO_FILE = "@!n!@:"
|
|
20
|
+
|
|
21
|
+
_CONVERTED_DEFAULT = _CONVERT_PREFIX + _DEFAULT_PREFIX
|
|
22
|
+
_CONVERTED_NO_FILE = _CONVERT_PREFIX + _NO_FILE
|
|
23
|
+
_CONVERTED_DEFAULT_NO_FILE = _CONVERTED_DEFAULT + _NO_FILE
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _load_config_values(info_file: Optional[str] = None) -> Optional[Dict[Any, Any]]:
|
|
27
|
+
if info_file is None:
|
|
28
|
+
config_content = MetaflowCodeContent.get_config()
|
|
29
|
+
else:
|
|
30
|
+
try:
|
|
31
|
+
with open(info_file, encoding="utf-8") as f:
|
|
32
|
+
config_content = json.load(f)
|
|
33
|
+
except IOError:
|
|
34
|
+
return None
|
|
35
|
+
if config_content:
|
|
36
|
+
return config_content.get("user_configs", {})
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ConvertPath(click.Path):
|
|
41
|
+
name = "ConvertPath"
|
|
42
|
+
|
|
43
|
+
def convert(self, value, param, ctx):
|
|
44
|
+
if isinstance(value, str) and value.startswith(_CONVERT_PREFIX):
|
|
45
|
+
return value
|
|
46
|
+
is_default = False
|
|
47
|
+
if value and value.startswith(_DEFAULT_PREFIX):
|
|
48
|
+
is_default = True
|
|
49
|
+
value = value[len(_DEFAULT_PREFIX) :]
|
|
50
|
+
value = super().convert(value, param, ctx)
|
|
51
|
+
return self.convert_value(value, is_default)
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def mark_as_default(value):
|
|
55
|
+
if value is None:
|
|
56
|
+
return None
|
|
57
|
+
return _DEFAULT_PREFIX + str(value)
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def convert_value(value, is_default):
|
|
61
|
+
default_str = _DEFAULT_PREFIX if is_default else ""
|
|
62
|
+
if value is None:
|
|
63
|
+
return None
|
|
64
|
+
try:
|
|
65
|
+
with open(value, "r", encoding="utf-8") as f:
|
|
66
|
+
content = f.read()
|
|
67
|
+
except OSError:
|
|
68
|
+
return _CONVERT_PREFIX + default_str + _NO_FILE + value
|
|
69
|
+
return _CONVERT_PREFIX + default_str + content
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ConvertDictOrStr(click.ParamType):
|
|
73
|
+
name = "ConvertDictOrStr"
|
|
74
|
+
|
|
75
|
+
def convert(self, value, param, ctx):
|
|
76
|
+
is_default = False
|
|
77
|
+
if isinstance(value, str):
|
|
78
|
+
if value.startswith(_CONVERT_PREFIX):
|
|
79
|
+
return value
|
|
80
|
+
if value.startswith(_DEFAULT_PREFIX):
|
|
81
|
+
is_default = True
|
|
82
|
+
value = value[len(_DEFAULT_PREFIX) :]
|
|
83
|
+
|
|
84
|
+
return self.convert_value(value, is_default)
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def convert_value(value, is_default):
|
|
88
|
+
default_str = _DEFAULT_PREFIX if is_default else ""
|
|
89
|
+
if value is None:
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
if isinstance(value, dict):
|
|
93
|
+
return _CONVERT_PREFIX + default_str + json.dumps(value)
|
|
94
|
+
|
|
95
|
+
if value.startswith(_CONVERT_PREFIX):
|
|
96
|
+
return value
|
|
97
|
+
|
|
98
|
+
return _CONVERT_PREFIX + default_str + value
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def mark_as_default(value):
|
|
102
|
+
if value is None:
|
|
103
|
+
return None
|
|
104
|
+
if isinstance(value, dict):
|
|
105
|
+
return _DEFAULT_PREFIX + json.dumps(value)
|
|
106
|
+
return _DEFAULT_PREFIX + str(value)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class MultipleTuple(click.Tuple):
|
|
110
|
+
# Small wrapper around a click.Tuple to allow the environment variable for
|
|
111
|
+
# configurations to be a JSON string. Otherwise the default behavior is splitting
|
|
112
|
+
# by whitespace which is totally not what we want
|
|
113
|
+
# You can now pass multiple configuration options through an environment variable
|
|
114
|
+
# using something like:
|
|
115
|
+
# METAFLOW_FLOW_CONFIG_VALUE='{"config1": {"key0": "value0"}, "config2": {"key1": "value1"}}'
|
|
116
|
+
# or METAFLOW_FLOW_CONFIG='{"config1": "file1", "config2": "file2"}'
|
|
117
|
+
|
|
118
|
+
def split_envvar_value(self, rv):
|
|
119
|
+
loaded = json.loads(rv)
|
|
120
|
+
return list(
|
|
121
|
+
item if isinstance(item, str) else json.dumps(item)
|
|
122
|
+
for pair in loaded.items()
|
|
123
|
+
for item in pair
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class ConfigInput:
|
|
128
|
+
# ConfigInput is an internal class responsible for processing all the --config and
|
|
129
|
+
# --config-value options.
|
|
130
|
+
# It gathers information from the --local-config-file (to figure out
|
|
131
|
+
# where options are stored) and is also responsible for processing any `--config` or
|
|
132
|
+
# `--config-value` options. Note that the process_configs function will be called
|
|
133
|
+
# *twice* (once for the configs and another for the config-values). This makes
|
|
134
|
+
# this function a little bit more tricky. We need to wait for both calls before
|
|
135
|
+
# being able to process anything.
|
|
136
|
+
|
|
137
|
+
# It will then store this information in the flow spec for use later in processing.
|
|
138
|
+
# It is stored in the flow spec to avoid being global to support the Runner.
|
|
139
|
+
|
|
140
|
+
loaded_configs = None # type: Optional[Dict[str, Dict[Any, Any]]]
|
|
141
|
+
config_file = None # type: Optional[str]
|
|
142
|
+
|
|
143
|
+
def __init__(
|
|
144
|
+
self,
|
|
145
|
+
req_configs: List[str],
|
|
146
|
+
defaults: Dict[str, Tuple[Union[str, Dict[Any, Any]], bool]],
|
|
147
|
+
parsers: Dict[str, Union[str, Callable[[str], Dict[Any, Any]]]],
|
|
148
|
+
):
|
|
149
|
+
self._req_configs = set(req_configs)
|
|
150
|
+
self._defaults = defaults
|
|
151
|
+
self._parsers = parsers
|
|
152
|
+
self._path_values = None
|
|
153
|
+
self._value_values = None
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def make_key_name(name: str) -> str:
|
|
157
|
+
# Special mark to indicate that the configuration value is not content or a file
|
|
158
|
+
# name but a value that should be read in the config file (effectively where
|
|
159
|
+
# the value has already been materialized).
|
|
160
|
+
return "kv." + name.lower()
|
|
161
|
+
|
|
162
|
+
@classmethod
|
|
163
|
+
def set_config_file(cls, config_file: str):
|
|
164
|
+
cls.config_file = config_file
|
|
165
|
+
|
|
166
|
+
@classmethod
|
|
167
|
+
def get_config(cls, config_name: str) -> Optional[Dict[Any, Any]]:
|
|
168
|
+
if cls.loaded_configs is None:
|
|
169
|
+
all_configs = _load_config_values(cls.config_file)
|
|
170
|
+
if all_configs is None:
|
|
171
|
+
raise MetaflowException(
|
|
172
|
+
"Could not load expected configuration values "
|
|
173
|
+
"from the CONFIG_PARAMETERS file. This is a Metaflow bug. "
|
|
174
|
+
"Please contact support."
|
|
175
|
+
)
|
|
176
|
+
cls.loaded_configs = all_configs
|
|
177
|
+
return cls.loaded_configs[config_name]
|
|
178
|
+
|
|
179
|
+
def process_configs(
|
|
180
|
+
self,
|
|
181
|
+
flow_name: str,
|
|
182
|
+
param_name: str,
|
|
183
|
+
param_value: Dict[str, Optional[str]],
|
|
184
|
+
quiet: bool,
|
|
185
|
+
datastore: str,
|
|
186
|
+
click_obj: Optional[Any] = None,
|
|
187
|
+
):
|
|
188
|
+
from ..cli import echo_always, echo_dev_null # Prevent circular import
|
|
189
|
+
from ..flowspec import FlowStateItems # Prevent circular import
|
|
190
|
+
|
|
191
|
+
flow_cls = getattr(current_flow, "flow_cls", None)
|
|
192
|
+
if flow_cls is None:
|
|
193
|
+
# This is an error
|
|
194
|
+
raise MetaflowInternalError(
|
|
195
|
+
"Config values should be processed for a FlowSpec"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# This function is called by click when processing all the --config and
|
|
199
|
+
# --config-value options.
|
|
200
|
+
# The value passed in is a list of tuples (name, value).
|
|
201
|
+
# Click will provide:
|
|
202
|
+
# - all the defaults if nothing is provided on the command line
|
|
203
|
+
# - provide *just* the passed in value if anything is provided on the command
|
|
204
|
+
# line.
|
|
205
|
+
#
|
|
206
|
+
# We need to get all config and config-value options and click will call this
|
|
207
|
+
# function twice. We will first get all the values on the command line and
|
|
208
|
+
# *then* merge with the defaults to form a full set of values.
|
|
209
|
+
# We therefore get a full set of values where:
|
|
210
|
+
# - the name will correspond to the configuration name
|
|
211
|
+
# - the value will be:
|
|
212
|
+
# - the default (including None if there is no default). If the default is
|
|
213
|
+
# not None, it will start with _CONVERTED_DEFAULT since Click will make
|
|
214
|
+
# the value go through ConvertPath or ConvertDictOrStr
|
|
215
|
+
# - the actual value passed through prefixed with _CONVERT_PREFIX
|
|
216
|
+
|
|
217
|
+
debug.userconf_exec(
|
|
218
|
+
"Processing configs for %s -- incoming values: %s"
|
|
219
|
+
% (param_name, str(param_value))
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
do_return = self._value_values is None and self._path_values is None
|
|
223
|
+
# We only keep around non default values. We could simplify by checking just one
|
|
224
|
+
# value and if it is default it means all are but this doesn't seem much more effort
|
|
225
|
+
# and is clearer
|
|
226
|
+
if param_name == "config_value":
|
|
227
|
+
self._value_values = {
|
|
228
|
+
k.lower(): v
|
|
229
|
+
for k, v in param_value.items()
|
|
230
|
+
if v is not None and not v.startswith(_CONVERTED_DEFAULT)
|
|
231
|
+
}
|
|
232
|
+
else:
|
|
233
|
+
self._path_values = {
|
|
234
|
+
k.lower(): v
|
|
235
|
+
for k, v in param_value.items()
|
|
236
|
+
if v is not None and not v.startswith(_CONVERTED_DEFAULT)
|
|
237
|
+
}
|
|
238
|
+
if do_return:
|
|
239
|
+
# One of values["value"] or values["path"] is None -- we are in the first
|
|
240
|
+
# go around
|
|
241
|
+
debug.userconf_exec("Incomplete config options; waiting for more")
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
# The second go around, we process all the values and merge them.
|
|
245
|
+
|
|
246
|
+
# If we are processing options that start with kv., we know we are in a subprocess
|
|
247
|
+
# and ignore other stuff. In particular, environment variables used to pass
|
|
248
|
+
# down configurations (like METAFLOW_FLOW_CONFIG) could still be present and
|
|
249
|
+
# would cause an issue -- we can ignore those as the kv. values should trump
|
|
250
|
+
# everything else.
|
|
251
|
+
# NOTE: These are all *non default* keys
|
|
252
|
+
all_keys = set(self._value_values).union(self._path_values)
|
|
253
|
+
|
|
254
|
+
if all_keys and click_obj:
|
|
255
|
+
click_obj.has_cl_config_options = True
|
|
256
|
+
# Make sure we have at least some non default keys (we need some if we have
|
|
257
|
+
# all kv)
|
|
258
|
+
has_all_kv = all_keys and all(
|
|
259
|
+
self._value_values.get(k, "").startswith(_CONVERT_PREFIX + "kv.")
|
|
260
|
+
for k in all_keys
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
to_return = {}
|
|
264
|
+
|
|
265
|
+
if not has_all_kv:
|
|
266
|
+
# Check that the user didn't provide *both* a path and a value. Again, these
|
|
267
|
+
# are only user-provided (not defaults)
|
|
268
|
+
common_keys = set(self._value_values or []).intersection(
|
|
269
|
+
[k for k, v in self._path_values.items()] or []
|
|
270
|
+
)
|
|
271
|
+
if common_keys:
|
|
272
|
+
exc = click.UsageError(
|
|
273
|
+
"Cannot provide both a value and a file for the same configuration. "
|
|
274
|
+
"Found such values for '%s'" % "', '".join(common_keys)
|
|
275
|
+
)
|
|
276
|
+
if click_obj:
|
|
277
|
+
click_obj.delayed_config_exception = exc
|
|
278
|
+
return None
|
|
279
|
+
raise exc
|
|
280
|
+
|
|
281
|
+
all_values = dict(self._path_values)
|
|
282
|
+
all_values.update(self._value_values)
|
|
283
|
+
|
|
284
|
+
debug.userconf_exec("All config values: %s" % str(all_values))
|
|
285
|
+
|
|
286
|
+
merged_configs = {}
|
|
287
|
+
# Now look at everything (including defaults)
|
|
288
|
+
for name, (val, is_path) in self._defaults.items():
|
|
289
|
+
n = name.lower()
|
|
290
|
+
if n in all_values:
|
|
291
|
+
# We have the value provided by the user -- use that.
|
|
292
|
+
merged_configs[n] = all_values[n]
|
|
293
|
+
else:
|
|
294
|
+
# No value provided by the user -- use the default
|
|
295
|
+
if isinstance(val, DeployTimeField):
|
|
296
|
+
# This supports a default value that is a deploy-time field (similar
|
|
297
|
+
# to Parameter).)
|
|
298
|
+
# We will form our own context and pass it down -- note that you cannot
|
|
299
|
+
# use configs in the default value of configs as this introduces a bit
|
|
300
|
+
# of circularity. Note also that quiet and datastore are *eager*
|
|
301
|
+
# options so are available here.
|
|
302
|
+
param_ctx = ParameterContext(
|
|
303
|
+
flow_name=flow_name,
|
|
304
|
+
user_name=get_username(),
|
|
305
|
+
parameter_name=n,
|
|
306
|
+
logger=(echo_dev_null if quiet else echo_always),
|
|
307
|
+
ds_type=datastore,
|
|
308
|
+
configs=None,
|
|
309
|
+
)
|
|
310
|
+
val = val.fun(param_ctx)
|
|
311
|
+
if is_path:
|
|
312
|
+
# This is a file path
|
|
313
|
+
merged_configs[n] = ConvertPath.convert_value(val, True)
|
|
314
|
+
else:
|
|
315
|
+
# This is a value
|
|
316
|
+
merged_configs[n] = ConvertDictOrStr.convert_value(val, True)
|
|
317
|
+
else:
|
|
318
|
+
debug.userconf_exec("Fast path due to pre-processed values")
|
|
319
|
+
merged_configs = self._value_values
|
|
320
|
+
|
|
321
|
+
if click_obj:
|
|
322
|
+
click_obj.has_config_options = True
|
|
323
|
+
|
|
324
|
+
debug.userconf_exec("Configs merged with defaults: %s" % str(merged_configs))
|
|
325
|
+
|
|
326
|
+
missing_configs = set()
|
|
327
|
+
no_file = []
|
|
328
|
+
no_default_file = []
|
|
329
|
+
msgs = []
|
|
330
|
+
for name, val in merged_configs.items():
|
|
331
|
+
if val is None:
|
|
332
|
+
missing_configs.add(name)
|
|
333
|
+
to_return[name] = None
|
|
334
|
+
flow_cls._flow_state.self_data[FlowStateItems.CONFIGS][name] = None
|
|
335
|
+
continue
|
|
336
|
+
if val.startswith(_CONVERTED_NO_FILE):
|
|
337
|
+
no_file.append(name)
|
|
338
|
+
continue
|
|
339
|
+
if val.startswith(_CONVERTED_DEFAULT_NO_FILE):
|
|
340
|
+
no_default_file.append(name)
|
|
341
|
+
continue
|
|
342
|
+
|
|
343
|
+
val = val[len(_CONVERT_PREFIX) :] # Remove the _CONVERT_PREFIX
|
|
344
|
+
if val.startswith(_DEFAULT_PREFIX): # Remove the _DEFAULT_PREFIX if needed
|
|
345
|
+
val = val[len(_DEFAULT_PREFIX) :]
|
|
346
|
+
if val.startswith("kv."):
|
|
347
|
+
# This means to load it from a file
|
|
348
|
+
try:
|
|
349
|
+
read_value = self.get_config(val[3:])
|
|
350
|
+
except KeyError as e:
|
|
351
|
+
exc = click.UsageError(
|
|
352
|
+
"Could not find configuration '%s' in INFO file" % val
|
|
353
|
+
)
|
|
354
|
+
if click_obj:
|
|
355
|
+
click_obj.delayed_config_exception = exc
|
|
356
|
+
return None
|
|
357
|
+
raise exc from e
|
|
358
|
+
flow_cls._flow_state.self_data[FlowStateItems.CONFIGS][
|
|
359
|
+
name
|
|
360
|
+
] = read_value
|
|
361
|
+
to_return[name] = (
|
|
362
|
+
ConfigValue(read_value) if read_value is not None else None
|
|
363
|
+
)
|
|
364
|
+
else:
|
|
365
|
+
if self._parsers[name]:
|
|
366
|
+
read_value = self._call_parser(self._parsers[name], val)
|
|
367
|
+
else:
|
|
368
|
+
try:
|
|
369
|
+
read_value = json.loads(val)
|
|
370
|
+
except json.JSONDecodeError as e:
|
|
371
|
+
msgs.append(
|
|
372
|
+
"configuration value for '%s' is not valid JSON: %s"
|
|
373
|
+
% (name, e)
|
|
374
|
+
)
|
|
375
|
+
continue
|
|
376
|
+
# TODO: Support YAML
|
|
377
|
+
flow_cls._flow_state.self_data[FlowStateItems.CONFIGS][
|
|
378
|
+
name
|
|
379
|
+
] = read_value
|
|
380
|
+
to_return[name] = (
|
|
381
|
+
ConfigValue(read_value) if read_value is not None else None
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
reqs = missing_configs.intersection(self._req_configs)
|
|
385
|
+
for missing in reqs:
|
|
386
|
+
msgs.append("missing configuration for '%s'" % missing)
|
|
387
|
+
for missing in no_file:
|
|
388
|
+
msgs.append(
|
|
389
|
+
"configuration file '%s' could not be read for '%s'"
|
|
390
|
+
% (merged_configs[missing][len(_CONVERTED_NO_FILE) :], missing)
|
|
391
|
+
)
|
|
392
|
+
for missing in no_default_file:
|
|
393
|
+
msgs.append(
|
|
394
|
+
"default configuration file '%s' could not be read for '%s'"
|
|
395
|
+
% (merged_configs[missing][len(_CONVERTED_DEFAULT_NO_FILE) :], missing)
|
|
396
|
+
)
|
|
397
|
+
if msgs:
|
|
398
|
+
exc = click.UsageError(
|
|
399
|
+
"Bad values passed for configuration options: %s" % ", ".join(msgs)
|
|
400
|
+
)
|
|
401
|
+
if click_obj:
|
|
402
|
+
click_obj.delayed_config_exception = exc
|
|
403
|
+
return None
|
|
404
|
+
raise exc
|
|
405
|
+
|
|
406
|
+
debug.userconf_exec("Finalized configs: %s" % str(to_return))
|
|
407
|
+
return to_return
|
|
408
|
+
|
|
409
|
+
def process_configs_click(self, ctx, param, value):
|
|
410
|
+
return self.process_configs(
|
|
411
|
+
ctx.obj.flow.name,
|
|
412
|
+
param.name,
|
|
413
|
+
dict(value),
|
|
414
|
+
ctx.params["quiet"],
|
|
415
|
+
ctx.params["datastore"],
|
|
416
|
+
click_obj=ctx.obj,
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
def __str__(self):
|
|
420
|
+
return repr(self)
|
|
421
|
+
|
|
422
|
+
def __repr__(self):
|
|
423
|
+
return "ConfigInput"
|
|
424
|
+
|
|
425
|
+
@staticmethod
|
|
426
|
+
def _call_parser(parser, val):
|
|
427
|
+
if isinstance(parser, str):
|
|
428
|
+
if len(parser) and parser[0] == ".":
|
|
429
|
+
parser = "metaflow" + parser
|
|
430
|
+
path, func = parser.rsplit(".", 1)
|
|
431
|
+
try:
|
|
432
|
+
func_module = importlib.import_module(path)
|
|
433
|
+
except ImportError as e:
|
|
434
|
+
raise ValueError("Cannot locate parser %s" % parser) from e
|
|
435
|
+
parser = getattr(func_module, func, None)
|
|
436
|
+
if parser is None or not callable(parser):
|
|
437
|
+
raise ValueError(
|
|
438
|
+
"Parser %s is either not part of %s or not a callable"
|
|
439
|
+
% (func, path)
|
|
440
|
+
)
|
|
441
|
+
return parser(val)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
class LocalFileInput(click.Path):
|
|
445
|
+
# Small wrapper around click.Path to set the value from which to read configuration
|
|
446
|
+
# values. This is set immediately upon processing the --local-config-file
|
|
447
|
+
# option and will therefore then be available when processing any of the other
|
|
448
|
+
# --config options (which will call ConfigInput.process_configs)
|
|
449
|
+
name = "LocalFileInput"
|
|
450
|
+
|
|
451
|
+
def convert(self, value, param, ctx):
|
|
452
|
+
v = super().convert(value, param, ctx)
|
|
453
|
+
ConfigInput.set_config_file(value)
|
|
454
|
+
return v
|
|
455
|
+
|
|
456
|
+
def __str__(self):
|
|
457
|
+
return repr(self)
|
|
458
|
+
|
|
459
|
+
def __repr__(self):
|
|
460
|
+
return "LocalFileInput"
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def config_options_with_config_input(cmd):
|
|
464
|
+
help_strs = []
|
|
465
|
+
required_names = []
|
|
466
|
+
defaults = {}
|
|
467
|
+
config_seen = set()
|
|
468
|
+
parsers = {}
|
|
469
|
+
flow_cls = getattr(current_flow, "flow_cls", None)
|
|
470
|
+
if flow_cls is None:
|
|
471
|
+
return cmd, None
|
|
472
|
+
|
|
473
|
+
parameters = [p for _, p in flow_cls._get_parameters() if p.IS_CONFIG_PARAMETER]
|
|
474
|
+
# List all the configuration options
|
|
475
|
+
for arg in parameters[::-1]:
|
|
476
|
+
kwargs = arg.option_kwargs(False)
|
|
477
|
+
if arg.name.lower() in config_seen:
|
|
478
|
+
msg = (
|
|
479
|
+
"Multiple configurations use the same name '%s'. Note that names are "
|
|
480
|
+
"case-insensitive. Please change the "
|
|
481
|
+
"names of some of your configurations" % arg.name
|
|
482
|
+
)
|
|
483
|
+
raise MetaflowException(msg)
|
|
484
|
+
config_seen.add(arg.name.lower())
|
|
485
|
+
if kwargs["required"]:
|
|
486
|
+
required_names.append(arg.name)
|
|
487
|
+
|
|
488
|
+
defaults[arg.name.lower()] = (
|
|
489
|
+
arg.kwargs.get("default", None),
|
|
490
|
+
arg._default_is_file,
|
|
491
|
+
)
|
|
492
|
+
help_strs.append(" - %s: %s" % (arg.name.lower(), kwargs.get("help", "")))
|
|
493
|
+
parsers[arg.name.lower()] = arg.parser
|
|
494
|
+
|
|
495
|
+
if not config_seen:
|
|
496
|
+
# No configurations -- don't add anything; we set it to False so that it
|
|
497
|
+
# can be checked whether or not we called this.
|
|
498
|
+
return cmd, False
|
|
499
|
+
|
|
500
|
+
help_str = (
|
|
501
|
+
"Configuration options for the flow. "
|
|
502
|
+
"Multiple configurations can be specified. Cannot be used with resume."
|
|
503
|
+
)
|
|
504
|
+
help_str = "\n\n".join([help_str] + help_strs)
|
|
505
|
+
config_input = ConfigInput(required_names, defaults, parsers)
|
|
506
|
+
cb_func = config_input.process_configs_click
|
|
507
|
+
|
|
508
|
+
cmd.params.insert(
|
|
509
|
+
0,
|
|
510
|
+
click.Option(
|
|
511
|
+
["--config-value", "config_value"],
|
|
512
|
+
nargs=2,
|
|
513
|
+
multiple=True,
|
|
514
|
+
type=MultipleTuple([click.Choice(config_seen), ConvertDictOrStr()]),
|
|
515
|
+
callback=cb_func,
|
|
516
|
+
help=help_str,
|
|
517
|
+
envvar="METAFLOW_FLOW_CONFIG_VALUE",
|
|
518
|
+
show_default=False,
|
|
519
|
+
default=[
|
|
520
|
+
(
|
|
521
|
+
k,
|
|
522
|
+
(
|
|
523
|
+
ConvertDictOrStr.mark_as_default(v[0])
|
|
524
|
+
if not callable(v[0]) and not v[1]
|
|
525
|
+
else None
|
|
526
|
+
),
|
|
527
|
+
)
|
|
528
|
+
for k, v in defaults.items()
|
|
529
|
+
],
|
|
530
|
+
required=False,
|
|
531
|
+
),
|
|
532
|
+
)
|
|
533
|
+
cmd.params.insert(
|
|
534
|
+
0,
|
|
535
|
+
click.Option(
|
|
536
|
+
["--config", "config"],
|
|
537
|
+
nargs=2,
|
|
538
|
+
multiple=True,
|
|
539
|
+
type=MultipleTuple([click.Choice(config_seen), ConvertPath()]),
|
|
540
|
+
callback=cb_func,
|
|
541
|
+
help=help_str,
|
|
542
|
+
envvar="METAFLOW_FLOW_CONFIG",
|
|
543
|
+
show_default=False,
|
|
544
|
+
default=[
|
|
545
|
+
(
|
|
546
|
+
k,
|
|
547
|
+
(
|
|
548
|
+
ConvertPath.mark_as_default(v[0])
|
|
549
|
+
if not callable(v[0]) and v[1]
|
|
550
|
+
else None
|
|
551
|
+
),
|
|
552
|
+
)
|
|
553
|
+
for k, v in defaults.items()
|
|
554
|
+
],
|
|
555
|
+
required=False,
|
|
556
|
+
),
|
|
557
|
+
)
|
|
558
|
+
return cmd, config_input
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def config_options(cmd):
|
|
562
|
+
cmd, _ = config_options_with_config_input(cmd)
|
|
563
|
+
return cmd
|