metaflow 2.17.3__py2.py3-none-any.whl → 2.17.4__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.
metaflow/runtime.py CHANGED
@@ -15,11 +15,13 @@ import tempfile
15
15
  import time
16
16
  import subprocess
17
17
  from datetime import datetime
18
+ from enum import Enum
18
19
  from io import BytesIO
19
20
  from itertools import chain
20
21
  from functools import partial
21
22
  from concurrent import futures
22
23
 
24
+ from typing import Dict, Tuple
23
25
  from metaflow.datastore.exceptions import DataException
24
26
  from contextlib import contextmanager
25
27
 
@@ -60,6 +62,7 @@ PROGRESS_INTERVAL = 300 # s
60
62
  # leveraging the TaskDataStoreSet.
61
63
  PREFETCH_DATA_ARTIFACTS = [
62
64
  "_foreach_stack",
65
+ "_iteration_stack",
63
66
  "_task_ok",
64
67
  "_transition",
65
68
  "_control_mapper_tasks",
@@ -67,6 +70,14 @@ PREFETCH_DATA_ARTIFACTS = [
67
70
  ]
68
71
  RESUME_POLL_SECONDS = 60
69
72
 
73
+
74
+ class LoopBehavior(Enum):
75
+ NONE = "none"
76
+ ENTERING = "entering"
77
+ EXITING = "exiting"
78
+ LOOPING = "looping"
79
+
80
+
70
81
  # Runtime must use logsource=RUNTIME_LOG_SOURCE for all loglines that it
71
82
  # formats according to mflog. See a comment in mflog.__init__
72
83
  mflog_msg = partial(mflog.decorate, RUNTIME_LOG_SOURCE)
@@ -290,6 +301,7 @@ class NativeRuntime(object):
290
301
  pathspec_index,
291
302
  cloned_task_pathspec_index,
292
303
  finished_tuple,
304
+ iteration_tuple,
293
305
  ubf_context,
294
306
  generate_task_obj,
295
307
  verbose=False,
@@ -334,7 +346,7 @@ class NativeRuntime(object):
334
346
  self._metadata,
335
347
  origin_ds_set=self._origin_ds_set,
336
348
  )
337
- self._finished[(step_name, finished_tuple)] = task_pathspec
349
+ self._finished[(step_name, finished_tuple, iteration_tuple)] = task_pathspec
338
350
  self._is_cloned[task_pathspec] = True
339
351
  except Exception as e:
340
352
  self._logger(
@@ -415,6 +427,7 @@ class NativeRuntime(object):
415
427
  finished_tuple = tuple(
416
428
  [s._replace(value=0) for s in task_ds.get("_foreach_stack", ())]
417
429
  )
430
+ iteration_tuple = tuple(task_ds.get("_iteration_stack", ()))
418
431
  cloned_task_pathspec_index = pathspec_index.split("/")[1]
419
432
  if task_ds.get("_control_task_is_mapper_zero", False):
420
433
  # Replace None with index 0 for control task as it is part of the
@@ -440,6 +453,7 @@ class NativeRuntime(object):
440
453
  pathspec_index,
441
454
  cloned_task_pathspec_index,
442
455
  finished_tuple,
456
+ iteration_tuple,
443
457
  is_ubf_mapper_task,
444
458
  ubf_context,
445
459
  )
@@ -454,6 +468,7 @@ class NativeRuntime(object):
454
468
  pathspec_index,
455
469
  cloned_task_pathspec_index,
456
470
  finished_tuple,
471
+ iteration_tuple,
457
472
  ubf_context=ubf_context,
458
473
  generate_task_obj=generate_task_obj and (not is_ubf_mapper_task),
459
474
  verbose=verbose,
@@ -464,6 +479,7 @@ class NativeRuntime(object):
464
479
  pathspec_index,
465
480
  cloned_task_pathspec_index,
466
481
  finished_tuple,
482
+ iteration_tuple,
467
483
  is_ubf_mapper_task,
468
484
  ubf_context,
469
485
  ) in inputs
@@ -484,6 +500,7 @@ class NativeRuntime(object):
484
500
  self._queue_push("start", {"input_paths": [self._params_task.path]})
485
501
  else:
486
502
  self._queue_push("start", {})
503
+
487
504
  progress_tstamp = time.time()
488
505
  with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8") as config_file:
489
506
  # Configurations are passed through a file to avoid overloading the
@@ -504,7 +521,74 @@ class NativeRuntime(object):
504
521
  ):
505
522
  # 1. are any of the current workers finished?
506
523
  if self._cloned_tasks:
