ob-metaflow 2.12.36.3__py2.py3-none-any.whl → 2.13.0.1__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.
Potentially problematic release.
This version of ob-metaflow might be problematic. Click here for more details.
- metaflow/__init__.py +3 -0
- metaflow/cli.py +180 -718
- 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 +360 -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/datastore/flow_datastore.py +2 -2
- metaflow/decorators.py +63 -2
- metaflow/exception.py +8 -2
- metaflow/extension_support/plugins.py +41 -27
- metaflow/flowspec.py +175 -23
- metaflow/graph.py +28 -27
- metaflow/includefile.py +50 -22
- metaflow/lint.py +35 -20
- metaflow/metaflow_config.py +6 -1
- metaflow/package.py +17 -3
- metaflow/parameters.py +87 -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/argo/argo_workflows_deployer_objects.py +47 -1
- metaflow/plugins/aws/batch/batch_decorator.py +2 -2
- metaflow/plugins/aws/secrets_manager/aws_secrets_manager_secrets_provider.py +13 -10
- metaflow/plugins/aws/step_functions/step_functions.py +32 -0
- metaflow/plugins/aws/step_functions/step_functions_cli.py +1 -0
- metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +3 -0
- metaflow/plugins/cards/card_creator.py +1 -0
- metaflow/plugins/cards/card_decorator.py +46 -8
- 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/bootstrap.py +196 -61
- metaflow/plugins/pypi/conda_decorator.py +20 -10
- metaflow/plugins/pypi/conda_environment.py +76 -21
- metaflow/plugins/pypi/micromamba.py +42 -15
- metaflow/plugins/pypi/pip.py +8 -3
- metaflow/plugins/pypi/pypi_decorator.py +11 -9
- metaflow/plugins/timeout_decorator.py +2 -2
- metaflow/runner/click_api.py +240 -50
- metaflow/runner/deployer.py +1 -1
- metaflow/runner/deployer_impl.py +8 -3
- metaflow/runner/metaflow_runner.py +10 -2
- metaflow/runner/nbdeploy.py +2 -0
- metaflow/runner/nbrun.py +1 -1
- metaflow/runner/subprocess_manager.py +3 -1
- metaflow/runner/utils.py +41 -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 +548 -0
- metaflow/user_configs/config_parameters.py +405 -0
- metaflow/util.py +17 -0
- metaflow/version.py +1 -1
- {ob_metaflow-2.12.36.3.dist-info → ob_metaflow-2.13.0.1.dist-info}/METADATA +3 -2
- {ob_metaflow-2.12.36.3.dist-info → ob_metaflow-2.13.0.1.dist-info}/RECORD +65 -55
- {ob_metaflow-2.12.36.3.dist-info → ob_metaflow-2.13.0.1.dist-info}/LICENSE +0 -0
- {ob_metaflow-2.12.36.3.dist-info → ob_metaflow-2.13.0.1.dist-info}/WHEEL +0 -0
- {ob_metaflow-2.12.36.3.dist-info → ob_metaflow-2.13.0.1.dist-info}/entry_points.txt +0 -0
- {ob_metaflow-2.12.36.3.dist-info → ob_metaflow-2.13.0.1.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,13 @@ 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
|
+
ConfigValue,
|
|
28
|
+
CustomFlowDecorator,
|
|
29
|
+
CustomStepDecorator,
|
|
30
|
+
MutableFlow,
|
|
31
|
+
MutableStep,
|
|
32
|
+
)
|
|
23
33
|
from .util import to_pod
|
|
24
34
|
from .metaflow_config import INCLUDE_FOREACH_STACK, MAXIMUM_FOREACH_VALUE_CHARS
|
|
25
35
|
|
|
@@ -65,15 +75,36 @@ class ParallelUBF(UnboundedForeachInput):
|
|
|
65
75
|
return item or 0 # item is None for the control task, but it is also split 0
|
|
66
76
|
|
|
67
77
|
|
|
78
|
+
class _FlowState(Enum):
|
|
79
|
+
CONFIGS = 1
|
|
80
|
+
CONFIG_DECORATORS = 2
|
|
81
|
+
CACHED_PARAMETERS = 3
|
|
82
|
+
|
|
83
|
+
|
|
68
84
|
class FlowSpecMeta(type):
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
# class
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
def __init__(cls, name, bases, attrs):
|
|
86
|
+
super().__init__(name, bases, attrs)
|
|
87
|
+
if name == "FlowSpec":
|
|
88
|
+
return
|
|
89
|
+
# We store some state in the flow class itself. This is primarily used to
|
|
90
|
+
# attach global state to a flow. It is *not* an actual global because of
|
|
91
|
+
# Runner/NBRunner. This is also created here in the meta class to avoid it being
|
|
92
|
+
# shared between different children classes.
|
|
93
|
+
|
|
94
|
+
# We should move _flow_decorators into this structure as well but keeping it
|
|
95
|
+
# out to limit the changes for now.
|
|
96
|
+
cls._flow_decorators = {}
|
|
97
|
+
|
|
98
|
+
# Keys are _FlowState enum values
|
|
99
|
+
cls._flow_state = {}
|
|
100
|
+
|
|
101
|
+
cls._init_attrs()
|
|
102
|
+
|
|
103
|
+
def _init_attrs(cls):
|
|
104
|
+
# Graph and steps are specific to the class -- store here so we can access
|
|
105
|
+
# in class method _process_config_decorators
|
|
106
|
+
cls._graph = FlowGraph(cls)
|
|
107
|
+
cls._steps = [getattr(cls, node.name) for node in cls._graph]
|
|
77
108
|
|
|
78
109
|
|
|
79
110
|
class FlowSpec(metaclass=FlowSpecMeta):
|
|
@@ -96,6 +127,7 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
96
127
|
"_cached_input",
|
|
97
128
|
"_graph",
|
|
98
129
|
"_flow_decorators",
|
|
130
|
+
"_flow_state",
|
|
99
131
|
"_steps",
|
|
100
132
|
"index",
|
|
101
133
|
"input",
|
|
@@ -122,9 +154,6 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
122
154
|
self._transition = None
|
|
123
155
|
self._cached_input = {}
|
|
124
156
|
|
|
125
|
-
self._graph = FlowGraph(self.__class__)
|
|
126
|
-
self._steps = [getattr(self, node.name) for node in self._graph]
|
|
127
|
-
|
|
128
157
|
if use_cli:
|
|
129
158
|
with parameters.flow_context(self.__class__) as _:
|
|
130
159
|
from . import cli
|
|
@@ -148,16 +177,12 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
148
177
|
fname = fname[:-1]
|
|
149
178
|
return os.path.basename(fname)
|
|
150
179
|
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
180
|
+
@classmethod
|
|
181
|
+
def _check_parameters(cls, config_parameters=False):
|
|
159
182
|
seen = set()
|
|
160
|
-
for
|
|
183
|
+
for _, param in cls._get_parameters():
|
|
184
|
+
if param.IS_CONFIG_PARAMETER != config_parameters:
|
|
185
|
+
continue
|
|
161
186
|
norm = param.name.lower()
|
|
162
187
|
if norm in seen:
|
|
163
188
|
raise MetaflowException(
|
|
@@ -166,17 +191,136 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
166
191
|
"case-insensitive." % param.name
|
|
167
192
|
)
|
|
168
193
|
seen.add(norm)
|
|
169
|
-
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
def _process_config_decorators(cls, config_options, ignore_errors=False):
|
|
197
|
+
|
|
198
|
+
# Fast path for no user configurations
|
|
199
|
+
if not cls._flow_state.get(_FlowState.CONFIG_DECORATORS):
|
|
200
|
+
# Process parameters to allow them to also use config values easily
|
|
201
|
+
for var, param in cls._get_parameters():
|
|
202
|
+
if param.IS_CONFIG_PARAMETER:
|
|
203
|
+
continue
|
|
204
|
+
param.init(ignore_errors)
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
debug.userconf_exec("Processing mutating step/flow decorators")
|
|
208
|
+
# We need to convert all the user configurations from DelayedEvaluationParameters
|
|
209
|
+
# to actual values so they can be used as is in the config decorators.
|
|
210
|
+
|
|
211
|
+
# We then reset them to be proper configs so they can be re-evaluated in
|
|
212
|
+
# _set_constants
|
|
213
|
+
to_reset_configs = []
|
|
214
|
+
cls._check_parameters(config_parameters=True)
|
|
215
|
+
for var, param in cls._get_parameters():
|
|
216
|
+
if not param.IS_CONFIG_PARAMETER:
|
|
217
|
+
continue
|
|
218
|
+
# Note that a config with no default and not required will be None
|
|
219
|
+
val = config_options.get(param.name.replace("-", "_").lower())
|
|
220
|
+
if isinstance(val, DelayedEvaluationParameter):
|
|
221
|
+
val = val()
|
|
222
|
+
# We store the value as well so that in _set_constants, we don't try
|
|
223
|
+
# to recompute (no guarantee that it is stable)
|
|
224
|
+
param._store_value(val)
|
|
225
|
+
to_reset_configs.append((var, param))
|
|
226
|
+
debug.userconf_exec("Setting config %s to %s" % (var, str(val)))
|
|
227
|
+
setattr(cls, var, val)
|
|
228
|
+
|
|
229
|
+
# Run all the decorators. Step decorators are directly in the step and
|
|
230
|
+
# we will run those first and *then* we run all the flow level decorators
|
|
231
|
+
for step in cls._steps:
|
|
232
|
+
for deco in step.config_decorators:
|
|
233
|
+
if isinstance(deco, CustomStepDecorator):
|
|
234
|
+
debug.userconf_exec(
|
|
235
|
+
"Evaluating step level decorator %s for %s"
|
|
236
|
+
% (deco.__class__.__name__, step.name)
|
|
237
|
+
)
|
|
238
|
+
deco.evaluate(MutableStep(cls, step))
|
|
239
|
+
else:
|
|
240
|
+
raise MetaflowInternalError(
|
|
241
|
+
"A non CustomFlowDecorator found in step custom decorators"
|
|
242
|
+
)
|
|
243
|
+
if step.config_decorators:
|
|
244
|
+
# We remove all mention of the custom step decorator
|
|
245
|
+
setattr(cls, step.name, step)
|
|
246
|
+
|
|
247
|
+
mutable_flow = MutableFlow(cls)
|
|
248
|
+
for deco in cls._flow_state[_FlowState.CONFIG_DECORATORS]:
|
|
249
|
+
if isinstance(deco, CustomFlowDecorator):
|
|
250
|
+
# Sanity check to make sure we are applying the decorator to the right
|
|
251
|
+
# class
|
|
252
|
+
if not deco._flow_cls == cls and not issubclass(cls, deco._flow_cls):
|
|
253
|
+
raise MetaflowInternalError(
|
|
254
|
+
"CustomFlowDecorator registered on the wrong flow -- "
|
|
255
|
+
"expected %s but got %s"
|
|
256
|
+
% (deco._flow_cls.__name__, cls.__name__)
|
|
257
|
+
)
|
|
258
|
+
debug.userconf_exec(
|
|
259
|
+
"Evaluating flow level decorator %s" % deco.__class__.__name__
|
|
260
|
+
)
|
|
261
|
+
deco.evaluate(mutable_flow)
|
|
262
|
+
# We reset cached_parameters on the very off chance that the user added
|
|
263
|
+
# more configurations based on the configuration
|
|
264
|
+
if _FlowState.CACHED_PARAMETERS in cls._flow_state:
|
|
265
|
+
del cls._flow_state[_FlowState.CACHED_PARAMETERS]
|
|
266
|
+
else:
|
|
267
|
+
raise MetaflowInternalError(
|
|
268
|
+
"A non CustomFlowDecorator found in flow custom decorators"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Process parameters to allow them to also use config values easily
|
|
272
|
+
for var, param in cls._get_parameters():
|
|
273
|
+
if param.IS_CONFIG_PARAMETER:
|
|
274
|
+
continue
|
|
275
|
+
param.init()
|
|
276
|
+
# Reset all configs that were already present in the class.
|
|
277
|
+
# TODO: This means that users can't override configs directly. Not sure if this
|
|
278
|
+
# is a pattern we want to support
|
|
279
|
+
for var, param in to_reset_configs:
|
|
280
|
+
setattr(cls, var, param)
|
|
281
|
+
|
|
282
|
+
# Reset cached parameters again since we added back the config parameters
|
|
283
|
+
if _FlowState.CACHED_PARAMETERS in cls._flow_state:
|
|
284
|
+
del cls._flow_state[_FlowState.CACHED_PARAMETERS]
|
|
285
|
+
|
|
286
|
+
# Set the current flow class we are in (the one we just created)
|
|
287
|
+
parameters.replace_flow_context(cls)
|
|
288
|
+
|
|
289
|
+
# Re-calculate class level attributes after modifying the class
|
|
290
|
+
cls._init_attrs()
|
|
291
|
+
return cls
|
|
292
|
+
|
|
293
|
+
def _set_constants(self, graph, kwargs, config_options):
|
|
294
|
+
from metaflow.decorators import (
|
|
295
|
+
flow_decorators,
|
|
296
|
+
) # To prevent circular dependency
|
|
297
|
+
|
|
298
|
+
# Persist values for parameters and other constants (class level variables)
|
|
299
|
+
# only once. This method is called before persist_constants is called to
|
|
300
|
+
# persist all values set using setattr
|
|
301
|
+
self._check_parameters(config_parameters=False)
|
|
302
|
+
|
|
303
|
+
seen = set()
|
|
170
304
|
self._success = True
|
|
171
305
|
|
|
172
306
|
parameters_info = []
|
|
173
307
|
for var, param in self._get_parameters():
|
|
174
308
|
seen.add(var)
|
|
175
|
-
|
|
309
|
+
if param.IS_CONFIG_PARAMETER:
|
|
310
|
+
# Use computed value if already evaluated, else get from config_options
|
|
311
|
+
val = param._computed_value or config_options.get(
|
|
312
|
+
param.name.replace("-", "_").lower()
|
|
313
|
+
)
|
|
314
|
+
else:
|
|
315
|
+
val = kwargs[param.name.replace("-", "_").lower()]
|
|
176
316
|
# Support for delayed evaluation of parameters.
|
|
177
317
|
if isinstance(val, DelayedEvaluationParameter):
|
|
178
318
|
val = val()
|
|
179
319
|
val = val.split(param.separator) if val and param.separator else val
|
|
320
|
+
if isinstance(val, ConfigValue):
|
|
321
|
+
# We store config values as dict so they are accessible with older
|
|
322
|
+
# metaflow clients. It also makes it easier to access.
|
|
323
|
+
val = val.to_dict()
|
|
180
324
|
setattr(self, var, val)
|
|
181
325
|
parameters_info.append({"name": var, "type": param.__class__.__name__})
|
|
182
326
|
|
|
@@ -218,6 +362,12 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
218
362
|
|
|
219
363
|
@classmethod
|
|
220
364
|
def _get_parameters(cls):
|
|
365
|
+
cached = cls._flow_state.get(_FlowState.CACHED_PARAMETERS)
|
|
366
|
+
if cached is not None:
|
|
367
|
+
for var in cached:
|
|
368
|
+
yield var, getattr(cls, var)
|
|
369
|
+
return
|
|
370
|
+
build_list = []
|
|
221
371
|
for var in dir(cls):
|
|
222
372
|
if var[0] == "_" or var in cls._NON_PARAMETERS:
|
|
223
373
|
continue
|
|
@@ -226,7 +376,9 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
226
376
|
except:
|
|
227
377
|
continue
|
|
228
378
|
if isinstance(val, Parameter):
|
|
379
|
+
build_list.append(var)
|
|
229
380
|
yield var, val
|
|
381
|
+
cls._flow_state[_FlowState.CACHED_PARAMETERS] = build_list
|
|
230
382
|
|
|
231
383
|
def _set_datastore(self, datastore):
|
|
232
384
|
self._datastore = datastore
|
metaflow/graph.py
CHANGED
|
@@ -45,9 +45,12 @@ def deindent_docstring(doc):
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
class DAGNode(object):
|
|
48
|
-
def __init__(self, func_ast, decos, doc):
|
|
48
|
+
def __init__(self, func_ast, decos, doc, source_file, lineno):
|
|
49
49
|
self.name = func_ast.name
|
|
50
|
-
self.
|
|
50
|
+
self.source_file = source_file
|
|
51
|
+
# lineno is the start line of decorators in source_file
|
|
52
|
+
# func_ast.lineno is lines from decorators start to def of function
|
|
53
|
+
self.func_lineno = lineno + func_ast.lineno - 1
|
|
51
54
|
self.decorators = decos
|
|
52
55
|
self.doc = deindent_docstring(doc)
|
|
53
56
|
self.parallel_step = any(getattr(deco, "IS_PARALLEL", False) for deco in decos)
|
|
@@ -62,7 +65,7 @@ class DAGNode(object):
|
|
|
62
65
|
self.foreach_param = None
|
|
63
66
|
self.num_parallel = 0
|
|
64
67
|
self.parallel_foreach = False
|
|
65
|
-
self._parse(func_ast)
|
|
68
|
+
self._parse(func_ast, lineno)
|
|
66
69
|
|
|
67
70
|
# these attributes are populated by _traverse_graph
|
|
68
71
|
self.in_funcs = set()
|
|
@@ -74,7 +77,7 @@ class DAGNode(object):
|
|
|
74
77
|
def _expr_str(self, expr):
|
|
75
78
|
return "%s.%s" % (expr.value.id, expr.attr)
|
|
76
79
|
|
|
77
|
-
def _parse(self, func_ast):
|
|
80
|
+
def _parse(self, func_ast, lineno):
|
|
78
81
|
self.num_args = len(func_ast.args.args)
|
|
79
82
|
tail = func_ast.body[-1]
|
|
80
83
|
|
|
@@ -94,7 +97,7 @@ class DAGNode(object):
|
|
|
94
97
|
|
|
95
98
|
self.has_tail_next = True
|
|
96
99
|
self.invalid_tail_next = True
|
|
97
|
-
self.tail_next_lineno = tail.lineno
|
|
100
|
+
self.tail_next_lineno = lineno + tail.lineno - 1
|
|
98
101
|
self.out_funcs = [e.attr for e in tail.value.args]
|
|
99
102
|
|
|
100
103
|
keywords = dict(
|
|
@@ -131,7 +134,7 @@ class DAGNode(object):
|
|
|
131
134
|
return
|
|
132
135
|
|
|
133
136
|
def __str__(self):
|
|
134
|
-
return """*[{0.name} {0.type} (line {0.func_lineno})]*
|
|
137
|
+
return """*[{0.name} {0.type} ({0.source_file} line {0.func_lineno})]*
|
|
135
138
|
in_funcs={in_funcs}
|
|
136
139
|
out_funcs={out_funcs}
|
|
137
140
|
split_parents={parents}
|
|
@@ -156,18 +159,6 @@ class DAGNode(object):
|
|
|
156
159
|
)
|
|
157
160
|
|
|
158
161
|
|
|
159
|
-
class StepVisitor(ast.NodeVisitor):
|
|
160
|
-
def __init__(self, nodes, flow):
|
|
161
|
-
self.nodes = nodes
|
|
162
|
-
self.flow = flow
|
|
163
|
-
super(StepVisitor, self).__init__()
|
|
164
|
-
|
|
165
|
-
def visit_FunctionDef(self, node):
|
|
166
|
-
func = getattr(self.flow, node.name)
|
|
167
|
-
if hasattr(func, "is_step"):
|
|
168
|
-
self.nodes[node.name] = DAGNode(node, func.decorators, func.__doc__)
|
|
169
|
-
|
|
170
|
-
|
|
171
162
|
class FlowGraph(object):
|
|
172
163
|
def __init__(self, flow):
|
|
173
164
|
self.name = flow.__name__
|
|
@@ -179,13 +170,20 @@ class FlowGraph(object):
|
|
|
179
170
|
self._postprocess()
|
|
180
171
|
|
|
181
172
|
def _create_nodes(self, flow):
|
|
182
|
-
module = __import__(flow.__module__)
|
|
183
|
-
tree = ast.parse(inspect.getsource(module)).body
|
|
184
|
-
root = [n for n in tree if isinstance(n, ast.ClassDef) and n.name == self.name][
|
|
185
|
-
0
|
|
186
|
-
]
|
|
187
173
|
nodes = {}
|
|
188
|
-
|
|
174
|
+
for element in dir(flow):
|
|
175
|
+
func = getattr(flow, element)
|
|
176
|
+
if callable(func) and hasattr(func, "is_step"):
|
|
177
|
+
source_file = inspect.getsourcefile(func)
|
|
178
|
+
source_lines, lineno = inspect.getsourcelines(func)
|
|
179
|
+
# This also works for code (strips out leading whitspace based on
|
|
180
|
+
# first line)
|
|
181
|
+
source_code = deindent_docstring("".join(source_lines))
|
|
182
|
+
function_ast = ast.parse(source_code).body[0]
|
|
183
|
+
node = DAGNode(
|
|
184
|
+
function_ast, func.decorators, func.__doc__, source_file, lineno
|
|
185
|
+
)
|
|
186
|
+
nodes[element] = node
|
|
189
187
|
return nodes
|
|
190
188
|
|
|
191
189
|
def _postprocess(self):
|
|
@@ -201,6 +199,10 @@ class FlowGraph(object):
|
|
|
201
199
|
|
|
202
200
|
def _traverse_graph(self):
|
|
203
201
|
def traverse(node, seen, split_parents):
|
|
202
|
+
try:
|
|
203
|
+
self.sorted_nodes.remove(node.name)
|
|
204
|
+
except ValueError:
|
|
205
|
+
pass
|
|
204
206
|
self.sorted_nodes.append(node.name)
|
|
205
207
|
if node.type in ("split", "foreach"):
|
|
206
208
|
node.split_parents = split_parents
|
|
@@ -240,9 +242,7 @@ class FlowGraph(object):
|
|
|
240
242
|
return iter(self.nodes.values())
|
|
241
243
|
|
|
242
244
|
def __str__(self):
|
|
243
|
-
return "\n".join(
|
|
244
|
-
str(n) for _, n in sorted((n.func_lineno, n) for n in self.nodes.values())
|
|
245
|
-
)
|
|
245
|
+
return "\n".join(str(self[n]) for n in self.sorted_nodes)
|
|
246
246
|
|
|
247
247
|
def output_dot(self):
|
|
248
248
|
def edge_specs():
|
|
@@ -286,6 +286,7 @@ class FlowGraph(object):
|
|
|
286
286
|
"name": name,
|
|
287
287
|
"type": node_to_type(node),
|
|
288
288
|
"line": node.func_lineno,
|
|
289
|
+
"source_file": node.source_file,
|
|
289
290
|
"doc": node.doc,
|
|
290
291
|
"decorators": [
|
|
291
292
|
{
|
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, ignore_errors=False):
|
|
294
|
+
super(IncludeFile, self).init(ignore_errors)
|
|
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
|