ob-metaflow 2.12.30.2__py2.py3-none-any.whl → 2.13.6.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 (96) hide show
  1. metaflow/__init__.py +3 -0
  2. metaflow/cards.py +1 -0
  3. metaflow/cli.py +185 -717
  4. metaflow/cli_args.py +17 -0
  5. metaflow/cli_components/__init__.py +0 -0
  6. metaflow/cli_components/dump_cmd.py +96 -0
  7. metaflow/cli_components/init_cmd.py +51 -0
  8. metaflow/cli_components/run_cmds.py +362 -0
  9. metaflow/cli_components/step_cmd.py +176 -0
  10. metaflow/cli_components/utils.py +140 -0
  11. metaflow/cmd/develop/stub_generator.py +9 -2
  12. metaflow/datastore/flow_datastore.py +2 -2
  13. metaflow/decorators.py +63 -2
  14. metaflow/exception.py +8 -2
  15. metaflow/extension_support/plugins.py +42 -27
  16. metaflow/flowspec.py +176 -23
  17. metaflow/graph.py +28 -27
  18. metaflow/includefile.py +50 -22
  19. metaflow/lint.py +35 -20
  20. metaflow/metadata_provider/heartbeat.py +23 -8
  21. metaflow/metaflow_config.py +10 -1
  22. metaflow/multicore_utils.py +31 -14
  23. metaflow/package.py +17 -3
  24. metaflow/parameters.py +97 -25
  25. metaflow/plugins/__init__.py +22 -0
  26. metaflow/plugins/airflow/airflow.py +18 -17
  27. metaflow/plugins/airflow/airflow_cli.py +1 -0
  28. metaflow/plugins/argo/argo_client.py +0 -2
  29. metaflow/plugins/argo/argo_workflows.py +195 -132
  30. metaflow/plugins/argo/argo_workflows_cli.py +1 -1
  31. metaflow/plugins/argo/argo_workflows_decorator.py +2 -4
  32. metaflow/plugins/argo/argo_workflows_deployer_objects.py +51 -9
  33. metaflow/plugins/argo/jobset_input_paths.py +0 -1
  34. metaflow/plugins/aws/aws_utils.py +6 -1
  35. metaflow/plugins/aws/batch/batch_client.py +1 -3
  36. metaflow/plugins/aws/batch/batch_decorator.py +13 -13
  37. metaflow/plugins/aws/secrets_manager/aws_secrets_manager_secrets_provider.py +13 -10
  38. metaflow/plugins/aws/step_functions/dynamo_db_client.py +0 -3
  39. metaflow/plugins/aws/step_functions/production_token.py +1 -1
  40. metaflow/plugins/aws/step_functions/step_functions.py +33 -1
  41. metaflow/plugins/aws/step_functions/step_functions_cli.py +1 -1
  42. metaflow/plugins/aws/step_functions/step_functions_decorator.py +0 -1
  43. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +7 -9
  44. metaflow/plugins/cards/card_cli.py +7 -2
  45. metaflow/plugins/cards/card_creator.py +1 -0
  46. metaflow/plugins/cards/card_decorator.py +79 -8
  47. metaflow/plugins/cards/card_modules/basic.py +56 -5
  48. metaflow/plugins/cards/card_modules/card.py +16 -1
  49. metaflow/plugins/cards/card_modules/components.py +64 -16
  50. metaflow/plugins/cards/card_modules/main.js +27 -25
  51. metaflow/plugins/cards/card_modules/test_cards.py +4 -4
  52. metaflow/plugins/cards/component_serializer.py +1 -1
  53. metaflow/plugins/datatools/s3/s3.py +12 -4
  54. metaflow/plugins/datatools/s3/s3op.py +3 -3
  55. metaflow/plugins/events_decorator.py +338 -186
  56. metaflow/plugins/kubernetes/kube_utils.py +84 -1
  57. metaflow/plugins/kubernetes/kubernetes.py +40 -92
  58. metaflow/plugins/kubernetes/kubernetes_cli.py +32 -7
  59. metaflow/plugins/kubernetes/kubernetes_decorator.py +76 -4
  60. metaflow/plugins/kubernetes/kubernetes_job.py +23 -20
  61. metaflow/plugins/kubernetes/kubernetes_jobsets.py +41 -20
  62. metaflow/plugins/kubernetes/spot_metadata_cli.py +69 -0
  63. metaflow/plugins/kubernetes/spot_monitor_sidecar.py +109 -0
  64. metaflow/plugins/parallel_decorator.py +4 -1
  65. metaflow/plugins/project_decorator.py +33 -5
  66. metaflow/plugins/pypi/bootstrap.py +249 -81
  67. metaflow/plugins/pypi/conda_decorator.py +20 -10
  68. metaflow/plugins/pypi/conda_environment.py +83 -27
  69. metaflow/plugins/pypi/micromamba.py +82 -37
  70. metaflow/plugins/pypi/pip.py +9 -6
  71. metaflow/plugins/pypi/pypi_decorator.py +11 -9
  72. metaflow/plugins/pypi/utils.py +4 -2
  73. metaflow/plugins/timeout_decorator.py +2 -2
  74. metaflow/runner/click_api.py +240 -50
  75. metaflow/runner/deployer.py +1 -1
  76. metaflow/runner/deployer_impl.py +12 -11
  77. metaflow/runner/metaflow_runner.py +68 -34
  78. metaflow/runner/nbdeploy.py +2 -0
  79. metaflow/runner/nbrun.py +1 -1
  80. metaflow/runner/subprocess_manager.py +61 -10
  81. metaflow/runner/utils.py +208 -44
  82. metaflow/runtime.py +216 -112
  83. metaflow/sidecar/sidecar_worker.py +1 -1
  84. metaflow/tracing/tracing_modules.py +4 -1
  85. metaflow/user_configs/__init__.py +0 -0
  86. metaflow/user_configs/config_decorators.py +563 -0
  87. metaflow/user_configs/config_options.py +548 -0
  88. metaflow/user_configs/config_parameters.py +436 -0
  89. metaflow/util.py +22 -0
  90. metaflow/version.py +1 -1
  91. {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/METADATA +12 -3
  92. {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/RECORD +96 -84
  93. {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/WHEEL +1 -1
  94. {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/LICENSE +0 -0
  95. {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/entry_points.txt +0 -0
  96. {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,436 @@
1
+ import collections.abc
2
+ import json
3
+ import os
4
+ import re
5
+
6
+ from typing import Any, Callable, Dict, List, Optional, Tuple, 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 isinstance(key, str) and 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(name)
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
+ "(or their flow decorators)."
201
+ )
202
+ if self._access is not None:
203
+ # Build the final expression by adding all the fields in access as . fields
204
+ access_list = [self._config_expr]
205
+ for a in self._access:
206
+ if isinstance(a, str):
207
+ access_list.append(a)
208
+ elif isinstance(a, DelayEvaluator):
209
+ # Supports things like config[other_config.selector].var
210
+ access_list.append(a())
211
+ else:
212
+ raise MetaflowException(
213
+ "Field '%s' of type '%s' is not supported" % (str(a), type(a))
214
+ )
215
+ self._config_expr = ".".join(access_list)
216
+ # Evaluate the expression setting the config values as local variables
217
+ try:
218
+ return eval(
219
+ self._config_expr,
220
+ globals(),
221
+ {
222
+ k: ConfigValue(v)
223
+ for k, v in flow_cls._flow_state.get(_FlowState.CONFIGS, {}).items()
224
+ },
225
+ )
226
+ except NameError as e:
227
+ potential_config_name = self._config_expr.split(".")[0]
228
+ if potential_config_name not in flow_cls._flow_state.get(
229
+ _FlowState.CONFIGS, {}
230
+ ):
231
+ raise MetaflowException(
232
+ "Config '%s' not found in the flow (maybe not required and not "
233
+ "provided?)" % potential_config_name
234
+ ) from e
235
+ raise
236
+
237
+
238
+ def config_expr(expr: str) -> DelayEvaluator:
239
+ """
240
+ Function to allow you to use an expression involving a config parameter in
241
+ places where it may not be directory accessible or if you want a more complicated
242
+ expression than just a single variable.
243
+
244
+ You can use it as follows:
245
+ - When the config is not directly accessible:
246
+
247
+ @project(name=config_expr("config").project.name)
248
+ class MyFlow(FlowSpec):
249
+ config = Config("config")
250
+ ...
251
+ - When you want a more complex expression:
252
+ class MyFlow(FlowSpec):
253
+ config = Config("config")
254
+
255
+ @environment(vars={"foo": config_expr("config.bar.baz.lower()")})
256
+ @step
257
+ def start(self):
258
+ ...
259
+
260
+ Parameters
261
+ ----------
262
+ expr : str
263
+ Expression using the config values.
264
+ """
265
+ return DelayEvaluator(expr)
266
+
267
+
268
+ class Config(Parameter, collections.abc.Mapping):
269
+ """
270
+ Includes a configuration for this flow.
271
+
272
+ `Config` is a special type of `Parameter` but differs in a few key areas:
273
+ - it is immutable and determined at deploy time (or prior to running if not deploying
274
+ to a scheduler)
275
+ - as such, it can be used anywhere in your code including in Metaflow decorators
276
+
277
+ The value of the configuration is determines as follows:
278
+ - use the user-provided file path or value. It is an error to provide both
279
+ - if none are present:
280
+ - if a default file path (default) is provided, attempt to read this file
281
+ - if the file is present, use that value. Note that the file will be used
282
+ even if it has an invalid syntax
283
+ - if the file is not present, and a default value is present, use that
284
+ - if still None and is required, this is an error.
285
+
286
+ Parameters
287
+ ----------
288
+ name : str
289
+ User-visible configuration name.
290
+ default : Union[str, Callable[[ParameterContext], str], optional, default None
291
+ Default path from where to read this configuration. A function implies that the
292
+ value will be computed using that function.
293
+ You can only specify default or default_value.
294
+ default_value : Union[str, Dict[str, Any], Callable[[ParameterContext, Union[str, Dict[str, Any]]], Any], optional, default None
295
+ Default value for the parameter. A function
296
+ implies that the value will be computed using that function.
297
+ You can only specify default or default_value.
298
+ help : str, optional, default None
299
+ Help text to show in `run --help`.
300
+ required : bool, optional, default None
301
+ Require that the user specified a value for the configuration. Note that if
302
+ a default is provided, the required flag is ignored. A value of None is
303
+ equivalent to False.
304
+ parser : Union[str, Callable[[str], Dict[Any, Any]]], optional, default None
305
+ If a callable, it is a function that can parse the configuration string
306
+ into an arbitrarily nested dictionary. If a string, the string should refer to
307
+ a function (like "my_parser_package.my_parser.my_parser_function") which should
308
+ be able to parse the configuration string into an arbitrarily nested dictionary.
309
+ If the name starts with a ".", it is assumed to be relative to "metaflow".
310
+ show_default : bool, default True
311
+ If True, show the default value in the help text.
312
+ """
313
+
314
+ IS_CONFIG_PARAMETER = True
315
+
316
+ def __init__(
317
+ self,
318
+ name: str,
319
+ default: Optional[Union[str, Callable[[ParameterContext], str]]] = None,
320
+ default_value: Optional[
321
+ Union[
322
+ str,
323
+ Dict[str, Any],
324
+ Callable[[ParameterContext], Union[str, Dict[str, Any]]],
325
+ ]
326
+ ] = None,
327
+ help: Optional[str] = None,
328
+ required: Optional[bool] = None,
329
+ parser: Optional[Union[str, Callable[[str], Dict[Any, Any]]]] = None,
330
+ **kwargs: Dict[str, str]
331
+ ):
332
+
333
+ if default and default_value:
334
+ raise MetaflowException(
335
+ "For config '%s', you can only specify default or default_value, not both"
336
+ % name
337
+ )
338
+ self._default_is_file = default is not None
339
+ kwargs["default"] = default or default_value
340
+ super(Config, self).__init__(
341
+ name, required=required, help=help, type=str, **kwargs
342
+ )
343
+ super(Config, self).init()
344
+
345
+ if isinstance(kwargs.get("default", None), str):
346
+ kwargs["default"] = json.dumps(kwargs["default"])
347
+ self.parser = parser
348
+ self._computed_value = None
349
+
350
+ self._delayed_evaluator = None
351
+
352
+ def load_parameter(self, v):
353
+ if v is None:
354
+ return None
355
+ return ConfigValue(v)
356
+
357
+ def _store_value(self, v: Any) -> None:
358
+ self._computed_value = v
359
+
360
+ def _init_delayed_evaluator(self) -> None:
361
+ if self._delayed_evaluator is None:
362
+ self._delayed_evaluator = DelayEvaluator(self.name.lower())
363
+
364
+ # Support <config>.<var> syntax
365
+ def __getattr__(self, name):
366
+ # Need to return a new DelayEvaluator everytime because the evaluator will
367
+ # contain the "path" (ie: .name) and can be further accessed.
368
+ return getattr(DelayEvaluator(self.name.lower()), name)
369
+
370
+ # Next three methods are to implement mapping to support **<config> syntax. We
371
+ # need to be careful, however, to also support a regular `config["key"]` syntax
372
+ # which calls into `__getitem__` and therefore behaves like __getattr__ above.
373
+ def __iter__(self):
374
+ self._init_delayed_evaluator()
375
+ yield from self._delayed_evaluator
376
+
377
+ def __len__(self):
378
+ self._init_delayed_evaluator()
379
+ return len(self._delayed_evaluator)
380
+
381
+ def __getitem__(self, key):
382
+ self._init_delayed_evaluator()
383
+ if isinstance(key, str) and key.startswith(UNPACK_KEY):
384
+ return self._delayed_evaluator[key]
385
+ return DelayEvaluator(self.name.lower())[key]
386
+
387
+
388
+ def resolve_delayed_evaluator(v: Any, ignore_errors: bool = False) -> Any:
389
+ # NOTE: We don't ignore errors in downstream calls because we want to have either
390
+ # all or nothing for the top-level call by the user.
391
+ try:
392
+ if isinstance(v, DelayEvaluator):
393
+ return v()
394
+ if isinstance(v, dict):
395
+ return {
396
+ resolve_delayed_evaluator(k): resolve_delayed_evaluator(v)
397
+ for k, v in v.items()
398
+ }
399
+ if isinstance(v, list):
400
+ return [resolve_delayed_evaluator(x) for x in v]
401
+ if isinstance(v, tuple):
402
+ return tuple(resolve_delayed_evaluator(x) for x in v)
403
+ if isinstance(v, set):
404
+ return {resolve_delayed_evaluator(x) for x in v}
405
+ return v
406
+ except Exception as e:
407
+ if ignore_errors:
408
+ # Assumption is that default value of None is always allowed.
409
+ # This code path is *only* used when evaluating Parameters AND they
410
+ # use configs in their attributes AND the runner/deployer is being used
411
+ # AND CLICK_API_PROCESS_CONFIG is False. In those cases, all attributes in
412
+ # Parameter can be set to None except for required and show_default
413
+ # and even in those cases, a wrong value will have very limited consequence.
414
+ return None
415
+ raise e
416
+
417
+
418
+ def unpack_delayed_evaluator(
419
+ to_unpack: Dict[str, Any], ignore_errors: bool = False
420
+ ) -> Tuple[Dict[str, Any], List[str]]:
421
+ result = {}
422
+ new_keys = []
423
+ for k, v in to_unpack.items():
424
+ if not isinstance(k, str) or not k.startswith(UNPACK_KEY):
425
+ result[k] = v
426
+ else:
427
+ # k.startswith(UNPACK_KEY)
428
+ try:
429
+ new_vals = resolve_delayed_evaluator(v)
430
+ new_keys.extend(new_vals.keys())
431
+ result.update(new_vals)
432
+ except Exception as e:
433
+ if ignore_errors:
434
+ continue
435
+ raise e
436
+ return result, new_keys
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:
@@ -436,12 +453,17 @@ def to_pod(value):
436
453
  Value to convert to POD format. The value can be a string, number, list,
437
454
  dictionary, or a nested structure of these types.
438
455
  """
456
+ # Prevent circular imports
457
+ from metaflow.parameters import DeployTimeField
458
+
439
459
  if isinstance(value, (str, int, float)):
440
460
  return value
441
461
  if isinstance(value, dict):
442
462
  return {to_pod(k): to_pod(v) for k, v in value.items()}
443
463
  if isinstance(value, (list, set, tuple)):
444
464
  return [to_pod(v) for v in value]
465
+ if isinstance(value, DeployTimeField):
466
+ return value.print_representation
445
467
  return str(value)
446
468
 
447
469
 
metaflow/version.py CHANGED
@@ -1 +1 @@
1
- metaflow_version = "2.12.30.2"
1
+ metaflow_version = "2.13.6.1"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: ob-metaflow
3
- Version: 2.12.30.2
3
+ Version: 2.13.6.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,15 @@ 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.30.2; extra == "stubs"
15
+ Requires-Dist: metaflow-stubs==2.13.6.1; extra == "stubs"
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: license
21
+ Dynamic: provides-extra
22
+ Dynamic: requires-dist
23
+ Dynamic: summary
16
24
 
17
25
  ![Metaflow_Logo_Horizontal_FullColor_Ribbon_Dark_RGB](https://user-images.githubusercontent.com/763451/89453116-96a57e00-d713-11ea-9fa6-82b29d4d6eff.png)
18
26
 
@@ -83,3 +91,4 @@ There are several ways to get in touch with us:
83
91
 
84
92
  ## Contributing
85
93
  We welcome contributions to Metaflow. Please see our [contribution guide](https://docs.metaflow.org/introduction/contributing-to-metaflow) for more details.
94
+