507
- finished_tasks = self._cloned_tasks
524
+ finished_tasks = []
525
+
526
+ # For loops (right now just recursive steps), we need to find
527
+ # the exact frontier because if we queue all "successors" to all
528
+ # the finished iterations, we would incorrectly launch multiple
529
+ # successors. We therefore have to strip out all non-last
530
+ # iterations *per* foreach branch.
531
+ idx_per_finished_id = (
532
+ {}
533
+ ) # type: Dict[Tuple[str, Tuple[int, ...], Tuple[int, Tuple[int, ...]]]]
534
+ for task in self._cloned_tasks:
535
+ step_name, foreach_stack, iteration_stack = task.finished_id
536
+ existing_task_idx = idx_per_finished_id.get(
537
+ (step_name, foreach_stack), None
538
+ )
539
+ if existing_task_idx is not None:
540
+ len_diff = len(iteration_stack) - len(
541
+ existing_task_idx[1]
542
+ )
543
+ # In this case, we need to keep only the latest iteration
544
+ if (
545
+ len_diff == 0
546
+ and iteration_stack > existing_task_idx[1]
547
+ ) or len_diff == -1:
548
+ # We remove the one we currently have and replace
549
+ # by this one. The second option means that we are
550
+ # adding the finished iteration marker.
551
+ existing_task = finished_tasks[existing_task_idx[0]]
552
+ # These are the first two lines of _queue_tasks
553
+ # We still consider the tasks finished so we need
554
+ # to update state to be clean.
555
+ self._finished[existing_task.finished_id] = (
556
+ existing_task.path
557
+ )
558
+ self._is_cloned[existing_task.path] = (
559
+ existing_task.is_cloned
560
+ )
561
+
562
+ finished_tasks[existing_task_idx[0]] = task
563
+ idx_per_finished_id[(step_name, foreach_stack)] = (
564
+ existing_task_idx[0],
565
+ iteration_stack,
566
+ )
567
+ elif (
568
+ len_diff == 0
569
+ and iteration_stack < existing_task_idx[1]
570
+ ) or len_diff == 1:
571
+ # The second option is when we have already marked
572
+ # the end of the iteration in self._finished and
573
+ # are now seeing a previous iteration.
574
+ # We just mark the task as finished but we don't
575
+ # put it in the finished_tasks list to pass to
576
+ # the _queue_tasks function
577
+ self._finished[task.finished_id] = task.path
578
+ self._is_cloned[task.path] = task.is_cloned
579
+ else:
580
+ raise MetaflowInternalError(
581
+ "Unexpected recursive cloned tasks -- "
582
+ "this is a bug, please report it."
583
+ )
584
+ else:
585
+ # New entry
586
+ finished_tasks.append(task)
587
+ idx_per_finished_id[(step_name, foreach_stack)] = (
588
+ len(finished_tasks) - 1,
589
+ iteration_stack,
590
+ )
591
+
508
592
  # reset the list of cloned tasks and let poll_workers handle
509
593
  # the remaining transition
510
594
  self._cloned_tasks = []
@@ -578,7 +662,7 @@ class NativeRuntime(object):
578
662
  self._run_exit_hooks()
579
663
 
580
664
  # assert that end was executed and it was successful
581
- if ("end", ()) in self._finished:
665
+ if ("end", (), ()) in self._finished:
582
666
  if self._run_url:
