metaflow 2.12.12__py2.py3-none-any.whl → 2.12.14__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/cli.py CHANGED
@@ -650,7 +650,7 @@ def resume(
650
650
  )
651
651
 
652
652
  if step_to_rerun is None:
653
- clone_steps = set()
653
+ steps_to_rerun = set()
654
654
  else:
655
655
  # validate step name
656
656
  if step_to_rerun not in obj.graph.nodes:
@@ -660,7 +660,7 @@ def resume(
660
660
  step_to_rerun, ",".join(list(obj.graph.nodes.keys()))
661
661
  )
662
662
  )
663
- clone_steps = {step_to_rerun}
663
+ steps_to_rerun = {step_to_rerun}
664
664
 
665
665
  if run_id:
666
666
  # Run-ids that are provided by the metadata service are always integers.
@@ -688,7 +688,7 @@ def resume(
688
688
  clone_run_id=origin_run_id,
689
689
  clone_only=clone_only,
690
690
  reentrant=reentrant,
691
- clone_steps=clone_steps,
691
+ steps_to_rerun=steps_to_rerun,
692
692
  max_workers=max_workers,
693
693
  max_num_splits=max_num_splits,
694
694
  max_log_size=max_log_size * 1024 * 1024,
metaflow/client/core.py CHANGED
@@ -1027,6 +1027,8 @@ class MetaflowData(object):
1027
1027
  self._artifacts = dict((art.id, art) for art in artifacts)
1028
1028
 
1029
1029
  def __getattr__(self, name: str):
1030
+ if name not in self._artifacts:
1031
+ raise AttributeError(name)
1030
1032
  return self._artifacts[name].data
1031
1033
 
1032
1034
  def __contains__(self, var):
metaflow/flowspec.py CHANGED
@@ -17,6 +17,7 @@ from .exception import (
17
17
  )
18
18
  from .graph import FlowGraph
19
19
  from .unbounded_foreach import UnboundedForeachInput
20
+ from .util import to_pod
20
21
  from .metaflow_config import INCLUDE_FOREACH_STACK, MAXIMUM_FOREACH_VALUE_CHARS
21
22
 
22
23
  # For Python 3 compatibility
@@ -201,7 +202,7 @@ class FlowSpec(metaclass=_FlowSpecMeta):
201
202
  "decorators": [
202
203
  {
203
204
  "name": deco.name,
204
- "attributes": deco.attributes,
205
+ "attributes": to_pod(deco.attributes),
205
206
  "statically_defined": deco.statically_defined,
206
207
  }
207
208
  for deco in flow_decorators(self)
metaflow/graph.py CHANGED
@@ -3,6 +3,9 @@ import ast
3
3
  import re
4
4
 
5
5
 
6
+ from .util import to_pod
7
+
8
+
6
9
  def deindent_docstring(doc):
7
10
  if doc:
8
11
  # Find the indent to remove from the docstring. We consider the following possibilities:
@@ -72,7 +75,6 @@ class DAGNode(object):
72
75
  return "%s.%s" % (expr.value.id, expr.attr)
73
76
 
74
77
  def _parse(self, func_ast):
75
-
76
78
  self.num_args = len(func_ast.args.args)
77
79
  tail = func_ast.body[-1]
78
80
 
@@ -171,6 +173,8 @@ class FlowGraph(object):
171
173
  self.name = flow.__name__
172
174
  self.nodes = self._create_nodes(flow)
173
175
  self.doc = deindent_docstring(flow.__doc__)
176
+ # nodes sorted in topological order.
177
+ self.sorted_nodes = []
174
178
  self._traverse_graph()
175
179
  self._postprocess()
176
180
 
@@ -197,6 +201,7 @@ class FlowGraph(object):
197
201
 
198
202
  def _traverse_graph(self):
199
203
  def traverse(node, seen, split_parents):
204
+ self.sorted_nodes.append(node.name)
200
205
  if node.type in ("split", "foreach"):
201
206
  node.split_parents = split_parents
202
207
  split_parents = split_parents + [node.name]
@@ -262,7 +267,6 @@ class FlowGraph(object):
262
267
  )
263
268
 
264
269
  def output_steps(self):
265
-
266
270
  steps_info = {}
267
271
  graph_structure = []
268
272
 
@@ -286,7 +290,7 @@ class FlowGraph(object):
286
290
  "decorators": [
287
291
  {
288
292
  "name": deco.name,
289
- "attributes": deco.attributes,
293
+ "attributes": to_pod(deco.attributes),
290
294
  "statically_defined": deco.statically_defined,
291
295
  }
292
296
  for deco in node.decorators
@@ -15,6 +15,17 @@ if sys.platform == "darwin":
15
15
  ## value, either set `METAFLOW_DEFAULT_DATASTORE` in your configuration file or set
16
16
  ## an environment variable called `METAFLOW_DEFAULT_DATASTORE`
17
17
 
18
+ ##
19
+ # Constants (NOTE: these need to live before any from_conf)
20
+ ##
21
+
22
+ # Path to the local directory to store artifacts for 'local' datastore.
23
+ DATASTORE_LOCAL_DIR = ".metaflow"
24
+
25
+ # Local configuration file (in .metaflow) containing overrides per-project
26
+ LOCAL_CONFIG_FILE = "config.json"
27
+
28
+
18
29
  ###
19
30
  # Default configuration
20
31
  ###
@@ -42,8 +53,6 @@ USER = from_conf("USER")
42
53
  ###
43
54
  # Datastore configuration
44
55
  ###
45
- # Path to the local directory to store artifacts for 'local' datastore.
46
- DATASTORE_LOCAL_DIR = ".metaflow"
47
56
  DATASTORE_SYSROOT_LOCAL = from_conf("DATASTORE_SYSROOT_LOCAL")
48
57
  # S3 bucket and prefix to store artifacts for 's3' datastore.
49
58
  DATASTORE_SYSROOT_S3 = from_conf("DATASTORE_SYSROOT_S3")
@@ -32,8 +32,40 @@ def init_config():
32
32
  return config
33
33
 
34
34
 
35
+ def init_local_config():
36
+ # This function is heavily inspired from LocalStorage.get_datastore_root_from_config
37
+ # but simplifies certain things and also does not depend on DATASTORE_SYSROOT_LOCAL.
38
+ #
39
+ # In other words, since this config is meant to be local to a directory, it does not
40
+ # check in DATASTORE_SYSROOT_LOCAL but only up the current getcwd() path. This also
41
+ # prevents nasty circular dependencies :)
42
+
43
+ from metaflow.metaflow_config import DATASTORE_LOCAL_DIR, LOCAL_CONFIG_FILE
44
+
45
+ current_path = os.getcwd()
46
+ check_dir = os.path.join(current_path, DATASTORE_LOCAL_DIR)
47
+ check_dir = os.path.realpath(check_dir)
48
+ while not os.path.isdir(check_dir):
49
+ new_path = os.path.dirname(current_path)
50
+ if new_path == current_path: # No longer making upward progress
51
+ return {}
52
+ current_path = new_path
53
+ check_dir = os.path.join(current_path, DATASTORE_LOCAL_DIR)
54
+ path_to_config = os.path.join(check_dir, LOCAL_CONFIG_FILE)
55
+ # We found a directory to look for the config file in
56
+ if os.path.exists(path_to_config):
57
+ with open(path_to_config, encoding="utf-8") as f:
58
+ return json.load(f)
59
+ return {}
60
+
61
+
35
62
  # Initialize defaults required to setup environment variables.
36
- METAFLOW_CONFIG = init_config()
63
+ # (initialized lazily in from_conf since init_local_config requires
64
+ # some configuration values
65
+
66
+ METAFLOW_CONFIG = None
67
+
68
+ METAFLOW_LOCAL_CONFIG = None
37
69
 
38
70
  _all_configs = {}
39
71
 
@@ -51,7 +83,13 @@ def config_values(include=0):
51
83
 
52
84
  def from_conf(name, default=None, validate_fn=None):
53
85
  """
54
- First try to pull value from environment, then from metaflow config JSON
86
+ Pull value from the environment or configuration.
87
+ Order is:
88
+ 1. Environment (use any environment variable explicitly set by user)
89
+ 2. Local config (use any value set in the local config file -- so stuff in
90
+ .metaflow/project.json for example)
91
+ 3. Global config (use any value set in the global config file)
92
+ 4. Default
55
93
 
56
94
  Prior to a value being returned, we will validate using validate_fn (if provided).
57
95
  Only non-None values are validated.
@@ -59,9 +97,19 @@ def from_conf(name, default=None, validate_fn=None):
59
97
  validate_fn should accept (name, value).
60
98
  If the value validates, return None, else raise an MetaflowException.
61
99
  """
100
+ global METAFLOW_CONFIG, METAFLOW_LOCAL_CONFIG
101
+
102
+ if METAFLOW_CONFIG is None:
103
+ METAFLOW_CONFIG = init_config()
104
+ if METAFLOW_LOCAL_CONFIG is None:
105
+ METAFLOW_LOCAL_CONFIG = init_local_config()
106
+
62
107
  is_default = True
63
108
  env_name = "METAFLOW_%s" % name
64
- value = os.environ.get(env_name, METAFLOW_CONFIG.get(env_name, default))
109
+ value = os.environ.get(
110
+ env_name,
111
+ METAFLOW_LOCAL_CONFIG.get(env_name, METAFLOW_CONFIG.get(env_name, default)),
112
+ )
65
113
  if validate_fn and value is not None:
66
114
  validate_fn(env_name, value)
67
115
  if default is not None:
@@ -89,10 +89,16 @@ class MetaflowEnvironment(object):
89
89
  It should work silently if everything goes well.
90
90
  """
91
91
  if datastore_type == "s3":
92
- return (
93
- '%s -m awscli ${METAFLOW_S3_ENDPOINT_URL:+--endpoint-url=\\"${METAFLOW_S3_ENDPOINT_URL}\\"} '
94
- + "s3 cp %s job.tar >/dev/null"
95
- ) % (self._python(), code_package_url)
92
+ from .plugins.aws.aws_utils import parse_s3_full_path
93
+
94
+ bucket, s3_object = parse_s3_full_path(code_package_url)
95
+ # NOTE: the script quoting is extremely sensitive due to the way shlex.split operates and this being inserted
96
+ # into a quoted command elsewhere.
97
+ return "{python} -c '{script}'".format(
98
+ python=self._python(),
99
+ script='import boto3, os; boto3.client(\\"s3\\", endpoint_url=os.getenv(\\"METAFLOW_S3_ENDPOINT_URL\\")).download_file(\\"%s\\", \\"%s\\", \\"job.tar\\")'
100
+ % (bucket, s3_object),
101
+ )
96
102
  elif datastore_type == "azure":
97
103
  from .plugins.azure.azure_utils import parse_azure_full_path
98
104
 
@@ -119,25 +125,34 @@ class MetaflowEnvironment(object):
119
125
  )
120
126
 
121
127
  def _get_install_dependencies_cmd(self, datastore_type):
122
- cmds = ["%s -m pip install requests -qqq" % self._python()]
123
- if datastore_type == "s3":
124
- cmds.append("%s -m pip install awscli boto3 -qqq" % self._python())
125
- elif datastore_type == "azure":
126
- cmds.append(
127
- "%s -m pip install azure-identity azure-storage-blob azure-keyvault-secrets simple-azure-blob-downloader -qqq"
128
- % self._python()
129
- )
130
- elif datastore_type == "gs":
131
- cmds.append(
132
- "%s -m pip install google-cloud-storage google-auth simple-gcp-object-downloader google-cloud-secret-manager -qqq"
133
- % self._python()
134
- )
135
- else:
128
+ base_cmd = "{} -m pip install -qqq".format(self._python())
129
+
130
+ datastore_packages = {
131
+ "s3": ["boto3"],
132
+ "azure": [
133
+ "azure-identity",
134
+ "azure-storage-blob",
135
+ "azure-keyvault-secrets",
136
+ "simple-azure-blob-downloader",
137
+ ],
138
+ "gs": [
139
+ "google-cloud-storage",
140
+ "google-auth",
141
+ "simple-gcp-object-downloader",
142
+ "google-cloud-secret-manager",
143
+ ],
144
+ }
145
+
146
+ if datastore_type not in datastore_packages:
136
147
  raise NotImplementedError(
137
- "We don't know how to generate an install dependencies cmd for datastore %s"
138
- % datastore_type
148
+ "Unknown datastore type: {}".format(datastore_type)
139
149
  )
140
- return " && ".join(cmds)
150
+
151
+ cmd = "{} {}".format(
152
+ base_cmd, " ".join(datastore_packages[datastore_type] + ["requests"])
153
+ )
154
+ # skip pip installs if we know that packages might already be available
155
+ return "if [ -z $METAFLOW_SKIP_INSTALL_DEPENDENCIES ]; then {}; fi".format(cmd)
141
156
 
142
157
  def get_package_commands(self, code_package_url, datastore_type):
143
158
  cmds = [
@@ -1658,6 +1658,9 @@ class ArgoWorkflows(object):
1658
1658
 
1659
1659
  # support Metaflow sandboxes
1660
1660
  env["METAFLOW_INIT_SCRIPT"] = KUBERNETES_SANDBOX_INIT_SCRIPT
1661
+ env["METAFLOW_KUBERNETES_SANDBOX_INIT_SCRIPT"] = (
1662
+ KUBERNETES_SANDBOX_INIT_SCRIPT
1663
+ )
1661
1664
 
1662
1665
  # support for @secret
1663
1666
  env["METAFLOW_DEFAULT_SECRETS_BACKEND_TYPE"] = DEFAULT_SECRETS_BACKEND_TYPE
@@ -1673,6 +1676,12 @@ class ArgoWorkflows(object):
1673
1676
  )
1674
1677
  env["METAFLOW_DATASTORE_SYSROOT_AZURE"] = DATASTORE_SYSROOT_AZURE
1675
1678
  env["METAFLOW_CARD_AZUREROOT"] = CARD_AZUREROOT
1679
+ env["METAFLOW_ARGO_WORKFLOWS_KUBERNETES_SECRETS"] = (
1680
+ ARGO_WORKFLOWS_KUBERNETES_SECRETS
1681
+ )
1682
+ env["METAFLOW_ARGO_WORKFLOWS_ENV_VARS_TO_SKIP"] = (
1683
+ ARGO_WORKFLOWS_ENV_VARS_TO_SKIP
1684
+ )
1676
1685
 
1677
1686
  # support for GCP
1678
1687
  env["METAFLOW_DATASTORE_SYSROOT_GS"] = DATASTORE_SYSROOT_GS
@@ -172,7 +172,7 @@ def argo_workflows(obj, name=None):
172
172
  )
173
173
  @click.option(
174
174
  "--enable-heartbeat-daemon/--no-enable-heartbeat-daemon",
175
- default=False,
175
+ default=True,
176
176
  show_default=True,
177
177
  help="Use a daemon container to broadcast heartbeats.",
178
178
  )
@@ -186,7 +186,7 @@ def argo_workflows(obj, name=None):
186
186
  )
187
187
  @click.option(
188
188
  "--enable-error-msg-capture/--no-enable-error-msg-capture",
189
- default=False,
189
+ default=True,
190
190
  show_default=True,
191
191
  help="Capture stack trace of first failed task in exit hook.",
192
192
  )
@@ -226,7 +226,9 @@ def trigger(instance: DeployedFlow, **kwargs):
226
226
  )
227
227
 
228
228
  command_obj = instance.deployer.spm.get(pid)
229
- content = handle_timeout(tfp_runner_attribute, command_obj)
229
+ content = handle_timeout(
230
+ tfp_runner_attribute, command_obj, instance.deployer.file_read_timeout
231
+ )
230
232
 
231
233
  if command_obj.process.returncode == 0:
232
234
  triggered_run = TriggeredRun(deployer=instance.deployer, content=content)
@@ -4,6 +4,22 @@ import requests
4
4
  from metaflow.exception import MetaflowException
5
5
 
6
6
 
7
+ def parse_s3_full_path(s3_uri):
8
+ from urllib.parse import urlparse
9
+
10
+ # <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
11
+ scheme, netloc, path, _, _, _ = urlparse(s3_uri)
12
+ assert scheme == "s3"
13
+ assert netloc is not None
14
+
15
+ bucket = netloc
16
+ path = path.lstrip("/").rstrip("/")
17
+ if path == "":
18
+ path = None
19
+
20
+ return bucket, path
21
+
22
+
7
23
  def get_ec2_instance_metadata():
8
24
  """
9
25
  Fetches the EC2 instance metadata through AWS instance metadata service
@@ -123,6 +123,7 @@ class BatchDecorator(StepDecorator):
123
123
  "ephemeral_storage": None,
124
124
  "log_driver": None,
125
125
  "log_options": None,
126
+ "executable": None,
126
127
  }
127
128
  resource_defaults = {
128
129
  "cpu": "1",
@@ -193,7 +193,9 @@ def trigger(instance: DeployedFlow, **kwargs):
193
193
  )
194
194
 
195
195
  command_obj = instance.deployer.spm.get(pid)
196
- content = handle_timeout(tfp_runner_attribute, command_obj)
196
+ content = handle_timeout(
197
+ tfp_runner_attribute, command_obj, instance.deployer.file_read_timeout
198
+ )
197
199
 
198
200
  if command_obj.process.returncode == 0:
199
201
  triggered_run = TriggeredRun(deployer=instance.deployer, content=content)
@@ -17,6 +17,8 @@ from metaflow.metaflow_config import (
17
17
  ARGO_EVENTS_INTERNAL_WEBHOOK_URL,
18
18
  ARGO_EVENTS_SERVICE_ACCOUNT,
19
19
  ARGO_EVENTS_WEBHOOK_AUTH,
20
+ ARGO_WORKFLOWS_KUBERNETES_SECRETS,
21
+ ARGO_WORKFLOWS_ENV_VARS_TO_SKIP,
20
22
  AWS_SECRETS_MANAGER_DEFAULT_REGION,
21
23
  AZURE_KEY_VAULT_PREFIX,
22
24
  AZURE_STORAGE_BLOB_SERVICE_ENDPOINT,
@@ -280,6 +282,18 @@ class Kubernetes(object):
280
282
  .environment_variable(
281
283
  "METAFLOW_INIT_SCRIPT", KUBERNETES_SANDBOX_INIT_SCRIPT
282
284
  )
285
+ .environment_variable(
286
+ "METAFLOW_KUBERNETES_SANDBOX_INIT_SCRIPT",
287
+ KUBERNETES_SANDBOX_INIT_SCRIPT,
288
+ )
289
+ .environment_variable(
290
+ "METAFLOW_ARGO_WORKFLOWS_KUBERNETES_SECRETS",
291
+ ARGO_WORKFLOWS_KUBERNETES_SECRETS,
292
+ )
293
+ .environment_variable(
294
+ "METAFLOW_ARGO_WORKFLOWS_ENV_VARS_TO_SKIP",
295
+ ARGO_WORKFLOWS_ENV_VARS_TO_SKIP,
296
+ )
283
297
  .environment_variable("METAFLOW_OTEL_ENDPOINT", OTEL_ENDPOINT)
284
298
  # Skip setting METAFLOW_DATASTORE_SYSROOT_LOCAL because metadata sync
285
299
  # between the local user instance and the remote Kubernetes pod
@@ -565,6 +579,18 @@ class Kubernetes(object):
565
579
  .environment_variable(
566
580
  "METAFLOW_INIT_SCRIPT", KUBERNETES_SANDBOX_INIT_SCRIPT
567
581
  )
582
+ .environment_variable(
583
+ "METAFLOW_KUBERNETES_SANDBOX_INIT_SCRIPT",
584
+ KUBERNETES_SANDBOX_INIT_SCRIPT,
585
+ )
586
+ .environment_variable(
587
+ "METAFLOW_ARGO_WORKFLOWS_KUBERNETES_SECRETS",
588
+ ARGO_WORKFLOWS_KUBERNETES_SECRETS,
589
+ )
590
+ .environment_variable(
591
+ "METAFLOW_ARGO_WORKFLOWS_ENV_VARS_TO_SKIP",
592
+ ARGO_WORKFLOWS_ENV_VARS_TO_SKIP,
593
+ )
568
594
  .environment_variable("METAFLOW_OTEL_ENDPOINT", OTEL_ENDPOINT)
569
595
  # Skip setting METAFLOW_DATASTORE_SYSROOT_LOCAL because metadata sync
570
596
  # between the local user instance and the remote Kubernetes pod
@@ -97,6 +97,9 @@ class KubernetesDecorator(StepDecorator):
97
97
  Shared memory size (in MiB) required for this step
98
98
  port: int, optional
99
99
  Port number to specify in the Kubernetes job object
100
+ compute_pool : str, optional, default None
101
+ Compute pool to be used for for this step.
102
+ If not specified, any accessible compute pool within the perimeter is used.
100
103
  """
101
104
 
102
105
  name = "kubernetes"
@@ -121,6 +124,8 @@ class KubernetesDecorator(StepDecorator):
121
124
  "persistent_volume_claims": None, # e.g., {"pvc-name": "/mnt/vol", "another-pvc": "/mnt/vol2"}
122
125
  "shared_memory": None,
123
126
  "port": None,
127
+ "compute_pool": None,
128
+ "executable": None,
124
129
  }
125
130
  package_url = None
126
131
  package_sha = None
@@ -153,6 +158,12 @@ class KubernetesDecorator(StepDecorator):
153
158
  self.attributes["node_selector"] = parse_kube_keyvalue_list(
154
159
  self.attributes["node_selector"].split(",")
155
160
  )
161
+ if self.attributes["compute_pool"]:
162
+ if self.attributes["node_selector"] is None:
163
+ self.attributes["node_selector"] = {}
164
+ self.attributes["node_selector"].update(
165
+ {"outerbounds.co/compute-pool": self.attributes["compute_pool"]}
166
+ )
156
167
 
157
168
  if self.attributes["tolerations"]:
158
169
  try:
@@ -370,9 +381,13 @@ class KubernetesDecorator(StepDecorator):
370
381
  cli_args.command_args.append(self.package_sha)
371
382
  cli_args.command_args.append(self.package_url)
372
383
 
384
+ # skip certain keys as CLI arguments
385
+ _skip_keys = ["compute_pool"]
373
386
  # --namespace is used to specify Metaflow namespace (a different
374
387
  # concept from k8s namespace).
375
388
  for k, v in self.attributes.items():
389
+ if k in _skip_keys:
390
+ continue
376
391
  if k == "namespace":
377
392
  cli_args.command_options["k8s_namespace"] = v
378
393
  elif k in {"node_selector"} and v:
@@ -100,9 +100,9 @@ class CondaStepDecorator(StepDecorator):
100
100
  # --environment=pypi to --environment=conda
101
101
  _supported_virtual_envs.extend(["pypi"])
102
102
 
103
- # TODO: Hardcoded for now to support Docker environment.
103
+ # TODO: Hardcoded for now to support the fast bakery environment.
104
104
  # We should introduce a more robust mechanism for appending supported environments, for example from within extensions.
105
- _supported_virtual_envs.extend(["docker"])
105
+ _supported_virtual_envs.extend(["fast-bakery"])
106
106
 
107
107
  # The --environment= requirement ensures that valid virtual environments are
108
108
  # created for every step to execute it, greatly simplifying the @conda
@@ -344,9 +344,9 @@ class CondaFlowDecorator(FlowDecorator):
344
344
  # --environment=pypi to --environment=conda
345
345
  _supported_virtual_envs.extend(["pypi"])
346
346
 
347
- # TODO: Hardcoded for now to support Docker environment.
347
+ # TODO: Hardcoded for now to support the fast bakery environment.
348
348
  # We should introduce a more robust mechanism for appending supported environments, for example from within extensions.
349
- _supported_virtual_envs.extend(["docker"])
349
+ _supported_virtual_envs.extend(["fast-bakery"])
350
350
 
351
351
  # The --environment= requirement ensures that valid virtual environments are
352
352
  # created for every step to execute it, greatly simplifying the @conda
@@ -24,6 +24,12 @@ class PyPIStepDecorator(StepDecorator):
24
24
  name = "pypi"
25
25
  defaults = {"packages": {}, "python": None, "disabled": None} # wheels
26
26
 
27
+ def __init__(self, attributes=None, statically_defined=False):
28
+ self._user_defined_attributes = (
29
+ attributes.copy() if attributes is not None else {}
30
+ )
31
+ super().__init__(attributes, statically_defined)
32
+
27
33
  def step_init(self, flow, graph, step, decos, environment, flow_datastore, logger):
28
34
  # The init_environment hook for Environment creates the relevant virtual
29
35
  # environments. The step_init hook sets up the relevant state for that hook to
@@ -70,9 +76,9 @@ class PyPIStepDecorator(StepDecorator):
70
76
  # --environment=pypi to --environment=conda
71
77
  _supported_virtual_envs.extend(["pypi"])
72
78
 
73
- # TODO: Hardcoded for now to support Docker environment.
79
+ # TODO: Hardcoded for now to support the fast bakery environment.
74
80
  # We should introduce a more robust mechanism for appending supported environments, for example from within extensions.
75
- _supported_virtual_envs.extend(["docker"])
81
+ _supported_virtual_envs.extend(["fast-bakery"])
76
82
 
77
83
  # The --environment= requirement ensures that valid virtual environments are
78
84
  # created for every step to execute it, greatly simplifying the @pypi
@@ -88,6 +94,9 @@ class PyPIStepDecorator(StepDecorator):
88
94
  )
89
95
  )
90
96
 
97
+ def is_attribute_user_defined(self, name):
98
+ return name in self._user_defined_attributes
99
+
91
100
 
92
101
  class PyPIFlowDecorator(FlowDecorator):
93
102
  """
@@ -123,9 +132,9 @@ class PyPIFlowDecorator(FlowDecorator):
123
132
  # --environment=pypi to --environment=conda
124
133
  _supported_virtual_envs.extend(["pypi"])
125
134
 
126
- # TODO: Hardcoded for now to support Docker environment.
135
+ # TODO: Hardcoded for now to support the fast bakery environment.
127
136
  # We should introduce a more robust mechanism for appending supported environments, for example from within extensions.
128
- _supported_virtual_envs.extend(["docker"])
137
+ _supported_virtual_envs.extend(["fast-bakery"])
129
138
 
130
139
  # The --environment= requirement ensures that valid virtual environments are
131
140
  # created for every step to execute it, greatly simplifying the @conda
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import sys
3
3
  import json
4
+ import time
4
5
  import importlib
5
6
  import functools
6
7
  import tempfile
@@ -11,7 +12,9 @@ from metaflow.runner.subprocess_manager import CommandManager, SubprocessManager
11
12
  from metaflow.runner.utils import read_from_file_when_ready
12
13
 
13
14
 
14
- def handle_timeout(tfp_runner_attribute, command_obj: CommandManager):
15
+ def handle_timeout(
16
+ tfp_runner_attribute, command_obj: CommandManager, file_read_timeout: int
17
+ ):
15
18
  """
16
19
  Handle the timeout for a running subprocess command that reads a file
17
20
  and raises an error with appropriate logs if a TimeoutError occurs.
@@ -35,7 +38,9 @@ def handle_timeout(tfp_runner_attribute, command_obj: CommandManager):
35
38
  stdout and stderr logs.
36
39
  """
37
40
  try:
38
- content = read_from_file_when_ready(tfp_runner_attribute.name, timeout=10)
41
+ content = read_from_file_when_ready(
42
+ tfp_runner_attribute.name, timeout=file_read_timeout
43
+ )
39
44
  return content
40
45
  except TimeoutError as e:
41
46
  stdout_log = open(command_obj.log_files["stdout"]).read()
@@ -102,6 +107,8 @@ class Deployer(object):
102
107
  cwd : Optional[str], default None
103
108
  The directory to run the subprocess in; if not specified, the current
104
109
  directory is used.
110
+ file_read_timeout : int, default 3600
111
+ The timeout until which we try to read the deployer attribute file.
105
112
  **kwargs : Any
106
113
  Additional arguments that you would pass to `python myflow.py` before
107
114
  the deployment command.
@@ -114,6 +121,7 @@ class Deployer(object):
114
121
  profile: Optional[str] = None,
115
122
  env: Optional[Dict] = None,
116
123
  cwd: Optional[str] = None,
124
+ file_read_timeout: int = 3600,
117
125
  **kwargs
118
126
  ):
119
127
  self.flow_file = flow_file
@@ -121,6 +129,7 @@ class Deployer(object):
121
129
  self.profile = profile
122
130
  self.env = env
123
131
  self.cwd = cwd
132
+ self.file_read_timeout = file_read_timeout
124
133
  self.top_level_kwargs = kwargs
125
134
 
126
135
  from metaflow.plugins import DEPLOYER_IMPL_PROVIDERS
@@ -155,6 +164,7 @@ class Deployer(object):
155
164
  profile=self.profile,
156
165
  env=self.env,
157
166
  cwd=self.cwd,
167
+ file_read_timeout=self.file_read_timeout,
158
168
  **self.top_level_kwargs
159
169
  )
