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,598 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import json
|
|
3
|
+
import collections.abc
|
|
4
|
+
import copy
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
from typing import (
|
|
9
|
+
Any,
|
|
10
|
+
Callable,
|
|
11
|
+
Dict,
|
|
12
|
+
Iterable,
|
|
13
|
+
Iterator,
|
|
14
|
+
List,
|
|
15
|
+
Optional,
|
|
16
|
+
Tuple,
|
|
17
|
+
TYPE_CHECKING,
|
|
18
|
+
Union,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
from ..exception import MetaflowException
|
|
23
|
+
|
|
24
|
+
from ..parameters import (
|
|
25
|
+
Parameter,
|
|
26
|
+
ParameterContext,
|
|
27
|
+
current_flow,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from metaflow import FlowSpec
|
|
32
|
+
|
|
33
|
+
# _tracefunc_depth = 0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# def tracefunc(func):
|
|
37
|
+
# """Decorates a function to show its trace."""
|
|
38
|
+
|
|
39
|
+
# @functools.wraps(func)
|
|
40
|
+
# def tracefunc_closure(*args, **kwargs):
|
|
41
|
+
# global _tracefunc_depth
|
|
42
|
+
# """The closure."""
|
|
43
|
+
# print(f"{_tracefunc_depth}: {func.__name__}(args={args}, kwargs={kwargs})")
|
|
44
|
+
# _tracefunc_depth += 1
|
|
45
|
+
# result = func(*args, **kwargs)
|
|
46
|
+
# _tracefunc_depth -= 1
|
|
47
|
+
# print(f"{_tracefunc_depth} => {result}")
|
|
48
|
+
# return result
|
|
49
|
+
|
|
50
|
+
# return tracefunc_closure
|
|
51
|
+
|
|
52
|
+
ID_PATTERN = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
|
|
53
|
+
|
|
54
|
+
UNPACK_KEY = "_unpacked_delayed_"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def dump_config_values(flow: "FlowSpec"):
|
|
58
|
+
from ..flowspec import FlowStateItems # Prevent circular import
|
|
59
|
+
|
|
60
|
+
configs = flow._flow_state[FlowStateItems.CONFIGS]
|
|
61
|
+
if configs:
|
|
62
|
+
return {"user_configs": configs}
|
|
63
|
+
return {}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ConfigValue(collections.abc.Mapping, dict):
|
|
67
|
+
"""
|
|
68
|
+
ConfigValue is a thin wrapper around an arbitrarily nested dictionary-like
|
|
69
|
+
configuration object. It allows you to access elements of this nested structure
|
|
70
|
+
using either a "." notation or a [] notation. As an example, if your configuration
|
|
71
|
+
object is:
|
|
72
|
+
{"foo": {"bar": 42}}
|
|
73
|
+
you can access the value 42 using either config["foo"]["bar"] or config.foo.bar.
|
|
74
|
+
|
|
75
|
+
All "keys"" need to be valid Python identifiers
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
# Thin wrapper to allow configuration values to be accessed using a "." notation
|
|
79
|
+
# as well as a [] notation.
|
|
80
|
+
|
|
81
|
+
# We inherit from dict to allow the isinstanceof check to work easily and also
|
|
82
|
+
# to provide a simple json dumps functionality.
|
|
83
|
+
|
|
84
|
+
def __init__(self, data: Union["ConfigValue", Dict[str, Any]]):
|
|
85
|
+
self._data = {k: self._construct(v) for k, v in data.items()}
|
|
86
|
+
|
|
87
|
+
# Enable json dumps
|
|
88
|
+
dict.__init__(self, self._data)
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def fromkeys(cls, iterable: Iterable, value: Any = None) -> "ConfigValue":
|
|
92
|
+
"""
|
|
93
|
+
Creates a new ConfigValue object from the given iterable and value.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
iterable : Iterable
|
|
98
|
+
Iterable to create the ConfigValue from.
|
|
99
|
+
value : Any, optional
|
|
100
|
+
Value to set for each key in the iterable.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
ConfigValue
|
|
105
|
+
A new ConfigValue object.
|
|
106
|
+
"""
|
|
107
|
+
return cls(dict.fromkeys(iterable, value))
|
|
108
|
+
|
|
109
|
+
def to_dict(self) -> Dict[Any, Any]:
|
|
110
|
+
"""
|
|
111
|
+
Returns a dictionary representation of this configuration object.
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
Dict[Any, Any]
|
|
116
|
+
Dictionary equivalent of this configuration object.
|
|
117
|
+
"""
|
|
118
|
+
return self._to_dict(self._data)
|
|
119
|
+
|
|
120
|
+
def copy(self) -> "ConfigValue":
|
|
121
|
+
return self.__copy__()
|
|
122
|
+
|
|
123
|
+
def clear(self) -> None:
|
|
124
|
+
# Prevent configuration modification
|
|
125
|
+
raise TypeError("ConfigValue is immutable")
|
|
126
|
+
|
|
127
|
+
def update(self, *args, **kwargs) -> None:
|
|
128
|
+
# Prevent configuration modification
|
|
129
|
+
raise TypeError("ConfigValue is immutable")
|
|
130
|
+
|
|
131
|
+
def setdefault(self, key: Any, default: Any = None) -> Any:
|
|
132
|
+
# Prevent configuration modification
|
|
133
|
+
raise TypeError("ConfigValue is immutable")
|
|
134
|
+
|
|
135
|
+
def pop(self, key: Any, default: Any = None) -> Any:
|
|
136
|
+
# Prevent configuration modification
|
|
137
|
+
raise TypeError("ConfigValue is immutable")
|
|
138
|
+
|
|
139
|
+
def popitem(self) -> Tuple[Any, Any]:
|
|
140
|
+
# Prevent configuration modification
|
|
141
|
+
raise TypeError("ConfigValue is immutable")
|
|
142
|
+
|
|
143
|
+
def __getattr__(self, key: str) -> Any:
|
|
144
|
+
"""
|
|
145
|
+
Access an element of this configuration
|
|
146
|
+
|
|
147
|
+
Parameters
|
|
148
|
+
----------
|
|
149
|
+
key : str
|
|
150
|
+
Element to access
|
|
151
|
+
|
|
152
|
+
Returns
|
|
153
|
+
-------
|
|
154
|
+
Any
|
|
155
|
+
Element of the configuration
|
|
156
|
+
"""
|
|
157
|
+
if key == "_data":
|
|
158
|
+
# Called during unpickling. Special case to not run into infinite loop
|
|
159
|
+
# below.
|
|
160
|
+
raise AttributeError(key)
|
|
161
|
+
|
|
162
|
+
if key in self._data:
|
|
163
|
+
return self[key]
|
|
164
|
+
raise AttributeError(key)
|
|
165
|
+
|
|
166
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
167
|
+
# Prevent configuration modification
|
|
168
|
+
if name == "_data":
|
|
169
|
+
return super().__setattr__(name, value)
|
|
170
|
+
raise TypeError("ConfigValue is immutable")
|
|
171
|
+
|
|
172
|
+
def __getitem__(self, key: Any) -> Any:
|
|
173
|
+
"""
|
|
174
|
+
Access an element of this configuration
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
key : Any
|
|
179
|
+
Element to access
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
Any
|
|
184
|
+
Element of the configuration
|
|
185
|
+
"""
|
|
186
|
+
return self._data[key]
|
|
187
|
+
|
|
188
|
+
def __setitem__(self, key: Any, value: Any) -> None:
|
|
189
|
+
# Prevent configuration modification
|
|
190
|
+
raise TypeError("ConfigValue is immutable")
|
|
191
|
+
|
|
192
|
+
def __delattr__(self, key) -> None:
|
|
193
|
+
# Prevent configuration modification
|
|
194
|
+
raise TypeError("ConfigValue is immutable")
|
|
195
|
+
|
|
196
|
+
def __delitem__(self, key: Any) -> None:
|
|
197
|
+
# Prevent configuration modification
|
|
198
|
+
raise TypeError("ConfigValue is immutable")
|
|
199
|
+
|
|
200
|
+
def __len__(self) -> int:
|
|
201
|
+
return len(self._data)
|
|
202
|
+
|
|
203
|
+
def __iter__(self) -> Iterator:
|
|
204
|
+
return iter(self._data)
|
|
205
|
+
|
|
206
|
+
def __eq__(self, other: Any) -> bool:
|
|
207
|
+
if isinstance(other, ConfigValue):
|
|
208
|
+
return self._data == other._data
|
|
209
|
+
if isinstance(other, dict):
|
|
210
|
+
return self._data == other
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
def __ne__(self, other: Any) -> bool:
|
|
214
|
+
return not self.__eq__(other)
|
|
215
|
+
|
|
216
|
+
def __copy__(self) -> "ConfigValue":
|
|
217
|
+
cls = self.__class__
|
|
218
|
+
result = cls.__new__(cls)
|
|
219
|
+
result.__dict__.update({k: copy.copy(v) for k, v in self.__dict__.items()})
|
|
220
|
+
return result
|
|
221
|
+
|
|
222
|
+
def __repr__(self) -> str:
|
|
223
|
+
return repr(self._data)
|
|
224
|
+
|
|
225
|
+
def __str__(self) -> str:
|
|
226
|
+
return str(self._data)
|
|
227
|
+
|
|
228
|
+
def __dir__(self) -> Iterable[str]:
|
|
229
|
+
return dir(type(self)) + [k for k in self._data.keys() if ID_PATTERN.match(k)]
|
|
230
|
+
|
|
231
|
+
def __contains__(self, key: Any) -> bool:
|
|
232
|
+
try:
|
|
233
|
+
self[key]
|
|
234
|
+
except KeyError:
|
|
235
|
+
return False
|
|
236
|
+
return True
|
|
237
|
+
|
|
238
|
+
def keys(self):
|
|
239
|
+
"""
|
|
240
|
+
Returns the keys of this configuration object.
|
|
241
|
+
|
|
242
|
+
Returns
|
|
243
|
+
-------
|
|
244
|
+
Any
|
|
245
|
+
Keys of this configuration object.
|
|
246
|
+
"""
|
|
247
|
+
return self._data.keys()
|
|
248
|
+
|
|
249
|
+
@classmethod
|
|
250
|
+
def _construct(cls, obj: Any) -> Any:
|
|
251
|
+
# Internal method to construct a ConfigValue so that all mappings internally
|
|
252
|
+
# are also converted to ConfigValue
|
|
253
|
+
if isinstance(obj, ConfigValue):
|
|
254
|
+
v = obj
|
|
255
|
+
elif isinstance(obj, collections.abc.Mapping):
|
|
256
|
+
v = ConfigValue({k: cls._construct(v) for k, v in obj.items()})
|
|
257
|
+
elif isinstance(obj, (list, tuple, set)):
|
|
258
|
+
v = type(obj)([cls._construct(x) for x in obj])
|
|
259
|
+
else:
|
|
260
|
+
v = obj
|
|
261
|
+
return v
|
|
262
|
+
|
|
263
|
+
@classmethod
|
|
264
|
+
def _to_dict(cls, obj: Any) -> Any:
|
|
265
|
+
# Internal method to convert all nested mappings to dicts
|
|
266
|
+
if isinstance(obj, collections.abc.Mapping):
|
|
267
|
+
v = {k: cls._to_dict(v) for k, v in obj.items()}
|
|
268
|
+
elif isinstance(obj, (list, tuple, set)):
|
|
269
|
+
v = type(obj)([cls._to_dict(x) for x in obj])
|
|
270
|
+
else:
|
|
271
|
+
v = obj
|
|
272
|
+
return v
|
|
273
|
+
|
|
274
|
+
def __reduce__(self):
|
|
275
|
+
return (self.__class__, (self.to_dict(),))
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class DelayEvaluator(collections.abc.Mapping):
|
|
279
|
+
"""
|
|
280
|
+
Small wrapper that allows the evaluation of a Config() value in a delayed manner.
|
|
281
|
+
This is used when we want to use config.* values in decorators for example.
|
|
282
|
+
|
|
283
|
+
It also allows the following "delayed" access on an obj that is a DelayEvaluation
|
|
284
|
+
- obj.x.y.z (ie: accessing members of DelayEvaluator; accesses will be delayed until
|
|
285
|
+
the DelayEvaluator is evaluated)
|
|
286
|
+
- **obj (ie: unpacking the DelayEvaluator as a dictionary). Note that this requires
|
|
287
|
+
special handling in whatever this is being unpacked into, specifically the handling
|
|
288
|
+
of _unpacked_delayed_*
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
def __init__(self, ex: str, saved_globals: Optional[Dict[str, Any]] = None):
|
|
292
|
+
self._config_expr = ex
|
|
293
|
+
self._globals = saved_globals
|
|
294
|
+
if ID_PATTERN.match(self._config_expr):
|
|
295
|
+
# This is a variable only so allow things like config_expr("config").var
|
|
296
|
+
self._is_var_only = True
|
|
297
|
+
self._access = []
|
|
298
|
+
else:
|
|
299
|
+
self._is_var_only = False
|
|
300
|
+
self._access = None
|
|
301
|
+
self._cached_expr = None
|
|
302
|
+
|
|
303
|
+
def __copy__(self):
|
|
304
|
+
c = DelayEvaluator(self._config_expr)
|
|
305
|
+
c._access = self._access.copy() if self._access is not None else None
|
|
306
|
+
# Globals are not copied -- always kept as a reference
|
|
307
|
+
return c
|
|
308
|
+
|
|
309
|
+
def __deepcopy__(self, memo):
|
|
310
|
+
c = DelayEvaluator(self._config_expr)
|
|
311
|
+
c._access = (
|
|
312
|
+
copy.deepcopy(self._access, memo) if self._access is not None else None
|
|
313
|
+
)
|
|
314
|
+
# Globals are not copied -- always kept as a reference
|
|
315
|
+
return c
|
|
316
|
+
|
|
317
|
+
def __iter__(self):
|
|
318
|
+
yield "%s%d" % (UNPACK_KEY, id(self))
|
|
319
|
+
|
|
320
|
+
def __getitem__(self, key):
|
|
321
|
+
if isinstance(key, str) and key == "%s%d" % (UNPACK_KEY, id(self)):
|
|
322
|
+
return self
|
|
323
|
+
if self._access is None:
|
|
324
|
+
raise KeyError(key)
|
|
325
|
+
|
|
326
|
+
# Make a copy so that we can support something like
|
|
327
|
+
# foo = delay_evaluator["blah"]
|
|
328
|
+
# bar = delay_evaluator["baz"]
|
|
329
|
+
# and don't end up with a access list that contains both "blah" and "baz"
|
|
330
|
+
c = self.__copy__()
|
|
331
|
+
c._access.append(key)
|
|
332
|
+
c._cached_expr = None
|
|
333
|
+
return c
|
|
334
|
+
|
|
335
|
+
def __len__(self):
|
|
336
|
+
return 1
|
|
337
|
+
|
|
338
|
+
def __getattr__(self, name):
|
|
339
|
+
if self._access is None:
|
|
340
|
+
raise AttributeError(name)
|
|
341
|
+
c = self.__copy__()
|
|
342
|
+
c._access.append(name)
|
|
343
|
+
c._cached_expr = None
|
|
344
|
+
return c
|
|
345
|
+
|
|
346
|
+
def __call__(self, ctx=None, deploy_time=False):
|
|
347
|
+
from ..flowspec import FlowStateItems # Prevent circular import
|
|
348
|
+
|
|
349
|
+
# Two additional arguments are only used by DeployTimeField which will call
|
|
350
|
+
# this function with those two additional arguments. They are ignored.
|
|
351
|
+
flow_cls = getattr(current_flow, "flow_cls", None)
|
|
352
|
+
if flow_cls is None:
|
|
353
|
+
# We are not executing inside a flow (ie: not the CLI)
|
|
354
|
+
raise MetaflowException(
|
|
355
|
+
"Config object can only be used directly in the FlowSpec defining them "
|
|
356
|
+
"(or their flow decorators)."
|
|
357
|
+
)
|
|
358
|
+
if self._cached_expr is not None:
|
|
359
|
+
to_eval_expr = self._cached_expr
|
|
360
|
+
elif self._access is not None:
|
|
361
|
+
# Build the final expression by adding all the fields in access as . fields
|
|
362
|
+
access_list = [self._config_expr]
|
|
363
|
+
for a in self._access:
|
|
364
|
+
if isinstance(a, str):
|
|
365
|
+
access_list.append(a)
|
|
366
|
+
elif isinstance(a, DelayEvaluator):
|
|
367
|
+
# Supports things like config[other_config.selector].var
|
|
368
|
+
access_list.append(a())
|
|
369
|
+
else:
|
|
370
|
+
raise MetaflowException(
|
|
371
|
+
"Field '%s' of type '%s' is not supported" % (str(a), type(a))
|
|
372
|
+
)
|
|
373
|
+
to_eval_expr = self._cached_expr = ".".join(access_list)
|
|
374
|
+
else:
|
|
375
|
+
to_eval_expr = self._cached_expr = self._config_expr
|
|
376
|
+
# Evaluate the expression setting the config values as local variables
|
|
377
|
+
try:
|
|
378
|
+
return eval(
|
|
379
|
+
to_eval_expr,
|
|
380
|
+
self._globals or globals(),
|
|
381
|
+
{
|
|
382
|
+
k: ConfigValue(v) if v is not None else None
|
|
383
|
+
for k, v in flow_cls._flow_state[FlowStateItems.CONFIGS].items()
|
|
384
|
+
},
|
|
385
|
+
)
|
|
386
|
+
except NameError as e:
|
|
387
|
+
raise MetaflowException(
|
|
388
|
+
"Config expression '%s' could not be evaluated: %s"
|
|
389
|
+
% (to_eval_expr, str(e))
|
|
390
|
+
) from e
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def config_expr(expr: str) -> DelayEvaluator:
|
|
394
|
+
"""
|
|
395
|
+
Function to allow you to use an expression involving a config parameter in
|
|
396
|
+
places where it may not be directory accessible or if you want a more complicated
|
|
397
|
+
expression than just a single variable.
|
|
398
|
+
|
|
399
|
+
You can use it as follows:
|
|
400
|
+
- When the config is not directly accessible:
|
|
401
|
+
|
|
402
|
+
@project(name=config_expr("config").project.name)
|
|
403
|
+
class MyFlow(FlowSpec):
|
|
404
|
+
config = Config("config")
|
|
405
|
+
...
|
|
406
|
+
- When you want a more complex expression:
|
|
407
|
+
class MyFlow(FlowSpec):
|
|
408
|
+
config = Config("config")
|
|
409
|
+
|
|
410
|
+
@environment(vars={"foo": config_expr("config.bar.baz.lower()")})
|
|
411
|
+
@step
|
|
412
|
+
def start(self):
|
|
413
|
+
...
|
|
414
|
+
|
|
415
|
+
Parameters
|
|
416
|
+
----------
|
|
417
|
+
expr : str
|
|
418
|
+
Expression using the config values.
|
|
419
|
+
"""
|
|
420
|
+
# Get globals where the expression is defined so that the user can use
|
|
421
|
+
# something like `config_expr("my_func()")` in the expression.
|
|
422
|
+
parent_globals = inspect.currentframe().f_back.f_globals
|
|
423
|
+
return DelayEvaluator(expr, saved_globals=parent_globals)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
class Config(Parameter, collections.abc.Mapping):
|
|
427
|
+
"""
|
|
428
|
+
Includes a configuration for this flow.
|
|
429
|
+
|
|
430
|
+
`Config` is a special type of `Parameter` but differs in a few key areas:
|
|
431
|
+
- it is immutable and determined at deploy time (or prior to running if not deploying
|
|
432
|
+
to a scheduler)
|
|
433
|
+
- as such, it can be used anywhere in your code including in Metaflow decorators
|
|
434
|
+
|
|
435
|
+
The value of the configuration is determines as follows:
|
|
436
|
+
- use the user-provided file path or value. It is an error to provide both
|
|
437
|
+
- if none are present:
|
|
438
|
+
- if a default file path (default) is provided, attempt to read this file
|
|
439
|
+
- if the file is present, use that value. Note that the file will be used
|
|
440
|
+
even if it has an invalid syntax
|
|
441
|
+
- if the file is not present, and a default value is present, use that
|
|
442
|
+
- if still None and is required, this is an error.
|
|
443
|
+
|
|
444
|
+
Parameters
|
|
445
|
+
----------
|
|
446
|
+
name : str
|
|
447
|
+
User-visible configuration name.
|
|
448
|
+
default : Union[str, Callable[[ParameterContext], str], optional, default None
|
|
449
|
+
Default path from where to read this configuration. A function implies that the
|
|
450
|
+
value will be computed using that function.
|
|
451
|
+
You can only specify default or default_value, not both.
|
|
452
|
+
default_value : Union[str, Dict[str, Any], Callable[[ParameterContext, Union[str, Dict[str, Any]]], Any], optional, default None
|
|
453
|
+
Default value for the parameter. A function
|
|
454
|
+
implies that the value will be computed using that function.
|
|
455
|
+
You can only specify default or default_value, not both.
|
|
456
|
+
help : str, optional, default None
|
|
457
|
+
Help text to show in `run --help`.
|
|
458
|
+
required : bool, optional, default None
|
|
459
|
+
Require that the user specifies a value for the configuration. Note that if
|
|
460
|
+
a default or default_value is provided, the required flag is ignored.
|
|
461
|
+
A value of None is equivalent to False.
|
|
462
|
+
parser : Union[str, Callable[[str], Dict[Any, Any]]], optional, default None
|
|
463
|
+
If a callable, it is a function that can parse the configuration string
|
|
464
|
+
into an arbitrarily nested dictionary. If a string, the string should refer to
|
|
465
|
+
a function (like "my_parser_package.my_parser.my_parser_function") which should
|
|
466
|
+
be able to parse the configuration string into an arbitrarily nested dictionary.
|
|
467
|
+
If the name starts with a ".", it is assumed to be relative to "metaflow".
|
|
468
|
+
show_default : bool, default True
|
|
469
|
+
If True, show the default value in the help text.
|
|
470
|
+
"""
|
|
471
|
+
|
|
472
|
+
IS_CONFIG_PARAMETER = True
|
|
473
|
+
|
|
474
|
+
def __init__(
|
|
475
|
+
self,
|
|
476
|
+
name: str,
|
|
477
|
+
default: Optional[Union[str, Callable[[ParameterContext], str]]] = None,
|
|
478
|
+
default_value: Optional[
|
|
479
|
+
Union[
|
|
480
|
+
str,
|
|
481
|
+
Dict[str, Any],
|
|
482
|
+
Callable[[ParameterContext], Union[str, Dict[str, Any]]],
|
|
483
|
+
]
|
|
484
|
+
] = None,
|
|
485
|
+
help: Optional[str] = None,
|
|
486
|
+
required: Optional[bool] = None,
|
|
487
|
+
parser: Optional[Union[str, Callable[[str], Dict[Any, Any]]]] = None,
|
|
488
|
+
**kwargs: Dict[str, str]
|
|
489
|
+
):
|
|
490
|
+
if default is not None and default_value is not None:
|
|
491
|
+
raise MetaflowException(
|
|
492
|
+
"For config '%s', you can only specify default or default_value, not both"
|
|
493
|
+
% name
|
|
494
|
+
)
|
|
495
|
+
self._default_is_file = default is not None
|
|
496
|
+
kwargs["default"] = default if default is not None else default_value
|
|
497
|
+
super(Config, self).__init__(
|
|
498
|
+
name, required=required, help=help, type=str, **kwargs
|
|
499
|
+
)
|
|
500
|
+
super(Config, self).init()
|
|
501
|
+
|
|
502
|
+
if isinstance(kwargs.get("default", None), str):
|
|
503
|
+
kwargs["default"] = json.dumps(kwargs["default"])
|
|
504
|
+
self.parser = parser
|
|
505
|
+
self._computed_value = None
|
|
506
|
+
|
|
507
|
+
self._delayed_evaluator = None
|
|
508
|
+
|
|
509
|
+
def load_parameter(self, v):
|
|
510
|
+
return ConfigValue(v) if v is not None else None
|
|
511
|
+
|
|
512
|
+
def _store_value(self, v: Any) -> None:
|
|
513
|
+
self._computed_value = v
|
|
514
|
+
|
|
515
|
+
def _init_delayed_evaluator(self) -> None:
|
|
516
|
+
if self._delayed_evaluator is None:
|
|
517
|
+
self._delayed_evaluator = DelayEvaluator(self.name.lower())
|
|
518
|
+
|
|
519
|
+
# Support <config>.<var> syntax
|
|
520
|
+
def __getattr__(self, name):
|
|
521
|
+
# Need to return a new DelayEvaluator everytime because the evaluator will
|
|
522
|
+
# contain the "path" (ie: .name) and can be further accessed.
|
|
523
|
+
return getattr(DelayEvaluator(self.name.lower()), name)
|
|
524
|
+
|
|
525
|
+
# Next three methods are to implement mapping to support **<config> syntax. We
|
|
526
|
+
# need to be careful, however, to also support a regular `config["key"]` syntax
|
|
527
|
+
# which calls into `__getitem__` and therefore behaves like __getattr__ above.
|
|
528
|
+
def __iter__(self):
|
|
529
|
+
self._init_delayed_evaluator()
|
|
530
|
+
yield from self._delayed_evaluator
|
|
531
|
+
|
|
532
|
+
def __len__(self):
|
|
533
|
+
self._init_delayed_evaluator()
|
|
534
|
+
return len(self._delayed_evaluator)
|
|
535
|
+
|
|
536
|
+
def __getitem__(self, key):
|
|
537
|
+
self._init_delayed_evaluator()
|
|
538
|
+
if isinstance(key, str) and key.startswith(UNPACK_KEY):
|
|
539
|
+
return self._delayed_evaluator[key]
|
|
540
|
+
return DelayEvaluator(self.name.lower())[key]
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def resolve_delayed_evaluator(
|
|
544
|
+
v: Any, ignore_errors: bool = False, to_dict: bool = False
|
|
545
|
+
) -> Any:
|
|
546
|
+
# NOTE: We don't ignore errors in downstream calls because we want to have either
|
|
547
|
+
# all or nothing for the top-level call by the user.
|
|
548
|
+
try:
|
|
549
|
+
if isinstance(v, DelayEvaluator):
|
|
550
|
+
to_return = v()
|
|
551
|
+
if to_dict and isinstance(to_return, ConfigValue):
|
|
552
|
+
to_return = to_return.to_dict()
|
|
553
|
+
return to_return
|
|
554
|
+
if isinstance(v, dict):
|
|
555
|
+
return {
|
|
556
|
+
resolve_delayed_evaluator(
|
|
557
|
+
k, to_dict=to_dict
|
|
558
|
+
): resolve_delayed_evaluator(v, to_dict=to_dict)
|
|
559
|
+
for k, v in v.items()
|
|
560
|
+
}
|
|
561
|
+
if isinstance(v, list):
|
|
562
|
+
return [resolve_delayed_evaluator(x, to_dict=to_dict) for x in v]
|
|
563
|
+
if isinstance(v, tuple):
|
|
564
|
+
return tuple(resolve_delayed_evaluator(x, to_dict=to_dict) for x in v)
|
|
565
|
+
if isinstance(v, set):
|
|
566
|
+
return {resolve_delayed_evaluator(x, to_dict=to_dict) for x in v}
|
|
567
|
+
return v
|
|
568
|
+
except Exception as e:
|
|
569
|
+
if ignore_errors:
|
|
570
|
+
# Assumption is that default value of None is always allowed.
|
|
571
|
+
# This code path is *only* used when evaluating Parameters AND they
|
|
572
|
+
# use configs in their attributes AND the runner/deployer is being used
|
|
573
|
+
# AND CLICK_API_PROCESS_CONFIG is False. In those cases, all attributes in
|
|
574
|
+
# Parameter can be set to None except for required and show_default
|
|
575
|
+
# and even in those cases, a wrong value will have very limited consequence.
|
|
576
|
+
return None
|
|
577
|
+
raise e
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def unpack_delayed_evaluator(
|
|
581
|
+
to_unpack: Dict[str, Any], ignore_errors: bool = False
|
|
582
|
+
) -> Tuple[Dict[str, Any], List[str]]:
|
|
583
|
+
result = {}
|
|
584
|
+
new_keys = []
|
|
585
|
+
for k, v in to_unpack.items():
|
|
586
|
+
if not isinstance(k, str) or not k.startswith(UNPACK_KEY):
|
|
587
|
+
result[k] = v
|
|
588
|
+
else:
|
|
589
|
+
# k.startswith(UNPACK_KEY)
|
|
590
|
+
try:
|
|
591
|
+
new_vals = resolve_delayed_evaluator(v, to_dict=True)
|
|
592
|
+
new_keys.extend(new_vals.keys())
|
|
593
|
+
result.update(new_vals)
|
|
594
|
+
except Exception as e:
|
|
595
|
+
if ignore_errors:
|
|
596
|
+
continue
|
|
597
|
+
raise e
|
|
598
|
+
return result, new_keys
|
|
File without changes
|