583
667
  self._logger(
584
668
  "Done! See the run in the UI at %s" % self._run_url,
@@ -604,7 +688,7 @@ class NativeRuntime(object):
604
688
  if not exit_hook_decos:
605
689
  return
606
690
 
607
- successful = ("end", ()) in self._finished or self._clone_only
691
+ successful = ("end", (), ()) in self._finished or self._clone_only
608
692
  pathspec = f"{self._graph.name}/{self._run_id}"
609
693
  flow_file = self._environment.get_environment_info()["script"]
610
694
 
@@ -672,29 +756,60 @@ class NativeRuntime(object):
672
756
 
673
757
  # Given the current task information (task_index), the type of transition,
674
758
  # and the split index, return the new task index.
675
- def _translate_index(self, task, next_step, type, split_index=None):
676
- match = re.match(r"^(.+)\[(.*)\]$", task.task_index)
759
+ def _translate_index(
760
+ self, task, next_step, type, split_index=None, loop_mode=LoopBehavior.NONE
761
+ ):
762
+ match = re.match(r"^(.+)\[(.*)\]\[(.*)\]$", task.task_index)
677
763
  if match:
678
- _, foreach_index = match.groups()
764
+ _, foreach_index, iteration_index = match.groups()
679
765
  # Convert foreach_index to a list of integers
680
766
  if len(foreach_index) > 0:
681
767
  foreach_index = foreach_index.split(",")
682
768
  else:
683
769
  foreach_index = []
770
+ # Ditto for iteration_index
771
+ if len(iteration_index) > 0:
772
+ iteration_index = iteration_index.split(",")
773
+ else:
774
+ iteration_index = []
684
775
  else:
685
776
  raise ValueError(
686
- "Index not in the format of {run_id}/{step_name}[{foreach_index}]"
777
+ "Index not in the format of {run_id}/{step_name}[{foreach_index}][{iteration_index}]"
687
778
  )
779
+ if loop_mode == LoopBehavior.NONE:
780
+ # Check if we are entering a looping construct. Right now, only recursive
781
+ # steps are looping constructs
782
+ next_step_node = self._graph[next_step]
783
+ if (
784
+ next_step_node.type == "split-switch"
785
+ and next_step in next_step_node.out_funcs
786
+ ):
787
+ loop_mode = LoopBehavior.ENTERING
788
+
789
+ # Update iteration_index
790
+ if loop_mode == LoopBehavior.ENTERING:
791
+ # We are entering a loop, so we add a new iteration level
792
+ iteration_index.append("0")
793
+ elif loop_mode == LoopBehavior.EXITING:
794
+ iteration_index = iteration_index[:-1]
795
+ elif loop_mode == LoopBehavior.LOOPING:
796
+ if len(iteration_index) == 0:
797
+ raise MetaflowInternalError(
798
+ "In looping mode but there is no iteration index"
799
+ )
800
+ iteration_index[-1] = str(int(iteration_index[-1]) + 1)
801
+ iteration_index = ",".join(iteration_index)
802
+
688
803
  if type == "linear":
689
- return "%s[%s]" % (next_step, ",".join(foreach_index))
804
+ return "%s[%s][%s]" % (next_step, ",".join(foreach_index), iteration_index)
690
805
  elif type == "join":
691
806
  indices = []
692
807
  if len(foreach_index) > 0:
693
808
  indices = foreach_index[:-1]
694
- return "%s[%s]" % (next_step, ",".join(indices))
809
+ return "%s[%s][%s]" % (next_step, ",".join(indices), iteration_index)
695
810
  elif type == "split":
696
811
  foreach_index.append(str(split_index))
697
- return "%s[%s]" % (next_step, ",".join(foreach_index))
812
+ return "%s[%s][%s]" % (next_step, ",".join(foreach_index), iteration_index)
698
813
 
699
814
  # Store the parameters needed for task creation, so that pushing on items
700
815
  # onto the run_queue is an inexpensive operation.
@@ -778,17 +893,19 @@ class NativeRuntime(object):
778
893
  # tasks is incorrect and contains the pathspec of the *cloned* run
779
894
  # but we don't use it for anything. We could look to clean it up though
780
895
  if not task.is_cloned:
781
- _, foreach_stack = task.finished_id
896
+ _, foreach_stack, iteration_stack = task.finished_id
782
897
  top = foreach_stack[-1]
783
898
  bottom = list(foreach_stack[:-1])
784
899
  for i in range(num_splits):
785
900
  s = tuple(bottom + [top._replace(index=i)])
786
- self._finished[(task.step, s)] = mapper_tasks[i]
901
+ self._finished[(task.step, s, iteration_stack)] = mapper_tasks[
902
+ i
903
+ ]
787
904
  self._is_cloned[mapper_tasks[i]] = False
788
905
 
789
906
  # Find and check status of control task and retrieve its pathspec
790
907
  # for retrieving unbounded foreach cardinality.
791
- _, foreach_stack = task.finished_id
908
+ _, foreach_stack, iteration_stack = task.finished_id
792
909
  top = foreach_stack[-1]
793
910
  bottom = list(foreach_stack[:-1])
794
911
  s = tuple(bottom + [top._replace(index=None)])
@@ -797,7 +914,7 @@ class NativeRuntime(object):
797
914
  # it will have index=0 instead of index=None.
798
915
  if task.results.get("_control_task_is_mapper_zero", False):
799
916
  s = tuple(bottom + [top._replace(index=0)])
800
- control_path = self._finished.get((task.step, s))
917
+ control_path = self._finished.get((task.step, s, iteration_stack))
801
918
  if control_path:
802
919
  # Control task was successful.
803
920
  # Additionally check the state of (sibling) mapper tasks as well
@@ -806,7 +923,9 @@ class NativeRuntime(object):
806
923
  required_tasks = []
807
924
  for i in range(num_splits):
808
925
  s = tuple(bottom + [top._replace(index=i)])
809
- required_tasks.append(self._finished.get((task.step, s)))
926
+ required_tasks.append(
927
+ self._finished.get((task.step, s, iteration_stack))
928
+ )
810
929
 
811
930
  if all(required_tasks):
812
931
  index = self._translate_index(task, next_step, "join")
@@ -819,7 +938,7 @@ class NativeRuntime(object):
819
938
  else:
820
939
  # matching_split is the split-parent of the finished task
821
940
  matching_split = self._graph[self._graph[next_step].split_parents[-1]]
822
- _, foreach_stack = task.finished_id
941
+ _, foreach_stack, iteration_stack = task.finished_id
823
942
 
824
943
  direct_parents = set(self._graph[next_step].in_funcs)
825
944
 
@@ -837,7 +956,7 @@ class NativeRuntime(object):
837
956
  filter(
838
957
  lambda x: x is not None,
839
958
  [
840
- self._finished.get((p, s))
959
+ self._finished.get((p, s, iteration_stack))
841
960
  for p in direct_parents
842
961
  for s in siblings(foreach_stack)
843
962
  ],
@@ -852,11 +971,12 @@ class NativeRuntime(object):
852
971
  filter(
853
972
  lambda x: x is not None,
854
973
  [
855
- self._finished.get((p, foreach_stack))
974
+ self._finished.get((p, foreach_stack, iteration_stack))
856
975
  for p in direct_parents
857
976
  ],
858
977
  )
859
978
  )
979
+
860
980
  required_count = len(matching_split.out_funcs)
861
981
  join_type = "linear"
862
982
  index = self._translate_index(task, next_step, "linear")
@@ -868,9 +988,18 @@ class NativeRuntime(object):
868
988
  index,
869
989
  )
