metaflow 2.12.36__py2.py3-none-any.whl → 2.12.38__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 +3 -0
- metaflow/cli.py +84 -697
- 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 +51 -0
- metaflow/cli_components/run_cmds.py +358 -0
- metaflow/cli_components/step_cmd.py +189 -0
- metaflow/cli_components/utils.py +140 -0
- metaflow/cmd/develop/stub_generator.py +9 -2
- metaflow/decorators.py +63 -2
- metaflow/extension_support/plugins.py +41 -27
- metaflow/flowspec.py +156 -16
- metaflow/includefile.py +50 -22
- metaflow/metaflow_config.py +1 -1
- metaflow/package.py +17 -3
- metaflow/parameters.py +80 -23
- metaflow/plugins/__init__.py +4 -0
- metaflow/plugins/airflow/airflow_cli.py +1 -0
- metaflow/plugins/argo/argo_workflows.py +41 -1
- metaflow/plugins/argo/argo_workflows_cli.py +1 -0
- metaflow/plugins/aws/batch/batch_decorator.py +2 -2
- metaflow/plugins/aws/step_functions/step_functions.py +32 -0
- metaflow/plugins/aws/step_functions/step_functions_cli.py +1 -0
- metaflow/plugins/datatools/s3/s3op.py +3 -3
- metaflow/plugins/kubernetes/kubernetes_cli.py +1 -1
- metaflow/plugins/kubernetes/kubernetes_decorator.py +2 -2
- metaflow/plugins/pypi/conda_decorator.py +22 -0
- metaflow/plugins/pypi/pypi_decorator.py +1 -0
- metaflow/plugins/timeout_decorator.py +2 -2
- metaflow/runner/click_api.py +73 -19
- metaflow/runtime.py +111 -73
- metaflow/sidecar/sidecar_worker.py +1 -1
- metaflow/user_configs/__init__.py +0 -0
- metaflow/user_configs/config_decorators.py +563 -0
- metaflow/user_configs/config_options.py +495 -0
- metaflow/user_configs/config_parameters.py +386 -0
- metaflow/util.py +17 -0
- metaflow/version.py +1 -1
- {metaflow-2.12.36.dist-info → metaflow-2.12.38.dist-info}/METADATA +3 -2
- {metaflow-2.12.36.dist-info → metaflow-2.12.38.dist-info}/RECORD +45 -35
- {metaflow-2.12.36.dist-info → metaflow-2.12.38.dist-info}/LICENSE +0 -0
- {metaflow-2.12.36.dist-info → metaflow-2.12.38.dist-info}/WHEEL +0 -0
- {metaflow-2.12.36.dist-info → metaflow-2.12.38.dist-info}/entry_points.txt +0 -0
- {metaflow-2.12.36.dist-info → metaflow-2.12.38.dist-info}/top_level.txt +0 -0
metaflow/flowspec.py
CHANGED
@@ -4,15 +4,18 @@ import sys
|
|
4
4
|
import traceback
|
5
5
|
import reprlib
|
6
6
|
|
7
|
+
from enum import Enum
|
7
8
|
from itertools import islice
|
8
9
|
from types import FunctionType, MethodType
|
9
|
-
from typing import Any, Callable, List, Optional, Tuple
|
10
|
+
from typing import TYPE_CHECKING, Any, Callable, Generator, List, Optional, Tuple
|
10
11
|
|
11
12
|
from . import cmd_with_io, parameters
|
13
|
+
from .debug import debug
|
12
14
|
from .parameters import DelayedEvaluationParameter, Parameter
|
13
15
|
from .exception import (
|
14
16
|
MetaflowException,
|
15
17
|
MissingInMergeArtifactsException,
|
18
|
+
MetaflowInternalError,
|
16
19
|
UnhandledInMergeArtifactsException,
|
17
20
|
)
|
18
21
|
|
@@ -20,6 +23,12 @@ from .extension_support import extension_info
|
|
20
23
|
|
21
24
|
from .graph import FlowGraph
|
22
25
|
from .unbounded_foreach import UnboundedForeachInput
|
26
|
+
from .user_configs.config_decorators import (
|
27
|
+
CustomFlowDecorator,
|
28
|
+
CustomStepDecorator,
|
29
|
+
MutableFlow,
|
30
|
+
MutableStep,
|
31
|
+
)
|
23
32
|
from .util import to_pod
|
24
33
|
from .metaflow_config import INCLUDE_FOREACH_STACK, MAXIMUM_FOREACH_VALUE_CHARS
|
25
34
|
|
@@ -65,14 +74,27 @@ class ParallelUBF(UnboundedForeachInput):
|
|
65
74
|
return item or 0 # item is None for the control task, but it is also split 0
|
66
75
|
|
67
76
|
|
77
|
+
class _FlowState(Enum):
|
78
|
+
CONFIGS = 1
|
79
|
+
CONFIG_DECORATORS = 2
|
80
|
+
CACHED_PARAMETERS = 3
|
81
|
+
|
82
|
+
|
68
83
|
class FlowSpecMeta(type):
|
69
84
|
def __new__(cls, name, bases, dct):
|
70
85
|
f = super().__new__(cls, name, bases, dct)
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
86
|
+
# We store some state in the flow class itself. This is primarily used to
|
87
|
+
# attach global state to a flow. It is *not* an actual global because of
|
88
|
+
# Runner/NBRunner. This is also created here in the meta class to avoid it being
|
89
|
+
# shared between different children classes.
|
90
|
+
|
91
|
+
# We should move _flow_decorators into this structure as well but keeping it
|
92
|
+
# out to limit the changes for now.
|
75
93
|
f._flow_decorators = {}
|
94
|
+
|
95
|
+
# Keys are _FlowState enum values
|
96
|
+
f._flow_state = {}
|
97
|
+
|
76
98
|
return f
|
77
99
|
|
78
100
|
|
@@ -96,6 +118,7 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
96
118
|
"_cached_input",
|
97
119
|
"_graph",
|
98
120
|
"_flow_decorators",
|
121
|
+
"_flow_state",
|
99
122
|
"_steps",
|
100
123
|
"index",
|
101
124
|
"input",
|
@@ -148,16 +171,11 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
148
171
|
fname = fname[:-1]
|
149
172
|
return os.path.basename(fname)
|
150
173
|
|
151
|
-
def
|
152
|
-
from metaflow.decorators import (
|
153
|
-
flow_decorators,
|
154
|
-
) # To prevent circular dependency
|
155
|
-
|
156
|
-
# Persist values for parameters and other constants (class level variables)
|
157
|
-
# only once. This method is called before persist_constants is called to
|
158
|
-
# persist all values set using setattr
|
174
|
+
def _check_parameters(self, config_parameters=False):
|
159
175
|
seen = set()
|
160
|
-
for
|
176
|
+
for _, param in self._get_parameters():
|
177
|
+
if param.IS_CONFIG_PARAMETER != config_parameters:
|
178
|
+
continue
|
161
179
|
norm = param.name.lower()
|
162
180
|
if norm in seen:
|
163
181
|
raise MetaflowException(
|
@@ -166,13 +184,127 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
166
184
|
"case-insensitive." % param.name
|
167
185
|
)
|
168
186
|
seen.add(norm)
|
169
|
-
|
187
|
+
|
188
|
+
def _process_config_decorators(self, config_options):
|
189
|
+
current_cls = self.__class__
|
190
|
+
|
191
|
+
# Fast path for no user configurations
|
192
|
+
if not self._flow_state.get(_FlowState.CONFIG_DECORATORS):
|
193
|
+
# Process parameters to allow them to also use config values easily
|
194
|
+
for var, param in self._get_parameters():
|
195
|
+
if param.IS_CONFIG_PARAMETER:
|
196
|
+
continue
|
197
|
+
param.init()
|
198
|
+
return self
|
199
|
+
|
200
|
+
debug.userconf_exec("Processing mutating step/flow decorators")
|
201
|
+
# We need to convert all the user configurations from DelayedEvaluationParameters
|
202
|
+
# to actual values so they can be used as is in the config decorators.
|
203
|
+
|
204
|
+
# We then reset them to be proper configs so they can be re-evaluated in
|
205
|
+
# _set_constants
|
206
|
+
to_reset_configs = []
|
207
|
+
self._check_parameters(config_parameters=True)
|
208
|
+
for var, param in self._get_parameters():
|
209
|
+
if not param.IS_CONFIG_PARAMETER:
|
210
|
+
continue
|
211
|
+
# Note that a config with no default and not required will be None
|
212
|
+
val = config_options.get(param.name.replace("-", "_").lower())
|
213
|
+
if isinstance(val, DelayedEvaluationParameter):
|
214
|
+
val = val()
|
215
|
+
# We store the value as well so that in _set_constants, we don't try
|
216
|
+
# to recompute (no guarantee that it is stable)
|
217
|
+
param._store_value(val)
|
218
|
+
to_reset_configs.append((var, param))
|
219
|
+
debug.userconf_exec("Setting config %s to %s" % (var, str(val)))
|
220
|
+
setattr(current_cls, var, val)
|
221
|
+
|
222
|
+
# Run all the decorators. Step decorators are directly in the step and
|
223
|
+
# we will run those first and *then* we run all the flow level decorators
|
224
|
+
for step in self._steps:
|
225
|
+
for deco in step.config_decorators:
|
226
|
+
if isinstance(deco, CustomStepDecorator):
|
227
|
+
debug.userconf_exec(
|
228
|
+
"Evaluating step level decorator %s for %s"
|
229
|
+
% (deco.__class__.__name__, step.name)
|
230
|
+
)
|
231
|
+
deco.evaluate(MutableStep(current_cls, step))
|
232
|
+
else:
|
233
|
+
raise MetaflowInternalError(
|
234
|
+
"A non CustomFlowDecorator found in step custom decorators"
|
235
|
+
)
|
236
|
+
if step.config_decorators:
|
237
|
+
# We remove all mention of the custom step decorator
|
238
|
+
setattr(current_cls, step.name, step)
|
239
|
+
|
240
|
+
mutable_flow = MutableFlow(current_cls)
|
241
|
+
for deco in self._flow_state[_FlowState.CONFIG_DECORATORS]:
|
242
|
+
if isinstance(deco, CustomFlowDecorator):
|
243
|
+
# Sanity check to make sure we are applying the decorator to the right
|
244
|
+
# class
|
245
|
+
if not deco._flow_cls == current_cls and not issubclass(
|
246
|
+
current_cls, deco._flow_cls
|
247
|
+
):
|
248
|
+
raise MetaflowInternalError(
|
249
|
+
"CustomFlowDecorator registered on the wrong flow -- "
|
250
|
+
"expected %s but got %s"
|
251
|
+
% (deco._flow_cls.__name__, current_cls.__name__)
|
252
|
+
)
|
253
|
+
debug.userconf_exec(
|
254
|
+
"Evaluating flow level decorator %s" % deco.__class__.__name__
|
255
|
+
)
|
256
|
+
deco.evaluate(mutable_flow)
|
257
|
+
# We reset cached_parameters on the very off chance that the user added
|
258
|
+
# more configurations based on the configuration
|
259
|
+
if _FlowState.CACHED_PARAMETERS in current_cls._flow_state:
|
260
|
+
del current_cls._flow_state[_FlowState.CACHED_PARAMETERS]
|
261
|
+
else:
|
262
|
+
raise MetaflowInternalError(
|
263
|
+
"A non CustomFlowDecorator found in flow custom decorators"
|
264
|
+
)
|
265
|
+
|
266
|
+
# Process parameters to allow them to also use config values easily
|
267
|
+
for var, param in self._get_parameters():
|
268
|
+
if param.IS_CONFIG_PARAMETER:
|
269
|
+
continue
|
270
|
+
param.init()
|
271
|
+
# Reset all configs that were already present in the class.
|
272
|
+
# TODO: This means that users can't override configs directly. Not sure if this
|
273
|
+
# is a pattern we want to support
|
274
|
+
for var, param in to_reset_configs:
|
275
|
+
setattr(current_cls, var, param)
|
276
|
+
|
277
|
+
# Reset cached parameters again since we added back the config parameters
|
278
|
+
if _FlowState.CACHED_PARAMETERS in current_cls._flow_state:
|
279
|
+
del current_cls._flow_state[_FlowState.CACHED_PARAMETERS]
|
280
|
+
|
281
|
+
# Set the current flow class we are in (the one we just created)
|
282
|
+
parameters.replace_flow_context(current_cls)
|
283
|
+
return current_cls(use_cli=False)
|
284
|
+
|
285
|
+
def _set_constants(self, graph, kwargs, config_options):
|
286
|
+
from metaflow.decorators import (
|
287
|
+
flow_decorators,
|
288
|
+
) # To prevent circular dependency
|
289
|
+
|
290
|
+
# Persist values for parameters and other constants (class level variables)
|
291
|
+
# only once. This method is called before persist_constants is called to
|
292
|
+
# persist all values set using setattr
|
293
|
+
self._check_parameters(config_parameters=False)
|
294
|
+
|
295
|
+
seen = set()
|
170
296
|
self._success = True
|
171
297
|
|
172
298
|
parameters_info = []
|
173
299
|
for var, param in self._get_parameters():
|
174
300
|
seen.add(var)
|
175
|
-
|
301
|
+
if param.IS_CONFIG_PARAMETER:
|
302
|
+
# Use computed value if already evaluated, else get from config_options
|
303
|
+
val = param._computed_value or config_options.get(
|
304
|
+
param.name.replace("-", "_").lower()
|
305
|
+
)
|
306
|
+
else:
|
307
|
+
val = kwargs[param.name.replace("-", "_").lower()]
|
176
308
|
# Support for delayed evaluation of parameters.
|
177
309
|
if isinstance(val, DelayedEvaluationParameter):
|
178
310
|
val = val()
|
@@ -218,6 +350,12 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
218
350
|
|
219
351
|
@classmethod
|
220
352
|
def _get_parameters(cls):
|
353
|
+
cached = cls._flow_state.get(_FlowState.CACHED_PARAMETERS)
|
354
|
+
if cached is not None:
|
355
|
+
for var in cached:
|
356
|
+
yield var, getattr(cls, var)
|
357
|
+
return
|
358
|
+
build_list = []
|
221
359
|
for var in dir(cls):
|
222
360
|
if var[0] == "_" or var in cls._NON_PARAMETERS:
|
223
361
|
continue
|
@@ -226,7 +364,9 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
226
364
|
except:
|
227
365
|
continue
|
228
366
|
if isinstance(val, Parameter):
|
367
|
+
build_list.append(var)
|
229
368
|
yield var, val
|
369
|
+
cls._flow_state[_FlowState.CACHED_PARAMETERS] = build_list
|
230
370
|
|
231
371
|
def _set_datastore(self, datastore):
|
232
372
|
self._datastore = datastore
|
metaflow/includefile.py
CHANGED
@@ -20,6 +20,7 @@ from .parameters import (
|
|
20
20
|
)
|
21
21
|
|
22
22
|
from .plugins import DATACLIENTS
|
23
|
+
from .user_configs.config_parameters import ConfigValue
|
23
24
|
from .util import get_username
|
24
25
|
|
25
26
|
import functools
|
@@ -136,6 +137,7 @@ class FilePathClass(click.ParamType):
|
|
136
137
|
parameter_name=param.name,
|
137
138
|
logger=ctx.obj.echo,
|
138
139
|
ds_type=ctx.obj.datastore_impl.TYPE,
|
140
|
+
configs=None,
|
139
141
|
)
|
140
142
|
|
141
143
|
if len(value) > 0 and (value.startswith("{") or value.startswith('"{')):
|
@@ -243,29 +245,63 @@ class IncludeFile(Parameter):
|
|
243
245
|
default : Union[str, Callable[ParameterContext, str]]
|
244
246
|
Default path to a local file. A function
|
245
247
|
implies that the parameter corresponds to a *deploy-time parameter*.
|
246
|
-
is_text : bool, default
|
248
|
+
is_text : bool, optional, default None
|
247
249
|
Convert the file contents to a string using the provided `encoding`.
|
248
|
-
If False, the artifact is stored in `bytes`.
|
249
|
-
|
250
|
-
|
251
|
-
|
250
|
+
If False, the artifact is stored in `bytes`. A value of None is equivalent to
|
251
|
+
True.
|
252
|
+
encoding : str, optional, default None
|
253
|
+
Use this encoding to decode the file contexts if `is_text=True`. A value of None
|
254
|
+
is equivalent to "utf-8".
|
255
|
+
required : bool, optional, default None
|
252
256
|
Require that the user specified a value for the parameter.
|
253
|
-
`required=True` implies that the `default` is not used.
|
257
|
+
`required=True` implies that the `default` is not used. A value of None is
|
258
|
+
equivalent to False
|
254
259
|
help : str, optional
|
255
260
|
Help text to show in `run --help`.
|
256
261
|
show_default : bool, default True
|
257
|
-
If True, show the default value in the help text.
|
262
|
+
If True, show the default value in the help text. A value of None is equivalent
|
263
|
+
to True.
|
258
264
|
"""
|
259
265
|
|
260
266
|
def __init__(
|
261
267
|
self,
|
262
268
|
name: str,
|
263
|
-
required: bool =
|
264
|
-
is_text: bool =
|
265
|
-
encoding: str =
|
269
|
+
required: Optional[bool] = None,
|
270
|
+
is_text: Optional[bool] = None,
|
271
|
+
encoding: Optional[str] = None,
|
266
272
|
help: Optional[str] = None,
|
267
273
|
**kwargs: Dict[str, str]
|
268
274
|
):
|
275
|
+
self._includefile_overrides = {}
|
276
|
+
if is_text is not None:
|
277
|
+
self._includefile_overrides["is_text"] = is_text
|
278
|
+
if encoding is not None:
|
279
|
+
self._includefile_overrides["encoding"] = encoding
|
280
|
+
# NOTA: Right now, there is an issue where these can't be overridden by config
|
281
|
+
# in all circumstances. Ignoring for now.
|
282
|
+
super(IncludeFile, self).__init__(
|
283
|
+
name,
|
284
|
+
required=required,
|
285
|
+
help=help,
|
286
|
+
type=FilePathClass(
|
287
|
+
self._includefile_overrides.get("is_text", True),
|
288
|
+
self._includefile_overrides.get("encoding", "utf-8"),
|
289
|
+
),
|
290
|
+
**kwargs,
|
291
|
+
)
|
292
|
+
|
293
|
+
def init(self):
|
294
|
+
super(IncludeFile, self).init()
|
295
|
+
|
296
|
+
# This will use the values set explicitly in the args if present, else will
|
297
|
+
# use and remove from kwargs else will use True/utf-8
|
298
|
+
is_text = self._includefile_overrides.get(
|
299
|
+
"is_text", self.kwargs.pop("is_text", True)
|
300
|
+
)
|
301
|
+
encoding = self._includefile_overrides.get(
|
302
|
+
"encoding", self.kwargs.pop("encoding", "utf-8")
|
303
|
+
)
|
304
|
+
|
269
305
|
# If a default is specified, it needs to be uploaded when the flow is deployed
|
270
306
|
# (for example when doing a `step-functions create`) so we make the default
|
271
307
|
# be a DeployTimeField. This means that it will be evaluated in two cases:
|
@@ -275,7 +311,7 @@ class IncludeFile(Parameter):
|
|
275
311
|
# In the first case, we will need to fully upload the file whereas in the
|
276
312
|
# second case, we can just return the string as the FilePath.convert method
|
277
313
|
# will take care of evaluating things.
|
278
|
-
v = kwargs.get("default")
|
314
|
+
v = self.kwargs.get("default")
|
279
315
|
if v is not None:
|
280
316
|
# If the default is a callable, we have two DeployTimeField:
|
281
317
|
# - the callable nature of the default will require us to "call" the default
|
@@ -288,23 +324,15 @@ class IncludeFile(Parameter):
|
|
288
324
|
# (call the default)
|
289
325
|
if callable(v) and not isinstance(v, DeployTimeField):
|
290
326
|
# If default is a callable, make it a DeployTimeField (the inner one)
|
291
|
-
v = DeployTimeField(name, str, "default", v, return_str=True)
|
292
|
-
kwargs["default"] = DeployTimeField(
|
293
|
-
name,
|
327
|
+
v = DeployTimeField(self.name, str, "default", v, return_str=True)
|
328
|
+
self.kwargs["default"] = DeployTimeField(
|
329
|
+
self.name,
|
294
330
|
str,
|
295
331
|
"default",
|
296
332
|
IncludeFile._eval_default(is_text, encoding, v),
|
297
333
|
print_representation=v,
|
298
334
|
)
|
299
335
|
|
300
|
-
super(IncludeFile, self).__init__(
|
301
|
-
name,
|
302
|
-
required=required,
|
303
|
-
help=help,
|
304
|
-
type=FilePathClass(is_text, encoding),
|
305
|
-
**kwargs,
|
306
|
-
)
|
307
|
-
|
308
336
|
def load_parameter(self, v):
|
309
337
|
if v is None:
|
310
338
|
return v
|
metaflow/metaflow_config.py
CHANGED
@@ -440,7 +440,7 @@ ESCAPE_HATCH_WARNING = from_conf("ESCAPE_HATCH_WARNING", True)
|
|
440
440
|
###
|
441
441
|
# Debug configuration
|
442
442
|
###
|
443
|
-
DEBUG_OPTIONS = ["subcommand", "sidecar", "s3client", "tracing", "stubgen"]
|
443
|
+
DEBUG_OPTIONS = ["subcommand", "sidecar", "s3client", "tracing", "stubgen", "userconf"]
|
444
444
|
|
445
445
|
for typ in DEBUG_OPTIONS:
|
446
446
|
vars()["DEBUG_%s" % typ.upper()] = from_conf("DEBUG_%s" % typ.upper(), False)
|
metaflow/package.py
CHANGED
@@ -6,6 +6,7 @@ import time
|
|
6
6
|
import json
|
7
7
|
from io import BytesIO
|
8
8
|
|
9
|
+
from .user_configs.config_parameters import CONFIG_FILE, dump_config_values
|
9
10
|
from .extension_support import EXT_PKG, package_mfext_all
|
10
11
|
from .metaflow_config import DEFAULT_PACKAGE_SUFFIXES
|
11
12
|
from .exception import MetaflowException
|
@@ -151,11 +152,23 @@ class MetaflowPackage(object):
|
|
151
152
|
for path_tuple in self._walk(flowdir, suffixes=self.suffixes):
|
152
153
|
yield path_tuple
|
153
154
|
|
155
|
+
def _add_configs(self, tar):
|
156
|
+
buf = BytesIO()
|
157
|
+
buf.write(json.dumps(dump_config_values(self._flow)).encode("utf-8"))
|
158
|
+
self._add_file(tar, os.path.basename(CONFIG_FILE), buf)
|
159
|
+
|
154
160
|
def _add_info(self, tar):
|
155
|
-
info = tarfile.TarInfo(os.path.basename(INFO_FILE))
|
156
|
-
env = self.environment.get_environment_info(include_ext_info=True)
|
157
161
|
buf = BytesIO()
|
158
|
-
buf.write(
|
162
|
+
buf.write(
|
163
|
+
json.dumps(
|
164
|
+
self.environment.get_environment_info(include_ext_info=True)
|
165
|
+
).encode("utf-8")
|
166
|
+
)
|
167
|
+
self._add_file(tar, os.path.basename(INFO_FILE), buf)
|
168
|
+
|
169
|
+
@staticmethod
|
170
|
+
def _add_file(tar, filename, buf):
|
171
|
+
info = tarfile.TarInfo(filename)
|
159
172
|
buf.seek(0)
|
160
173
|
info.size = len(buf.getvalue())
|
161
174
|
# Setting this default to Dec 3, 2019
|
@@ -175,6 +188,7 @@ class MetaflowPackage(object):
|
|
175
188
|
fileobj=buf, mode="w:gz", compresslevel=3, dereference=True
|
176
189
|
) as tar:
|
177
190
|
self._add_info(tar)
|
191
|
+
self._add_configs(tar)
|
178
192
|
for path, arcname in self.path_tuples():
|
179
193
|
tar.add(path, arcname=arcname, recursive=False, filter=no_mtime)
|
180
194
|
|
metaflow/parameters.py
CHANGED
@@ -3,7 +3,7 @@ import json
|
|
3
3
|
from contextlib import contextmanager
|
4
4
|
from threading import local
|
5
5
|
|
6
|
-
from typing import Any, Callable, Dict, NamedTuple, Optional, Type, Union
|
6
|
+
from typing import Any, Callable, Dict, NamedTuple, Optional, TYPE_CHECKING, Type, Union
|
7
7
|
|
8
8
|
from metaflow._vendor import click
|
9
9
|
|
@@ -14,6 +14,9 @@ from .exception import (
|
|
14
14
|
MetaflowException,
|
15
15
|
)
|
16
16
|
|
17
|
+
if TYPE_CHECKING:
|
18
|
+
from .user_configs.config_parameters import ConfigValue
|
19
|
+
|
17
20
|
try:
|
18
21
|
# Python2
|
19
22
|
strtype = basestring
|
@@ -32,6 +35,7 @@ ParameterContext = NamedTuple(
|
|
32
35
|
("parameter_name", str),
|
33
36
|
("logger", Callable[..., None]),
|
34
37
|
("ds_type", str),
|
38
|
+
("configs", Optional["ConfigValue"]),
|
35
39
|
],
|
36
40
|
)
|
37
41
|
|
@@ -72,6 +76,16 @@ def flow_context(flow_cls):
|
|
72
76
|
context_proto = None
|
73
77
|
|
74
78
|
|
79
|
+
def replace_flow_context(flow_cls):
|
80
|
+
"""
|
81
|
+
Replace the current flow context with a new flow class. This is used
|
82
|
+
when we change the current flow class after having run user configuration functions
|
83
|
+
"""
|
84
|
+
current_flow.flow_cls_stack = current_flow.flow_cls_stack[1:]
|
85
|
+
current_flow.flow_cls_stack.insert(0, flow_cls)
|
86
|
+
current_flow.flow_cls = current_flow.flow_cls_stack[0]
|
87
|
+
|
88
|
+
|
75
89
|
class JSONTypeClass(click.ParamType):
|
76
90
|
name = "JSON"
|
77
91
|
|
@@ -210,12 +224,18 @@ class DeployTimeField(object):
|
|
210
224
|
def deploy_time_eval(value):
|
211
225
|
if isinstance(value, DeployTimeField):
|
212
226
|
return value(deploy_time=True)
|
227
|
+
elif isinstance(value, DelayedEvaluationParameter):
|
228
|
+
return value(return_str=True)
|
213
229
|
else:
|
214
230
|
return value
|
215
231
|
|
216
232
|
|
217
233
|
# this is called by cli.main
|
218
|
-
def set_parameter_context(flow_name, echo, datastore):
|
234
|
+
def set_parameter_context(flow_name, echo, datastore, configs):
|
235
|
+
from .user_configs.config_parameters import (
|
236
|
+
ConfigValue,
|
237
|
+
) # Prevent circular dependency
|
238
|
+
|
219
239
|
global context_proto
|
220
240
|
context_proto = ParameterContext(
|
221
241
|
flow_name=flow_name,
|
@@ -223,6 +243,7 @@ def set_parameter_context(flow_name, echo, datastore):
|
|
223
243
|
parameter_name=None,
|
224
244
|
logger=echo,
|
225
245
|
ds_type=datastore.TYPE,
|
246
|
+
configs=ConfigValue(dict(configs)),
|
226
247
|
)
|
227
248
|
|
228
249
|
|
@@ -279,7 +300,11 @@ class Parameter(object):
|
|
279
300
|
----------
|
280
301
|
name : str
|
281
302
|
User-visible parameter name.
|
282
|
-
default : str
|
303
|
+
default : Union[str, float, int, bool, Dict[str, Any],
|
304
|
+
Callable[
|
305
|
+
[ParameterContext], Union[str, float, int, bool, Dict[str, Any]]
|
306
|
+
],
|
307
|
+
], optional, default None
|
283
308
|
Default value for the parameter. Use a special `JSONType` class to
|
284
309
|
indicate that the value must be a valid JSON object. A function
|
285
310
|
implies that the parameter corresponds to a *deploy-time parameter*.
|
@@ -288,15 +313,19 @@ class Parameter(object):
|
|
288
313
|
If `default` is not specified, define the parameter type. Specify
|
289
314
|
one of `str`, `float`, `int`, `bool`, or `JSONType`. If None, defaults
|
290
315
|
to the type of `default` or `str` if none specified.
|
291
|
-
help : str, optional
|
316
|
+
help : str, optional, default None
|
292
317
|
Help text to show in `run --help`.
|
293
|
-
required : bool, default
|
294
|
-
Require that the user specified a value for the parameter.
|
295
|
-
|
296
|
-
|
297
|
-
|
318
|
+
required : bool, optional, default None
|
319
|
+
Require that the user specified a value for the parameter. Note that if
|
320
|
+
a default is provide, the required flag is ignored.
|
321
|
+
A value of None is equivalent to False.
|
322
|
+
show_default : bool, optional, default None
|
323
|
+
If True, show the default value in the help text. A value of None is equivalent
|
324
|
+
to True.
|
298
325
|
"""
|
299
326
|
|
327
|
+
IS_CONFIG_PARAMETER = False
|
328
|
+
|
300
329
|
def __init__(
|
301
330
|
self,
|
302
331
|
name: str,
|
@@ -307,31 +336,53 @@ class Parameter(object):
|
|
307
336
|
int,
|
308
337
|
bool,
|
309
338
|
Dict[str, Any],
|
310
|
-
Callable[
|
339
|
+
Callable[
|
340
|
+
[ParameterContext], Union[str, float, int, bool, Dict[str, Any]]
|
341
|
+
],
|
311
342
|
]
|
312
343
|
] = None,
|
313
344
|
type: Optional[
|
314
345
|
Union[Type[str], Type[float], Type[int], Type[bool], JSONTypeClass]
|
315
346
|
] = None,
|
316
347
|
help: Optional[str] = None,
|
317
|
-
required: bool =
|
318
|
-
show_default: bool =
|
348
|
+
required: Optional[bool] = None,
|
349
|
+
show_default: Optional[bool] = None,
|
319
350
|
**kwargs: Dict[str, Any]
|
320
351
|
):
|
321
352
|
self.name = name
|
322
353
|
self.kwargs = kwargs
|
323
|
-
|
354
|
+
self._override_kwargs = {
|
324
355
|
"default": default,
|
325
356
|
"type": type,
|
326
357
|
"help": help,
|
327
358
|
"required": required,
|
328
359
|
"show_default": show_default,
|
329
|
-
}
|
330
|
-
|
331
|
-
|
360
|
+
}
|
361
|
+
|
362
|
+
def init(self):
|
363
|
+
# Prevent circular import
|
364
|
+
from .user_configs.config_parameters import (
|
365
|
+
resolve_delayed_evaluator,
|
366
|
+
unpack_delayed_evaluator,
|
367
|
+
)
|
368
|
+
|
369
|
+
# Resolve any value from configurations
|
370
|
+
self.kwargs = unpack_delayed_evaluator(self.kwargs)
|
371
|
+
self.kwargs = resolve_delayed_evaluator(self.kwargs)
|
372
|
+
|
373
|
+
# This was the behavior before configs: values specified in args would override
|
374
|
+
# stuff in kwargs which is what we implement here as well
|
375
|
+
for key, value in self._override_kwargs.items():
|
376
|
+
if value is not None:
|
377
|
+
self.kwargs[key] = value
|
378
|
+
# Set two default values if no-one specified them
|
379
|
+
self.kwargs.setdefault("required", False)
|
380
|
+
self.kwargs.setdefault("show_default", True)
|
381
|
+
|
382
|
+
# Continue processing kwargs free of any configuration values :)
|
332
383
|
|
333
384
|
# TODO: check that the type is one of the supported types
|
334
|
-
param_type = self.kwargs["type"] = self._get_type(kwargs)
|
385
|
+
param_type = self.kwargs["type"] = self._get_type(self.kwargs)
|
335
386
|
|
336
387
|
reserved_params = [
|
337
388
|
"params",
|
@@ -356,23 +407,27 @@ class Parameter(object):
|
|
356
407
|
raise MetaflowException(
|
357
408
|
"Parameter name '%s' is a reserved "
|
358
409
|
"word. Please use a different "
|
359
|
-
"name for your parameter." % (name)
|
410
|
+
"name for your parameter." % (self.name)
|
360
411
|
)
|
361
412
|
|
362
413
|
# make sure the user is not trying to pass a function in one of the
|
363
414
|
# fields that don't support function-values yet
|
364
415
|
for field in ("show_default", "separator", "required"):
|
365
|
-
if callable(kwargs.get(field)):
|
416
|
+
if callable(self.kwargs.get(field)):
|
366
417
|
raise MetaflowException(
|
367
418
|
"Parameter *%s*: Field '%s' cannot "
|
368
|
-
"have a function as its value" % (name, field)
|
419
|
+
"have a function as its value" % (self.name, field)
|
369
420
|
)
|
370
421
|
|
371
422
|
# default can be defined as a function
|
372
423
|
default_field = self.kwargs.get("default")
|
373
424
|
if callable(default_field) and not isinstance(default_field, DeployTimeField):
|
374
425
|
self.kwargs["default"] = DeployTimeField(
|
375
|
-
|
426
|
+
self.name,
|
427
|
+
param_type,
|
428
|
+
"default",
|
429
|
+
self.kwargs["default"],
|
430
|
+
return_str=True,
|
376
431
|
)
|
377
432
|
|
378
433
|
# note that separator doesn't work with DeployTimeFields unless you
|
@@ -381,7 +436,7 @@ class Parameter(object):
|
|
381
436
|
if self.separator and not self.is_string_type:
|
382
437
|
raise MetaflowException(
|
383
438
|
"Parameter *%s*: Separator is only allowed "
|
384
|
-
"for string parameters." % name
|
439
|
+
"for string parameters." % self.name
|
385
440
|
)
|
386
441
|
|
387
442
|
def __repr__(self):
|
@@ -437,7 +492,9 @@ def add_custom_parameters(deploy_mode=False):
|
|
437
492
|
flow_cls = getattr(current_flow, "flow_cls", None)
|
438
493
|
if flow_cls is None:
|
439
494
|
return cmd
|
440
|
-
parameters = [
|
495
|
+
parameters = [
|
496
|
+
p for _, p in flow_cls._get_parameters() if not p.IS_CONFIG_PARAMETER
|
497
|
+
]
|
441
498
|
for arg in parameters[::-1]:
|
442
499
|
kwargs = arg.option_kwargs(deploy_mode)
|
443
500
|
cmd.params.insert(0, click.Option(("--" + arg.name,), **kwargs))
|
metaflow/plugins/__init__.py
CHANGED
@@ -164,6 +164,10 @@ def get_plugin_cli():
|
|
164
164
|
return resolve_plugins("cli")
|
165
165
|
|
166
166
|
|
167
|
+
def get_plugin_cli_path():
|
168
|
+
return resolve_plugins("cli", path_only=True)
|
169
|
+
|
170
|
+
|
167
171
|
STEP_DECORATORS = resolve_plugins("step_decorator")
|
168
172
|
FLOW_DECORATORS = resolve_plugins("flow_decorator")
|
169
173
|
ENVIRONMENTS = resolve_plugins("environment")
|
@@ -283,6 +283,7 @@ def make_flow(
|
|
283
283
|
):
|
284
284
|
# Attach @kubernetes.
|
285
285
|
decorators._attach_decorators(obj.flow, [KubernetesDecorator.name])
|
286
|
+
decorators._init(obj.flow)
|
286
287
|
|
287
288
|
decorators._init_step_decorators(
|
288
289
|
obj.flow, obj.graph, obj.environment, obj.flow_datastore, obj.logger
|