metaflow 2.15.20__py2.py3-none-any.whl → 2.16.0__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.
Files changed (74) hide show
  1. metaflow/__init__.py +7 -1
  2. metaflow/cli.py +16 -1
  3. metaflow/cli_components/init_cmd.py +1 -0
  4. metaflow/cli_components/run_cmds.py +6 -2
  5. metaflow/client/core.py +22 -30
  6. metaflow/datastore/task_datastore.py +0 -1
  7. metaflow/debug.py +5 -0
  8. metaflow/decorators.py +230 -70
  9. metaflow/extension_support/__init__.py +15 -8
  10. metaflow/extension_support/_empty_file.py +2 -2
  11. metaflow/flowspec.py +80 -53
  12. metaflow/graph.py +24 -2
  13. metaflow/meta_files.py +13 -0
  14. metaflow/metadata_provider/metadata.py +7 -1
  15. metaflow/metaflow_config.py +5 -0
  16. metaflow/metaflow_environment.py +82 -25
  17. metaflow/metaflow_version.py +1 -1
  18. metaflow/package/__init__.py +664 -0
  19. metaflow/packaging_sys/__init__.py +870 -0
  20. metaflow/packaging_sys/backend.py +113 -0
  21. metaflow/packaging_sys/distribution_support.py +153 -0
  22. metaflow/packaging_sys/tar_backend.py +86 -0
  23. metaflow/packaging_sys/utils.py +91 -0
  24. metaflow/packaging_sys/v1.py +476 -0
  25. metaflow/plugins/airflow/airflow.py +5 -1
  26. metaflow/plugins/airflow/airflow_cli.py +15 -4
  27. metaflow/plugins/argo/argo_workflows.py +23 -17
  28. metaflow/plugins/argo/argo_workflows_cli.py +16 -4
  29. metaflow/plugins/aws/batch/batch.py +22 -3
  30. metaflow/plugins/aws/batch/batch_cli.py +3 -0
  31. metaflow/plugins/aws/batch/batch_decorator.py +13 -5
  32. metaflow/plugins/aws/step_functions/step_functions.py +4 -1
  33. metaflow/plugins/aws/step_functions/step_functions_cli.py +15 -4
  34. metaflow/plugins/cards/card_decorator.py +0 -5
  35. metaflow/plugins/kubernetes/kubernetes.py +8 -1
  36. metaflow/plugins/kubernetes/kubernetes_cli.py +3 -0
  37. metaflow/plugins/kubernetes/kubernetes_decorator.py +13 -5
  38. metaflow/plugins/package_cli.py +25 -23
  39. metaflow/plugins/parallel_decorator.py +4 -2
  40. metaflow/plugins/pypi/bootstrap.py +8 -2
  41. metaflow/plugins/pypi/conda_decorator.py +39 -82
  42. metaflow/plugins/pypi/conda_environment.py +6 -2
  43. metaflow/plugins/pypi/pypi_decorator.py +4 -4
  44. metaflow/plugins/test_unbounded_foreach_decorator.py +2 -2
  45. metaflow/plugins/timeout_decorator.py +0 -1
  46. metaflow/plugins/uv/bootstrap.py +11 -0
  47. metaflow/plugins/uv/uv_environment.py +4 -2
  48. metaflow/pylint_wrapper.py +5 -1
  49. metaflow/runner/click_api.py +5 -4
  50. metaflow/runner/subprocess_manager.py +14 -2
  51. metaflow/runtime.py +37 -11
  52. metaflow/task.py +91 -7
  53. metaflow/user_configs/config_options.py +13 -8
  54. metaflow/user_configs/config_parameters.py +0 -4
  55. metaflow/user_decorators/__init__.py +0 -0
  56. metaflow/user_decorators/common.py +144 -0
  57. metaflow/user_decorators/mutable_flow.py +499 -0
  58. metaflow/user_decorators/mutable_step.py +424 -0
  59. metaflow/user_decorators/user_flow_decorator.py +263 -0
  60. metaflow/user_decorators/user_step_decorator.py +712 -0
  61. metaflow/util.py +4 -1
  62. metaflow/version.py +1 -1
  63. {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/METADATA +2 -2
  64. {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/RECORD +71 -60
  65. metaflow/info_file.py +0 -25
  66. metaflow/package.py +0 -203
  67. metaflow/user_configs/config_decorators.py +0 -568
  68. {metaflow-2.15.20.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/Makefile +0 -0
  69. {metaflow-2.15.20.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/Tiltfile +0 -0
  70. {metaflow-2.15.20.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/pick_services.sh +0 -0
  71. {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/WHEEL +0 -0
  72. {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/entry_points.txt +0 -0
  73. {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/licenses/LICENSE +0 -0
  74. {metaflow-2.15.20.dist-info → metaflow-2.16.0.dist-info}/top_level.txt +0 -0
metaflow/task.py CHANGED
@@ -6,6 +6,7 @@ import os
6
6
  import time
7
7
  import traceback
8
8
 
9
+
9
10
  from types import MethodType, FunctionType
10
11
 
11
12
  from metaflow.sidecar import Message, MessageTypes
@@ -24,6 +25,7 @@ from .unbounded_foreach import UBF_CONTROL
24
25
  from .util import all_equal, get_username, resolve_identity, unicode_type
25
26
  from .clone_util import clone_task_helper
26
27
  from .metaflow_current import current
28
+ from metaflow.user_configs.config_parameters import ConfigValue
27
29
  from metaflow.system import _system_logger, _system_monitor
28
30
  from metaflow.tracing import get_trace_id
29
31
  from metaflow.tuple_util import ForeachFrame
@@ -57,11 +59,89 @@ class MetaflowTask(object):
57
59
  self.monitor = monitor
58
60
  self.ubf_context = ubf_context
59
61
 
60
- def _exec_step_function(self, step_function, input_obj=None):
61
- if input_obj is None:
62
- step_function()
63
- else:
64
- step_function(input_obj)
62
+ def _exec_step_function(self, step_function, orig_step_func, input_obj=None):
63
+ wrappers_stack = []
64
+ wrapped_func = None
65
+ do_next = False
66
+ raised_exception = None
67
+ # If we have wrappers w1, w2 and w3, we need to execute
68
+ # - w3_pre
69
+ # - w2_pre
70
+ # - w1_pre
71
+ # - step_function
72
+ # - w1_post
73
+ # - w2_post
74
+ # - w3_post
75
+ # in that order. We do this by maintaining a stack of generators.
76
+ # Note that if any of the pre functions returns a function, we execute that
77
+ # instead of the rest of the inside part. This is useful if you want to create
78
+ # no-op function for example.
79
+ for w in reversed(orig_step_func.wrappers):
80
+ wrapped_func = w.pre_step(orig_step_func.name, self.flow, input_obj)
81
+ wrappers_stack.append(w)
82
+ if w.skip_step:
83
+ # We have nothing to run
84
+ do_next = w.skip_step
85
+ break
86
+ if wrapped_func:
87
+ break # We have nothing left to do since we now execute the
88
+ # wrapped function
89
+ # Else, we continue down the list of wrappers
90
+ try:
91
+ if not do_next:
92
+ if input_obj is None:
93
+ if wrapped_func:
94
+ do_next = wrapped_func(self.flow)
95
+ if not do_next:
96
+ do_next = True
97
+ else:
98
+ step_function()
99
+ else:
100
+ if wrapped_func:
101
+ do_next = wrapped_func(self.flow, input_obj)
102
+ if not do_next:
103
+ do_next = True
104
+ else:
105
+ step_function(input_obj)
106
+ except Exception as ex:
107
+ raised_exception = ex
108
+
109
+ if do_next:
110
+ # If we are skipping the step, or executed a wrapped function,
111
+ # we need to set the transition variables
112
+ # properly. We call the next function as needed
113
+ graph_node = self.flow._graph[step_function.name]
114
+ out_funcs = [getattr(self.flow, f) for f in graph_node.out_funcs]
115
+ if out_funcs:
116
+ if isinstance(do_next, bool):
117
+ # We need to extract things from the self.next. This is not possible
118
+ # in the case where there was a num_parallel.
119
+ if graph_node.parallel_foreach:
120
+ raise RuntimeError(
121
+ "Skipping a parallel foreach step without providing "
122
+ "the arguments to the self.next call is not supported. "
123
+ )
124
+ if graph_node.foreach_param:
125
+ self.flow.next(*out_funcs, foreach=graph_node.foreach_param)
126
+ else:
127
+ self.flow.next(*out_funcs)
128
+ elif isinstance(do_next, dict):
129
+ # Here it is a dictionary so we just call the next method with
130
+ # those arguments
131
+ self.flow.next(*out_funcs, **do_next)
132
+ else:
133
+ raise RuntimeError(
134
+ "Invalid value passed to self.next; expected "
135
+ " bool of a dictionary; got: %s" % do_next
136
+ )
137
+ # We back out of the stack of generators
138
+ for w in reversed(wrappers_stack):
139
+ raised_exception = w.post_step(
140
+ step_function.name, self.flow, raised_exception
141
+ )
142
+ if raised_exception:
143
+ # We have an exception that we need to propagate
144
+ raise raised_exception
65
145
 
66
146
  def _init_parameters(self, parameter_ds, passdown=True):
67
147
  cls = self.flow.__class__
@@ -538,6 +618,9 @@ class MetaflowTask(object):
538
618
  output.save_metadata(
539
619
  {
540
620
  "task_begin": {
621
+ "code_package_metadata": os.environ.get(
622
+ "METAFLOW_CODE_METADATA", ""
623
+ ),
541
624
  "code_package_sha": os.environ.get("METAFLOW_CODE_SHA"),
542
625
  "code_package_ds": os.environ.get("METAFLOW_CODE_DS"),
543
626
  "code_package_url": os.environ.get("METAFLOW_CODE_URL"),
@@ -651,6 +734,7 @@ class MetaflowTask(object):
651
734
  inputs,
652
735
  )
653
736
 
737
+ orig_step_func = step_func
654
738
  for deco in decorators:
655
739
  # decorators can actually decorate the step function,
656
740
  # or they can replace it altogether. This functionality
@@ -667,9 +751,9 @@ class MetaflowTask(object):
667
751
  )
668
752
 
669
753
  if join_type:
670
- self._exec_step_function(step_func, input_obj)
754
+ self._exec_step_function(step_func, orig_step_func, input_obj)
671
755
  else:
672
- self._exec_step_function(step_func)
756
+ self._exec_step_function(step_func, orig_step_func)
673
757
 
674
758
  for deco in decorators:
675
759
  deco.task_post_step(
@@ -7,8 +7,9 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
7
7
  from metaflow._vendor import click
8
8
  from metaflow.debug import debug
9
9
 
10
- from .config_parameters import CONFIG_FILE, ConfigValue
10
+ from .config_parameters import ConfigValue
11
11
  from ..exception import MetaflowException, MetaflowInternalError
12
+ from ..packaging_sys import MetaflowCodeContent
12
13
  from ..parameters import DeployTimeField, ParameterContext, current_flow
13
14
  from ..util import get_username
14
15
 
@@ -24,12 +25,16 @@ _CONVERTED_DEFAULT_NO_FILE = _CONVERTED_DEFAULT + _NO_FILE
24
25
 
25
26
  def _load_config_values(info_file: Optional[str] = None) -> Optional[Dict[Any, Any]]:
26
27
  if info_file is None:
27
- info_file = os.path.basename(CONFIG_FILE)
28
- try:
29
- with open(info_file, encoding="utf-8") as contents:
30
- return json.load(contents).get("user_configs", {})
31
- except IOError:
32
- return None
28
+ config_content = MetaflowCodeContent.get_config()
29
+ else:
30
+ try:
31
+ with open(info_file, encoding="utf-8") as f:
32
+ config_content = json.load(f)
33
+ except IOError:
34
+ return None
35
+ if config_content:
36
+ return config_content.get("user_configs", {})
37
+ return None
33
38
 
34
39
 
35
40
  class ConvertPath(click.Path):
@@ -437,7 +442,7 @@ class LocalFileInput(click.Path):
437
442
  # Small wrapper around click.Path to set the value from which to read configuration
438
443
  # values. This is set immediately upon processing the --local-config-file
439
444
  # option and will therefore then be available when processing any of the other
440
- # --config options (which will call ConfigInput.process_configs
445
+ # --config options (which will call ConfigInput.process_configs)
441
446
  name = "LocalFileInput"
442
447
 
443
448
  def convert(self, value, param, ctx):
@@ -49,10 +49,6 @@ if TYPE_CHECKING:
49
49
 
50
50
  # return tracefunc_closure
51
51
 
52
- CONFIG_FILE = os.path.join(
53
- os.path.dirname(os.path.abspath(__file__)), "CONFIG_PARAMETERS"
54
- )
55
-
56
52
  ID_PATTERN = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
57
53
 
58
54
  UNPACK_KEY = "_unpacked_delayed_"
File without changes
@@ -0,0 +1,144 @@
1
+ from typing import Dict, Optional, List, Tuple
2
+
3
+
4
+ class _TrieNode:
5
+ def __init__(
6
+ self, parent: Optional["_TrieNode"] = None, component: Optional[str] = None
7
+ ):
8
+ self.parent = parent
9
+ self.component = component
10
+ self.children = {} # type: Dict[str, "_TrieNode"]
11
+ self.total_children = 0
12
+ self.value = None
13
+ self.end_value = None
14
+
15
+ def traverse(self, value: type) -> Optional["_TrieNode"]:
16
+ if self.total_children == 0:
17
+ self.end_value = value
18
+ else:
19
+ self.end_value = None
20
+ self.total_children += 1
21
+
22
+ def remove_child(self, child_name: str) -> bool:
23
+ if child_name in self.children:
24
+ del self.children[child_name]
25
+ self.total_children -= 1
26
+ return True
27
+ return False
28
+
29
+
30
+ class ClassPath_Trie:
31
+ def __init__(self):
32
+ self.root = _TrieNode(None, None)
33
+ self.inited = False
34
+ self._value_to_node = {} # type: Dict[type, _TrieNode]
35
+
36
+ def init(self, initial_nodes: Optional[List[Tuple[str, type]]] = None):
37
+ # We need to do this so we can delay import of STEP_DECORATORS
38
+ self.inited = True
39
+ for classpath_name, value in initial_nodes or []:
40
+ self.insert(classpath_name, value)
41
+
42
+ def insert(self, classpath_name: str, value: type):
43
+ node = self.root
44
+ components = reversed(classpath_name.split("."))
45
+ for c in components:
46
+ node = node.children.setdefault(c, _TrieNode(node, c))
47
+ node.traverse(value)
48
+ node.total_children -= (
49
+ 1 # We do not count the last node as having itself as a child
50
+ )
51
+ node.value = value
52
+ self._value_to_node[value] = node
53
+
54
+ def search(self, classpath_name: str) -> Optional[type]:
55
+ node = self.root
56
+ components = reversed(classpath_name.split("."))
57
+ for c in components:
58
+ if c not in node.children:
59
+ return None
60
+ node = node.children[c]
61
+ return node.value
62
+
63
+ def remove(self, classpath_name: str):
64
+ components = list(reversed(classpath_name.split(".")))
65
+
66
+ def _remove(node: _TrieNode, components, depth):
67
+ if depth == len(components):
68
+ if node.value is not None:
69
+ del self._value_to_node[node.value]
70
+ node.value = None
71
+ return len(node.children) == 0
72
+ return False
73
+ c = components[depth]
74
+ if c not in node.children:
75
+ return False
76
+ did_delete_child = _remove(node.children[c], components, depth + 1)
77
+ if did_delete_child:
78
+ node.remove_child(c)
79
+ if node.total_children == 1:
80
+ # If we have one total child left, we have at least one
81
+ # child and that one has an end_value
82
+ for child in node.children.values():
83
+ assert (
84
+ child.end_value
85
+ ), "Node with one child must have an end_value"
86
+ node.end_value = child.end_value
87
+ return node.total_children == 0
88
+ return False
89
+
90
+ _remove(self.root, components, 0)
91
+
92
+ def unique_prefix_value(self, classpath_name: str) -> Optional[type]:
93
+ node = self.root
94
+ components = reversed(classpath_name.split("."))
95
+ for c in components:
96
+ if c not in node.children:
97
+ return None
98
+ node = node.children[c]
99
+ # If we reach here, it means the classpath_name is a prefix.
100
+ # We check if it has only one path forward (end_value will be non None)
101
+ # If value is not None, we also consider this to be a unique "prefix"
102
+ # This happens since this trie is also filled with metaflow default decorators
103
+ return node.end_value or node.value
104
+
105
+ def unique_prefix_for_type(self, value: type) -> Optional[str]:
106
+ node = self._value_to_node.get(value, None)
107
+ if node is None:
108
+ return None
109
+ components = []
110
+ while node:
111
+ if node.end_value == value:
112
+ components = []
113
+ if node.component is not None:
114
+ components.append(node.component)
115
+ node = node.parent
116
+ return ".".join(components)
117
+
118
+ def get_unique_prefixes(self) -> Dict[str, type]:
119
+ """
120
+ Get all unique prefixes in the trie.
121
+
122
+ Returns
123
+ -------
124
+ List[str]
125
+ A list of unique prefixes.
126
+ """
127
+ to_return = {}
128
+
129
+ def _collect(node, current_prefix):
130
+ if node.end_value is not None:
131
+ to_return[current_prefix] = node.end_value
132
+ # We stop there and don't look further since we found the unique prefix
133
+ return
134
+ if node.value is not None:
135
+ to_return[current_prefix] = node.value
136
+ # We continue to look for more unique prefixes
137
+ for child_name, child_node in node.children.items():
138
+ _collect(
139
+ child_node,
140
+ f"{current_prefix}.{child_name}" if current_prefix else child_name,
141
+ )
142
+
143
+ _collect(self.root, "")
144
+ return {".".join(reversed(k.split("."))): v for k, v in to_return.items()}