870
990
 
871
- def _queue_task_switch(self, task, next_steps):
991
+ def _queue_task_switch(self, task, next_steps, is_recursive):
872
992
  chosen_step = next_steps[0]
873
- index = self._translate_index(task, chosen_step, "linear")
993
+
994
+ loop_mode = LoopBehavior.NONE
995
+ if is_recursive:
996
+ if chosen_step != task.step:
997
+ # We are exiting a loop
998
+ loop_mode = LoopBehavior.EXITING
999
+ else:
1000
+ # We are staying in the loop
1001
+ loop_mode = LoopBehavior.LOOPING
1002
+ index = self._translate_index(task, chosen_step, "linear", None, loop_mode)
874
1003
  self._queue_push(chosen_step, {"input_paths": [task.path]}, index)
875
1004
 
876
1005
  def _queue_task_foreach(self, task, next_steps):
@@ -949,7 +1078,9 @@ class NativeRuntime(object):
949
1078
  next_steps = []
950
1079
  foreach = None
951
1080
  expected = self._graph[task.step].out_funcs
1081
+
952
1082
  if self._graph[task.step].type == "split-switch":
1083
+ is_recursive = task.step in self._graph[task.step].out_funcs
953
1084
  if len(next_steps) != 1:
954
1085
  msg = (
955
1086
  "Switch step *{step}* should transition to exactly "
@@ -970,6 +1101,15 @@ class NativeRuntime(object):
970
1101
  expected=", ".join(expected),
971
1102
  )
972
1103
  )
1104
+ # When exiting a recursive loop, we mark that the loop itself has
1105
+ # finished by adding a special entry in self._finished which has
1106
+ # an iteration stack that is shorter (ie: we are out of the loop) so
1107
+ # that we can then find it when looking at successor tasks to launch.
1108
+ if is_recursive and next_steps[0] != task.step:
1109
+ step_name, finished_tuple, iteration_tuple = task.finished_id
1110
+ self._finished[
1111
+ (step_name, finished_tuple, iteration_tuple[:-1])
1112
+ ] = task.path
973
1113
  elif next_steps != expected:
