ob-metaflow 2.15.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/__init__.py +10 -3
- metaflow/_vendor/imghdr/__init__.py +186 -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 +4 -0
- metaflow/cli.py +125 -21
- metaflow/cli_components/init_cmd.py +1 -0
- metaflow/cli_components/run_cmds.py +204 -40
- metaflow/cli_components/step_cmd.py +160 -4
- metaflow/client/__init__.py +1 -0
- metaflow/client/core.py +198 -130
- metaflow/client/filecache.py +59 -32
- metaflow/cmd/code/__init__.py +2 -1
- metaflow/cmd/develop/stub_generator.py +49 -18
- metaflow/cmd/develop/stubs.py +9 -27
- metaflow/cmd/make_wrapper.py +30 -0
- metaflow/datastore/__init__.py +1 -0
- metaflow/datastore/content_addressed_store.py +40 -9
- metaflow/datastore/datastore_set.py +10 -1
- metaflow/datastore/flow_datastore.py +124 -4
- metaflow/datastore/spin_datastore.py +91 -0
- metaflow/datastore/task_datastore.py +92 -6
- metaflow/debug.py +5 -0
- metaflow/decorators.py +331 -82
- metaflow/extension_support/__init__.py +414 -356
- metaflow/extension_support/_empty_file.py +2 -2
- metaflow/flowspec.py +322 -82
- metaflow/graph.py +178 -15
- metaflow/includefile.py +25 -3
- metaflow/lint.py +94 -3
- metaflow/meta_files.py +13 -0
- metaflow/metadata_provider/metadata.py +13 -2
- metaflow/metaflow_config.py +66 -4
- metaflow/metaflow_environment.py +91 -25
- metaflow/metaflow_profile.py +18 -0
- metaflow/metaflow_version.py +16 -1
- 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 +6 -2
- metaflow/plugins/__init__.py +6 -0
- metaflow/plugins/airflow/airflow.py +11 -1
- metaflow/plugins/airflow/airflow_cli.py +16 -5
- metaflow/plugins/argo/argo_client.py +42 -20
- metaflow/plugins/argo/argo_events.py +6 -6
- metaflow/plugins/argo/argo_workflows.py +1023 -344
- metaflow/plugins/argo/argo_workflows_cli.py +396 -94
- metaflow/plugins/argo/argo_workflows_decorator.py +9 -0
- metaflow/plugins/argo/argo_workflows_deployer_objects.py +75 -49
- metaflow/plugins/argo/capture_error.py +5 -2
- metaflow/plugins/argo/conditional_input_paths.py +35 -0
- metaflow/plugins/argo/exit_hooks.py +209 -0
- metaflow/plugins/argo/param_val.py +19 -0
- metaflow/plugins/aws/aws_client.py +6 -0
- metaflow/plugins/aws/aws_utils.py +33 -1
- metaflow/plugins/aws/batch/batch.py +72 -5
- metaflow/plugins/aws/batch/batch_cli.py +24 -3
- metaflow/plugins/aws/batch/batch_decorator.py +57 -6
- metaflow/plugins/aws/step_functions/step_functions.py +28 -3
- metaflow/plugins/aws/step_functions/step_functions_cli.py +49 -4
- metaflow/plugins/aws/step_functions/step_functions_deployer.py +3 -0
- metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +30 -0
- metaflow/plugins/cards/card_cli.py +20 -1
- metaflow/plugins/cards/card_creator.py +24 -1
- metaflow/plugins/cards/card_datastore.py +21 -49
- metaflow/plugins/cards/card_decorator.py +58 -6
- metaflow/plugins/cards/card_modules/basic.py +38 -9
- metaflow/plugins/cards/card_modules/bundle.css +1 -1
- metaflow/plugins/cards/card_modules/chevron/renderer.py +1 -1
- metaflow/plugins/cards/card_modules/components.py +592 -3
- metaflow/plugins/cards/card_modules/convert_to_native_type.py +34 -5
- 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 +56 -41
- metaflow/plugins/cards/card_modules/test_cards.py +22 -6
- metaflow/plugins/cards/component_serializer.py +1 -8
- metaflow/plugins/cards/metadata.py +22 -0
- metaflow/plugins/catch_decorator.py +9 -0
- metaflow/plugins/datastores/local_storage.py +12 -6
- metaflow/plugins/datastores/spin_storage.py +12 -0
- metaflow/plugins/datatools/s3/s3.py +49 -17
- metaflow/plugins/datatools/s3/s3op.py +113 -66
- metaflow/plugins/env_escape/client_modules.py +102 -72
- metaflow/plugins/events_decorator.py +127 -121
- 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/kubernetes/kubernetes.py +12 -1
- metaflow/plugins/kubernetes/kubernetes_cli.py +11 -0
- metaflow/plugins/kubernetes/kubernetes_decorator.py +25 -6
- metaflow/plugins/kubernetes/kubernetes_job.py +12 -4
- metaflow/plugins/kubernetes/kubernetes_jobsets.py +31 -30
- metaflow/plugins/metadata_providers/local.py +76 -82
- metaflow/plugins/metadata_providers/service.py +13 -9
- metaflow/plugins/metadata_providers/spin.py +16 -0
- metaflow/plugins/package_cli.py +36 -24
- metaflow/plugins/parallel_decorator.py +11 -2
- metaflow/plugins/parsers.py +16 -0
- metaflow/plugins/pypi/bootstrap.py +7 -1
- metaflow/plugins/pypi/conda_decorator.py +41 -82
- metaflow/plugins/pypi/conda_environment.py +14 -6
- metaflow/plugins/pypi/micromamba.py +9 -1
- metaflow/plugins/pypi/pip.py +41 -5
- metaflow/plugins/pypi/pypi_decorator.py +4 -4
- metaflow/plugins/pypi/utils.py +22 -0
- metaflow/plugins/secrets/__init__.py +3 -0
- metaflow/plugins/secrets/secrets_decorator.py +14 -178
- 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/test_unbounded_foreach_decorator.py +2 -2
- metaflow/plugins/timeout_decorator.py +0 -1
- metaflow/plugins/uv/bootstrap.py +29 -1
- metaflow/plugins/uv/uv_environment.py +5 -3
- metaflow/pylint_wrapper.py +5 -1
- metaflow/runner/click_api.py +79 -26
- metaflow/runner/deployer.py +208 -6
- metaflow/runner/deployer_impl.py +32 -12
- metaflow/runner/metaflow_runner.py +266 -33
- metaflow/runner/subprocess_manager.py +21 -1
- metaflow/runner/utils.py +27 -16
- metaflow/runtime.py +660 -66
- metaflow/task.py +255 -26
- metaflow/user_configs/config_options.py +33 -21
- metaflow/user_configs/config_parameters.py +220 -58
- 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 +197 -7
- metaflow/vendor.py +23 -7
- metaflow/version.py +1 -1
- {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/Makefile +13 -2
- {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/Tiltfile +107 -7
- {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/pick_services.sh +1 -0
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/METADATA +2 -3
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/RECORD +162 -121
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/WHEEL +1 -1
- 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/_vendor/v3_5/zipp.py +0 -329
- metaflow/info_file.py +0 -25
- metaflow/package.py +0 -203
- metaflow/user_configs/config_decorators.py +0 -568
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/entry_points.txt +0 -0
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/licenses/LICENSE +0 -0
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
import
|
|
1
|
+
import inspect
|
|
2
2
|
import json
|
|
3
|
+
import collections.abc
|
|
4
|
+
import copy
|
|
3
5
|
import os
|
|
4
6
|
import re
|
|
5
7
|
|
|
6
|
-
from typing import
|
|
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
|
+
)
|
|
7
20
|
|
|
8
21
|
|
|
9
22
|
from ..exception import MetaflowException
|
|
@@ -36,25 +49,21 @@ if TYPE_CHECKING:
|
|
|
36
49
|
|
|
37
50
|
# return tracefunc_closure
|
|
38
51
|
|
|
39
|
-
CONFIG_FILE = os.path.join(
|
|
40
|
-
os.path.dirname(os.path.abspath(__file__)), "CONFIG_PARAMETERS"
|
|
41
|
-
)
|
|
42
|
-
|
|
43
52
|
ID_PATTERN = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
|
|
44
53
|
|
|
45
54
|
UNPACK_KEY = "_unpacked_delayed_"
|
|
46
55
|
|
|
47
56
|
|
|
48
57
|
def dump_config_values(flow: "FlowSpec"):
|
|
49
|
-
from ..flowspec import
|
|
58
|
+
from ..flowspec import FlowStateItems # Prevent circular import
|
|
50
59
|
|
|
51
|
-
configs = flow._flow_state.
|
|
60
|
+
configs = flow._flow_state[FlowStateItems.CONFIGS]
|
|
52
61
|
if configs:
|
|
53
62
|
return {"user_configs": configs}
|
|
54
63
|
return {}
|
|
55
64
|
|
|
56
65
|
|
|
57
|
-
class ConfigValue(collections.abc.Mapping):
|
|
66
|
+
class ConfigValue(collections.abc.Mapping, dict):
|
|
58
67
|
"""
|
|
59
68
|
ConfigValue is a thin wrapper around an arbitrarily nested dictionary-like
|
|
60
69
|
configuration object. It allows you to access elements of this nested structure
|
|
@@ -69,8 +78,67 @@ class ConfigValue(collections.abc.Mapping):
|
|
|
69
78
|
# Thin wrapper to allow configuration values to be accessed using a "." notation
|
|
70
79
|
# as well as a [] notation.
|
|
71
80
|
|
|
72
|
-
|
|
73
|
-
|
|
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")
|
|
74
142
|
|
|
75
143
|
def __getattr__(self, key: str) -> Any:
|
|
76
144
|
"""
|
|
@@ -115,33 +183,96 @@ class ConfigValue(collections.abc.Mapping):
|
|
|
115
183
|
Any
|
|
116
184
|
Element of the configuration
|
|
117
185
|
"""
|
|
118
|
-
|
|
119
|
-
if isinstance(value, dict):
|
|
120
|
-
value = ConfigValue(value)
|
|
121
|
-
return value
|
|
186
|
+
return self._data[key]
|
|
122
187
|
|
|
123
|
-
def
|
|
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:
|
|
124
201
|
return len(self._data)
|
|
125
202
|
|
|
126
|
-
def __iter__(self):
|
|
203
|
+
def __iter__(self) -> Iterator:
|
|
127
204
|
return iter(self._data)
|
|
128
205
|
|
|
129
|
-
def
|
|
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:
|
|
130
223
|
return repr(self._data)
|
|
131
224
|
|
|
132
|
-
def __str__(self):
|
|
133
|
-
return
|
|
225
|
+
def __str__(self) -> str:
|
|
226
|
+
return str(self._data)
|
|
134
227
|
|
|
135
|
-
def
|
|
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):
|
|
136
239
|
"""
|
|
137
|
-
Returns
|
|
240
|
+
Returns the keys of this configuration object.
|
|
138
241
|
|
|
139
242
|
Returns
|
|
140
243
|
-------
|
|
141
|
-
|
|
142
|
-
|
|
244
|
+
Any
|
|
245
|
+
Keys of this configuration object.
|
|
143
246
|
"""
|
|
144
|
-
return
|
|
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(),))
|
|
145
276
|
|
|
146
277
|
|
|
147
278
|
class DelayEvaluator(collections.abc.Mapping):
|
|
@@ -157,8 +288,9 @@ class DelayEvaluator(collections.abc.Mapping):
|
|
|
157
288
|
of _unpacked_delayed_*
|
|
158
289
|
"""
|
|
159
290
|
|
|
160
|
-
def __init__(self, ex: str):
|
|
291
|
+
def __init__(self, ex: str, saved_globals: Optional[Dict[str, Any]] = None):
|
|
161
292
|
self._config_expr = ex
|
|
293
|
+
self._globals = saved_globals
|
|
162
294
|
if ID_PATTERN.match(self._config_expr):
|
|
163
295
|
# This is a variable only so allow things like config_expr("config").var
|
|
164
296
|
self._is_var_only = True
|
|
@@ -166,6 +298,21 @@ class DelayEvaluator(collections.abc.Mapping):
|
|
|
166
298
|
else:
|
|
167
299
|
self._is_var_only = False
|
|
168
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
|
|
169
316
|
|
|
170
317
|
def __iter__(self):
|
|
171
318
|
yield "%s%d" % (UNPACK_KEY, id(self))
|
|
@@ -175,8 +322,15 @@ class DelayEvaluator(collections.abc.Mapping):
|
|
|
175
322
|
return self
|
|
176
323
|
if self._access is None:
|
|
177
324
|
raise KeyError(key)
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
180
334
|
|
|
181
335
|
def __len__(self):
|
|
182
336
|
return 1
|
|
@@ -184,11 +338,13 @@ class DelayEvaluator(collections.abc.Mapping):
|
|
|
184
338
|
def __getattr__(self, name):
|
|
185
339
|
if self._access is None:
|
|
186
340
|
raise AttributeError(name)
|
|
187
|
-
self.
|
|
188
|
-
|
|
341
|
+
c = self.__copy__()
|
|
342
|
+
c._access.append(name)
|
|
343
|
+
c._cached_expr = None
|
|
344
|
+
return c
|
|
189
345
|
|
|
190
346
|
def __call__(self, ctx=None, deploy_time=False):
|
|
191
|
-
from ..flowspec import
|
|
347
|
+
from ..flowspec import FlowStateItems # Prevent circular import
|
|
192
348
|
|
|
193
349
|
# Two additional arguments are only used by DeployTimeField which will call
|
|
194
350
|
# this function with those two additional arguments. They are ignored.
|
|
@@ -199,7 +355,9 @@ class DelayEvaluator(collections.abc.Mapping):
|
|
|
199
355
|
"Config object can only be used directly in the FlowSpec defining them "
|
|
200
356
|
"(or their flow decorators)."
|
|
201
357
|
)
|
|
202
|
-
if self.
|
|
358
|
+
if self._cached_expr is not None:
|
|
359
|
+
to_eval_expr = self._cached_expr
|
|
360
|
+
elif self._access is not None:
|
|
203
361
|
# Build the final expression by adding all the fields in access as . fields
|
|
204
362
|
access_list = [self._config_expr]
|
|
205
363
|
for a in self._access:
|
|
@@ -212,27 +370,24 @@ class DelayEvaluator(collections.abc.Mapping):
|
|
|
212
370
|
raise MetaflowException(
|
|
213
371
|
"Field '%s' of type '%s' is not supported" % (str(a), type(a))
|
|
214
372
|
)
|
|
215
|
-
self.
|
|
373
|
+
to_eval_expr = self._cached_expr = ".".join(access_list)
|
|
374
|
+
else:
|
|
375
|
+
to_eval_expr = self._cached_expr = self._config_expr
|
|
216
376
|
# Evaluate the expression setting the config values as local variables
|
|
217
377
|
try:
|
|
218
378
|
return eval(
|
|
219
|
-
|
|
220
|
-
globals(),
|
|
379
|
+
to_eval_expr,
|
|
380
|
+
self._globals or globals(),
|
|
221
381
|
{
|
|
222
|
-
k: ConfigValue(v)
|
|
223
|
-
for k, v in flow_cls._flow_state.
|
|
382
|
+
k: ConfigValue(v) if v is not None else None
|
|
383
|
+
for k, v in flow_cls._flow_state[FlowStateItems.CONFIGS].items()
|
|
224
384
|
},
|
|
225
385
|
)
|
|
226
386
|
except NameError as e:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
)
|
|
231
|
-
raise MetaflowException(
|
|
232
|
-
"Config '%s' not found in the flow (maybe not required and not "
|
|
233
|
-
"provided?)" % potential_config_name
|
|
234
|
-
) from e
|
|
235
|
-
raise
|
|
387
|
+
raise MetaflowException(
|
|
388
|
+
"Config expression '%s' could not be evaluated: %s"
|
|
389
|
+
% (to_eval_expr, str(e))
|
|
390
|
+
) from e
|
|
236
391
|
|
|
237
392
|
|
|
238
393
|
def config_expr(expr: str) -> DelayEvaluator:
|
|
@@ -262,7 +417,10 @@ def config_expr(expr: str) -> DelayEvaluator:
|
|
|
262
417
|
expr : str
|
|
263
418
|
Expression using the config values.
|
|
264
419
|
"""
|
|
265
|
-
|
|
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)
|
|
266
424
|
|
|
267
425
|
|
|
268
426
|
class Config(Parameter, collections.abc.Mapping):
|
|
@@ -329,7 +487,6 @@ class Config(Parameter, collections.abc.Mapping):
|
|
|
329
487
|
parser: Optional[Union[str, Callable[[str], Dict[Any, Any]]]] = None,
|
|
330
488
|
**kwargs: Dict[str, str]
|
|
331
489
|
):
|
|
332
|
-
|
|
333
490
|
if default is not None and default_value is not None:
|
|
334
491
|
raise MetaflowException(
|
|
335
492
|
"For config '%s', you can only specify default or default_value, not both"
|
|
@@ -350,9 +507,7 @@ class Config(Parameter, collections.abc.Mapping):
|
|
|
350
507
|
self._delayed_evaluator = None
|
|
351
508
|
|
|
352
509
|
def load_parameter(self, v):
|
|
353
|
-
if v is None
|
|
354
|
-
return None
|
|
355
|
-
return ConfigValue(v)
|
|
510
|
+
return ConfigValue(v) if v is not None else None
|
|
356
511
|
|
|
357
512
|
def _store_value(self, v: Any) -> None:
|
|
358
513
|
self._computed_value = v
|
|
@@ -385,23 +540,30 @@ class Config(Parameter, collections.abc.Mapping):
|
|
|
385
540
|
return DelayEvaluator(self.name.lower())[key]
|
|
386
541
|
|
|
387
542
|
|
|
388
|
-
def resolve_delayed_evaluator(
|
|
543
|
+
def resolve_delayed_evaluator(
|
|
544
|
+
v: Any, ignore_errors: bool = False, to_dict: bool = False
|
|
545
|
+
) -> Any:
|
|
389
546
|
# NOTE: We don't ignore errors in downstream calls because we want to have either
|
|
390
547
|
# all or nothing for the top-level call by the user.
|
|
391
548
|
try:
|
|
392
549
|
if isinstance(v, DelayEvaluator):
|
|
393
|
-
|
|
550
|
+
to_return = v()
|
|
551
|
+
if to_dict and isinstance(to_return, ConfigValue):
|
|
552
|
+
to_return = to_return.to_dict()
|
|
553
|
+
return to_return
|
|
394
554
|
if isinstance(v, dict):
|
|
395
555
|
return {
|
|
396
|
-
resolve_delayed_evaluator(
|
|
556
|
+
resolve_delayed_evaluator(
|
|
557
|
+
k, to_dict=to_dict
|
|
558
|
+
): resolve_delayed_evaluator(v, to_dict=to_dict)
|
|
397
559
|
for k, v in v.items()
|
|
398
560
|
}
|
|
399
561
|
if isinstance(v, list):
|
|
400
|
-
return [resolve_delayed_evaluator(x) for x in v]
|
|
562
|
+
return [resolve_delayed_evaluator(x, to_dict=to_dict) for x in v]
|
|
401
563
|
if isinstance(v, tuple):
|
|
402
|
-
return tuple(resolve_delayed_evaluator(x) for x in v)
|
|
564
|
+
return tuple(resolve_delayed_evaluator(x, to_dict=to_dict) for x in v)
|
|
403
565
|
if isinstance(v, set):
|
|
404
|
-
return {resolve_delayed_evaluator(x) for x in v}
|
|
566
|
+
return {resolve_delayed_evaluator(x, to_dict=to_dict) for x in v}
|
|
405
567
|
return v
|
|
406
568
|
except Exception as e:
|
|
407
569
|
if ignore_errors:
|
|
@@ -426,7 +588,7 @@ def unpack_delayed_evaluator(
|
|
|
426
588
|
else:
|
|
427
589
|
# k.startswith(UNPACK_KEY)
|
|
428
590
|
try:
|
|
429
|
-
new_vals = resolve_delayed_evaluator(v)
|
|
591
|
+
new_vals = resolve_delayed_evaluator(v, to_dict=True)
|
|
430
592
|
new_keys.extend(new_vals.keys())
|
|
431
593
|
result.update(new_vals)
|
|
432
594
|
except Exception as e:
|
|
File without changes
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from typing import Dict, Optional, List, Tuple
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class _TrieNode:
|
|
5
|
+
def __init__(
|
|
6
|
+
self, parent: Optional["_TrieNode"] = None, component: Optional[str] = None
|
|
7
|
+
):
|
|
8
|
+
self.parent = parent
|
|
9
|
+
self.component = component
|
|
10
|
+
self.children = {} # type: Dict[str, "_TrieNode"]
|
|
11
|
+
self.total_children = 0
|
|
12
|
+
self.value = None
|
|
13
|
+
self.end_value = None
|
|
14
|
+
|
|
15
|
+
def traverse(self, value: type) -> Optional["_TrieNode"]:
|
|
16
|
+
if self.total_children == 0:
|
|
17
|
+
self.end_value = value
|
|
18
|
+
else:
|
|
19
|
+
self.end_value = None
|
|
20
|
+
self.total_children += 1
|
|
21
|
+
|
|
22
|
+
def remove_child(self, child_name: str) -> bool:
|
|
23
|
+
if child_name in self.children:
|
|
24
|
+
del self.children[child_name]
|
|
25
|
+
self.total_children -= 1
|
|
26
|
+
return True
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ClassPath_Trie:
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self.root = _TrieNode(None, None)
|
|
33
|
+
self.inited = False
|
|
34
|
+
self._value_to_node = {} # type: Dict[type, _TrieNode]
|
|
35
|
+
|
|
36
|
+
def init(self, initial_nodes: Optional[List[Tuple[str, type]]] = None):
|
|
37
|
+
# We need to do this so we can delay import of STEP_DECORATORS
|
|
38
|
+
self.inited = True
|
|
39
|
+
for classpath_name, value in initial_nodes or []:
|
|
40
|
+
self.insert(classpath_name, value)
|
|
41
|
+
|
|
42
|
+
def insert(self, classpath_name: str, value: type):
|
|
43
|
+
node = self.root
|
|
44
|
+
components = reversed(classpath_name.split("."))
|
|
45
|
+
for c in components:
|
|
46
|
+
node = node.children.setdefault(c, _TrieNode(node, c))
|
|
47
|
+
node.traverse(value)
|
|
48
|
+
node.total_children -= (
|
|
49
|
+
1 # We do not count the last node as having itself as a child
|
|
50
|
+
)
|
|
51
|
+
node.value = value
|
|
52
|
+
self._value_to_node[value] = node
|
|
53
|
+
|
|
54
|
+
def search(self, classpath_name: str) -> Optional[type]:
|
|
55
|
+
node = self.root
|
|
56
|
+
components = reversed(classpath_name.split("."))
|
|
57
|
+
for c in components:
|
|
58
|
+
if c not in node.children:
|
|
59
|
+
return None
|
|
60
|
+
node = node.children[c]
|
|
61
|
+
return node.value
|
|
62
|
+
|
|
63
|
+
def remove(self, classpath_name: str):
|
|
64
|
+
components = list(reversed(classpath_name.split(".")))
|
|
65
|
+
|
|
66
|
+
def _remove(node: _TrieNode, components, depth):
|
|
67
|
+
if depth == len(components):
|
|
68
|
+
if node.value is not None:
|
|
69
|
+
del self._value_to_node[node.value]
|
|
70
|
+
node.value = None
|
|
71
|
+
return len(node.children) == 0
|
|
72
|
+
return False
|
|
73
|
+
c = components[depth]
|
|
74
|
+
if c not in node.children:
|
|
75
|
+
return False
|
|
76
|
+
did_delete_child = _remove(node.children[c], components, depth + 1)
|
|
77
|
+
if did_delete_child:
|
|
78
|
+
node.remove_child(c)
|
|
79
|
+
if node.total_children == 1:
|
|
80
|
+
# If we have one total child left, we have at least one
|
|
81
|
+
# child and that one has an end_value
|
|
82
|
+
for child in node.children.values():
|
|
83
|
+
assert (
|
|
84
|
+
child.end_value
|
|
85
|
+
), "Node with one child must have an end_value"
|
|
86
|
+
node.end_value = child.end_value
|
|
87
|
+
return node.total_children == 0
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
_remove(self.root, components, 0)
|
|
91
|
+
|
|
92
|
+
def unique_prefix_value(self, classpath_name: str) -> Optional[type]:
|
|
93
|
+
node = self.root
|
|
94
|
+
components = reversed(classpath_name.split("."))
|
|
95
|
+
for c in components:
|
|
96
|
+
if c not in node.children:
|
|
97
|
+
return None
|
|
98
|
+
node = node.children[c]
|
|
99
|
+
# If we reach here, it means the classpath_name is a prefix.
|
|
100
|
+
# We check if it has only one path forward (end_value will be non None)
|
|
101
|
+
# If value is not None, we also consider this to be a unique "prefix"
|
|
102
|
+
# This happens since this trie is also filled with metaflow default decorators
|
|
103
|
+
return node.end_value or node.value
|
|
104
|
+
|
|
105
|
+
def unique_prefix_for_type(self, value: type) -> Optional[str]:
|
|
106
|
+
node = self._value_to_node.get(value, None)
|
|
107
|
+
if node is None:
|
|
108
|
+
return None
|
|
109
|
+
components = []
|
|
110
|
+
while node:
|
|
111
|
+
if node.end_value == value:
|
|
112
|
+
components = []
|
|
113
|
+
if node.component is not None:
|
|
114
|
+
components.append(node.component)
|
|
115
|
+
node = node.parent
|
|
116
|
+
return ".".join(components)
|
|
117
|
+
|
|
118
|
+
def get_unique_prefixes(self) -> Dict[str, type]:
|
|
119
|
+
"""
|
|
120
|
+
Get all unique prefixes in the trie.
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
List[str]
|
|
125
|
+
A list of unique prefixes.
|
|
126
|
+
"""
|
|
127
|
+
to_return = {}
|
|
128
|
+
|
|
129
|
+
def _collect(node, current_prefix):
|
|
130
|
+
if node.end_value is not None:
|
|
131
|
+
to_return[current_prefix] = node.end_value
|
|
132
|
+
# We stop there and don't look further since we found the unique prefix
|
|
133
|
+
return
|
|
134
|
+
if node.value is not None:
|
|
135
|
+
to_return[current_prefix] = node.value
|
|
136
|
+
# We continue to look for more unique prefixes
|
|
137
|
+
for child_name, child_node in node.children.items():
|
|
138
|
+
_collect(
|
|
139
|
+
child_node,
|
|
140
|
+
f"{current_prefix}.{child_name}" if current_prefix else child_name,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
_collect(self.root, "")
|
|
144
|
+
return {".".join(reversed(k.split("."))): v for k, v in to_return.items()}
|