ob-metaflow 2.12.36.1__py2.py3-none-any.whl → 2.12.36.2__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.

Files changed (56) hide show
  1. metaflow/__init__.py +3 -0
  2. metaflow/cli.py +84 -697
  3. metaflow/cli_args.py +17 -0
  4. metaflow/cli_components/__init__.py +0 -0
  5. metaflow/cli_components/dump_cmd.py +96 -0
  6. metaflow/cli_components/init_cmd.py +51 -0
  7. metaflow/cli_components/run_cmds.py +358 -0
  8. metaflow/cli_components/step_cmd.py +189 -0
  9. metaflow/cli_components/utils.py +140 -0
  10. metaflow/cmd/develop/stub_generator.py +9 -2
  11. metaflow/decorators.py +63 -2
  12. metaflow/extension_support/plugins.py +41 -27
  13. metaflow/flowspec.py +156 -16
  14. metaflow/includefile.py +50 -22
  15. metaflow/metaflow_config.py +1 -1
  16. metaflow/package.py +17 -3
  17. metaflow/parameters.py +80 -23
  18. metaflow/plugins/__init__.py +4 -0
  19. metaflow/plugins/airflow/airflow_cli.py +1 -0
  20. metaflow/plugins/argo/argo_workflows.py +44 -4
  21. metaflow/plugins/argo/argo_workflows_cli.py +1 -0
  22. metaflow/plugins/argo/argo_workflows_deployer_objects.py +5 -1
  23. metaflow/plugins/aws/batch/batch_decorator.py +2 -2
  24. metaflow/plugins/aws/step_functions/step_functions.py +32 -0
  25. metaflow/plugins/aws/step_functions/step_functions_cli.py +1 -0
  26. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +3 -0
  27. metaflow/plugins/datatools/s3/s3op.py +3 -3
  28. metaflow/plugins/kubernetes/kubernetes.py +3 -3
  29. metaflow/plugins/kubernetes/kubernetes_cli.py +1 -1
  30. metaflow/plugins/kubernetes/kubernetes_decorator.py +2 -2
  31. metaflow/plugins/kubernetes/kubernetes_job.py +3 -3
  32. metaflow/plugins/pypi/conda_decorator.py +20 -10
  33. metaflow/plugins/pypi/pypi_decorator.py +11 -9
  34. metaflow/plugins/timeout_decorator.py +2 -2
  35. metaflow/runner/click_api.py +73 -19
  36. metaflow/runner/deployer.py +1 -1
  37. metaflow/runner/deployer_impl.py +2 -2
  38. metaflow/runner/metaflow_runner.py +4 -1
  39. metaflow/runner/nbdeploy.py +2 -0
  40. metaflow/runner/nbrun.py +1 -1
  41. metaflow/runner/subprocess_manager.py +3 -1
  42. metaflow/runner/utils.py +37 -20
  43. metaflow/runtime.py +111 -73
  44. metaflow/sidecar/sidecar_worker.py +1 -1
  45. metaflow/user_configs/__init__.py +0 -0
  46. metaflow/user_configs/config_decorators.py +563 -0
  47. metaflow/user_configs/config_options.py +495 -0
  48. metaflow/user_configs/config_parameters.py +386 -0
  49. metaflow/util.py +17 -0
  50. metaflow/version.py +1 -1
  51. {ob_metaflow-2.12.36.1.dist-info → ob_metaflow-2.12.36.2.dist-info}/METADATA +3 -2
  52. {ob_metaflow-2.12.36.1.dist-info → ob_metaflow-2.12.36.2.dist-info}/RECORD +56 -46
  53. {ob_metaflow-2.12.36.1.dist-info → ob_metaflow-2.12.36.2.dist-info}/LICENSE +0 -0
  54. {ob_metaflow-2.12.36.1.dist-info → ob_metaflow-2.12.36.2.dist-info}/WHEEL +0 -0
  55. {ob_metaflow-2.12.36.1.dist-info → ob_metaflow-2.12.36.2.dist-info}/entry_points.txt +0 -0
  56. {ob_metaflow-2.12.36.1.dist-info → ob_metaflow-2.12.36.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,386 @@
1
+ import collections.abc
2
+ import json
3
+ import os
4
+ import re
5
+
6
+ from typing import Any, Callable, Dict, Optional, TYPE_CHECKING, Union
7
+
8
+
9
+ from ..exception import MetaflowException
10
+
11
+ from ..parameters import (
12
+ Parameter,
13
+ ParameterContext,
14
+ current_flow,
15
+ )
16
+
17
+ if TYPE_CHECKING:
18
+ from metaflow import FlowSpec
19
+
20
+ # _tracefunc_depth = 0
21
+
22
+
23
+ # def tracefunc(func):
24
+ # """Decorates a function to show its trace."""
25
+
26
+ # @functools.wraps(func)
27
+ # def tracefunc_closure(*args, **kwargs):
28
+ # global _tracefunc_depth
29
+ # """The closure."""
30
+ # print(f"{_tracefunc_depth}: {func.__name__}(args={args}, kwargs={kwargs})")
31
+ # _tracefunc_depth += 1
32
+ # result = func(*args, **kwargs)
33
+ # _tracefunc_depth -= 1
34
+ # print(f"{_tracefunc_depth} => {result}")
35
+ # return result
36
+
37
+ # return tracefunc_closure
38
+
39
+ CONFIG_FILE = os.path.join(
40
+ os.path.dirname(os.path.abspath(__file__)), "CONFIG_PARAMETERS"
41
+ )
42
+
43
+ ID_PATTERN = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
44
+
45
+ UNPACK_KEY = "_unpacked_delayed_"
46
+
47
+
48
+ def dump_config_values(flow: "FlowSpec"):
49
+ from ..flowspec import _FlowState # Prevent circular import
50
+
51
+ configs = flow._flow_state.get(_FlowState.CONFIGS)
52
+ if configs:
53
+ return {"user_configs": configs}
54
+ return {}
55
+
56
+
57
+ class ConfigValue(collections.abc.Mapping):
58
+ """
59
+ ConfigValue is a thin wrapper around an arbitrarily nested dictionary-like
60
+ configuration object. It allows you to access elements of this nested structure
61
+ using either a "." notation or a [] notation. As an example, if your configuration
62
+ object is:
63
+ {"foo": {"bar": 42}}
64
+ you can access the value 42 using either config["foo"]["bar"] or config.foo.bar.
65
+
66
+ All "keys"" need to be valid Python identifiers
67
+ """
68
+
69
+ # Thin wrapper to allow configuration values to be accessed using a "." notation
70
+ # as well as a [] notation.
71
+
72
+ def __init__(self, data: Dict[str, Any]):
73
+ if any(not ID_PATTERN.match(k) for k in data.keys()):
74
+ raise MetaflowException(
75
+ "All keys in the configuration must be valid Python identifiers"
76
+ )
77
+ self._data = data
78
+
79
+ def __getattr__(self, key: str) -> Any:
80
+ """
81
+ Access an element of this configuration
82
+
83
+ Parameters
84
+ ----------
85
+ key : str
86
+ Element to access
87
+
88
+ Returns
89
+ -------
90
+ Any
91
+ Element of the configuration
92
+ """
93
+ if key == "_data":
94
+ # Called during unpickling. Special case to not run into infinite loop
95
+ # below.
96
+ raise AttributeError(key)
97
+
98
+ if key in self._data:
99
+ return self[key]
100
+ raise AttributeError(key)
101
+
102
+ def __setattr__(self, name: str, value: Any) -> None:
103
+ # Prevent configuration modification
104
+ if name == "_data":
105
+ return super().__setattr__(name, value)
106
+ raise TypeError("ConfigValue is immutable")
107
+
108
+ def __getitem__(self, key: Any) -> Any:
109
+ """
110
+ Access an element of this configuration
111
+
112
+ Parameters
113
+ ----------
114
+ key : Any
115
+ Element to access
116
+
117
+ Returns
118
+ -------
119
+ Any
120
+ Element of the configuration
121
+ """
122
+ value = self._data[key]
123
+ if isinstance(value, dict):
124
+ value = ConfigValue(value)
125
+ return value
126
+
127
+ def __len__(self):
128
+ return len(self._data)
129
+
130
+ def __iter__(self):
131
+ return iter(self._data)
132
+
133
+ def __repr__(self):
134
+ return repr(self._data)
135
+
136
+ def __str__(self):
137
+ return json.dumps(self._data)
138
+
139
+ def to_dict(self) -> Dict[Any, Any]:
140
+ """
141
+ Returns a dictionary representation of this configuration object.
142
+
143
+ Returns
144
+ -------
145
+ Dict[Any, Any]
146
+ Dictionary equivalent of this configuration object.
147
+ """
148
+ return dict(self._data)
149
+
150
+
151
+ class DelayEvaluator(collections.abc.Mapping):
152
+ """
153
+ Small wrapper that allows the evaluation of a Config() value in a delayed manner.
154
+ This is used when we want to use config.* values in decorators for example.
155
+
156
+ It also allows the following "delayed" access on an obj that is a DelayEvaluation
157
+ - obj.x.y.z (ie: accessing members of DelayEvaluator; accesses will be delayed until
158
+ the DelayEvaluator is evaluated)
159
+ - **obj (ie: unpacking the DelayEvaluator as a dictionary). Note that this requires
160
+ special handling in whatever this is being unpacked into, specifically the handling
161
+ of _unpacked_delayed_*
162
+ """
163
+
164
+ def __init__(self, ex: str):
165
+ self._config_expr = ex
166
+ if ID_PATTERN.match(self._config_expr):
167
+ # This is a variable only so allow things like config_expr("config").var
168
+ self._is_var_only = True
169
+ self._access = []
170
+ else:
171
+ self._is_var_only = False
172
+ self._access = None
173
+
174
+ def __iter__(self):
175
+ yield "%s%d" % (UNPACK_KEY, id(self))
176
+
177
+ def __getitem__(self, key):
178
+ if key == "%s%d" % (UNPACK_KEY, id(self)):
179
+ return self
180
+ raise KeyError(key)
181
+
182
+ def __len__(self):
183
+ return 1
184
+
185
+ def __getattr__(self, name):
186
+ if self._access is None:
187
+ raise AttributeError()
188
+ self._access.append(name)
189
+ return self
190
+
191
+ def __call__(self, ctx=None, deploy_time=False):
192
+ from ..flowspec import _FlowState # Prevent circular import
193
+
194
+ # Two additional arguments are only used by DeployTimeField which will call
195
+ # this function with those two additional arguments. They are ignored.
196
+ flow_cls = getattr(current_flow, "flow_cls", None)
197
+ if flow_cls is None:
198
+ # We are not executing inside a flow (ie: not the CLI)
199
+ raise MetaflowException(
200
+ "Config object can only be used directly in the FlowSpec defining them. "
201
+ "If using outside of the FlowSpec, please use ConfigEval"
202
+ )
203
+ if self._access is not None:
204
+ # Build the final expression by adding all the fields in access as . fields
205
+ self._config_expr = ".".join([self._config_expr] + self._access)
206
+ # Evaluate the expression setting the config values as local variables
207
+ try:
208
+ return eval(
209
+ self._config_expr,
210
+ globals(),
211
+ {
212
+ k: ConfigValue(v)
213
+ for k, v in flow_cls._flow_state.get(_FlowState.CONFIGS, {}).items()
214
+ },
215
+ )
216
+ except NameError as e:
217
+ potential_config_name = self._config_expr.split(".")[0]
218
+ if potential_config_name not in flow_cls._flow_state.get(
219
+ _FlowState.CONFIGS, {}
220
+ ):
221
+ raise MetaflowException(
222
+ "Config '%s' not found in the flow (maybe not required and not "
223
+ "provided?)" % potential_config_name
224
+ ) from e
225
+ raise
226
+
227
+
228
+ def config_expr(expr: str) -> DelayEvaluator:
229
+ """
230
+ Function to allow you to use an expression involving a config parameter in
231
+ places where it may not be directory accessible or if you want a more complicated
232
+ expression than just a single variable.
233
+
234
+ You can use it as follows:
235
+ - When the config is not directly accessible:
236
+
237
+ @project(name=config_expr("config").project.name)
238
+ class MyFlow(FlowSpec):
239
+ config = Config("config")
240
+ ...
241
+ - When you want a more complex expression:
242
+ class MyFlow(FlowSpec):
243
+ config = Config("config")
244
+
245
+ @environment(vars={"foo": config_expr("config.bar.baz.lower()")})
246
+ @step
247
+ def start(self):
248
+ ...
249
+
250
+ Parameters
251
+ ----------
252
+ expr : str
253
+ Expression using the config values.
254
+ """
255
+ return DelayEvaluator(expr)
256
+
257
+
258
+ class Config(Parameter, collections.abc.Mapping):
259
+ """
260
+ Includes a configuration for this flow.
261
+
262
+ `Config` is a special type of `Parameter` but differs in a few key areas:
263
+ - it is immutable and determined at deploy time (or prior to running if not deploying
264
+ to a scheduler)
265
+ - as such, it can be used anywhere in your code including in Metaflow decorators
266
+
267
+ The value of the configuration is determines as follows:
268
+ - use the user-provided file path or value. It is an error to provide both
269
+ - if none are present:
270
+ - if a default file path (default) is provided, attempt to read this file
271
+ - if the file is present, use that value. Note that the file will be used
272
+ even if it has an invalid syntax
273
+ - if the file is not present, and a default value is present, use that
274
+ - if still None and is required, this is an error.
275
+
276
+ Parameters
277
+ ----------
278
+ name : str
279
+ User-visible configuration name.
280
+ default : Union[str, Callable[[ParameterContext], str], optional, default None
281
+ Default path from where to read this configuration. A function implies that the
282
+ value will be computed using that function.
283
+ You can only specify default or default_value.
284
+ default_value : Union[str, Dict[str, Any], Callable[[ParameterContext, Union[str, Dict[str, Any]]], Any], optional, default None
285
+ Default value for the parameter. A function
286
+ implies that the value will be computed using that function.
287
+ You can only specify default or default_value.
288
+ help : str, optional, default None
289
+ Help text to show in `run --help`.
290
+ required : bool, optional, default None
291
+ Require that the user specified a value for the configuration. Note that if
292
+ a default is provided, the required flag is ignored. A value of None is
293
+ equivalent to False.
294
+ parser : Union[str, Callable[[str], Dict[Any, Any]]], optional, default None
295
+ If a callable, it is a function that can parse the configuration string
296
+ into an arbitrarily nested dictionary. If a string, the string should refer to
297
+ a function (like "my_parser_package.my_parser.my_parser_function") which should
298
+ be able to parse the configuration string into an arbitrarily nested dictionary.
299
+ If the name starts with a ".", it is assumed to be relative to "metaflow".
300
+ show_default : bool, default True
301
+ If True, show the default value in the help text.
302
+ """
303
+
304
+ IS_CONFIG_PARAMETER = True
305
+
306
+ def __init__(
307
+ self,
308
+ name: str,
309
+ default: Optional[Union[str, Callable[[ParameterContext], str]]] = None,
310
+ default_value: Optional[
311
+ Union[
312
+ str,
313
+ Dict[str, Any],
314
+ Callable[[ParameterContext], Union[str, Dict[str, Any]]],
315
+ ]
316
+ ] = None,
317
+ help: Optional[str] = None,
318
+ required: Optional[bool] = None,
319
+ parser: Optional[Union[str, Callable[[str], Dict[Any, Any]]]] = None,
320
+ **kwargs: Dict[str, str]
321
+ ):
322
+
323
+ if default and default_value:
324
+ raise MetaflowException(
325
+ "For config '%s', you can only specify default or default_value, not both"
326
+ % name
327
+ )
328
+ self._default_is_file = default is not None
329
+ kwargs["default"] = default or default_value
330
+ super(Config, self).__init__(
331
+ name, required=required, help=help, type=str, **kwargs
332
+ )
333
+ super(Config, self).init()
334
+
335
+ if isinstance(kwargs.get("default", None), str):
336
+ kwargs["default"] = json.dumps(kwargs["default"])
337
+ self.parser = parser
338
+ self._computed_value = None
339
+
340
+ def load_parameter(self, v):
341
+ return v
342
+
343
+ def _store_value(self, v: Any) -> None:
344
+ self._computed_value = v
345
+
346
+ # Support <config>.<var> syntax
347
+ def __getattr__(self, name):
348
+ return DelayEvaluator(self.name.lower()).__getattr__(name)
349
+
350
+ # Next three methods are to implement mapping to support **<config> syntax
351
+ def __iter__(self):
352
+ return iter(DelayEvaluator(self.name.lower()))
353
+
354
+ def __len__(self):
355
+ return len(DelayEvaluator(self.name.lower()))
356
+
357
+ def __getitem__(self, key):
358
+ return DelayEvaluator(self.name.lower())[key]
359
+
360
+
361
+ def resolve_delayed_evaluator(v: Any) -> Any:
362
+ if isinstance(v, DelayEvaluator):
363
+ return v()
364
+ if isinstance(v, dict):
365
+ return {
366
+ resolve_delayed_evaluator(k): resolve_delayed_evaluator(v)
367
+ for k, v in v.items()
368
+ }
369
+ if isinstance(v, list):
370
+ return [resolve_delayed_evaluator(x) for x in v]
371
+ if isinstance(v, tuple):
372
+ return tuple(resolve_delayed_evaluator(x) for x in v)
373
+ if isinstance(v, set):
374
+ return {resolve_delayed_evaluator(x) for x in v}
375
+ return v
376
+
377
+
378
+ def unpack_delayed_evaluator(to_unpack: Dict[str, Any]) -> Dict[str, Any]:
379
+ result = {}
380
+ for k, v in to_unpack.items():
381
+ if not isinstance(k, str) or not k.startswith(UNPACK_KEY):
382
+ result[k] = v
383
+ else:
384
+ # k.startswith(UNPACK_KEY)
385
+ result.update(resolve_delayed_evaluator(v[k]))
386
+ return result
metaflow/util.py CHANGED
@@ -296,6 +296,9 @@ def get_metaflow_root():
296
296
 
297
297
 
298
298
  def dict_to_cli_options(params):
299
+ # Prevent circular imports
300
+ from .user_configs.config_options import ConfigInput
301
+
299
302
  for k, v in params.items():
300
303
  # Omit boolean options set to false or None, but preserve options with an empty
301
304
  # string argument.
@@ -304,6 +307,20 @@ def dict_to_cli_options(params):
304
307
  # keyword in Python, so we call it 'decospecs' in click args
305
308
  if k == "decospecs":
306
309
  k = "with"
310
+ if k in ("config_file_options", "config_value_options"):
311
+ # Special handling here since we gather them all in one option but actually
312
+ # need to send them one at a time using --config-value <name> kv.<name>
313
+ # Note it can be either config_file_options or config_value_options depending
314
+ # on click processing order.
315
+ for config_name in v.keys():
316
+ yield "--config-value"
317
+ yield to_unicode(config_name)
318
+ yield to_unicode(ConfigInput.make_key_name(config_name))
319
+ continue
320
+ if k == "local_config_file":
321
+ # Skip this value -- it should only be used locally and never when
322
+ # forming another command line
323
+ continue
307
324
  k = k.replace("_", "-")
308
325
  v = v if isinstance(v, (list, tuple, set)) else [v]
309
326
  for value in v:
metaflow/version.py CHANGED
@@ -1 +1 @@
1
- metaflow_version = "2.12.36.1"
1
+ metaflow_version = "2.12.36.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ob-metaflow
3
- Version: 2.12.36.1
3
+ Version: 2.12.36.2
4
4
  Summary: Metaflow: More Data Science, Less Engineering
5
5
  Author: Netflix, Outerbounds & the Metaflow Community
6
6
  Author-email: help@outerbounds.co
@@ -12,7 +12,7 @@ Requires-Dist: boto3
12
12
  Requires-Dist: pylint
13
13
  Requires-Dist: kubernetes
14
14
  Provides-Extra: stubs
15
- Requires-Dist: metaflow-stubs==2.12.36.1; extra == "stubs"
15
+ Requires-Dist: metaflow-stubs==2.12.36.2; extra == "stubs"
16
16
 
17
17
  ![Metaflow_Logo_Horizontal_FullColor_Ribbon_Dark_RGB](https://user-images.githubusercontent.com/763451/89453116-96a57e00-d713-11ea-9fa6-82b29d4d6eff.png)
18
18
 
@@ -83,3 +83,4 @@ There are several ways to get in touch with us:
83
83
 
84
84
  ## Contributing
85
85
  We welcome contributions to Metaflow. Please see our [contribution guide](https://docs.metaflow.org/introduction/contributing-to-metaflow) for more details.
86
+