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,717 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
_py_ver = sys.version_info[:2]
|
|
5
|
+
|
|
6
|
+
if _py_ver >= (3, 8):
|
|
7
|
+
from metaflow._vendor.typeguard import TypeCheckError, check_type
|
|
8
|
+
elif _py_ver >= (3, 7):
|
|
9
|
+
from metaflow._vendor.v3_7.typeguard import TypeCheckError, check_type
|
|
10
|
+
else:
|
|
11
|
+
raise RuntimeError(
|
|
12
|
+
"""
|
|
13
|
+
The Metaflow Programmatic API is not supported for versions of Python less than 3.7
|
|
14
|
+
"""
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
import datetime
|
|
18
|
+
import functools
|
|
19
|
+
import importlib
|
|
20
|
+
import inspect
|
|
21
|
+
import itertools
|
|
22
|
+
import uuid
|
|
23
|
+
import json
|
|
24
|
+
from collections import OrderedDict
|
|
25
|
+
from typing import Any, Callable, Dict, List, Optional, Type
|
|
26
|
+
from typing import OrderedDict as TOrderedDict
|
|
27
|
+
from typing import Tuple as TTuple
|
|
28
|
+
from typing import Union
|
|
29
|
+
|
|
30
|
+
from metaflow import FlowSpec, Parameter
|
|
31
|
+
from metaflow._vendor import click
|
|
32
|
+
from metaflow._vendor.click.types import (
|
|
33
|
+
BoolParamType,
|
|
34
|
+
Choice,
|
|
35
|
+
DateTime,
|
|
36
|
+
File,
|
|
37
|
+
FloatParamType,
|
|
38
|
+
IntParamType,
|
|
39
|
+
Path,
|
|
40
|
+
StringParamType,
|
|
41
|
+
Tuple,
|
|
42
|
+
UUIDParameterType,
|
|
43
|
+
)
|
|
44
|
+
from metaflow.decorators import add_decorator_options
|
|
45
|
+
from metaflow.exception import MetaflowException
|
|
46
|
+
from metaflow.flowspec import FlowStateItems
|
|
47
|
+
from metaflow.includefile import FilePathClass
|
|
48
|
+
from metaflow.metaflow_config import CLICK_API_PROCESS_CONFIG
|
|
49
|
+
from metaflow.parameters import JSONTypeClass, flow_context
|
|
50
|
+
from metaflow.user_configs.config_options import (
|
|
51
|
+
ConfigValue,
|
|
52
|
+
ConvertDictOrStr,
|
|
53
|
+
ConvertPath,
|
|
54
|
+
LocalFileInput,
|
|
55
|
+
MultipleTuple,
|
|
56
|
+
config_options_with_config_input,
|
|
57
|
+
)
|
|
58
|
+
from metaflow.user_decorators.user_flow_decorator import FlowMutator
|
|
59
|
+
|
|
60
|
+
# Define a recursive type alias for JSON
|
|
61
|
+
JSON = Union[Dict[str, "JSON"], List["JSON"], str, int, float, bool, None]
|
|
62
|
+
|
|
63
|
+
click_to_python_types = {
|
|
64
|
+
StringParamType: str,
|
|
65
|
+
IntParamType: int,
|
|
66
|
+
FloatParamType: float,
|
|
67
|
+
BoolParamType: bool,
|
|
68
|
+
UUIDParameterType: uuid.UUID,
|
|
69
|
+
Path: str,
|
|
70
|
+
DateTime: datetime.datetime,
|
|
71
|
+
Tuple: tuple,
|
|
72
|
+
Choice: str,
|
|
73
|
+
File: str,
|
|
74
|
+
JSONTypeClass: JSON,
|
|
75
|
+
FilePathClass: str,
|
|
76
|
+
LocalFileInput: str,
|
|
77
|
+
MultipleTuple: TTuple[str, Union[JSON, ConfigValue]],
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _method_sanity_check(
|
|
82
|
+
possible_arg_params: TOrderedDict[str, click.Argument],
|
|
83
|
+
possible_opt_params: TOrderedDict[str, click.Option],
|
|
84
|
+
annotations: TOrderedDict[str, Any],
|
|
85
|
+
defaults: TOrderedDict[str, Any],
|
|
86
|
+
**kwargs
|
|
87
|
+
) -> Dict[str, Any]:
|
|
88
|
+
method_params = {"args": {}, "options": {}, "defaults": defaults}
|
|
89
|
+
|
|
90
|
+
possible_params = OrderedDict()
|
|
91
|
+
possible_params.update(possible_arg_params)
|
|
92
|
+
possible_params.update(possible_opt_params)
|
|
93
|
+
|
|
94
|
+
# supplied kwargs
|
|
95
|
+
for supplied_k, supplied_v in kwargs.items():
|
|
96
|
+
if supplied_k not in possible_params:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
"Unknown argument: '%s', possible args are: %s"
|
|
99
|
+
% (supplied_k, ", ".join(possible_params.keys()))
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
check_type(supplied_v, annotations[supplied_k])
|
|
104
|
+
except TypeCheckError:
|
|
105
|
+
raise TypeError(
|
|
106
|
+
"Invalid type for '%s' (%s), expected: '%s', default is '%s' but found '%s'"
|
|
107
|
+
% (
|
|
108
|
+
supplied_k,
|
|
109
|
+
type(supplied_k),
|
|
110
|
+
annotations[supplied_k],
|
|
111
|
+
defaults[supplied_k],
|
|
112
|
+
str(supplied_v),
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Clean up values to make them into what click expects
|
|
117
|
+
if annotations[supplied_k] == JSON:
|
|
118
|
+
# JSON should be a string (json dumps)
|
|
119
|
+
supplied_v = json.dumps(supplied_v)
|
|
120
|
+
elif supplied_k == "config_value":
|
|
121
|
+
# Special handling of config value because we need to go look in the tuple
|
|
122
|
+
new_list = []
|
|
123
|
+
for cfg_name, cfg_value in supplied_v:
|
|
124
|
+
if isinstance(cfg_value, ConfigValue):
|
|
125
|
+
# ConfigValue should be JSONified and converted to a string
|
|
126
|
+
new_list.append((cfg_name, json.dumps(cfg_value.to_dict())))
|
|
127
|
+
elif isinstance(cfg_value, dict):
|
|
128
|
+
# ConfigValue passed as a dictionary
|
|
129
|
+
new_list.append((cfg_name, json.dumps(cfg_value)))
|
|
130
|
+
else:
|
|
131
|
+
raise TypeError(
|
|
132
|
+
"Invalid type for a config-value, expected a ConfigValue or "
|
|
133
|
+
"dict but got '%s'" % type(cfg_value)
|
|
134
|
+
)
|
|
135
|
+
supplied_v = new_list
|
|
136
|
+
|
|
137
|
+
if supplied_k in possible_arg_params:
|
|
138
|
+
cli_name = possible_arg_params[supplied_k].opts[0].strip("-")
|
|
139
|
+
method_params["args"][cli_name] = supplied_v
|
|
140
|
+
elif supplied_k in possible_opt_params:
|
|
141
|
+
if possible_opt_params[supplied_k].is_bool_flag:
|
|
142
|
+
# it is a boolean flag..
|
|
143
|
+
if supplied_v == True:
|
|
144
|
+
cli_name = possible_opt_params[supplied_k].opts[0].strip("-")
|
|
145
|
+
elif supplied_v == False:
|
|
146
|
+
if possible_opt_params[supplied_k].secondary_opts:
|
|
147
|
+
cli_name = (
|
|
148
|
+
possible_opt_params[supplied_k].secondary_opts[0].strip("-")
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
continue
|
|
152
|
+
supplied_v = "flag"
|
|
153
|
+
else:
|
|
154
|
+
cli_name = possible_opt_params[supplied_k].opts[0].strip("-")
|
|
155
|
+
method_params["options"][cli_name] = supplied_v
|
|
156
|
+
|
|
157
|
+
# possible kwargs
|
|
158
|
+
for _, possible_v in possible_params.items():
|
|
159
|
+
cli_name = possible_v.opts[0].strip("-")
|
|
160
|
+
if (
|
|
161
|
+
(cli_name not in method_params["args"])
|
|
162
|
+
and (cli_name not in method_params["options"])
|
|
163
|
+
) and possible_v.required:
|
|
164
|
+
raise ValueError("Missing argument: %s is required." % cli_name)
|
|
165
|
+
|
|
166
|
+
return method_params
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _cleanup_flow_parameters(cmd_obj: Union[click.Command, click.Group]):
|
|
170
|
+
if hasattr(cmd_obj, "original_params"):
|
|
171
|
+
cmd_obj.params = list(cmd_obj.original_params)
|
|
172
|
+
|
|
173
|
+
if isinstance(cmd_obj, click.Group):
|
|
174
|
+
for sub_cmd_name in cmd_obj.list_commands(None):
|
|
175
|
+
sub_cmd = cmd_obj.get_command(None, sub_cmd_name)
|
|
176
|
+
if sub_cmd:
|
|
177
|
+
_cleanup_flow_parameters(sub_cmd)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _lazy_load_command(
|
|
181
|
+
cli_collection: click.Group,
|
|
182
|
+
flow_parameters: Union[str, List[Parameter]],
|
|
183
|
+
_self,
|
|
184
|
+
name: str,
|
|
185
|
+
):
|
|
186
|
+
# Context is not used in get_command so we can pass None. Since we pin click,
|
|
187
|
+
# this won't change from under us.
|
|
188
|
+
|
|
189
|
+
if isinstance(flow_parameters, str):
|
|
190
|
+
# Resolve flow_parameters -- for start, this is a function which we
|
|
191
|
+
# need to call to figure out the actual parameters (may be changed by configs)
|
|
192
|
+
flow_parameters = getattr(_self, flow_parameters)()
|
|
193
|
+
cmd_obj = cli_collection.get_command(None, name)
|
|
194
|
+
if cmd_obj:
|
|
195
|
+
_cleanup_flow_parameters(cmd_obj)
|
|
196
|
+
if isinstance(cmd_obj, click.Group):
|
|
197
|
+
# TODO: possibly check for fake groups with cmd_obj.name in ["cli", "main"]
|
|
198
|
+
result = functools.partial(extract_group(cmd_obj, flow_parameters), _self)
|
|
199
|
+
elif isinstance(cmd_obj, click.Command):
|
|
200
|
+
result = functools.partial(extract_command(cmd_obj, flow_parameters), _self)
|
|
201
|
+
else:
|
|
202
|
+
raise RuntimeError(
|
|
203
|
+
"Cannot handle %s of type %s" % (cmd_obj.name, type(cmd_obj))
|
|
204
|
+
)
|
|
205
|
+
setattr(_self, name, result)
|
|
206
|
+
return result
|
|
207
|
+
else:
|
|
208
|
+
raise AttributeError()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def get_annotation(param: click.Parameter) -> TTuple[Type, bool]:
|
|
212
|
+
py_type = click_to_python_types[type(param.type)]
|
|
213
|
+
if param.nargs == -1:
|
|
214
|
+
# This is the equivalent of *args effectively
|
|
215
|
+
# so the type annotation should be the type of the
|
|
216
|
+
# elements in the list
|
|
217
|
+
return py_type, True
|
|
218
|
+
if not param.required:
|
|
219
|
+
if param.multiple or param.nargs > 1:
|
|
220
|
+
return Optional[Union[List[py_type], TTuple[py_type]]], False
|
|
221
|
+
else:
|
|
222
|
+
return Optional[py_type], False
|
|
223
|
+
else:
|
|
224
|
+
if param.multiple or param.nargs > 1:
|
|
225
|
+
return Union[List[py_type], TTuple[py_type]], False
|
|
226
|
+
else:
|
|
227
|
+
return py_type, False
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def get_inspect_param_obj(p: Union[click.Argument, click.Option], kind: str):
|
|
231
|
+
annotation, is_vararg = get_annotation(p)
|
|
232
|
+
return (
|
|
233
|
+
inspect.Parameter(
|
|
234
|
+
name="args" if is_vararg else p.name,
|
|
235
|
+
kind=inspect.Parameter.VAR_POSITIONAL if is_vararg else kind,
|
|
236
|
+
default=inspect.Parameter.empty if is_vararg else p.default,
|
|
237
|
+
annotation=annotation,
|
|
238
|
+
),
|
|
239
|
+
(
|
|
240
|
+
Optional[Union[TTuple[annotation], List[annotation]]]
|
|
241
|
+
if is_vararg
|
|
242
|
+
else annotation
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# Cache to store already loaded modules
|
|
248
|
+
loaded_modules = {}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def extract_flow_class_from_file(flow_file: str) -> FlowSpec:
|
|
252
|
+
if not os.path.exists(flow_file):
|
|
253
|
+
raise FileNotFoundError("Flow file not present at '%s'" % flow_file)
|
|
254
|
+
|
|
255
|
+
flow_dir = os.path.dirname(os.path.abspath(flow_file))
|
|
256
|
+
path_was_added = False
|
|
257
|
+
|
|
258
|
+
# Only add to path if it's not already there
|
|
259
|
+
if flow_dir not in sys.path:
|
|
260
|
+
sys.path.insert(0, flow_dir)
|
|
261
|
+
path_was_added = True
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
# Get module name from the file path
|
|
265
|
+
module_name = os.path.splitext(os.path.basename(flow_file))[0]
|
|
266
|
+
|
|
267
|
+
# Check if the module has already been loaded
|
|
268
|
+
if flow_file in loaded_modules:
|
|
269
|
+
module = loaded_modules[flow_file]
|
|
270
|
+
else:
|
|
271
|
+
# Load the module if it's not already loaded
|
|
272
|
+
spec = importlib.util.spec_from_file_location(module_name, flow_file)
|
|
273
|
+
module = importlib.util.module_from_spec(spec)
|
|
274
|
+
spec.loader.exec_module(module)
|
|
275
|
+
# Cache the loaded module
|
|
276
|
+
loaded_modules[flow_file] = module
|
|
277
|
+
|
|
278
|
+
classes = inspect.getmembers(
|
|
279
|
+
module, lambda x: inspect.isclass(x) or isinstance(x, FlowMutator)
|
|
280
|
+
)
|
|
281
|
+
flow_cls = None
|
|
282
|
+
|
|
283
|
+
for _, kls in classes:
|
|
284
|
+
if isinstance(kls, FlowMutator):
|
|
285
|
+
kls = kls._flow_cls
|
|
286
|
+
if (
|
|
287
|
+
kls is not FlowSpec
|
|
288
|
+
and kls.__module__ == module_name
|
|
289
|
+
and issubclass(kls, FlowSpec)
|
|
290
|
+
):
|
|
291
|
+
if flow_cls is not None:
|
|
292
|
+
raise MetaflowException(
|
|
293
|
+
"Multiple FlowSpec classes found in %s" % flow_file
|
|
294
|
+
)
|
|
295
|
+
flow_cls = kls
|
|
296
|
+
|
|
297
|
+
if flow_cls is None:
|
|
298
|
+
raise MetaflowException("No FlowSpec class found in %s" % flow_file)
|
|
299
|
+
return flow_cls
|
|
300
|
+
finally:
|
|
301
|
+
# Only remove from path if we added it
|
|
302
|
+
if path_was_added:
|
|
303
|
+
try:
|
|
304
|
+
sys.path.remove(flow_dir)
|
|
305
|
+
except ValueError:
|
|
306
|
+
# User's code might have removed it already
|
|
307
|
+
pass
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class MetaflowAPI(object):
|
|
311
|
+
def __init__(self, parent=None, flow_cls=None, config_input=None, **kwargs):
|
|
312
|
+
self._parent = parent
|
|
313
|
+
self._chain = [{self._API_NAME: kwargs}]
|
|
314
|
+
self._flow_cls = flow_cls
|
|
315
|
+
self._config_input = config_input
|
|
316
|
+
self._cached_computed_parameters = None
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def parent(self):
|
|
320
|
+
if self._parent:
|
|
321
|
+
return self._parent
|
|
322
|
+
return None
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def chain(self):
|
|
326
|
+
return self._chain
|
|
327
|
+
|
|
328
|
+
@property
|
|
329
|
+
def name(self):
|
|
330
|
+
return self._API_NAME
|
|
331
|
+
|
|
332
|
+
@classmethod
|
|
333
|
+
def from_cli(cls, flow_file: str, cli_collection: Callable) -> Callable:
|
|
334
|
+
flow_cls = extract_flow_class_from_file(flow_file)
|
|
335
|
+
|
|
336
|
+
with flow_context(flow_cls) as _:
|
|
337
|
+
cli_collection, config_input = config_options_with_config_input(
|
|
338
|
+
cli_collection
|
|
339
|
+
)
|
|
340
|
+
cli_collection = add_decorator_options(cli_collection)
|
|
341
|
+
|
|
342
|
+
def getattr_wrapper(_self, name):
|
|
343
|
+
# Functools.partial do not automatically bind self (no __get__)
|
|
344
|
+
with flow_context(flow_cls) as _:
|
|
345
|
+
# We also wrap this in the proper flow context because since commands
|
|
346
|
+
# are loaded lazily, we need the proper flow context to compute things
|
|
347
|
+
# like parameters. If we do not do this, the outer flow's context will
|
|
348
|
+
# be used.
|
|
349
|
+
return _self._internal_getattr(_self, name)
|
|
350
|
+
|
|
351
|
+
class_dict = {
|
|
352
|
+
"__module__": "metaflow",
|
|
353
|
+
"_API_NAME": flow_file,
|
|
354
|
+
"_internal_getattr": staticmethod(
|
|
355
|
+
functools.partial(
|
|
356
|
+
_lazy_load_command, cli_collection, "_compute_flow_parameters"
|
|
357
|
+
)
|
|
358
|
+
),
|
|
359
|
+
"__getattr__": getattr_wrapper,
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
to_return = type(flow_file, (MetaflowAPI,), class_dict)
|
|
363
|
+
to_return.__name__ = flow_file
|
|
364
|
+
|
|
365
|
+
(
|
|
366
|
+
params_sigs,
|
|
367
|
+
possible_arg_params,
|
|
368
|
+
possible_opt_params,
|
|
369
|
+
annotations,
|
|
370
|
+
defaults,
|
|
371
|
+
) = extract_all_params(cli_collection)
|
|
372
|
+
|
|
373
|
+
def _method(_self, *args, **kwargs):
|
|
374
|
+
method_params = _method_sanity_check(
|
|
375
|
+
possible_arg_params,
|
|
376
|
+
possible_opt_params,
|
|
377
|
+
annotations,
|
|
378
|
+
defaults,
|
|
379
|
+
**kwargs,
|
|
380
|
+
)
|
|
381
|
+
return to_return(
|
|
382
|
+
parent=None,
|
|
383
|
+
flow_cls=flow_cls,
|
|
384
|
+
config_input=config_input,
|
|
385
|
+
**method_params,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
m = _method
|
|
389
|
+
m.__name__ = cli_collection.name
|
|
390
|
+
m.__doc__ = getattr(cli_collection, "help", None)
|
|
391
|
+
m.__signature__ = inspect.signature(_method).replace(
|
|
392
|
+
parameters=params_sigs.values()
|
|
393
|
+
)
|
|
394
|
+
m.__annotations__ = annotations
|
|
395
|
+
m.__defaults__ = tuple(defaults.values())
|
|
396
|
+
|
|
397
|
+
return m
|
|
398
|
+
|
|
399
|
+
def execute(self) -> List[str]:
|
|
400
|
+
parents = []
|
|
401
|
+
current = self
|
|
402
|
+
while current.parent:
|
|
403
|
+
parents.append(current.parent)
|
|
404
|
+
current = current.parent
|
|
405
|
+
|
|
406
|
+
parents.reverse()
|
|
407
|
+
|
|
408
|
+
final_chain = list(itertools.chain.from_iterable([p.chain for p in parents]))
|
|
409
|
+
final_chain.extend(self.chain)
|
|
410
|
+
|
|
411
|
+
components = []
|
|
412
|
+
for each_cmd in final_chain:
|
|
413
|
+
for cmd, params in each_cmd.items():
|
|
414
|
+
components.append(cmd)
|
|
415
|
+
args = params.pop("args", {})
|
|
416
|
+
options = params.pop("options", {})
|
|
417
|
+
|
|
418
|
+
for _, v in args.items():
|
|
419
|
+
if v is None:
|
|
420
|
+
continue
|
|
421
|
+
if isinstance(v, (list, tuple)):
|
|
422
|
+
for i in v:
|
|
423
|
+
components.append(i)
|
|
424
|
+
else:
|
|
425
|
+
components.append(v)
|
|
426
|
+
for k, v in options.items():
|
|
427
|
+
if v is None:
|
|
428
|
+
continue
|
|
429
|
+
if isinstance(v, list):
|
|
430
|
+
for i in v:
|
|
431
|
+
if isinstance(i, tuple):
|
|
432
|
+
components.append("--%s" % k)
|
|
433
|
+
components.extend(map(str, i))
|
|
434
|
+
else:
|
|
435
|
+
components.append("--%s" % k)
|
|
436
|
+
components.append(str(i))
|
|
437
|
+
elif v is None:
|
|
438
|
+
continue # Skip None values -- they are defaults and converting
|
|
439
|
+
# them to string will not be what the user wants
|
|
440
|
+
else:
|
|
441
|
+
components.append("--%s" % k)
|
|
442
|
+
if v != "flag":
|
|
443
|
+
components.append(str(v))
|
|
444
|
+
|
|
445
|
+
return components
|
|
446
|
+
|
|
447
|
+
def _compute_flow_parameters(self):
|
|
448
|
+
if (
|
|
449
|
+
self._flow_cls is None
|
|
450
|
+
or self._config_input is None
|
|
451
|
+
or self._parent is not None
|
|
452
|
+
):
|
|
453
|
+
raise RuntimeError(
|
|
454
|
+
"Computing flow-level parameters for a non start API. "
|
|
455
|
+
"Please report to the Metaflow team."
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
if self._cached_computed_parameters is not None:
|
|
459
|
+
return self._cached_computed_parameters
|
|
460
|
+
self._cached_computed_parameters = []
|
|
461
|
+
|
|
462
|
+
config_options = None
|
|
463
|
+
if CLICK_API_PROCESS_CONFIG:
|
|
464
|
+
with flow_context(self._flow_cls) as _:
|
|
465
|
+
# We are going to resolve the configs first and then get the parameters.
|
|
466
|
+
# Note that configs may update/add parameters so the order is important
|
|
467
|
+
# Since part of the processing of configs happens by click, we need to
|
|
468
|
+
# "fake" it.
|
|
469
|
+
|
|
470
|
+
# Extract any config options as well as datastore and quiet options
|
|
471
|
+
method_params = self._chain[0][self._API_NAME]
|
|
472
|
+
opts = method_params["options"]
|
|
473
|
+
defaults = method_params["defaults"]
|
|
474
|
+
|
|
475
|
+
ds = opts.get("datastore", defaults["datastore"])
|
|
476
|
+
quiet = opts.get("quiet", defaults["quiet"])
|
|
477
|
+
|
|
478
|
+
# Order to find config or config_value:
|
|
479
|
+
# 1. Passed directly to the Click API
|
|
480
|
+
# 2. If not found, check if passed through an environment variable
|
|
481
|
+
# 3. If not found, use the default value
|
|
482
|
+
is_default = False
|
|
483
|
+
config_file = opts.get("config")
|
|
484
|
+
if config_file is None:
|
|
485
|
+
# Check if it was set through an environment variable -- we
|
|
486
|
+
# don't have click process them here so we need to "fake" it.
|
|
487
|
+
env_config_file = os.environ.get("METAFLOW_FLOW_CONFIG")
|
|
488
|
+
if env_config_file:
|
|
489
|
+
# Convert dict items to list of tuples
|
|
490
|
+
config_file = list(json.loads(env_config_file).items())
|
|
491
|
+
is_default = False
|
|
492
|
+
else:
|
|
493
|
+
is_default = True
|
|
494
|
+
config_file = defaults.get("config")
|
|
495
|
+
|
|
496
|
+
if config_file:
|
|
497
|
+
config_file = dict(
|
|
498
|
+
map(
|
|
499
|
+
lambda x: (
|
|
500
|
+
x[0],
|
|
501
|
+
ConvertPath.convert_value(x[1], is_default),
|
|
502
|
+
),
|
|
503
|
+
config_file,
|
|
504
|
+
)
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
is_default = False
|
|
508
|
+
config_value = opts.get("config-value")
|
|
509
|
+
if config_value is None:
|
|
510
|
+
env_config_value = os.environ.get("METAFLOW_FLOW_CONFIG_VALUE")
|
|
511
|
+
if env_config_value:
|
|
512
|
+
# Parse environment variable using MultipleTuple logic
|
|
513
|
+
loaded = json.loads(env_config_value)
|
|
514
|
+
# Convert dict items to list of tuples with JSON-serialized values
|
|
515
|
+
config_value = [
|
|
516
|
+
(k, json.dumps(v) if not isinstance(v, str) else v)
|
|
517
|
+
for k, v in loaded.items()
|
|
518
|
+
]
|
|
519
|
+
is_default = False
|
|
520
|
+
else:
|
|
521
|
+
is_default = True
|
|
522
|
+
config_value = defaults.get("config_value")
|
|
523
|
+
|
|
524
|
+
if config_value:
|
|
525
|
+
config_value = dict(
|
|
526
|
+
map(
|
|
527
|
+
lambda x: (
|
|
528
|
+
x[0],
|
|
529
|
+
ConvertDictOrStr.convert_value(x[1], is_default),
|
|
530
|
+
),
|
|
531
|
+
config_value,
|
|
532
|
+
)
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
if (config_file is None) ^ (config_value is None):
|
|
536
|
+
# If we have one, we should have the other
|
|
537
|
+
raise MetaflowException(
|
|
538
|
+
"Options were not properly set -- this is an internal error."
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
if config_file:
|
|
542
|
+
# Process both configurations; the second one will return all the merged
|
|
543
|
+
# configuration options properly processed.
|
|
544
|
+
self._config_input.process_configs(
|
|
545
|
+
self._flow_cls.__name__, "config", config_file, quiet, ds
|
|
546
|
+
)
|
|
547
|
+
config_options = self._config_input.process_configs(
|
|
548
|
+
self._flow_cls.__name__, "config_value", config_value, quiet, ds
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
# At this point, we are like in start() in cli.py -- we obtained the
|
|
552
|
+
# properly processed config_options which we can now use to process
|
|
553
|
+
# the config decorators (including StepMutator/FlowMutator)
|
|
554
|
+
# Note that if CLICK_API_PROCESS_CONFIG is False, we still do this because
|
|
555
|
+
# it will init all parameters (config_options will be None)
|
|
556
|
+
# We ignore any errors if we don't check the configs in the click API.
|
|
557
|
+
|
|
558
|
+
# Init all values in the flow mutators and then process them
|
|
559
|
+
for decorator in self._flow_cls._flow_state[FlowStateItems.FLOW_MUTATORS]:
|
|
560
|
+
decorator.external_init()
|
|
561
|
+
|
|
562
|
+
new_cls = self._flow_cls._process_config_decorators(
|
|
563
|
+
config_options, process_configs=CLICK_API_PROCESS_CONFIG
|
|
564
|
+
)
|
|
565
|
+
if new_cls:
|
|
566
|
+
self._flow_cls = new_cls
|
|
567
|
+
|
|
568
|
+
for _, param in self._flow_cls._get_parameters():
|
|
569
|
+
if param.IS_CONFIG_PARAMETER:
|
|
570
|
+
continue
|
|
571
|
+
self._cached_computed_parameters.append(param)
|
|
572
|
+
return self._cached_computed_parameters
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def extract_all_params(cmd_obj: Union[click.Command, click.Group]):
|
|
576
|
+
arg_params_sigs = OrderedDict()
|
|
577
|
+
opt_params_sigs = OrderedDict()
|
|
578
|
+
params_sigs = OrderedDict()
|
|
579
|
+
|
|
580
|
+
arg_parameters = OrderedDict()
|
|
581
|
+
opt_parameters = OrderedDict()
|
|
582
|
+
annotations = OrderedDict()
|
|
583
|
+
defaults = OrderedDict()
|
|
584
|
+
|
|
585
|
+
for each_param in cmd_obj.params:
|
|
586
|
+
if isinstance(each_param, click.Argument):
|
|
587
|
+
(
|
|
588
|
+
arg_params_sigs[each_param.name],
|
|
589
|
+
annotations[each_param.name],
|
|
590
|
+
) = get_inspect_param_obj(each_param, inspect.Parameter.POSITIONAL_ONLY)
|
|
591
|
+
arg_parameters[each_param.name] = each_param
|
|
592
|
+
elif isinstance(each_param, click.Option):
|
|
593
|
+
(
|
|
594
|
+
opt_params_sigs[each_param.name],
|
|
595
|
+
annotations[each_param.name],
|
|
596
|
+
) = get_inspect_param_obj(each_param, inspect.Parameter.KEYWORD_ONLY)
|
|
597
|
+
opt_parameters[each_param.name] = each_param
|
|
598
|
+
|
|
599
|
+
defaults[each_param.name] = each_param.default
|
|
600
|
+
|
|
601
|
+
# first, fill in positional arguments
|
|
602
|
+
for name, each_arg_param in arg_params_sigs.items():
|
|
603
|
+
params_sigs[name] = each_arg_param
|
|
604
|
+
# then, fill in keyword arguments
|
|
605
|
+
for name, each_opt_param in opt_params_sigs.items():
|
|
606
|
+
params_sigs[name] = each_opt_param
|
|
607
|
+
|
|
608
|
+
return params_sigs, arg_parameters, opt_parameters, annotations, defaults
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def extract_group(cmd_obj: click.Group, flow_parameters: List[Parameter]) -> Callable:
|
|
612
|
+
class_dict = {"__module__": "metaflow", "_API_NAME": cmd_obj.name}
|
|
613
|
+
for _, sub_cmd_obj in cmd_obj.commands.items():
|
|
614
|
+
if isinstance(sub_cmd_obj, click.Group):
|
|
615
|
+
# recursion
|
|
616
|
+
class_dict[sub_cmd_obj.name] = extract_group(sub_cmd_obj, flow_parameters)
|
|
617
|
+
elif isinstance(sub_cmd_obj, click.Command):
|
|
618
|
+
class_dict[sub_cmd_obj.name] = extract_command(sub_cmd_obj, flow_parameters)
|
|
619
|
+
else:
|
|
620
|
+
raise RuntimeError(
|
|
621
|
+
"Cannot handle %s of type %s" % (sub_cmd_obj.name, type(sub_cmd_obj))
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
resulting_class = type(cmd_obj.name, (MetaflowAPI,), class_dict)
|
|
625
|
+
resulting_class.__name__ = cmd_obj.name
|
|
626
|
+
|
|
627
|
+
(
|
|
628
|
+
params_sigs,
|
|
629
|
+
possible_arg_params,
|
|
630
|
+
possible_opt_params,
|
|
631
|
+
annotations,
|
|
632
|
+
defaults,
|
|
633
|
+
) = extract_all_params(cmd_obj)
|
|
634
|
+
|
|
635
|
+
def _method(_self, *args, **kwargs):
|
|
636
|
+
method_params = _method_sanity_check(
|
|
637
|
+
possible_arg_params, possible_opt_params, annotations, defaults, **kwargs
|
|
638
|
+
)
|
|
639
|
+
return resulting_class(parent=_self, flow_cls=None, **method_params)
|
|
640
|
+
|
|
641
|
+
m = _method
|
|
642
|
+
m.__name__ = cmd_obj.name
|
|
643
|
+
m.__doc__ = getattr(cmd_obj, "help", None)
|
|
644
|
+
m.__signature__ = inspect.signature(_method).replace(
|
|
645
|
+
parameters=params_sigs.values()
|
|
646
|
+
)
|
|
647
|
+
m.__annotations__ = annotations
|
|
648
|
+
m.__defaults__ = tuple(defaults.values())
|
|
649
|
+
|
|
650
|
+
return m
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def extract_command(
|
|
654
|
+
cmd_obj: click.Command, flow_parameters: List[Parameter]
|
|
655
|
+
) -> Callable:
|
|
656
|
+
if getattr(cmd_obj, "has_flow_params", False):
|
|
657
|
+
for p in flow_parameters[::-1]:
|
|
658
|
+
cmd_obj.params.insert(0, click.Option(("--" + p.name,), **p.kwargs))
|
|
659
|
+
|
|
660
|
+
(
|
|
661
|
+
params_sigs,
|
|
662
|
+
possible_arg_params,
|
|
663
|
+
possible_opt_params,
|
|
664
|
+
annotations,
|
|
665
|
+
defaults,
|
|
666
|
+
) = extract_all_params(cmd_obj)
|
|
667
|
+
|
|
668
|
+
def _method(_self, *args, **kwargs):
|
|
669
|
+
method_params = _method_sanity_check(
|
|
670
|
+
possible_arg_params, possible_opt_params, annotations, defaults, **kwargs
|
|
671
|
+
)
|
|
672
|
+
_self._chain.append({cmd_obj.name: method_params})
|
|
673
|
+
return _self.execute()
|
|
674
|
+
|
|
675
|
+
m = _method
|
|
676
|
+
m.__name__ = cmd_obj.name
|
|
677
|
+
m.__doc__ = getattr(cmd_obj, "help", None)
|
|
678
|
+
m.__signature__ = inspect.signature(_method).replace(
|
|
679
|
+
parameters=params_sigs.values()
|
|
680
|
+
)
|
|
681
|
+
m.__annotations__ = annotations
|
|
682
|
+
m.__defaults__ = tuple(defaults.values())
|
|
683
|
+
|
|
684
|
+
return m
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
if __name__ == "__main__":
|
|
688
|
+
from metaflow.cli import start
|
|
689
|
+
|
|
690
|
+
api = MetaflowAPI.from_cli("../try.py", start)
|
|
691
|
+
|
|
692
|
+
command = api(metadata="local").run(
|
|
693
|
+
tags=["abc", "def"],
|
|
694
|
+
decospecs=["kubernetes"],
|
|
695
|
+
max_workers=5,
|
|
696
|
+
alpha=3,
|
|
697
|
+
myfile="path/to/file",
|
|
698
|
+
)
|
|
699
|
+
print(" ".join(command))
|
|
700
|
+
|
|
701
|
+
command = (
|
|
702
|
+
api(metadata="local")
|
|
703
|
+
.kubernetes()
|
|
704
|
+
.step(
|
|
705
|
+
step_name="process",
|
|
706
|
+
code_package_metadata="some_version",
|
|
707
|
+
code_package_sha="some_sha",
|
|
708
|
+
code_package_url="some_url",
|
|
709
|
+
)
|
|
710
|
+
)
|
|
711
|
+
print(" ".join(command))
|
|
712
|
+
|
|
713
|
+
command = api().tag().add(tags=["abc", "def"])
|
|
714
|
+
print(" ".join(command))
|
|
715
|
+
|
|
716
|
+
command = getattr(api(decospecs=["retry"]), "argo-workflows")().create()
|
|
717
|
+
print(" ".join(command))
|