160
170
 
@@ -197,6 +207,33 @@ class TriggeredRun(object):
197
207
  else:
198
208
  setattr(self.__class__, k, property(fget=lambda _, v=v: v))
199
209
 
210
+ def wait_for_run(self, timeout=None):
211
+ """
212
+ Wait for the `run` property to become available.
213
+
214
+ Parameters
215
+ ----------
216
+ timeout : int, optional
217
+ Maximum time to wait for the `run` to become available, in seconds. If None, wait indefinitely.
218
+
219
+ Raises
220
+ ------
221
+ TimeoutError
222
+ If the `run` is not available within the specified timeout.
223
+ """
224
+ start_time = time.time()
225
+ check_interval = 5
226
+ while True:
227
+ if self.run is not None:
228
+ return self.run
229
+
230
+ if timeout is not None and (time.time() - start_time) > timeout:
231
+ raise TimeoutError(
232
+ "Timed out waiting for the run object to become available."
233
+ )
234
+
235
+ time.sleep(check_interval)
236
+
200
237
  @property
201
238
  def run(self):
202
239
  """
@@ -268,6 +305,8 @@ class DeployerImpl(object):
268
305
  cwd : Optional[str], default None
269
306
  The directory to run the subprocess in; if not specified, the current
270
307
  directory is used.
308
+ file_read_timeout : int, default 3600
309
+ The timeout until which we try to read the deployer attribute file.
271
310
  **kwargs : Any
272
311
  Additional arguments that you would pass to `python myflow.py` before
273
312
  the deployment command.
@@ -282,6 +321,7 @@ class DeployerImpl(object):
282
321
  profile: Optional[str] = None,
283
322
  env: Optional[Dict] = None,
284
323
  cwd: Optional[str] = None,
324
+ file_read_timeout: int = 3600,
285
325
  **kwargs
286
326
  ):
287
327
  if self.TYPE is None:
@@ -299,6 +339,7 @@ class DeployerImpl(object):
299
339
  self.profile = profile
300
340
  self.env = env
301
341
  self.cwd = cwd
342
+ self.file_read_timeout = file_read_timeout
302
343
 
303
344
  self.env_vars = os.environ.copy()
304
345
  self.env_vars.update(self.env or {})
@@ -349,7 +390,9 @@ class DeployerImpl(object):
349
390
  )
350
391
 
351
392
  command_obj = self.spm.get(pid)
352
- content = handle_timeout(tfp_runner_attribute, command_obj)
393
+ content = handle_timeout(
394
+ tfp_runner_attribute, command_obj, self.file_read_timeout
395
+ )
353
396
  content = json.loads(content)
354
397
  self.name = content.get("name")
355
398
  self.flow_name = content.get("flow_name")
@@ -211,6 +211,8 @@ class Runner(object):
211
211
  cwd : Optional[str], default None
212
212
  The directory to run the subprocess in; if not specified, the current
213
213
  directory is used.
214
+ file_read_timeout : int, default 3600
215
+ The timeout until which we try to read the runner attribute file.
214
216
  **kwargs : Any
215
217
  Additional arguments that you would pass to `python myflow.py` before
216
218
  the `run` command.
@@ -223,6 +225,7 @@ class Runner(object):
223
225
  profile: Optional[str] = None,
224
226
  env: Optional[Dict] = None,
225
227
  cwd: Optional[str] = None,
228
+ file_read_timeout: int = 3600,
226
229
  **kwargs
227
230
  ):
228
231
  # these imports are required here and not at the top
@@ -248,6 +251,7 @@ class Runner(object):
248
251
  self.env_vars["METAFLOW_PROFILE"] = profile
249
252
 
250
253
  self.cwd = cwd
254
+ self.file_read_timeout = file_read_timeout
251
255
  self.spm = SubprocessManager()
252
256
  self.top_level_kwargs = kwargs
253
257
  self.api = MetaflowAPI.from_cli(self.flow_file, start)
@@ -270,7 +274,9 @@ class Runner(object):
270
274
  clear_and_set_os_environ(self.old_env)
271
275
 
272
276
  # Set the correct metadata from the runner_attribute file corresponding to this run.
273
- content = read_from_file_when_ready(tfp_runner_attribute.name, timeout=10)
277
+ content = read_from_file_when_ready(
278
+ tfp_runner_attribute.name, timeout=self.file_read_timeout
279
+ )
274
280
  metadata_for_flow, pathspec = content.rsplit(":", maxsplit=1)
275
281
  metadata(metadata_for_flow)
276
282
  run_object = Run(pathspec, _namespace_check=False)
@@ -61,6 +61,7 @@ class NBDeployer(object):
61
61
  profile: Optional[str] = None,
62
62
  env: Optional[Dict] = None,
63
63
  base_dir: str = DEFAULT_DIR,
64
+ file_read_timeout: int = 3600,
64
65
  **kwargs,
65
66
  ):
66
67
  try:
@@ -78,6 +79,7 @@ class NBDeployer(object):
78
79
  self.profile = profile
79
80
  self.env = env
80
81
  self.cwd = base_dir
82
+ self.file_read_timeout = file_read_timeout
81
83
  self.top_level_kwargs = kwargs
82
84
 
83
85
  self.env_vars = os.environ.copy()
@@ -112,6 +114,7 @@ class NBDeployer(object):
112
114
  profile=self.profile,
113
115
  env=self.env_vars,
114
116
  cwd=self.cwd,
117
+ file_read_timeout=self.file_read_timeout,
115
118
  **kwargs,
116
119
  )
117
120
 
metaflow/runner/nbrun.py CHANGED
@@ -45,6 +45,8 @@ class NBRunner(object):
45
45
  base_dir : Optional[str], default None
46
46
  The directory to run the subprocess in; if not specified, a temporary
47
47
  directory is used.
48
+ file_read_timeout : int, default 3600
49
+ The timeout until which we try to read the runner attribute file.
48
50
  **kwargs : Any
49
51
  Additional arguments that you would pass to `python myflow.py` before
50
52
  the `run` command.
@@ -58,6 +60,7 @@ class NBRunner(object):
58
60
  profile: Optional[str] = None,
59
61
  env: Optional[Dict] = None,
60
62
  base_dir: str = DEFAULT_DIR,
63
+ file_read_timeout: int = 3600,
61
64
  **kwargs,
62
65
  ):
63
66
  try:
@@ -82,6 +85,7 @@ class NBRunner(object):
82
85
  self.env_vars["METAFLOW_PROFILE"] = profile
83
86
 
84
87
  self.base_dir = base_dir
88
+ self.file_read_timeout = file_read_timeout
85
89
 
86
90
  if not self.cell:
87
91
  raise ValueError("Couldn't find a cell.")
@@ -104,6 +108,7 @@ class NBRunner(object):
104
108
  profile=profile,
105
109
  env=self.env_vars,
106
110
  cwd=self.base_dir,
111
+ file_read_timeout=self.file_read_timeout,
107
112
  **kwargs,
108
113
  )
109
114
 
metaflow/runtime.py CHANGED
@@ -76,7 +76,7 @@ class NativeRuntime(object):
76
76
  clone_run_id=None,
77
77
  clone_only=False,
78
78
  reentrant=False,
79
- clone_steps=None,
79
+ steps_to_rerun=None,
80
80
  max_workers=MAX_WORKERS,
81
81
  max_num_splits=MAX_NUM_SPLITS,
82
82
  max_log_size=MAX_LOG_SIZE,
@@ -110,12 +110,21 @@ class NativeRuntime(object):
110
110
 
111
111
  self._clone_run_id = clone_run_id
112
112
  self._clone_only = clone_only
113
- self._clone_steps = {} if clone_steps is None else clone_steps
114
113
  self._cloned_tasks = []
115
114
  self._cloned_task_index = set()
116
115
  self._reentrant = reentrant
117
116
  self._run_url = None
118
117
 
118
+ # If steps_to_rerun is specified, we will not clone them in resume mode.
119
+ self._steps_to_rerun = steps_to_rerun or {}
120
+ # sorted_nodes are in topological order already, so we only need to
121
+ # iterate through the nodes once to get a stable set of rerun steps.
122
+ for step_name in self._graph.sorted_nodes:
123
+ if step_name in self._steps_to_rerun:
124
+ out_funcs = self._graph[step_name].out_funcs or []
125
+ for next_step in out_funcs:
126
+ self._steps_to_rerun.add(next_step)
127
+
119
128
  self._origin_ds_set = None
120
129
  if clone_run_id:
121
130
  # resume logic
@@ -166,7 +175,7 @@ class NativeRuntime(object):
166
175
  else:
167
176
  may_clone = all(self._is_cloned[path] for path in input_paths)
168
177
 
169
- if step in self._clone_steps:
178
+ if step in self._steps_to_rerun:
170
179
  may_clone = False
171
180
 
172
181
  if step == "_parameters":
@@ -301,16 +310,14 @@ class NativeRuntime(object):
301
310
  )
302
311
  except Exception as e:
303
312
  self._logger(
304
- "Cloning task from {}/{}/{} failed with error: {}".format(
305
- self._clone_run_id, step_name, task_id, str(e)
313
+ "Cloning {}/{}/{}/{} failed with error: {}".format(
314
+ self._flow.name, self._clone_run_id, step_name, task_id, str(e)
306
315
  )
307
316
  )
308
317
 
309
318
  def clone_original_run(self, generate_task_obj=False, verbose=True):
310
319
  self._logger(
311
- "Start cloning original run: {}/{}".format(
312
- self._flow.name, self._clone_run_id
313
- ),
320
+ "Cloning {}/{}".format(self._flow.name, self._clone_run_id),
314
321
  system_msg=True,
315
322
  )
316
323
 
@@ -336,7 +343,11 @@ class NativeRuntime(object):
336
343
  _, step_name, task_id = task_ds.pathspec.split("/")
337
344
  pathspec_index = task_ds.pathspec_index
338
345
 
339
- if task_ds["_task_ok"] and step_name != "_parameters":
346
+ if (
347
+ task_ds["_task_ok"]
348
+ and step_name != "_parameters"
349
+ and (step_name not in self._steps_to_rerun)
350
+ ):
340
351
  # "_unbounded_foreach" is a special flag to indicate that the transition is an unbounded foreach.
341
352
  # Both parent and splitted children tasks will have this flag set. The splitted control/mapper tasks
342
353
  # have no "foreach_param" because UBF is always followed by a join step.
@@ -390,7 +401,9 @@ class NativeRuntime(object):
390
401
  ) in inputs
391
402
  ]
392
403
  _, _ = futures.wait(all_tasks)
393
- self._logger("Cloning original run is done", system_msg=True)
404
+ self._logger(
405
+ "{}/{} cloned!".format(self._flow.name, self._clone_run_id), system_msg=True
406
+ )
394
407
  self._params_task.mark_resume_done()
395
408
 
396
409
  def execute(self):
@@ -1149,13 +1162,13 @@ class Task(object):
1149
1162
  self._should_skip_cloning = task_completed
1150
1163
  if self._should_skip_cloning:
1151
1164
  self.log(
1152
- "Skip cloning of previously run task %s" % self.clone_origin,
1165
+ "Skipping cloning of previously run task %s"
1166
+ % self.clone_origin,
1153
1167
  system_msg=True,
1154
1168
  )
1155
1169
  else:
1156
1170
  self.log(
1157
- "Cloning results of a previously run task %s"
1158
- % self.clone_origin,
1171
+ "Cloning previously run task %s" % self.clone_origin,
1159
1172
  system_msg=True,
1160
1173
  )
1161
1174
  else:
metaflow/util.py CHANGED
@@ -426,6 +426,25 @@ def tar_safe_extract(tar, path=".", members=None, *, numeric_owner=False):
426
426
  tar.extractall(path, members, numeric_owner=numeric_owner)
427
427
 
428
428
 
429
+ def to_pod(value):
430
+ """
431
+ Convert a python object to plain-old-data (POD) format.
432
+
433
+ Parameters
434
+ ----------
435
+ value : Any
436
+ Value to convert to POD format. The value can be a string, number, list,
437
+ dictionary, or a nested structure of these types.
438
+ """
439
+ if isinstance(value, (str, int, float)):
440
+ return value
441
+ if isinstance(value, dict):
442
+ return {to_pod(k): to_pod(v) for k, v in value.items()}
443
+ if isinstance(value, (list, set, tuple)):
444
+ return [to_pod(v) for v in value]
445
+ return str(value)
446
+
447
+
429
448
  if sys.version_info[:2] > (3, 5):
430
449
  from metaflow._vendor.packaging.version import parse as version_parse
431
450
  else:
metaflow/version.py CHANGED
@@ -1 +1 @@
1
- metaflow_version = "2.12.12"
1
+ metaflow_version = "2.12.14"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: metaflow
3
- Version: 2.12.12
3
+ Version: 2.12.14
4
4
  Summary: Metaflow: More Data Science, 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.12.12; extra == "stubs"
29
+ Requires-Dist: metaflow-stubs==2.12.14; extra == "stubs"
30
30
 
31
31
  ![Metaflow_Logo_Horizontal_FullColor_Ribbon_Dark_RGB](https://user-images.githubusercontent.com/763451/89453116-96a57e00-d713-11ea-9fa6-82b29d4d6eff.png)
32
32
 
@@ -1,7 +1,7 @@
1
1
  metaflow/R.py,sha256=CqVfIatvmjciuICNnoyyNGrwE7Va9iXfLdFbQa52hwA,3958
2
2
  metaflow/__init__.py,sha256=1TJeSmvQVELJpVGh9s9GoB6aBIK0_zEl9mpW4eSz64Q,5971
3
3
  metaflow/cards.py,sha256=tP1_RrtmqdFh741pqE4t98S7SA0MtGRlGvRICRZF1Mg,426
4
- metaflow/cli.py,sha256=2aP4GO3Kfairg_fopv5PvgSuogeo0Okpct5lhWSqnNE,34262
4
+ metaflow/cli.py,sha256=fa6dx6F2PKtq0oeMxRM2efA9LyE108jGZQvYCtiUS94,34274
5
5
  metaflow/cli_args.py,sha256=lcgBGNTvfaiPxiUnejAe60Upt9swG6lRy1_3OqbU6MY,2616
6
6
  metaflow/clone_util.py,sha256=XfUX0vssu_hPlyZfhFl1AOnKkLqvt33Qp8xNrmdocGg,2057
7
7
  metaflow/cmd_with_io.py,sha256=kl53HkAIyv0ecpItv08wZYczv7u3msD1VCcciqigqf0,588
@@ -10,15 +10,15 @@ metaflow/decorators.py,sha256=hbJwRlZuLbl6t7fOsTiH7Sux4Lx6zgEEpldIXoM5TQc,21540
10
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=KC1LHJQzzYkWib0DeQ4l_A2r8VaudywsSqIQuq1RDZU,4954
13
- metaflow/flowspec.py,sha256=C7X7jQ7HJsZNLWZA4PuKj61YPAjo0-dyg8ptVTaamrw,27145
14
- metaflow/graph.py,sha256=ZPxyG8uwVMk5YYgX4pQEQaPZtZM5Wy-G4NtJK73IEuA,11818
13
+ metaflow/flowspec.py,sha256=0-NnCKiX4oJ21Spb5PJkl2IvHoJxBcpi8CQrZEGB2Ag,27178
14
+ metaflow/graph.py,sha256=HFJ7V_bPSht_NHIm8BejrSqOX2fyBQpVOczRCliRw08,11975
15
15
  metaflow/includefile.py,sha256=yHczcZ_U0SrasxSNhZb3DIBzx8UZnrJCl3FzvpEQLOA,19753
16
16
  metaflow/integrations.py,sha256=LlsaoePRg03DjENnmLxZDYto3NwWc9z_PtU6nJxLldg,1480
17
17
  metaflow/lint.py,sha256=5rj1MlpluxyPTSINjtMoJ7viotyNzfjtBJSAihlAwMU,10870
18
- metaflow/metaflow_config.py,sha256=hz66w07GwDpxuewqrxmoEizgoyOMDRaxwXq7bUGTY2g,22674
19
- metaflow/metaflow_config_funcs.py,sha256=pCaiQ2ez9wXixJI3ehmf3QiW9lUqFrZnBZx1my_0wIg,4874
18
+ metaflow/metaflow_config.py,sha256=4PUd2-JWJs35SaobDgMg4RdpZaKjhEqPUFh2f0pjqnU,22853
19
+ metaflow/metaflow_config_funcs.py,sha256=5GlvoafV6SxykwfL8D12WXSfwjBN_NsyuKE_Q3gjGVE,6738
20
20
  metaflow/metaflow_current.py,sha256=5Kri7fzj-rtIJVr5xh5kPKwZ0T73_4egZybzlDR-fgc,7136
21
- metaflow/metaflow_environment.py,sha256=HJfhI3GrU-YbY7Etu9M-1q7EbwtEGFzCBhrUs45OLXY,7403
21
+ metaflow/metaflow_environment.py,sha256=xTVowXgia8puUcA0rcNn7iJpFao7e_TRp30RAnB_TW4,7910
22
22
  metaflow/metaflow_profile.py,sha256=jKPEW-hmAQO-htSxb9hXaeloLacAh41A35rMZH6G8pA,418
23
23
  metaflow/metaflow_version.py,sha256=mPQ6g_3XjNdi0NrxDzwlW8ZH0nMyYpwqmJ04P7TIdP0,4774
24
24
  metaflow/monitor.py,sha256=T0NMaBPvXynlJAO_avKtk8OIIRMyEuMAyF8bIp79aZU,5323
@@ -28,14 +28,14 @@ metaflow/parameters.py,sha256=l8qnhBG9C4wf_FkXWjq5sapUA6npLdR7pyB0PPQ-KF0,15712
28
28
  metaflow/procpoll.py,sha256=U2tE4iK_Mwj2WDyVTx_Uglh6xZ-jixQOo4wrM9OOhxg,2859
29
29
  metaflow/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  metaflow/pylint_wrapper.py,sha256=zzBY9YaSUZOGH-ypDKAv2B_7XcoyMZj-zCoCrmYqNRc,2865
31
- metaflow/runtime.py,sha256=_sOfR9jM_59DYW222nCX-7ZhkWKrAklSmMzqyL_dYjg,68275
31
+ metaflow/runtime.py,sha256=fbBObJJciagHWPzR3T7x9e_jez_RBnLZIHsXMvYnW_M,68875
32
32
  metaflow/tagging_util.py,sha256=ctyf0Q1gBi0RyZX6J0e9DQGNkNHblV_CITfy66axXB4,2346
33
33
  metaflow/task.py,sha256=uJHl8K4n3jNllWHSsG1vAZtDza0U2QbQcdg9GS_YPBE,28660
34
34
  metaflow/tuple_util.py,sha256=_G5YIEhuugwJ_f6rrZoelMFak3DqAR2tt_5CapS1XTY,830
35
35
  metaflow/unbounded_foreach.py,sha256=p184WMbrMJ3xKYHwewj27ZhRUsSj_kw1jlye5gA9xJk,387
36
- metaflow/util.py,sha256=e79A-UwdiJQ8nEDEwT5hOu-LU3p06iX2gD3RqnSQFlc,13061
36
+ metaflow/util.py,sha256=olAvJK3y1it_k99MhLulTaAJo7OFVt5rnrD-ulIFLCU,13616
37
37
  metaflow/vendor.py,sha256=FchtA9tH22JM-eEtJ2c9FpUdMn8sSb1VHuQS56EcdZk,5139
38
- metaflow/version.py,sha256=r2_eI44WDcUOnYX95VUMiUYzElNB324PEAelnI3impw,29
38
+ metaflow/version.py,sha256=JzanIZpE_JtUO3SVPb7SA7dd6HhMMiUj4SEW50Ilz7I,29
39
39
  metaflow/_vendor/__init__.py,sha256=y_CiwUD3l4eAKvTVDZeqgVujMy31cAM1qjAB-HfI-9s,353
40
40
  metaflow/_vendor/typing_extensions.py,sha256=0nUs5p1A_UrZigrAVBoOEM6TxU37zzPDUtiij1ZwpNc,110417
41
41
  metaflow/_vendor/zipp.py,sha256=ajztOH-9I7KA_4wqDYygtHa6xUBVZgFpmZ8FE74HHHI,8425
@@ -110,7 +110,7 @@ metaflow/_vendor/v3_6/importlib_metadata/_meta.py,sha256=_F48Hu_jFxkfKWz5wcYS8vO
110
110
  metaflow/_vendor/v3_6/importlib_metadata/_text.py,sha256=HCsFksZpJLeTP3NEk_ngrAeXVRRtTrtyh9eOABoRP4A,2166
111
111
  metaflow/_vendor/v3_6/importlib_metadata/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
112
  metaflow/client/__init__.py,sha256=1GtQB4Y_CBkzaxg32L1syNQSlfj762wmLrfrDxGi1b8,226
113
- metaflow/client/core.py,sha256=BG2xjB1n1boI4LAJ_ctZfy-DG7xqMp9YLHA0hTFeaps,74083
113
+ metaflow/client/core.py,sha256=L4COrMyQgSAWkXIPoXFFQ3OqGnoHfePsBZdJQvShU1E,74162
114
114
  metaflow/client/filecache.py,sha256=Wy0yhhCqC1JZgebqi7z52GCwXYnkAqMZHTtxThvwBgM,15229
115
115
  metaflow/cmd/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
116
116
  metaflow/cmd/configure_cmd.py,sha256=o-DKnUf2FBo_HiMVyoyzQaGBSMtpbEPEdFTQZ0hkU-k,33396
@@ -174,22 +174,22 @@ metaflow/plugins/airflow/sensors/s3_sensor.py,sha256=iDReG-7FKnumrtQg-HY6cCUAAqN
174
174
  metaflow/plugins/argo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
175
175
  metaflow/plugins/argo/argo_client.py,sha256=MKKhMCbWOPzf6z5zQQiyDRHHkAXcO7ipboDZDqAAvOk,15849
176
176
  metaflow/plugins/argo/argo_events.py,sha256=_C1KWztVqgi3zuH57pInaE9OzABc2NnncC-zdwOMZ-w,5909
177
- metaflow/plugins/argo/argo_workflows.py,sha256=hAzp11YSd98GvnUHSwZkto7bMCY6hvwCaO1Lplru3EA,169793
178
- metaflow/plugins/argo/argo_workflows_cli.py,sha256=GmCjk0EEO0eACwwgoXMLA2C5PgopFD34BzLTor6r46k,35706
177
+ metaflow/plugins/argo/argo_workflows.py,sha256=6xUkz1LKdCLbl-O-D83Y2G5mCKYcIciKts3x1PNAzCk,170173
178
+ metaflow/plugins/argo/argo_workflows_cli.py,sha256=ydCf7P0lR8KaZIwYTtV_hRw1J49okE0wtEW8Kcj2c4c,35704
179
179
  metaflow/plugins/argo/argo_workflows_decorator.py,sha256=yprszMdbE3rBTcEA9VR0IEnPjTprUauZBc4SBb-Q7sA,7878
180
- metaflow/plugins/argo/argo_workflows_deployer.py,sha256=yMIXAVoAuBLHCqQyFriV_Wc_Lp5D041Ay83R5pYNoXE,8066
180
+ metaflow/plugins/argo/argo_workflows_deployer.py,sha256=wSSZtThn_VPvE_Wu6NB1L0Q86LmBJh9g009v_lpvBPM,8125
181
181
  metaflow/plugins/argo/capture_error.py,sha256=Ys9dscGrTpW-ZCirLBU0gD9qBM0BjxyxGlUMKcwewQc,1852
182
182
  metaflow/plugins/argo/daemon.py,sha256=dJOS_UUISXBYffi3oGVKPwq4Pa4P_nGBGL15piPaPto,1776
183
183
  metaflow/plugins/argo/generate_input_paths.py,sha256=loYsI6RFX9LlFsHb7Fe-mzlTTtRdySoOu7sYDy-uXK0,881
184
184
  metaflow/plugins/argo/jobset_input_paths.py,sha256=_JhZWngA6p9Q_O2fx3pdzKI0WE-HPRHz_zFvY2pHPTQ,525
185
185
  metaflow/plugins/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
186
186
  metaflow/plugins/aws/aws_client.py,sha256=mO8UD6pxFaOnxDb3hTP3HB7Gqb_ZxoR-76LT683WHvI,4036
187
- metaflow/plugins/aws/aws_utils.py,sha256=mSMBTUQ-CELhyPb6w3_Yq6_Hvd_6vbhAojYkrt2RNt8,6941
187
+ metaflow/plugins/aws/aws_utils.py,sha256=dk92IRZ2QTF3PicBOtZMMOmS_FIncFqZPeL9EbCtXak,7310
188
188
  metaflow/plugins/aws/batch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
189
189
  metaflow/plugins/aws/batch/batch.py,sha256=e9ssahWM18GnipPK2sqYB-ztx9w7Eoo7YtWyEtufYxs,17787
190
190
  metaflow/plugins/aws/batch/batch_cli.py,sha256=6PTbyajRgdy0XmjyJLBTdKdiOB84dcovQQ8sFXlJqko,11749
191
191
  metaflow/plugins/aws/batch/batch_client.py,sha256=s9ZHhxQPPoBQijLUgn6_16QOaD4-22U_44uJbp-yLkI,28565
192
- metaflow/plugins/aws/batch/batch_decorator.py,sha256=M6DX5EmEKQAn_x0aOwnCN1WpDP1NLzrlGUsqexcZNTc,17492
192
+ metaflow/plugins/aws/batch/batch_decorator.py,sha256=kwgxEPCEoI6eZIpU5PuL442Ohg4_BfvwowoYgAnCzKE,17520
193
193
  metaflow/plugins/aws/secrets_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
194
194
  metaflow/plugins/aws/secrets_manager/aws_secrets_manager_secrets_provider.py,sha256=JtFUVu00Cg0FzAizgrPLXmrMqsT7YeQMkQlgeivUxcE,7986
195
195
  metaflow/plugins/aws/step_functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -202,7 +202,7 @@ metaflow/plugins/aws/step_functions/step_functions.py,sha256=8tBs4pvdIVgNoZRm0Dz
202
202
  metaflow/plugins/aws/step_functions/step_functions_cli.py,sha256=4CYw5xQwPaEV_iudF4_m4SSRS5LEq5UgrzVlCB6xT_U,25763
203
203
  metaflow/plugins/aws/step_functions/step_functions_client.py,sha256=DKpNwAIWElvWjFANs5Ku3rgzjxFoqAD6k-EF8Xhkg3Q,4754
204
204
  metaflow/plugins/aws/step_functions/step_functions_decorator.py,sha256=9hw_MX36RyFp6IowuAYaJzJg9UC5KCe1FNt1PcG7_J0,3791
205
- metaflow/plugins/aws/step_functions/step_functions_deployer.py,sha256=1AUBhgRuoivZQEXAMeCYLu2EU_mJNxAbtGnLjSb2DrU,7159
205
+ metaflow/plugins/aws/step_functions/step_functions_deployer.py,sha256=WrfQjvXnnInXwSePwoLUMb2EjqFG4RK1krO_8bW0qGI,7218
206
206
  metaflow/plugins/azure/__init__.py,sha256=GuuhTVC-zSdyAf79a1wiERMq0Zts7fwVT7t9fAf234A,100
207
207
  metaflow/plugins/azure/azure_credential.py,sha256=JmdGEbVzgxy8ucqnQDdTTI_atyMX9WSZUw3qYOo7RhE,2174
208
208
  metaflow/plugins/azure/azure_exceptions.py,sha256=NnbwpUC23bc61HZjJmeXztY0tBNn_Y_VpIpDDuYWIZ0,433
@@ -279,10 +279,10 @@ metaflow/plugins/gcp/gs_tail.py,sha256=Jl_wvnzU7dub07A-DOAuP5FeccNIrPM-CeL1xKFs1
279
279
  metaflow/plugins/gcp/gs_utils.py,sha256=ZmIGFse1qYyvAVrwga23PQUzF6dXEDLLsZ2F-YRmvow,2030
280
280
  metaflow/plugins/gcp/includefile_support.py,sha256=vIDeR-MiJuUh-2S2pV7Z7FBkhIWwtHXaRrj76MWGRiY,3869
281
281
  metaflow/plugins/kubernetes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
282
- metaflow/plugins/kubernetes/kubernetes.py,sha256=cYtDuJEqd0TOKy5vK1UybNLxROhHBJdOtYBYJrWhSMo,30035
282
+ metaflow/plugins/kubernetes/kubernetes.py,sha256=cr3TheUasxIBEwFZ3GEVbctaf8gW57BM5BDk80ikjPI,31063
283
283
  metaflow/plugins/kubernetes/kubernetes_cli.py,sha256=qBDdr1Lvtt-RO9pB-9_HTOPdzAmDvvJ0aiQ1OoCcrMU,10892
284
284
  metaflow/plugins/kubernetes/kubernetes_client.py,sha256=GKg-gT3qhXMRQV-sG1YyoOf3Z32NXr_wwEN2ytMVSEg,2471
285
- metaflow/plugins/kubernetes/kubernetes_decorator.py,sha256=DDc6N1Q0Cmcl44U-uZVBVr0tS4yL9N4GcAnf4eeX3Bk,24623
285
+ metaflow/plugins/kubernetes/kubernetes_decorator.py,sha256=CXStYHomuJJK_Yocpdo6OJadEQv5hDfSpO7GPL61ltw,25322
286
286
  metaflow/plugins/kubernetes/kubernetes_job.py,sha256=Cfkee8LbXC17jSXWoeNdomQRvF_8YSeXNg1gvxm6E_M,31806
287
287
  metaflow/plugins/kubernetes/kubernetes_jobsets.py,sha256=OBmLtX-ZUDQdCCfftUmRMernfmTNMwdTxPoCAp_NmwE,40957
288
288
  metaflow/plugins/metadata/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
@@ -290,11 +290,11 @@ metaflow/plugins/metadata/local.py,sha256=YhLJC5zjVJrvQFIyQ92ZBByiUmhCC762RUX7IT
290
290
  metaflow/plugins/metadata/service.py,sha256=ihq5F7KQZlxvYwzH_-jyP2aWN_I96i2vp92j_d697s8,20204
291
291
  metaflow/plugins/pypi/__init__.py,sha256=0YFZpXvX7HCkyBFglatual7XGifdA1RwC3U4kcizyak,1037
292
292
  metaflow/plugins/pypi/bootstrap.py,sha256=Tvc4_QKIx-A8j5Aq8ccWZrrxNM8csN40rK8HmxDx-Z8,5106
293
- metaflow/plugins/pypi/conda_decorator.py,sha256=phrUvVC5QrfNwPqIByrXsnpRDg1SNVsfpl1wbAVrykI,14679
293
+ metaflow/plugins/pypi/conda_decorator.py,sha256=TGs80qWk0zMtO0JfqB2cVMSt52YIxCd7hsS4KxSLMZ4,14707
294
294
  metaflow/plugins/pypi/conda_environment.py,sha256=tR6xvDx9zqlW0oF7j5dRebb0o_CHEdHVlaT4LNMJOAA,19307
295
295
  metaflow/plugins/pypi/micromamba.py,sha256=67FiIZZz0Kig9EcN7bZLObsE6Z1MFyo4Dp93fd3Grcc,12178
296
296
  metaflow/plugins/pypi/pip.py,sha256=7B06mPOs5MvY33xbzPVYZlBr1iKMYaN-n8uulL9zSVg,13649
297
- metaflow/plugins/pypi/pypi_decorator.py,sha256=Plmm4fhLECW-sj1QSFI84Gva7qqqwlJsqJ8laCRKIzw,6073
297
+ metaflow/plugins/pypi/pypi_decorator.py,sha256=h5cAnxkWjmj4Ad4q0AkABKwhHQHYfeexy12yMaaLgXQ,6443
298
298
  metaflow/plugins/pypi/pypi_environment.py,sha256=FYMg8kF3lXqcLfRYWD83a9zpVjcoo_TARqMGZ763rRk,230
299
299
  metaflow/plugins/pypi/utils.py,sha256=ds1Mnv_DaxGnLAYp7ozg_K6oyguGyNhvHfE-75Ia1YA,2836
300
300
  metaflow/plugins/secrets/__init__.py,sha256=mhJaN2eMS_ZZVewAMR2E-JdP5i0t3v9e6Dcwd-WpruE,310
@@ -302,10 +302,10 @@ metaflow/plugins/secrets/inline_secrets_provider.py,sha256=EChmoBGA1i7qM3jtYwPpL
302
302
  metaflow/plugins/secrets/secrets_decorator.py,sha256=s-sFzPWOjahhpr5fMj-ZEaHkDYAPTO0isYXGvaUwlG8,11273
303
303
  metaflow/runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
304
304
  metaflow/runner/click_api.py,sha256=vrrIb5DtVYhCW_hB7wPgj6Q0o-h1bBSWiYnKTOyC454,13452
305
- metaflow/runner/deployer.py,sha256=gpFCUKejmf5CdIq0YDiJt9399VhzwKNjYfbRoOJK9zg,12123
306
- metaflow/runner/metaflow_runner.py,sha256=7xjuCwqUcrniJ_QfwBN45gt1CcLJNCcCud6bZw_yGPk,15115
307
- metaflow/runner/nbdeploy.py,sha256=-TDjpn4KnGT-K97U8TAkXdkRo4MvvIzZDRsIjg2bBHg,3971
308
- metaflow/runner/nbrun.py,sha256=oouht4PlBO5Y1-9XeLrLGufyeCvZ38rKjB3Bxn1Ht18,7001
305
+ metaflow/runner/deployer.py,sha256=nArjnErc0rOaZW612VRKDOT5594jwzeu86w5zW1LX6U,13558
306
+ metaflow/runner/metaflow_runner.py,sha256=AO9nwr5qUbZWmsbFdjkUJrvFlaylz7WvxslvHsIqDYc,15371
307
+ metaflow/runner/nbdeploy.py,sha256=fP1s_5MeiDyT_igP82pB5EUqX9rOy2s06Hyc-OUbOvQ,4115
308
+ metaflow/runner/nbrun.py,sha256=lmvhzMCz7iC9LSPGRijifW1wMXxa4RW_jVmpdjQi22E,7261
309
309
  metaflow/runner/subprocess_manager.py,sha256=0knxWZYJx8srMv6wTPYKOC6tn4-airnyI7Vbqfb3iXY,19567
310
310
  metaflow/runner/utils.py,sha256=FibdEj8CDnx1a-Je5KUQTwHuNbtkFm1unXGarj0D8ok,1394
311
311
  metaflow/sidecar/__init__.py,sha256=1mmNpmQ5puZCpRmmYlCOeieZ4108Su9XQ4_EqF1FGOU,131
@@ -344,9 +344,9 @@ metaflow/tutorials/07-worldview/README.md,sha256=5vQTrFqulJ7rWN6r20dhot9lI2sVj9W
344
344
  metaflow/tutorials/07-worldview/worldview.ipynb,sha256=ztPZPI9BXxvW1QdS2Tfe7LBuVzvFvv0AToDnsDJhLdE,2237
345
345
  metaflow/tutorials/08-autopilot/README.md,sha256=GnePFp_q76jPs991lMUqfIIh5zSorIeWznyiUxzeUVE,1039
346
346
  metaflow/tutorials/08-autopilot/autopilot.ipynb,sha256=DQoJlILV7Mq9vfPBGW-QV_kNhWPjS5n6SJLqePjFYLY,3191
347
- metaflow-2.12.12.dist-info/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
348
- metaflow-2.12.12.dist-info/METADATA,sha256=Yqo1fVPmFeydfhJ57Fh7B-8h23eVZS2DU8XFMbWnOVo,5906
349
- metaflow-2.12.12.dist-info/WHEEL,sha256=M4n4zmFKzQZk4mLCcycNIzIXO7YPKE_b5Cw4PnhHEg8,109
350
- metaflow-2.12.12.dist-info/entry_points.txt,sha256=IKwTN1T3I5eJL3uo_vnkyxVffcgnRdFbKwlghZfn27k,57
351
- metaflow-2.12.12.dist-info/top_level.txt,sha256=v1pDHoWaSaKeuc5fKTRSfsXCKSdW1zvNVmvA-i0if3o,9
352
- metaflow-2.12.12.dist-info/RECORD,,
347
+ metaflow-2.12.14.dist-info/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
348
+ metaflow-2.12.14.dist-info/METADATA,sha256=LHUByGEAJnWelBfACmll3cpcjnPWjbRXr4gLPYv6FnA,5906
349
+ metaflow-2.12.14.dist-info/WHEEL,sha256=GUeE9LxUgRABPG7YM0jCNs9cBsAIx0YAkzCB88PMLgc,109
350
+ metaflow-2.12.14.dist-info/entry_points.txt,sha256=IKwTN1T3I5eJL3uo_vnkyxVffcgnRdFbKwlghZfn27k,57
351
+ metaflow-2.12.14.dist-info/top_level.txt,sha256=v1pDHoWaSaKeuc5fKTRSfsXCKSdW1zvNVmvA-i0if3o,9
352
+ metaflow-2.12.14.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (72.2.0)
2
+ Generator: setuptools (73.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any