974
1114
  msg = (
975
1115
  "Based on static analysis of the code, step *{step}* "
@@ -995,8 +1135,8 @@ class NativeRuntime(object):
995
1135
  # Next step is a foreach child
996
1136
  self._queue_task_foreach(task, next_steps)
997
1137
  elif self._graph[task.step].type == "split-switch":
998
- # Next step is switch - queue the chosen step
999
- self._queue_task_switch(task, next_steps)
1138
+ # Current step is switch - queue the chosen step
1139
+ self._queue_task_switch(task, next_steps, is_recursive)
1000
1140
  else:
1001
1141
  # Next steps are normal linear steps
1002
1142
  for step in next_steps:
@@ -1537,13 +1677,13 @@ class Task(object):
1537
1677
  @property
1538
1678
  def finished_id(self):
1539
1679
  # note: id is not available before the task has finished.
1540
- # Index already identifies the task within the foreach,
1541
- # we will remove foreach value so that it is easier to
1680
+ # Index already identifies the task within the foreach and loop.
1681
+ # We will remove foreach value so that it is easier to
1542
1682
  # identify siblings within a foreach.
1543
1683
  foreach_stack_tuple = tuple(
1544
1684
  [s._replace(value=0) for s in self.results["_foreach_stack"]]
1545
1685
  )
1546
- return (self.step, foreach_stack_tuple)
1686
+ return (self.step, foreach_stack_tuple, tuple(self.results["_iteration_stack"]))
1547
1687
 
1548
1688
  @property
1549
1689
  def is_cloned(self):
metaflow/task.py CHANGED
@@ -117,11 +117,19 @@ class MetaflowTask(object):
117
117
 
118
118
  # We back out of the stack of generators
119
119
  for w in reversed(wrappers_stack):
120
- r = w.post_step(orig_step_func.name, self.flow, raised_exception)
120
+ try:
121
+ r = w.post_step(orig_step_func.name, self.flow, raised_exception)
122
+ except Exception as ex:
123
+ r = ex
121
124
  if r is None or isinstance(r, Exception):
122
125
  raised_exception = None
123
126
  elif isinstance(r, tuple):
124
- raised_exception, fake_next_call_args = r
127
+ if len(r) == 2:
128
+ raised_exception, fake_next_call_args = r
129
+ else:
130
+ # The last argument is an exception to be re-raised. Used in
131
+ # user_step_decorator's post_step
132
+ raise r[2]
125
133
  else:
126
134
  raise RuntimeError(
127
135
  "Invalid return value from a UserStepDecorator. Expected an"
@@ -239,6 +247,7 @@ class MetaflowTask(object):
239
247
  # Prefetch 'foreach' related artifacts to improve time taken by
240
248
  # _init_foreach.
241
249
  prefetch_data_artifacts = [
250
+ "_iteration_stack",
242
251
  "_foreach_stack",
243
252
  "_foreach_num_splits",
244
253
  "_foreach_var",
@@ -375,6 +384,56 @@ class MetaflowTask(object):
375
384
  elif "_foreach_stack" in inputs[0]:
376
385
  self.flow._foreach_stack = inputs[0]["_foreach_stack"]
377
386
 
387
+ def _init_iteration(self, step_name, inputs, is_recursive_step):
388
+ # We track the iteration "stack" for loops. At this time, we
389
+ # only support one type of "looping" which is a recursive step but
390
+ # this can generalize to arbitrary well-scoped loops in the future.
391
+
392
+ # _iteration_stack will contain the iteration count for each loop
393
+ # level. Currently, there will be only no elements (no loops) or
394
+ # a single element (a single recursive step).
395
+
396
+ # We just need to determine the rules to add a new looping level,
397
+ # increment the looping level or pop the looping level. In our
398
+ # current support for only recursive steps, this is pretty straightforward:
399
+ # 1) if is_recursive_step:
400
+ # - we are entering a loop -- we are either entering for the first time
401
+ # or we are continuing the loop. Note that a recursive step CANNOT
402
+ # be a join step so there is always a single input
403
+ # 1a) If inputs[0]["_iteration_stack"] contains an element, we are looping
404
+ # so we increment the count
405
+ # 1b) If inputs[0]["_iteration_stack"] is empty, this is the first time we
406
+ # are entering the loop so we set the iteration count to 0
407
+ # 2) if it is not a recursive step, we need to determine if this is the step
408
+ # *after* the recursive step. The easiest way to determine that is to
409
+ # look at all inputs (there can be multiple in case of a join) and pop
410
+ # _iteration_stack if it is set. However, since we know that non recursive
411
+ # steps are *never* part of an iteration, we can simplify and just set it
412
+ # to [] without even checking anything. We will have to revisit this if/when
413
+ # more complex loop structures are supported.
414
+
415
+ # Note that just like _foreach_stack, we need to set _iteration_stack to *something*
416
+ # so that it doesn't get clobbered weirdly by merge_artifacts.
417
+
418
+ if is_recursive_step:
419
+ # Case 1)
420
+ if len(inputs) != 1:
421
+ raise MetaflowInternalError(
422
+ "Step *%s* is a recursive step but got multiple inputs." % step_name
423
+ )
424
+ inp = inputs[0]
425
+ if "_iteration_stack" not in inp or not inp["_iteration_stack"]:
426
+ # Case 1b)
427
+ self.flow._iteration_stack = [0]
428
+ else:
429
+ # Case 1a)
430
+ stack = inp["_iteration_stack"]
431
+ stack[-1] += 1
432
+ self.flow._iteration_stack = stack
433
+ else:
434
+ # Case 2)
435
+ self.flow._iteration_stack = []
436
+
378
437
  def _clone_flow(self, datastore):
379
438
  x = self.flow.__class__(use_cli=False)
380
439
  x._set_datastore(datastore)
@@ -563,6 +622,12 @@ class MetaflowTask(object):
563
622
  # 3. initialize foreach state
564
623
  self._init_foreach(step_name, join_type, inputs, split_index)
565
624
 
625
+ # 4. initialize the iteration state
626
+ is_recursive_step = (
627
+ node.type == "split-switch" and step_name in node.out_funcs
628
+ )
629
+ self._init_iteration(step_name, inputs, is_recursive_step)
630
+
566
631
  # Add foreach stack to metadata of the task
567
632
 
568
633
  foreach_stack = (
@@ -658,8 +658,14 @@ def user_step_decorator(*args, **kwargs):
658
658
  self._generator.send(None)
659
659
  except StopIteration as e:
660
660
  to_return = e.value
661
+ except Exception as e:
662
+ return e
661
663
  else:
662
- raise MetaflowException(" %s should only yield once" % self)
664
+ return (
665
+ None,
666
+ None,
667
+ MetaflowException(" %s should only yield once" % self),
668
+ )
663
669
  return to_return
664
670
 
665
671
  return WrapClass
metaflow/version.py CHANGED
@@ -1 +1 @@
1
- metaflow_version = "2.17.3"
1
+ metaflow_version = "2.17.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metaflow
3
- Version: 2.17.3
3
+ Version: 2.17.4
4
4
  Summary: Metaflow: More AI and ML, Less Engineering
5
5
  Author: Metaflow Developers
6
6
  Author-email: help@metaflow.org
@@ -26,7 +26,7 @@ License-File: LICENSE
26
26
  Requires-Dist: requests
27
27
  Requires-Dist: boto3
28
28
  Provides-Extra: stubs
29
- Requires-Dist: metaflow-stubs==2.17.3; extra == "stubs"
29
+ Requires-Dist: metaflow-stubs==2.17.4; extra == "stubs"
30
30
  Dynamic: author
31
31
  Dynamic: author-email
32
32
  Dynamic: classifier
@@ -11,10 +11,10 @@ metaflow/event_logger.py,sha256=joTVRqZPL87nvah4ZOwtqWX8NeraM_CXKXXGVpKGD8o,780
11
11
  metaflow/events.py,sha256=ahjzkSbSnRCK9RZ-9vTfUviz_6gMvSO9DGkJ86X80-k,5300
12
12
  metaflow/exception.py,sha256=_m9ZBJM0cooHRslDqfxCPQmkChqaTh6fGxp7HvISnYI,5161
13
13
  metaflow/flowspec.py,sha256=9wsO2_QoO_VHKusKdpslfbwQREOwf0fAzF-DSEA0iZ8,41968
14
- metaflow/graph.py,sha256=3Ova0lGbUZzxV89vqw3bX-dCSaVXfGUz3Tm9a8cmtSI,19198
14
+ metaflow/graph.py,sha256=UOeClj-JeORRlZWOQMI1CirkrCpTvVWvRcSwODCajMg,19263
15
15
  metaflow/includefile.py,sha256=RtISGl1V48qjkJBakUZ9yPpHV102h7pOIFiKP8PLHpc,20927
16
16
  metaflow/integrations.py,sha256=LlsaoePRg03DjENnmLxZDYto3NwWc9z_PtU6nJxLldg,1480
17
- metaflow/lint.py,sha256=2VLHNgxmHDtUcudvyfPCFIlbDy1pW7GFjNPBWtKZVjA,14102
17
+ metaflow/lint.py,sha256=dvgpMZoF506yJerCHHUu7A-nTVry95Fmr8Q3bPEaJCs,14374
18
18
  metaflow/meta_files.py,sha256=vlgJHI8GJUKzXoxdrVoH8yyCF5bhFgwYemUgnyd1wgM,342
19
19
  metaflow/metaflow_config.py,sha256=lsdfr_1eS4cP6g1OgLWePKzUIypUGVjNxffRhKlFWfA,24102
20
20
  metaflow/metaflow_config_funcs.py,sha256=5GlvoafV6SxykwfL8D12WXSfwjBN_NsyuKE_Q3gjGVE,6738
@@ -29,14 +29,14 @@ metaflow/parameters.py,sha256=b3rS6P-TeEj2JgPEKaiy0ys1B_JtOGJ6XM0KkY2layc,18649
29
29
  metaflow/procpoll.py,sha256=U2tE4iK_Mwj2WDyVTx_Uglh6xZ-jixQOo4wrM9OOhxg,2859
30
30
  metaflow/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
31
  metaflow/pylint_wrapper.py,sha256=tJjmdsgtbHTCqg_oA6fV6SbWq_3V5XUgE9xH0zJ1CGU,3004
32
- metaflow/runtime.py,sha256=QjmWeaphyP4U8CI0fN9rVFOXvkC0yNr7-bnSG0fNhWQ,79508
32
+ metaflow/runtime.py,sha256=3R44AFI04LWahbfa2v0yDYIWHIz6sau26p1SnzYFNew,86888
33
33
  metaflow/tagging_util.py,sha256=ctyf0Q1gBi0RyZX6J0e9DQGNkNHblV_CITfy66axXB4,2346
34
- metaflow/task.py,sha256=O8swwqdSPZ_IyAC0GzjLyRVIyHH2C5iv19AtC8shFXs,35932
34
+ metaflow/task.py,sha256=keErPaNH5gNko6V1saDT-qsICKu4EROau9DiCu4riHU,39192
35
35
  metaflow/tuple_util.py,sha256=_G5YIEhuugwJ_f6rrZoelMFak3DqAR2tt_5CapS1XTY,830
36
36
  metaflow/unbounded_foreach.py,sha256=p184WMbrMJ3xKYHwewj27ZhRUsSj_kw1jlye5gA9xJk,387
37
37
  metaflow/util.py,sha256=g2SOU_CRzJLgDM_UGF9QDMANMAIHAsDRXE6S76_YzsY,14594
38
38
  metaflow/vendor.py,sha256=A82CGHfStZGDP5pQ5XzRjFkbN1ZC-vFmghXIrzMDDNg,5868
39
- metaflow/version.py,sha256=XUi5503h1FTLCQTm2Er2t3lqs9HEd10aLotp-zKBeg8,28
39
+ metaflow/version.py,sha256=-kkCVrHWQPhCpJvbbJKGFXeit8uBl_Mos2kWpMTu48E,28
40
40
  metaflow/_vendor/__init__.py,sha256=y_CiwUD3l4eAKvTVDZeqgVujMy31cAM1qjAB-HfI-9s,353
41
41
  metaflow/_vendor/typing_extensions.py,sha256=q9zxWa6p6CzF1zZvSkygSlklduHf_b3K7MCxGz7MJRc,134519
42
42
  metaflow/_vendor/zipp.py,sha256=ajztOH-9I7KA_4wqDYygtHa6xUBVZgFpmZ8FE74HHHI,8425
@@ -176,7 +176,7 @@ metaflow/datastore/datastore_storage.py,sha256=7V43QuiWDQ_Q4oHw9y7Z7X9lYj3GI-LV1
176
176
  metaflow/datastore/exceptions.py,sha256=r7Ab5FvHIzyFh6kwiptA1lO5nLqWg0xRBoeYGefvapA,373
177
177
  metaflow/datastore/flow_datastore.py,sha256=rDMEHdYwub1PwLp2uaK-8CHdd8hiwxqeELXzsUfuqZs,10250
178
178
  metaflow/datastore/inputs.py,sha256=i43dXr2xvgtsgKMO9allgCR18bk80GeayeQFyUTH36w,449
179
- metaflow/datastore/task_datastore.py,sha256=bEti1X5rvKBQykfvsoAnmHXel_itZbI5MeLrEpWPHPQ,35059
179
+ metaflow/datastore/task_datastore.py,sha256=XZKC4YJ2kyFTDqwTARNavjQ-oSosjUJbNH777e9OF0c,35262
180
180
  metaflow/extension_support/__init__.py,sha256=xLkhh0IzQ70IfF9j6MopMY02SMpEVI_eguksIOEXbDs,52522
181
181
  metaflow/extension_support/_empty_file.py,sha256=vz61sSExf5DZH3JCqdfwkp7l_NrJR8HV175kG82yUas,133
182
182
  metaflow/extension_support/cmd.py,sha256=hk8iBUUINqvKCDxInKgWpum8ThiRZtHSJP7qBASHzl8,5711
@@ -289,7 +289,7 @@ metaflow/plugins/cards/card_modules/card.py,sha256=6sbqP5mwf7QWvQvX2N_bC78H9ixuI
289
289
  metaflow/plugins/cards/card_modules/components.py,sha256=Y-Yo7UgSOyTB3zs-wDfUqUKl0PI_BzeY1QGcy2fqE2M,26752
290
290
  metaflow/plugins/cards/card_modules/convert_to_native_type.py,sha256=Vcjqn5rfC0kVMdhqDwsYEjknXTbkG_ppraQrQGaQY_E,16245
291
291
  metaflow/plugins/cards/card_modules/main.css,sha256=avu7BTB9qj0M8LvqNLUhikUFQhmAJhQQ7REcUgh9zMw,11725
292
- metaflow/plugins/cards/card_modules/main.js,sha256=UHwjWFWevGPjOFIzWAqY63j2UdBq_z-LociaG4QVxfU,1043692
292
+ metaflow/plugins/cards/card_modules/main.js,sha256=1p26spBKCwglJgioKfmAAPgh4pP2Cdp0bq29fWyg58A,1045517
293
293
  metaflow/plugins/cards/card_modules/renderer_tools.py,sha256=uiTdKHWFInWgtfWArOUSQLnTwqVd4hAw49jfOfg8rGA,1642
294
294
  metaflow/plugins/cards/card_modules/test_cards.py,sha256=t2PZ3QnjCN_KUlDKxAt2FfK1BX2dp8rqK6TjtkCNyew,5876
295
295
  metaflow/plugins/cards/card_modules/chevron/__init__.py,sha256=SicpH_1eT76HUTA8aMuiGLRNKTqgYD1Qfutt2NdTL0E,156
@@ -427,13 +427,13 @@ metaflow/user_decorators/common.py,sha256=0u9NRLQ95TfJCjWVEOGT4MJ9WoQgAMBM9kJ2gJ
427
427
  metaflow/user_decorators/mutable_flow.py,sha256=EywKTN3cnXPQF_s62wQaC4a4aH14j8oeqzD3yZaiDxw,19467
428
428
  metaflow/user_decorators/mutable_step.py,sha256=-BY0UDXf_RCAEnC5JlLzEXGdiw1KD9oSrSxS_SWaB9Y,16791
429
429
  metaflow/user_decorators/user_flow_decorator.py,sha256=2yDwZq9QGv9W-7kEuKwa8o4ZkTvuHJ5ESz7VVrGViAI,9890
430
- metaflow/user_decorators/user_step_decorator.py,sha256=bZvg16RUWgfGrAx5tgKT_1aTK7urp0usK59jnV-yR8s,27054
431
- metaflow-2.17.3.data/data/share/metaflow/devtools/Makefile,sha256=TT4TCq8ALSfqYyGqDPocN5oPcZe2FqoCZxmGO1LmyCc,13760
432
- metaflow-2.17.3.data/data/share/metaflow/devtools/Tiltfile,sha256=Ty5p6AD3MwJAcAnOGv4yMz8fExAsnNQ11r8whK6uzzw,21381
433
- metaflow-2.17.3.data/data/share/metaflow/devtools/pick_services.sh,sha256=DCnrMXwtApfx3B4S-YiZESMyAFHbXa3VuNL0MxPLyiE,2196
434
- metaflow-2.17.3.dist-info/licenses/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
435
- metaflow-2.17.3.dist-info/METADATA,sha256=ovW_YDMG1cJMx7y4XvjrEDnbw25yEg25Ac44GvgPwMM,6740
436
- metaflow-2.17.3.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
437
- metaflow-2.17.3.dist-info/entry_points.txt,sha256=RvEq8VFlgGe_FfqGOZi0D7ze1hLD0pAtXeNyGfzc_Yc,103
438
- metaflow-2.17.3.dist-info/top_level.txt,sha256=v1pDHoWaSaKeuc5fKTRSfsXCKSdW1zvNVmvA-i0if3o,9
439
- metaflow-2.17.3.dist-info/RECORD,,
430
+ metaflow/user_decorators/user_step_decorator.py,sha256=4558NR8RJtN22OyTwCXO80bAMhMTaRGMoX12b1GMcPc,27232
431
+ metaflow-2.17.4.data/data/share/metaflow/devtools/Makefile,sha256=TT4TCq8ALSfqYyGqDPocN5oPcZe2FqoCZxmGO1LmyCc,13760
432
+ metaflow-2.17.4.data/data/share/metaflow/devtools/Tiltfile,sha256=Ty5p6AD3MwJAcAnOGv4yMz8fExAsnNQ11r8whK6uzzw,21381
433
+ metaflow-2.17.4.data/data/share/metaflow/devtools/pick_services.sh,sha256=DCnrMXwtApfx3B4S-YiZESMyAFHbXa3VuNL0MxPLyiE,2196
434
+ metaflow-2.17.4.dist-info/licenses/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
435
+ metaflow-2.17.4.dist-info/METADATA,sha256=fQRSl2xh3xdKu73eEo8LVKxaDwWTQJxk72v0LWzMHAs,6740
436
+ metaflow-2.17.4.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
437
+ metaflow-2.17.4.dist-info/entry_points.txt,sha256=RvEq8VFlgGe_FfqGOZi0D7ze1hLD0pAtXeNyGfzc_Yc,103
438
+ metaflow-2.17.4.dist-info/top_level.txt,sha256=v1pDHoWaSaKeuc5fKTRSfsXCKSdW1zvNVmvA-i0if3o,9
439
+ metaflow-2.17.4.dist-info/RECORD,,