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.

Files changed (65) hide show
  1. metaflow/__init__.py +3 -0
  2. metaflow/cli.py +180 -718
  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 +360 -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/datastore/flow_datastore.py +2 -2
  12. metaflow/decorators.py +63 -2
  13. metaflow/exception.py +8 -2
  14. metaflow/extension_support/plugins.py +41 -27
  15. metaflow/flowspec.py +175 -23
  16. metaflow/graph.py +28 -27
  17. metaflow/includefile.py +50 -22
  18. metaflow/lint.py +35 -20
  19. metaflow/metaflow_config.py +6 -1
  20. metaflow/package.py +17 -3
  21. metaflow/parameters.py +87 -23
  22. metaflow/plugins/__init__.py +4 -0
  23. metaflow/plugins/airflow/airflow_cli.py +1 -0
  24. metaflow/plugins/argo/argo_workflows.py +41 -1
  25. metaflow/plugins/argo/argo_workflows_cli.py +1 -0
  26. metaflow/plugins/argo/argo_workflows_deployer_objects.py +47 -1
  27. metaflow/plugins/aws/batch/batch_decorator.py +2 -2
  28. metaflow/plugins/aws/secrets_manager/aws_secrets_manager_secrets_provider.py +13 -10
  29. metaflow/plugins/aws/step_functions/step_functions.py +32 -0
  30. metaflow/plugins/aws/step_functions/step_functions_cli.py +1 -0
  31. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +3 -0
  32. metaflow/plugins/cards/card_creator.py +1 -0
  33. metaflow/plugins/cards/card_decorator.py +46 -8
  34. metaflow/plugins/datatools/s3/s3op.py +3 -3
  35. metaflow/plugins/kubernetes/kubernetes_cli.py +1 -1
  36. metaflow/plugins/kubernetes/kubernetes_decorator.py +2 -2
  37. metaflow/plugins/pypi/bootstrap.py +196 -61
  38. metaflow/plugins/pypi/conda_decorator.py +20 -10
  39. metaflow/plugins/pypi/conda_environment.py +76 -21
  40. metaflow/plugins/pypi/micromamba.py +42 -15
  41. metaflow/plugins/pypi/pip.py +8 -3
  42. metaflow/plugins/pypi/pypi_decorator.py +11 -9
  43. metaflow/plugins/timeout_decorator.py +2 -2
  44. metaflow/runner/click_api.py +240 -50
  45. metaflow/runner/deployer.py +1 -1
  46. metaflow/runner/deployer_impl.py +8 -3
  47. metaflow/runner/metaflow_runner.py +10 -2
  48. metaflow/runner/nbdeploy.py +2 -0
  49. metaflow/runner/nbrun.py +1 -1
  50. metaflow/runner/subprocess_manager.py +3 -1
  51. metaflow/runner/utils.py +41 -19
  52. metaflow/runtime.py +111 -73
  53. metaflow/sidecar/sidecar_worker.py +1 -1
  54. metaflow/user_configs/__init__.py +0 -0
  55. metaflow/user_configs/config_decorators.py +563 -0
  56. metaflow/user_configs/config_options.py +548 -0
  57. metaflow/user_configs/config_parameters.py +405 -0
  58. metaflow/util.py +17 -0
  59. metaflow/version.py +1 -1
  60. {ob_metaflow-2.12.36.3.dist-info → ob_metaflow-2.13.0.1.dist-info}/METADATA +3 -2
  61. {ob_metaflow-2.12.36.3.dist-info → ob_metaflow-2.13.0.1.dist-info}/RECORD +65 -55
  62. {ob_metaflow-2.12.36.3.dist-info → ob_metaflow-2.13.0.1.dist-info}/LICENSE +0 -0
  63. {ob_metaflow-2.12.36.3.dist-info → ob_metaflow-2.13.0.1.dist-info}/WHEEL +0 -0
  64. {ob_metaflow-2.12.36.3.dist-info → ob_metaflow-2.13.0.1.dist-info}/entry_points.txt +0 -0
  65. {ob_metaflow-2.12.36.3.dist-info → ob_metaflow-2.13.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,405 @@
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
+ self._data = data
74
+
75
+ def __getattr__(self, key: str) -> Any:
76
+ """
77
+ Access an element of this configuration
78
+
79
+ Parameters
80
+ ----------
81
+ key : str
82
+ Element to access
83
+
84
+ Returns
85
+ -------
86
+ Any
87
+ Element of the configuration
88
+ """
89
+ if key == "_data":
90
+ # Called during unpickling. Special case to not run into infinite loop
91
+ # below.
92
+ raise AttributeError(key)
93
+
94
+ if key in self._data:
95
+ return self[key]
96
+ raise AttributeError(key)
97
+
98
+ def __setattr__(self, name: str, value: Any) -> None:
99
+ # Prevent configuration modification
100
+ if name == "_data":
101
+ return super().__setattr__(name, value)
102
+ raise TypeError("ConfigValue is immutable")
103
+
104
+ def __getitem__(self, key: Any) -> Any:
105
+ """
106
+ Access an element of this configuration
107
+
108
+ Parameters
109
+ ----------
110
+ key : Any
111
+ Element to access
112
+
113
+ Returns
114
+ -------
115
+ Any
116
+ Element of the configuration
117
+ """
118
+ value = self._data[key]
119
+ if isinstance(value, dict):
120
+ value = ConfigValue(value)
121
+ return value
122
+
123
+ def __len__(self):
124
+ return len(self._data)
125
+
126
+ def __iter__(self):
127
+ return iter(self._data)
128
+
129
+ def __repr__(self):
130
+ return repr(self._data)
131
+
132
+ def __str__(self):
133
+ return json.dumps(self._data)
134
+
135
+ def to_dict(self) -> Dict[Any, Any]:
136
+ """
137
+ Returns a dictionary representation of this configuration object.
138
+
139
+ Returns
140
+ -------
141
+ Dict[Any, Any]
142
+ Dictionary equivalent of this configuration object.
143
+ """
144
+ return dict(self._data)
145
+
146
+
147
+ class DelayEvaluator(collections.abc.Mapping):
148
+ """
149
+ Small wrapper that allows the evaluation of a Config() value in a delayed manner.
150
+ This is used when we want to use config.* values in decorators for example.
151
+
152
+ It also allows the following "delayed" access on an obj that is a DelayEvaluation
153
+ - obj.x.y.z (ie: accessing members of DelayEvaluator; accesses will be delayed until
154
+ the DelayEvaluator is evaluated)
155
+ - **obj (ie: unpacking the DelayEvaluator as a dictionary). Note that this requires
156
+ special handling in whatever this is being unpacked into, specifically the handling
157
+ of _unpacked_delayed_*
158
+ """
159
+
160
+ def __init__(self, ex: str):
161
+ self._config_expr = ex
162
+ if ID_PATTERN.match(self._config_expr):
163
+ # This is a variable only so allow things like config_expr("config").var
164
+ self._is_var_only = True
165
+ self._access = []
166
+ else:
167
+ self._is_var_only = False
168
+ self._access = None
169
+
170
+ def __iter__(self):
171
+ yield "%s%d" % (UNPACK_KEY, id(self))
172
+
173
+ def __getitem__(self, key):
174
+ if key == "%s%d" % (UNPACK_KEY, id(self)):
175
+ return self
176
+ if self._access is None:
177
+ raise KeyError(key)
178
+ self._access.append(key)
179
+ return self
180
+
181
+ def __len__(self):
182
+ return 1
183
+
184
+ def __getattr__(self, name):
185
+ if self._access is None:
186
+ raise AttributeError()
187
+ self._access.append(name)
188
+ return self
189
+
190
+ def __call__(self, ctx=None, deploy_time=False):
191
+ from ..flowspec import _FlowState # Prevent circular import
192
+
193
+ # Two additional arguments are only used by DeployTimeField which will call
194
+ # this function with those two additional arguments. They are ignored.
195
+ flow_cls = getattr(current_flow, "flow_cls", None)
196
+ if flow_cls is None:
197
+ # We are not executing inside a flow (ie: not the CLI)
198
+ raise MetaflowException(
199
+ "Config object can only be used directly in the FlowSpec defining them. "
200
+ "If using outside of the FlowSpec, please use ConfigEval"
201
+ )
202
+ if self._access is not None:
203
+ # Build the final expression by adding all the fields in access as . fields
204
+ self._config_expr = ".".join([self._config_expr] + self._access)
205
+ # Evaluate the expression setting the config values as local variables
206
+ try:
207
+ return eval(
208
+ self._config_expr,
209
+ globals(),
210
+ {
211
+ k: ConfigValue(v)
212
+ for k, v in flow_cls._flow_state.get(_FlowState.CONFIGS, {}).items()
213
+ },
214
+ )
215
+ except NameError as e:
216
+ potential_config_name = self._config_expr.split(".")[0]
217
+ if potential_config_name not in flow_cls._flow_state.get(
218
+ _FlowState.CONFIGS, {}
219
+ ):
220
+ raise MetaflowException(
221
+ "Config '%s' not found in the flow (maybe not required and not "
222
+ "provided?)" % potential_config_name
223
+ ) from e
224
+ raise
225
+
226
+
227
+ def config_expr(expr: str) -> DelayEvaluator:
228
+ """
229
+ Function to allow you to use an expression involving a config parameter in
230
+ places where it may not be directory accessible or if you want a more complicated
231
+ expression than just a single variable.
232
+
233
+ You can use it as follows:
234
+ - When the config is not directly accessible:
235
+
236
+ @project(name=config_expr("config").project.name)
237
+ class MyFlow(FlowSpec):
238
+ config = Config("config")
239
+ ...
240
+ - When you want a more complex expression:
241
+ class MyFlow(FlowSpec):
242
+ config = Config("config")
243
+
244
+ @environment(vars={"foo": config_expr("config.bar.baz.lower()")})
245
+ @step
246
+ def start(self):
247
+ ...
248
+
249
+ Parameters
250
+ ----------
251
+ expr : str
252
+ Expression using the config values.
253
+ """
254
+ return DelayEvaluator(expr)
255
+
256
+
257
+ class Config(Parameter, collections.abc.Mapping):
258
+ """
259
+ Includes a configuration for this flow.
260
+
261
+ `Config` is a special type of `Parameter` but differs in a few key areas:
262
+ - it is immutable and determined at deploy time (or prior to running if not deploying
263
+ to a scheduler)
264
+ - as such, it can be used anywhere in your code including in Metaflow decorators
265
+
266
+ The value of the configuration is determines as follows:
267
+ - use the user-provided file path or value. It is an error to provide both
268
+ - if none are present:
269
+ - if a default file path (default) is provided, attempt to read this file
270
+ - if the file is present, use that value. Note that the file will be used
271
+ even if it has an invalid syntax
272
+ - if the file is not present, and a default value is present, use that
273
+ - if still None and is required, this is an error.
274
+
275
+ Parameters
276
+ ----------
277
+ name : str
278
+ User-visible configuration name.
279
+ default : Union[str, Callable[[ParameterContext], str], optional, default None
280
+ Default path from where to read this configuration. A function implies that the
281
+ value will be computed using that function.
282
+ You can only specify default or default_value.
283
+ default_value : Union[str, Dict[str, Any], Callable[[ParameterContext, Union[str, Dict[str, Any]]], Any], optional, default None
284
+ Default value for the parameter. A function
285
+ implies that the value will be computed using that function.
286
+ You can only specify default or default_value.
287
+ help : str, optional, default None
288
+ Help text to show in `run --help`.
289
+ required : bool, optional, default None
290
+ Require that the user specified a value for the configuration. Note that if
291
+ a default is provided, the required flag is ignored. A value of None is
292
+ equivalent to False.
293
+ parser : Union[str, Callable[[str], Dict[Any, Any]]], optional, default None
294
+ If a callable, it is a function that can parse the configuration string
295
+ into an arbitrarily nested dictionary. If a string, the string should refer to
296
+ a function (like "my_parser_package.my_parser.my_parser_function") which should
297
+ be able to parse the configuration string into an arbitrarily nested dictionary.
298
+ If the name starts with a ".", it is assumed to be relative to "metaflow".
299
+ show_default : bool, default True
300
+ If True, show the default value in the help text.
301
+ """
302
+
303
+ IS_CONFIG_PARAMETER = True
304
+
305
+ def __init__(
306
+ self,
307
+ name: str,
308
+ default: Optional[Union[str, Callable[[ParameterContext], str]]] = None,
309
+ default_value: Optional[
310
+ Union[
311
+ str,
312
+ Dict[str, Any],
313
+ Callable[[ParameterContext], Union[str, Dict[str, Any]]],
314
+ ]
315
+ ] = None,
316
+ help: Optional[str] = None,
317
+ required: Optional[bool] = None,
318
+ parser: Optional[Union[str, Callable[[str], Dict[Any, Any]]]] = None,
319
+ **kwargs: Dict[str, str]
320
+ ):
321
+
322
+ if default and default_value:
323
+ raise MetaflowException(
324
+ "For config '%s', you can only specify default or default_value, not both"
325
+ % name
326
+ )
327
+ self._default_is_file = default is not None
328
+ kwargs["default"] = default or default_value
329
+ super(Config, self).__init__(
330
+ name, required=required, help=help, type=str, **kwargs
331
+ )
332
+ super(Config, self).init()
333
+
334
+ if isinstance(kwargs.get("default", None), str):
335
+ kwargs["default"] = json.dumps(kwargs["default"])
336
+ self.parser = parser
337
+ self._computed_value = None
338
+
339
+ def load_parameter(self, v):
340
+ if v is None:
341
+ return None
342
+ return ConfigValue(v)
343
+
344
+ def _store_value(self, v: Any) -> None:
345
+ self._computed_value = v
346
+
347
+ # Support <config>.<var> syntax
348
+ def __getattr__(self, name):
349
+ return DelayEvaluator(self.name.lower()).__getattr__(name)
350
+
351
+ # Next three methods are to implement mapping to support **<config> syntax
352
+ def __iter__(self):
353
+ return iter(DelayEvaluator(self.name.lower()))
354
+
355
+ def __len__(self):
356
+ return len(DelayEvaluator(self.name.lower()))
357
+
358
+ def __getitem__(self, key):
359
+ return DelayEvaluator(self.name.lower())[key]
360
+
361
+
362
+ def resolve_delayed_evaluator(v: Any, ignore_errors: bool = False) -> Any:
363
+ try:
364
+ if isinstance(v, DelayEvaluator):
365
+ return v()
366
+ if isinstance(v, dict):
367
+ return {
368
+ resolve_delayed_evaluator(k): resolve_delayed_evaluator(v)
369
+ for k, v in v.items()
370
+ }
371
+ if isinstance(v, list):
372
+ return [resolve_delayed_evaluator(x) for x in v]
373
+ if isinstance(v, tuple):
374
+ return tuple(resolve_delayed_evaluator(x) for x in v)
375
+ if isinstance(v, set):
376
+ return {resolve_delayed_evaluator(x) for x in v}
377
+ return v
378
+ except Exception as e:
379
+ if ignore_errors:
380
+ # Assumption is that default value of None is always allowed.
381
+ # This code path is *only* used when evaluating Parameters AND they
382
+ # use configs in their attributes AND the runner/deployer is being used
383
+ # AND CLICK_API_PROCESS_CONFIG is False. In those cases, all attributes in
384
+ # Parameter can be set to None except for required and show_default
385
+ # and even in those cases, a wrong value will have very limited consequence.
386
+ return None
387
+ raise e
388
+
389
+
390
+ def unpack_delayed_evaluator(
391
+ to_unpack: Dict[str, Any], ignore_errors: bool = False
392
+ ) -> Dict[str, Any]:
393
+ result = {}
394
+ for k, v in to_unpack.items():
395
+ if not isinstance(k, str) or not k.startswith(UNPACK_KEY):
396
+ result[k] = v
397
+ else:
398
+ # k.startswith(UNPACK_KEY)
399
+ try:
400
+ result.update(resolve_delayed_evaluator(v[k]))
401
+ except Exception as e:
402
+ if ignore_errors:
403
+ continue
404
+ raise e
405
+ 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", "config_value"):
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 or config_value 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.3"
1
+ metaflow_version = "2.13.0.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ob-metaflow
3
- Version: 2.12.36.3
3
+ Version: 2.13.0.1
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.3; extra == "stubs"
15
+ Requires-Dist: metaflow-stubs==2.13.0.1; 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
+