metaflow 2.12.32__py2.py3-none-any.whl → 2.12.34__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/flowspec.py CHANGED
@@ -38,6 +38,7 @@ INTERNAL_ARTIFACTS_SET = set(
38
38
  "_unbounded_foreach",
39
39
  "_control_mapper_tasks",
40
40
  "_control_task_is_mapper_zero",
41
+ "_parallel_ubf_iter",
41
42
  ]
42
43
  )
43
44
 
@@ -6,7 +6,18 @@ from tempfile import NamedTemporaryFile
6
6
  import time
7
7
  import metaflow.tracing as tracing
8
8
 
9
- from typing import Any, Callable, Iterable, Iterator, List, Optional
9
+ from typing import (
10
+ Any,
11
+ Callable,
12
+ Iterable,
13
+ Iterator,
14
+ List,
15
+ Optional,
16
+ NoReturn,
17
+ Tuple,
18
+ TypeVar,
19
+ Union,
20
+ )
10
21
 
11
22
  try:
12
23
  # Python 2
@@ -30,7 +41,13 @@ class MulticoreException(Exception):
30
41
  pass
31
42
 
32
43
 
33
- def _spawn(func, arg, dir):
44
+ _A = TypeVar("_A")
45
+ _R = TypeVar("_R")
46
+
47
+
48
+ def _spawn(
49
+ func: Callable[[_A], _R], arg: _A, dir: Optional[str]
50
+ ) -> Union[Tuple[int, str], NoReturn]:
34
51
  with NamedTemporaryFile(prefix="parallel_map_", dir=dir, delete=False) as tmpfile:
35
52
  output_file = tmpfile.name
36
53
 
@@ -63,11 +80,11 @@ def _spawn(func, arg, dir):
63
80
 
64
81
 
65
82
  def parallel_imap_unordered(
66
- func: Callable[[Any], Any],
67
- iterable: Iterable[Any],
83
+ func: Callable[[_A], _R],
84
+ iterable: Iterable[_A],
68
85
  max_parallel: Optional[int] = None,
69
86
  dir: Optional[str] = None,
70
- ) -> Iterator[Any]:
87
+ ) -> Iterator[_R]:
71
88
  """
72
89
  Parallelizes execution of a function using multiprocessing. The result
73
90
  order is not guaranteed.
@@ -79,9 +96,9 @@ def parallel_imap_unordered(
79
96
  iterable : Iterable[Any]
80
97
  Iterable over arguments to pass to fun
81
98
  max_parallel int, optional, default None
82
- Maximum parallelism. If not specified, uses the number of CPUs
99
+ Maximum parallelism. If not specified, it uses the number of CPUs
83
100
  dir : str, optional, default None
84
- If specified, directory where temporary files are created
101
+ If specified, it's the directory where temporary files are created
85
102
 
86
103
  Yields
87
104
  ------
@@ -121,14 +138,14 @@ def parallel_imap_unordered(
121
138
 
122
139
 
123
140
  def parallel_map(
124
- func: Callable[[Any], Any],
125
- iterable: Iterable[Any],
141
+ func: Callable[[_A], _R],
142
+ iterable: Iterable[_A],
126
143
  max_parallel: Optional[int] = None,
127
144
  dir: Optional[str] = None,
128
- ) -> List[Any]:
145
+ ) -> List[_R]:
129
146
  """
130
147
  Parallelizes execution of a function using multiprocessing. The result
131
- order is that of the arguments in `iterable`
148
+ order is that of the arguments in `iterable`.
132
149
 
133
150
  Parameters
134
151
  ----------
@@ -137,9 +154,9 @@ def parallel_map(
137
154
  iterable : Iterable[Any]
138
155
  Iterable over arguments to pass to fun
139
156
  max_parallel int, optional, default None
140
- Maximum parallelism. If not specified, uses the number of CPUs
157
+ Maximum parallelism. If not specified, it uses the number of CPUs
141
158
  dir : str, optional, default None
142
- If specified, directory where temporary files are created
159
+ If specified, it's the directory where temporary files are created
143
160
 
144
161
  Returns
145
162
  -------
@@ -155,4 +172,4 @@ def parallel_map(
155
172
  res = parallel_imap_unordered(
156
173
  wrapper, enumerate(iterable), max_parallel=max_parallel, dir=dir
157
174
  )
158
- return [r for idx, r in sorted(res)]
175
+ return [r for _, r in sorted(res)]
@@ -10,7 +10,7 @@ from metaflow.metaflow_config import KUBERNETES_NAMESPACE
10
10
  from metaflow.plugins.argo.argo_workflows import ArgoWorkflows
11
11
  from metaflow.runner.deployer import Deployer, DeployedFlow, TriggeredRun
12
12
 
13
- from metaflow.runner.utils import get_lower_level_group, handle_timeout
13
+ from metaflow.runner.utils import get_lower_level_group, handle_timeout, temporary_fifo
14
14
 
15
15
 
16
16
  def generate_fake_flow_file_contents(
@@ -341,18 +341,14 @@ class ArgoWorkflowsDeployedFlow(DeployedFlow):
341
341
  Exception
342
342
  If there is an error during the trigger process.
343
343
  """
344
- with tempfile.TemporaryDirectory() as temp_dir:
345
- tfp_runner_attribute = tempfile.NamedTemporaryFile(
346
- dir=temp_dir, delete=False
347
- )
348
-
344
+ with temporary_fifo() as (attribute_file_path, attribute_file_fd):
349
345
  # every subclass needs to have `self.deployer_kwargs`
350
346
  command = get_lower_level_group(
351
347
  self.deployer.api,
352
348
  self.deployer.top_level_kwargs,
353
349
  self.deployer.TYPE,
354
350
  self.deployer.deployer_kwargs,
355
- ).trigger(deployer_attribute_file=tfp_runner_attribute.name, **kwargs)
351
+ ).trigger(deployer_attribute_file=attribute_file_path, **kwargs)
356
352
 
357
353
  pid = self.deployer.spm.run_command(
358
354
  [sys.executable, *command],
@@ -363,7 +359,7 @@ class ArgoWorkflowsDeployedFlow(DeployedFlow):
363
359
 
364
360
  command_obj = self.deployer.spm.get(pid)
365
361
  content = handle_timeout(
366
- tfp_runner_attribute, command_obj, self.deployer.file_read_timeout
362
+ attribute_file_fd, command_obj, self.deployer.file_read_timeout
367
363
  )
368
364
 
369
365
  if command_obj.process.returncode == 0:
@@ -6,7 +6,7 @@ from typing import ClassVar, Optional, List
6
6
  from metaflow.plugins.aws.step_functions.step_functions import StepFunctions
7
7
  from metaflow.runner.deployer import DeployedFlow, TriggeredRun
8
8
 
9
- from metaflow.runner.utils import get_lower_level_group, handle_timeout
9
+ from metaflow.runner.utils import get_lower_level_group, handle_timeout, temporary_fifo
10
10
 
11
11
 
12
12
  class StepFunctionsTriggeredRun(TriggeredRun):
@@ -196,18 +196,14 @@ class StepFunctionsDeployedFlow(DeployedFlow):
196
196
  Exception
197
197
  If there is an error during the trigger process.
198
198
  """
199
- with tempfile.TemporaryDirectory() as temp_dir:
200
- tfp_runner_attribute = tempfile.NamedTemporaryFile(
201
- dir=temp_dir, delete=False
202
- )
203
-
199
+ with temporary_fifo() as (attribute_file_path, attribute_file_fd):
204
200
  # every subclass needs to have `self.deployer_kwargs`
205
201
  command = get_lower_level_group(
206
202
  self.deployer.api,
207
203
  self.deployer.top_level_kwargs,
208
204
  self.deployer.TYPE,
209
205
  self.deployer.deployer_kwargs,
210
- ).trigger(deployer_attribute_file=tfp_runner_attribute.name, **kwargs)
206
+ ).trigger(deployer_attribute_file=attribute_file_path, **kwargs)
211
207
 
212
208
  pid = self.deployer.spm.run_command(
213
209
  [sys.executable, *command],
@@ -218,7 +214,7 @@ class StepFunctionsDeployedFlow(DeployedFlow):
218
214
 
219
215
  command_obj = self.deployer.spm.get(pid)
220
216
  content = handle_timeout(
221
- tfp_runner_attribute, command_obj, self.deployer.file_read_timeout
217
+ attribute_file_fd, command_obj, self.deployer.file_read_timeout
222
218
  )
223
219
 
224
220
  if command_obj.process.returncode == 0:
@@ -600,7 +600,9 @@ class S3(object):
600
600
  # returned are Unicode.
601
601
  key = getattr(key_value, "key", key_value)
602
602
  if self._s3root is None:
603
- parsed = urlparse(to_unicode(key))
603
+ # NOTE: S3 allows fragments as part of object names, e.g. /dataset #1/data.txt
604
+ # Without allow_fragments=False the parsed.path for an object name with fragments is incomplete.
605
+ parsed = urlparse(to_unicode(key), allow_fragments=False)
604
606
  if parsed.scheme == "s3" and parsed.path:
605
607
  return key
606
608
  else:
@@ -765,7 +767,9 @@ class S3(object):
765
767
  """
766
768
 
767
769
  url = self._url(key)
768
- src = urlparse(url)
770
+ # NOTE: S3 allows fragments as part of object names, e.g. /dataset #1/data.txt
771
+ # Without allow_fragments=False the parsed src.path for an object name with fragments is incomplete.
772
+ src = urlparse(url, allow_fragments=False)
769
773
 
770
774
  def _info(s3, tmp):
771
775
  resp = s3.head_object(Bucket=src.netloc, Key=src.path.lstrip('/"'))
@@ -891,7 +895,9 @@ class S3(object):
891
895
  DOWNLOAD_MAX_CHUNK = 2 * 1024 * 1024 * 1024 - 1
892
896
 
893
897
  url, r = self._url_and_range(key)
894
- src = urlparse(url)
898
+ # NOTE: S3 allows fragments as part of object names, e.g. /dataset #1/data.txt
899
+ # Without allow_fragments=False the parsed src.path for an object name with fragments is incomplete.
900
+ src = urlparse(url, allow_fragments=False)
895
901
 
896
902
  def _download(s3, tmp):
897
903
  if r:
@@ -1173,7 +1179,9 @@ class S3(object):
1173
1179
  blob.close = lambda: None
1174
1180
 
1175
1181
  url = self._url(key)
1176
- src = urlparse(url)
1182
+ # NOTE: S3 allows fragments as part of object names, e.g. /dataset #1/data.txt
1183
+ # Without allow_fragments=False the parsed src.path for an object name with fragments is incomplete.
1184
+ src = urlparse(url, allow_fragments=False)
1177
1185
  extra_args = None
1178
1186
  if content_type or metadata or self._encryption:
1179
1187
  extra_args = {}
@@ -170,7 +170,7 @@ class TriggerDecorator(FlowDecorator):
170
170
  # process every event in events
171
171
  for event in self.attributes["events"]:
172
172
  processed_event = self.process_event_name(event)
173
- self.triggers.append("processed event", processed_event)
173
+ self.triggers.append(processed_event)
174
174
  elif callable(self.attributes["events"]) and not isinstance(
175
175
  self.attributes["events"], DeployTimeField
176
176
  ):
@@ -2,12 +2,11 @@ import importlib
2
2
  import json
3
3
  import os
4
4
  import sys
5
- import tempfile
6
5
 
7
6
  from typing import Any, ClassVar, Dict, Optional, TYPE_CHECKING, Type
8
7
 
9
8
  from .subprocess_manager import SubprocessManager
10
- from .utils import get_lower_level_group, handle_timeout
9
+ from .utils import get_lower_level_group, handle_timeout, temporary_fifo
11
10
 
12
11
  if TYPE_CHECKING:
13
12
  import metaflow.runner.deployer
@@ -121,14 +120,11 @@ class DeployerImpl(object):
121
120
  def _create(
122
121
  self, create_class: Type["metaflow.runner.deployer.DeployedFlow"], **kwargs
123
122
  ) -> "metaflow.runner.deployer.DeployedFlow":
124
- with tempfile.TemporaryDirectory() as temp_dir:
125
- tfp_runner_attribute = tempfile.NamedTemporaryFile(
126
- dir=temp_dir, delete=False
127
- )
123
+ with temporary_fifo() as (attribute_file_path, attribute_file_fd):
128
124
  # every subclass needs to have `self.deployer_kwargs`
129
125
  command = get_lower_level_group(
130
126
  self.api, self.top_level_kwargs, self.TYPE, self.deployer_kwargs
131
- ).create(deployer_attribute_file=tfp_runner_attribute.name, **kwargs)
127
+ ).create(deployer_attribute_file=attribute_file_path, **kwargs)
132
128
 
133
129
  pid = self.spm.run_command(
134
130
  [sys.executable, *command],
@@ -139,7 +135,7 @@ class DeployerImpl(object):
139
135
 
140
136
  command_obj = self.spm.get(pid)
141
137
  content = handle_timeout(
142
- tfp_runner_attribute, command_obj, self.file_read_timeout
138
+ attribute_file_fd, command_obj, self.file_read_timeout
143
139
  )
144
140
  content = json.loads(content)
145
141
  self.name = content.get("name")
@@ -2,13 +2,16 @@ import importlib
2
2
  import os
3
3
  import sys
4
4
  import json
5
- import tempfile
6
5
 
7
6
  from typing import Dict, Iterator, Optional, Tuple
8
7
 
9
8
  from metaflow import Run
10
9
 
11
- from .utils import handle_timeout
10
+ from .utils import (
11
+ temporary_fifo,
12
+ handle_timeout,
13
+ async_handle_timeout,
14
+ )
12
15
  from .subprocess_manager import CommandManager, SubprocessManager
13
16
 
14
17
 
@@ -267,9 +270,22 @@ class Runner(object):
267
270
  async def __aenter__(self) -> "Runner":
268
271
  return self
269
272
 
270
- def __get_executing_run(self, tfp_runner_attribute, command_obj):
271
- content = handle_timeout(
272
- tfp_runner_attribute, command_obj, self.file_read_timeout
273
+ def __get_executing_run(self, attribute_file_fd, command_obj):
274
+ content = handle_timeout(attribute_file_fd, command_obj, self.file_read_timeout)
275
+ content = json.loads(content)
276
+ pathspec = "%s/%s" % (content.get("flow_name"), content.get("run_id"))
277
+
278
+ # Set the correct metadata from the runner_attribute file corresponding to this run.
279
+ metadata_for_flow = content.get("metadata")
280
+
281
+ run_object = Run(
282
+ pathspec, _namespace_check=False, _current_metadata=metadata_for_flow
283
+ )
284
+ return ExecutingRun(self, command_obj, run_object)
285
+
286
+ async def __async_get_executing_run(self, attribute_file_fd, command_obj):
287
+ content = await async_handle_timeout(
288
+ attribute_file_fd, command_obj, self.file_read_timeout
273
289
  )
274
290
  content = json.loads(content)
275
291
  pathspec = "%s/%s" % (content.get("flow_name"), content.get("run_id"))
@@ -298,12 +314,9 @@ class Runner(object):
298
314
  ExecutingRun
299
315
  ExecutingRun containing the results of the run.
300
316
  """
301
- with tempfile.TemporaryDirectory() as temp_dir:
302
- tfp_runner_attribute = tempfile.NamedTemporaryFile(
303
- dir=temp_dir, delete=False
304
- )
317
+ with temporary_fifo() as (attribute_file_path, attribute_file_fd):
305
318
  command = self.api(**self.top_level_kwargs).run(
306
- runner_attribute_file=tfp_runner_attribute.name, **kwargs
319
+ runner_attribute_file=attribute_file_path, **kwargs
307
320
  )
308
321
 
309
322
  pid = self.spm.run_command(
@@ -314,7 +327,7 @@ class Runner(object):
314
327
  )
315
328
  command_obj = self.spm.get(pid)
316
329
 
317
- return self.__get_executing_run(tfp_runner_attribute, command_obj)
330
+ return self.__get_executing_run(attribute_file_fd, command_obj)
318
331
 
319
332
  def resume(self, **kwargs):
320
333
  """
@@ -332,12 +345,9 @@ class Runner(object):
332
345
  ExecutingRun
333
346
  ExecutingRun containing the results of the resumed run.
334
347
  """
335
- with tempfile.TemporaryDirectory() as temp_dir:
336
- tfp_runner_attribute = tempfile.NamedTemporaryFile(
337
- dir=temp_dir, delete=False
338
- )
348
+ with temporary_fifo() as (attribute_file_path, attribute_file_fd):
339
349
  command = self.api(**self.top_level_kwargs).resume(
340
- runner_attribute_file=tfp_runner_attribute.name, **kwargs
350
+ runner_attribute_file=attribute_file_path, **kwargs
341
351
  )
342
352
 
343
353
  pid = self.spm.run_command(
@@ -348,7 +358,7 @@ class Runner(object):
348
358
  )
349
359
  command_obj = self.spm.get(pid)
350
360
 
351
- return self.__get_executing_run(tfp_runner_attribute, command_obj)
361
+ return self.__get_executing_run(attribute_file_fd, command_obj)
352
362
 
353
363
  async def async_run(self, **kwargs) -> ExecutingRun:
354
364
  """
@@ -368,12 +378,9 @@ class Runner(object):
368
378
  ExecutingRun
369
379
  ExecutingRun representing the run that was started.
370
380
  """
371
- with tempfile.TemporaryDirectory() as temp_dir:
372
- tfp_runner_attribute = tempfile.NamedTemporaryFile(
373
- dir=temp_dir, delete=False
374
- )
381
+ with temporary_fifo() as (attribute_file_path, attribute_file_fd):
375
382
  command = self.api(**self.top_level_kwargs).run(
376
- runner_attribute_file=tfp_runner_attribute.name, **kwargs
383
+ runner_attribute_file=attribute_file_path, **kwargs
377
384
  )
378
385
 
379
386
  pid = await self.spm.async_run_command(
@@ -383,7 +390,7 @@ class Runner(object):
383
390
  )
384
391
  command_obj = self.spm.get(pid)
385
392
 
386
- return self.__get_executing_run(tfp_runner_attribute, command_obj)
393
+ return await self.__async_get_executing_run(attribute_file_fd, command_obj)
387
394
 
388
395
  async def async_resume(self, **kwargs):
389
396
  """
@@ -403,12 +410,9 @@ class Runner(object):
403
410
  ExecutingRun
404
411
  ExecutingRun representing the resumed run that was started.
405
412
  """
406
- with tempfile.TemporaryDirectory() as temp_dir:
407
- tfp_runner_attribute = tempfile.NamedTemporaryFile(
408
- dir=temp_dir, delete=False
409
- )
413
+ with temporary_fifo() as (attribute_file_path, attribute_file_fd):
410
414
  command = self.api(**self.top_level_kwargs).resume(
411
- runner_attribute_file=tfp_runner_attribute.name, **kwargs
415
+ runner_attribute_file=attribute_file_path, **kwargs
412
416
  )
413
417
 
414
418
  pid = await self.spm.async_run_command(
@@ -418,7 +422,7 @@ class Runner(object):
418
422
  )
419
423
  command_obj = self.spm.get(pid)
420
424
 
421
- return self.__get_executing_run(tfp_runner_attribute, command_obj)
425
+ return await self.__async_get_executing_run(attribute_file_fd, command_obj)
422
426
 
423
427
  def __exit__(self, exc_type, exc_value, traceback):
424
428
  self.spm.cleanup()
@@ -9,26 +9,61 @@ import tempfile
9
9
  import threading
10
10
  from typing import Callable, Dict, Iterator, List, Optional, Tuple
11
11
 
12
+ from .utils import check_process_exited
12
13
 
13
- def kill_process_and_descendants(pid, termination_timeout):
14
+
15
+ def kill_processes_and_descendants(pids: List[str], termination_timeout: float):
14
16
  # TODO: there's a race condition that new descendants might
15
17
  # spawn b/w the invocations of 'pkill' and 'kill'.
16
18
  # Needs to be fixed in future.
17
19
  try:
18
- subprocess.check_call(["pkill", "-TERM", "-P", str(pid)])
19
- subprocess.check_call(["kill", "-TERM", str(pid)])
20
+ subprocess.check_call(["pkill", "-TERM", "-P", *pids])
21
+ subprocess.check_call(["kill", "-TERM", *pids])
20
22
  except subprocess.CalledProcessError:
21
23
  pass
22
24
 
23
25
  time.sleep(termination_timeout)
24
26
 
25
27
  try:
26
- subprocess.check_call(["pkill", "-KILL", "-P", str(pid)])
27
- subprocess.check_call(["kill", "-KILL", str(pid)])
28
+ subprocess.check_call(["pkill", "-KILL", "-P", *pids])
29
+ subprocess.check_call(["kill", "-KILL", *pids])
28
30
  except subprocess.CalledProcessError:
29
31
  pass
30
32
 
31
33
 
34
+ async def async_kill_processes_and_descendants(
35
+ pids: List[str], termination_timeout: float
36
+ ):
37
+ # TODO: there's a race condition that new descendants might
38
+ # spawn b/w the invocations of 'pkill' and 'kill'.
39
+ # Needs to be fixed in future.
40
+ try:
41
+ sub_term = await asyncio.create_subprocess_exec("pkill", "-TERM", "-P", *pids)
42
+ await sub_term.wait()
43
+ except Exception:
44
+ pass
45
+
46
+ try:
47
+ main_term = await asyncio.create_subprocess_exec("kill", "-TERM", *pids)
48
+ await main_term.wait()
49
+ except Exception:
50
+ pass
51
+
52
+ await asyncio.sleep(termination_timeout)
53
+
54
+ try:
55
+ sub_kill = await asyncio.create_subprocess_exec("pkill", "-KILL", "-P", *pids)
56
+ await sub_kill.wait()
57
+ except Exception:
58
+ pass
59
+
60
+ try:
61
+ main_kill = await asyncio.create_subprocess_exec("kill", "-KILL", *pids)
62
+ await main_kill.wait()
63
+ except Exception:
64
+ pass
65
+
66
+
32
67
  class LogReadTimeoutError(Exception):
33
68
  """Exception raised when reading logs times out."""
34
69
 
@@ -46,14 +81,28 @@ class SubprocessManager(object):
46
81
  loop = asyncio.get_running_loop()
47
82
  loop.add_signal_handler(
48
83
  signal.SIGINT,
49
- lambda: self._handle_sigint(signum=signal.SIGINT, frame=None),
84
+ lambda: asyncio.create_task(self._async_handle_sigint()),
50
85
  )
51
86
  except RuntimeError:
52
87
  signal.signal(signal.SIGINT, self._handle_sigint)
53
88
 
89
+ async def _async_handle_sigint(self):
90
+ pids = [
91
+ str(command.process.pid)
92
+ for command in self.commands.values()
93
+ if command.process and not check_process_exited(command)
94
+ ]
95
+ if pids:
96
+ await async_kill_processes_and_descendants(pids, termination_timeout=2)
97
+
54
98
  def _handle_sigint(self, signum, frame):
55
- for each_command in self.commands.values():
56
- each_command.kill(termination_timeout=2)
99
+ pids = [
100
+ str(command.process.pid)
101
+ for command in self.commands.values()
102
+ if command.process and not check_process_exited(command)
103
+ ]
104
+ if pids:
105
+ kill_processes_and_descendants(pids, termination_timeout=2)
57
106
 
58
107
  async def __aenter__(self) -> "SubprocessManager":
59
108
  return self
@@ -472,7 +521,7 @@ class CommandManager(object):
472
521
  """
473
522
 
474
523
  if self.process is not None:
475
- kill_process_and_descendants(self.process.pid, termination_timeout)
524
+ kill_processes_and_descendants([str(self.process.pid)], termination_timeout)
476
525
  else:
477
526
  print("No process to kill.")
478
527
 
metaflow/runner/utils.py CHANGED
@@ -2,9 +2,11 @@ import os
2
2
  import ast
3
3
  import time
4
4
  import asyncio
5
-
5
+ import tempfile
6
+ import select
7
+ from contextlib import contextmanager
6
8
  from subprocess import CalledProcessError
7
- from typing import Any, Dict, TYPE_CHECKING
9
+ from typing import Any, Dict, TYPE_CHECKING, ContextManager, Tuple
8
10
 
9
11
  if TYPE_CHECKING:
10
12
  import tempfile
@@ -39,45 +41,194 @@ def format_flowfile(cell):
39
41
  return "\n".join(lines)
40
42
 
41
43
 
42
- def check_process_status(
44
+ def check_process_exited(
43
45
  command_obj: "metaflow.runner.subprocess_manager.CommandManager",
44
- ):
46
+ ) -> bool:
45
47
  if isinstance(command_obj.process, asyncio.subprocess.Process):
46
48
  return command_obj.process.returncode is not None
47
49
  else:
48
50
  return command_obj.process.poll() is not None
49
51
 
50
52
 
51
- def read_from_file_when_ready(
52
- file_path: str,
53
+ @contextmanager
54
+ def temporary_fifo() -> ContextManager[Tuple[str, int]]:
55
+ """
56
+ Create and open the read side of a temporary FIFO in a non-blocking mode.
57
+
58
+ Returns
59
+ -------
60
+ str
61
+ Path to the temporary FIFO.
62
+ int
63
+ File descriptor of the temporary FIFO.
64
+ """
65
+ with tempfile.TemporaryDirectory() as temp_dir:
66
+ path = os.path.join(temp_dir, "fifo")
67
+ os.mkfifo(path)
68
+ # Blocks until the write side is opened unless in non-blocking mode
69
+ fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
70
+ try:
71
+ yield path, fd
72
+ finally:
73
+ os.close(fd)
74
+
75
+
76
+ def read_from_fifo_when_ready(
77
+ fifo_fd: int,
78
+ command_obj: "metaflow.runner.subprocess_manager.CommandManager",
79
+ encoding: str = "utf-8",
80
+ timeout: int = 3600,
81
+ ) -> str:
82
+ """
83
+ Read the content from the FIFO file descriptor when it is ready.
84
+
85
+ Parameters
86
+ ----------
87
+ fifo_fd : int
88
+ File descriptor of the FIFO.
89
+ command_obj : CommandManager
90
+ Command manager object that handles the write side of the FIFO.
91
+ encoding : str, optional
92
+ Encoding to use while reading the file, by default "utf-8".
93
+ timeout : int, optional
94
+ Timeout for reading the file in milliseconds, by default 3600.
95
+
96
+ Returns
97
+ -------
98
+ str
99
+ Content read from the FIFO.
100
+
101
+ Raises
102
+ ------
103
+ TimeoutError
104
+ If no event occurs on the FIFO within the timeout.
105
+ CalledProcessError
106
+ If the process managed by `command_obj` has exited without writing any
107
+ content to the FIFO.
108
+ """
109
+ content = bytearray()
110
+
111
+ poll = select.poll()
112
+ poll.register(fifo_fd, select.POLLIN)
113
+
114
+ while True:
115
+ poll_begin = time.time()
116
+ poll.poll(timeout)
117
+ timeout -= 1000 * (time.time() - poll_begin)
118
+
119
+ if timeout <= 0:
120
+ raise TimeoutError("Timeout while waiting for the file content")
121
+
122
+ try:
123
+ data = os.read(fifo_fd, 128)
124
+ while data:
125
+ content += data
126
+ data = os.read(fifo_fd, 128)
127
+
128
+ # Read from a non-blocking closed FIFO returns an empty byte array
129
+ break
130
+
131
+ except BlockingIOError:
132
+ # FIFO is open but no data is available yet
133
+ continue
134
+
135
+ if not content and check_process_exited(command_obj):
136
+ raise CalledProcessError(command_obj.process.returncode, command_obj.command)
137
+
138
+ return content.decode(encoding)
139
+
140
+
141
+ async def async_read_from_fifo_when_ready(
142
+ fifo_fd: int,
143
+ command_obj: "metaflow.runner.subprocess_manager.CommandManager",
144
+ encoding: str = "utf-8",
145
+ timeout: int = 3600,
146
+ ) -> str:
147
+ """
148
+ Read the content from the FIFO file descriptor when it is ready.
149
+
150
+ Parameters
151
+ ----------
152
+ fifo_fd : int
153
+ File descriptor of the FIFO.
154
+ command_obj : CommandManager
155
+ Command manager object that handles the write side of the FIFO.
156
+ encoding : str, optional
157
+ Encoding to use while reading the file, by default "utf-8".
158
+ timeout : int, optional
159
+ Timeout for reading the file in milliseconds, by default 3600.
160
+
161
+ Returns
162
+ -------
163
+ str
164
+ Content read from the FIFO.
165
+
166
+ Raises
167
+ ------
168
+ TimeoutError
169
+ If no event occurs on the FIFO within the timeout.
170
+ CalledProcessError
171
+ If the process managed by `command_obj` has exited without writing any
172
+ content to the FIFO.
173
+ """
174
+ return await asyncio.to_thread(
175
+ read_from_fifo_when_ready, fifo_fd, command_obj, encoding, timeout
176
+ )
177
+
178
+
179
+ def make_process_error_message(
53
180
  command_obj: "metaflow.runner.subprocess_manager.CommandManager",
54
- timeout: float = 5,
55
181
  ):
56
- start_time = time.time()
57
- with open(file_path, "r", encoding="utf-8") as file_pointer:
58
- content = file_pointer.read()
59
- while not content:
60
- if check_process_status(command_obj):
61
- # Check to make sure the file hasn't been read yet to avoid a race
62
- # where the file is written between the end of this while loop and the
63
- # poll call above.
64
- content = file_pointer.read()
65
- if content:
66
- break
67
- raise CalledProcessError(
68
- command_obj.process.returncode, command_obj.command
69
- )
70
- if time.time() - start_time > timeout:
71
- raise TimeoutError(
72
- "Timeout while waiting for file content from '%s'" % file_path
73
- )
74
- time.sleep(0.1)
75
- content = file_pointer.read()
76
- return content
182
+ stdout_log = open(command_obj.log_files["stdout"], encoding="utf-8").read()
183
+ stderr_log = open(command_obj.log_files["stderr"], encoding="utf-8").read()
184
+ command = " ".join(command_obj.command)
185
+ error_message = "Error executing: '%s':\n" % command
186
+ if stdout_log.strip():
187
+ error_message += "\nStdout:\n%s\n" % stdout_log
188
+ if stderr_log.strip():
189
+ error_message += "\nStderr:\n%s\n" % stderr_log
190
+ return error_message
77
191
 
78
192
 
79
193
  def handle_timeout(
80
- tfp_runner_attribute: "tempfile._TemporaryFileWrapper[str]",
194
+ attribute_file_fd: int,
195
+ command_obj: "metaflow.runner.subprocess_manager.CommandManager",
196
+ file_read_timeout: int,
197
+ ):
198
+ """
199
+ Handle the timeout for a running subprocess command that reads a file
200
+ and raises an error with appropriate logs if a TimeoutError occurs.
201
+
202
+ Parameters
203
+ ----------
204
+ attribute_file_fd : int
205
+ File descriptor belonging to the FIFO containing the attribute data.
206
+ command_obj : CommandManager
207
+ Command manager object that encapsulates the running command details.
208
+ file_read_timeout : int
209
+ Timeout for reading the file.
210
+
211
+ Returns
212
+ -------
213
+ str
214
+ Content read from the temporary file.
215
+
216
+ Raises
217
+ ------
218
+ RuntimeError
219
+ If a TimeoutError occurs, it raises a RuntimeError with the command's
220
+ stdout and stderr logs.
221
+ """
222
+ try:
223
+ return read_from_fifo_when_ready(
224
+ attribute_file_fd, command_obj=command_obj, timeout=file_read_timeout
225
+ )
226
+ except (CalledProcessError, TimeoutError) as e:
227
+ raise RuntimeError(make_process_error_message(command_obj)) from e
228
+
229
+
230
+ async def async_handle_timeout(
231
+ attribute_file_fd: "int",
81
232
  command_obj: "metaflow.runner.subprocess_manager.CommandManager",
82
233
  file_read_timeout: int,
83
234
  ):
@@ -87,8 +238,8 @@ def handle_timeout(
87
238
 
88
239
  Parameters
89
240
  ----------
90
- tfp_runner_attribute : NamedTemporaryFile
91
- Temporary file that stores runner attribute data.
241
+ attribute_file_fd : int
242
+ File descriptor belonging to the FIFO containing the attribute data.
92
243
  command_obj : CommandManager
93
244
  Command manager object that encapsulates the running command details.
94
245
  file_read_timeout : int
@@ -106,20 +257,11 @@ def handle_timeout(
106
257
  stdout and stderr logs.
107
258
  """
108
259
  try:
109
- content = read_from_file_when_ready(
110
- tfp_runner_attribute.name, command_obj, timeout=file_read_timeout
260
+ return await async_read_from_fifo_when_ready(
261
+ attribute_file_fd, command_obj=command_obj, timeout=file_read_timeout
111
262
  )
112
- return content
113
263
  except (CalledProcessError, TimeoutError) as e:
114
- stdout_log = open(command_obj.log_files["stdout"], encoding="utf-8").read()
115
- stderr_log = open(command_obj.log_files["stderr"], encoding="utf-8").read()
116
- command = " ".join(command_obj.command)
117
- error_message = "Error executing: '%s':\n" % command
118
- if stdout_log.strip():
119
- error_message += "\nStdout:\n%s\n" % stdout_log
120
- if stderr_log.strip():
121
- error_message += "\nStderr:\n%s\n" % stderr_log
122
- raise RuntimeError(error_message) from e
264
+ raise RuntimeError(make_process_error_message(command_obj)) from e
123
265
 
124
266
 
125
267
  def get_lower_level_group(
metaflow/util.py CHANGED
@@ -436,12 +436,17 @@ def to_pod(value):
436
436
  Value to convert to POD format. The value can be a string, number, list,
437
437
  dictionary, or a nested structure of these types.
438
438
  """
439
+ # Prevent circular imports
440
+ from metaflow.parameters import DeployTimeField
441
+
439
442
  if isinstance(value, (str, int, float)):
440
443
  return value
441
444
  if isinstance(value, dict):
442
445
  return {to_pod(k): to_pod(v) for k, v in value.items()}
443
446
  if isinstance(value, (list, set, tuple)):
444
447
  return [to_pod(v) for v in value]
448
+ if isinstance(value, DeployTimeField):
449
+ return value.print_representation
445
450
  return str(value)
446
451
 
447
452
 
metaflow/version.py CHANGED
@@ -1 +1 @@
1
- metaflow_version = "2.12.32"
1
+ metaflow_version = "2.12.34"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: metaflow
3
- Version: 2.12.32
3
+ Version: 2.12.34
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.32; extra == "stubs"
29
+ Requires-Dist: metaflow-stubs==2.12.34; 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
 
@@ -10,7 +10,7 @@ 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=v8jH21Teq_RPcfaIB-yyya8xv09wl4IHU-Ya0K7UPYI,27268
13
+ metaflow/flowspec.py,sha256=VEeaItArFwCJqRNq0e2i9gZrBSZAaq0XwmN_8dTcY1M,27298
14
14
  metaflow/graph.py,sha256=HFJ7V_bPSht_NHIm8BejrSqOX2fyBQpVOczRCliRw08,11975
15
15
  metaflow/includefile.py,sha256=rDJnxF0U7vD3cz9hhPkKlW_KS3ToaXnlOjhjNZ__Rx4,19628
16
16
  metaflow/info_file.py,sha256=wtf2_F0M6dgiUu74AFImM8lfy5RrUw5Yj7Rgs2swKRY,686
@@ -23,7 +23,7 @@ metaflow/metaflow_environment.py,sha256=rojFyGdyY56sN1HaEb1-0XX53Q3XPNnl0SaH-8xX
23
23
  metaflow/metaflow_profile.py,sha256=jKPEW-hmAQO-htSxb9hXaeloLacAh41A35rMZH6G8pA,418
24
24
  metaflow/metaflow_version.py,sha256=duhIzfKZtcxMVMs2uiBqBvUarSHJqyWDwMhaBOQd_g0,7491
25
25
  metaflow/monitor.py,sha256=T0NMaBPvXynlJAO_avKtk8OIIRMyEuMAyF8bIp79aZU,5323
26
- metaflow/multicore_utils.py,sha256=vdTNgczVLODifscUbbveJbuSDOl3Y9pAxhr7sqYiNf4,4760
26
+ metaflow/multicore_utils.py,sha256=yEo5T6Gemn4_vl8b6IOz7fsTUYtEyqa3AaKZgJY96Wc,4974
27
27
  metaflow/package.py,sha256=QutDP6WzjwGk1UCKXqBfXa9F10Q--FlRr0J7fwlple0,7399
28
28
  metaflow/parameters.py,sha256=pzjG0ssuVHPyYQqWE86dS3yYChEqbT90rOUcRD4wNog,16079
29
29
  metaflow/procpoll.py,sha256=U2tE4iK_Mwj2WDyVTx_Uglh6xZ-jixQOo4wrM9OOhxg,2859
@@ -34,9 +34,9 @@ metaflow/tagging_util.py,sha256=ctyf0Q1gBi0RyZX6J0e9DQGNkNHblV_CITfy66axXB4,2346
34
34
  metaflow/task.py,sha256=xVVLWy8NH16OlLu2VoOb1OfiFzcOVVCdQldlmb1Zb_w,29691
35
35
  metaflow/tuple_util.py,sha256=_G5YIEhuugwJ_f6rrZoelMFak3DqAR2tt_5CapS1XTY,830
36
36
  metaflow/unbounded_foreach.py,sha256=p184WMbrMJ3xKYHwewj27ZhRUsSj_kw1jlye5gA9xJk,387
37
- metaflow/util.py,sha256=olAvJK3y1it_k99MhLulTaAJo7OFVt5rnrD-ulIFLCU,13616
37
+ metaflow/util.py,sha256=w7oylILPaNAjtM8MR8dfUazTVBArV_CKPpqGs4HnowM,13785
38
38
  metaflow/vendor.py,sha256=FchtA9tH22JM-eEtJ2c9FpUdMn8sSb1VHuQS56EcdZk,5139
39
- metaflow/version.py,sha256=UlmCI1Z4dlLrwJowsJ1ORnkNpYz3GOen97kVpkivC5Q,29
39
+ metaflow/version.py,sha256=1QFMy_kk8f34f8BuD9G4-ZA0vcxYQo1O9WhpDuY8MXQ,29
40
40
  metaflow/_vendor/__init__.py,sha256=y_CiwUD3l4eAKvTVDZeqgVujMy31cAM1qjAB-HfI-9s,353
41
41
  metaflow/_vendor/typing_extensions.py,sha256=0nUs5p1A_UrZigrAVBoOEM6TxU37zzPDUtiij1ZwpNc,110417
42
42
  metaflow/_vendor/zipp.py,sha256=ajztOH-9I7KA_4wqDYygtHa6xUBVZgFpmZ8FE74HHHI,8425
@@ -148,7 +148,7 @@ metaflow/plugins/catch_decorator.py,sha256=UOM2taN_OL2RPpuJhwEOA9ZALm0-hHD0XS2Hn
148
148
  metaflow/plugins/debug_logger.py,sha256=mcF5HYzJ0NQmqCMjyVUk3iAP-heroHRIiVWQC6Ha2-I,879
149
149
  metaflow/plugins/debug_monitor.py,sha256=Md5X_sDOSssN9pt2D8YcaIjTK5JaQD55UAYTcF6xYF0,1099
150
150
  metaflow/plugins/environment_decorator.py,sha256=6m9j2B77d-Ja_l_9CTJ__0O6aB2a8Qt_lAZu6UjAcUA,587
151
- metaflow/plugins/events_decorator.py,sha256=oULWTJw426MA9bdfMS-7q72LOQ1rI4oc_fp8JJd0P9o,26475
151
+ metaflow/plugins/events_decorator.py,sha256=8YSapp_sT3UzNrb6cYBJ19-wmX_CKow6OOJN8kNVnpg,26456
152
152
  metaflow/plugins/logs_cli.py,sha256=77W5UNagU2mOKSMMvrQxQmBLRzvmjK-c8dWxd-Ygbqs,11410
153
153
  metaflow/plugins/package_cli.py,sha256=-J6D4cupHfWSZ4GEFo2yy9Je9oL3owRWm5pEJwaiqd4,1649
154
154
  metaflow/plugins/parallel_decorator.py,sha256=GIjZZVTqkvtnMuGE8RNtObX6CAJavZTxttqRujGmnGs,8973
@@ -179,7 +179,7 @@ metaflow/plugins/argo/argo_workflows.py,sha256=d-d_xvjUeb5t11kaHZ98yIrGAqYg__Fhx
179
179
  metaflow/plugins/argo/argo_workflows_cli.py,sha256=NdLwzfBcTsR72qLycZBesR4Pwv48o3Z_v6OfYrZuVEY,36721
180
180
  metaflow/plugins/argo/argo_workflows_decorator.py,sha256=QdM1rK9gM-lDhyZldK8WqvFqJDvfJ7i3JPR5Uzaq2as,7887
181
181
  metaflow/plugins/argo/argo_workflows_deployer.py,sha256=6kHxEnYXJwzNCM9swI8-0AckxtPWqwhZLerYkX8fxUM,4444
182
- metaflow/plugins/argo/argo_workflows_deployer_objects.py,sha256=jOlvn7dp2Qa_Y1lsOY_sta7i78qQVirunzgIH0O4w2E,12414
182
+ metaflow/plugins/argo/argo_workflows_deployer_objects.py,sha256=GJ1Jsm5KHYaBbJjKRz82Cwhi_PN1XnMiSmL1C0LNLYQ,12318
183
183
  metaflow/plugins/argo/capture_error.py,sha256=Ys9dscGrTpW-ZCirLBU0gD9qBM0BjxyxGlUMKcwewQc,1852
184
184
  metaflow/plugins/argo/generate_input_paths.py,sha256=loYsI6RFX9LlFsHb7Fe-mzlTTtRdySoOu7sYDy-uXK0,881
185
185
  metaflow/plugins/argo/jobset_input_paths.py,sha256=_JhZWngA6p9Q_O2fx3pdzKI0WE-HPRHz_zFvY2pHPTQ,525
@@ -204,7 +204,7 @@ metaflow/plugins/aws/step_functions/step_functions_cli.py,sha256=Ar9ed2nlZ_M0J3H
204
204
  metaflow/plugins/aws/step_functions/step_functions_client.py,sha256=DKpNwAIWElvWjFANs5Ku3rgzjxFoqAD6k-EF8Xhkg3Q,4754
205
205
  metaflow/plugins/aws/step_functions/step_functions_decorator.py,sha256=LoZC5BuQLqyFtfE-sGla26l2xXlCKN9aSvIlzPKV134,3800
206
206
  metaflow/plugins/aws/step_functions/step_functions_deployer.py,sha256=JKYtDhKivtXUWPklprZFzkqezh14loGDmk8mNk6QtpI,3714
207
- metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py,sha256=6frhj4JkJxH04Wocg-053ppD3xmDjd_3wIwSVfjeMrw,7315
207
+ metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py,sha256=EbBd0m1GL5E7hIP2sBFgyRXo2XTf6qMZdxLA8VjXGX8,7219
208
208
  metaflow/plugins/azure/__init__.py,sha256=GuuhTVC-zSdyAf79a1wiERMq0Zts7fwVT7t9fAf234A,100
209
209
  metaflow/plugins/azure/azure_credential.py,sha256=JmdGEbVzgxy8ucqnQDdTTI_atyMX9WSZUw3qYOo7RhE,2174
210
210
  metaflow/plugins/azure/azure_exceptions.py,sha256=NnbwpUC23bc61HZjJmeXztY0tBNn_Y_VpIpDDuYWIZ0,433
@@ -247,7 +247,7 @@ metaflow/plugins/datastores/s3_storage.py,sha256=CZdNqaKtxDXQbEg2YHyphph3hWcLIE5
247
247
  metaflow/plugins/datatools/__init__.py,sha256=ge4L16OBQLy2J_MMvoHg3lMfdm-MluQgRWoyZ5GCRnk,1267
248
248
  metaflow/plugins/datatools/local.py,sha256=FJvMOBcjdyhSPHmdLocBSiIT0rmKkKBmsaclxH75x08,4233
249
249
  metaflow/plugins/datatools/s3/__init__.py,sha256=14tr9fPjN3ULW5IOfKHeG7Uhjmgm7LMtQHfz1SFv-h8,248
250
- metaflow/plugins/datatools/s3/s3.py,sha256=-gkOKx4Ed-0u7eL2rOdbik98PJPltxDgfVPml0J9Ym4,66157
250
+ metaflow/plugins/datatools/s3/s3.py,sha256=dSUEf3v_BVyvYXlehWy04xcBtNDhKKR5Gnn7oXwagaw,67037
251
251
  metaflow/plugins/datatools/s3/s3op.py,sha256=h7l-OLQz4RLpyzsB-k903BYCDF9CM2_O9jLl-FQyMis,43454
252
252
  metaflow/plugins/datatools/s3/s3tail.py,sha256=boQjQGQMI-bvTqcMP2y7uSlSYLcvWOy7J3ZUaF78NAA,2597
253
253
  metaflow/plugins/datatools/s3/s3util.py,sha256=FgRgaVmEq7-i2dV7q8XK5w5PfFt-xJjZa8WrK8IJfdI,3769
@@ -306,12 +306,12 @@ metaflow/plugins/secrets/secrets_decorator.py,sha256=s-sFzPWOjahhpr5fMj-ZEaHkDYA
306
306
  metaflow/runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
307
307
  metaflow/runner/click_api.py,sha256=r7StpxnKkTwzs7_NG-estHVcztE-ey7wM6QSdYu6UCY,13943
308
308
  metaflow/runner/deployer.py,sha256=goCCDq9mRJIrH5-9HU6E6J55pYoju--CtM1zADTzHvY,8967
309
- metaflow/runner/deployer_impl.py,sha256=gJK30oujnRKw23jAXG5h3JLLkEUBB3GdgKmJHZxq8Mk,5736
310
- metaflow/runner/metaflow_runner.py,sha256=qq6EF9lFcX5dn93P5S8v-SDddhpFWfjrbGMAEHncNYc,14592
309
+ metaflow/runner/deployer_impl.py,sha256=QjMoTM1IviRK-dZ7WB3vGExpaScOCESlHxSR9ZoAkgA,5625
310
+ metaflow/runner/metaflow_runner.py,sha256=PrAEJhWqjbGuu4IsnxCSLLf8o4AqqRpeTM7rCMbOAvg,14827
311
311
  metaflow/runner/nbdeploy.py,sha256=UYo3Yfw1wlBFnUHjs_RngnWcWj9CDdMYaMqt9wZx0rk,4035
312
312
  metaflow/runner/nbrun.py,sha256=otWTsCkrVSdxlaxXNrRc9oyjdgQR8mHHhL8NAO0B5iE,7286
313
- metaflow/runner/subprocess_manager.py,sha256=jC_PIYIeAp_G__lf6WHZF3Lxzpp-WAQleMrRZq9j7nc,20467
314
- metaflow/runner/utils.py,sha256=WnEoJ4KJTOWzhAevH5atUcOBLMJE5wWq27E_4nWTqzQ,5035
313
+ metaflow/runner/subprocess_manager.py,sha256=lUD59T663Ar4ZcsUOk8VehaV1pRIg-QoWT_s_dFOVGI,21953
314
+ metaflow/runner/utils.py,sha256=JTWBFxQ2dru-dL2tI-8dIIVvQn_S3asGU9Ma6eeN7rc,8818
315
315
  metaflow/sidecar/__init__.py,sha256=1mmNpmQ5puZCpRmmYlCOeieZ4108Su9XQ4_EqF1FGOU,131
316
316
  metaflow/sidecar/sidecar.py,sha256=EspKXvPPNiyRToaUZ51PS5TT_PzrBNAurn_wbFnmGr0,1334
317
317
  metaflow/sidecar/sidecar_messages.py,sha256=zPsCoYgDIcDkkvdC9MEpJTJ3y6TSGm2JWkRc4vxjbFA,1071
@@ -348,9 +348,9 @@ metaflow/tutorials/07-worldview/README.md,sha256=5vQTrFqulJ7rWN6r20dhot9lI2sVj9W
348
348
  metaflow/tutorials/07-worldview/worldview.ipynb,sha256=ztPZPI9BXxvW1QdS2Tfe7LBuVzvFvv0AToDnsDJhLdE,2237
349
349
  metaflow/tutorials/08-autopilot/README.md,sha256=GnePFp_q76jPs991lMUqfIIh5zSorIeWznyiUxzeUVE,1039
350
350
  metaflow/tutorials/08-autopilot/autopilot.ipynb,sha256=DQoJlILV7Mq9vfPBGW-QV_kNhWPjS5n6SJLqePjFYLY,3191
351
- metaflow-2.12.32.dist-info/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
352
- metaflow-2.12.32.dist-info/METADATA,sha256=_gQS-g4TESv6MPnBVuDHjf9qwcWyfTcDbEY-XcUaFAM,5907
353
- metaflow-2.12.32.dist-info/WHEEL,sha256=pxeNX5JdtCe58PUSYP9upmc7jdRPgvT0Gm9kb1SHlVw,109
354
- metaflow-2.12.32.dist-info/entry_points.txt,sha256=IKwTN1T3I5eJL3uo_vnkyxVffcgnRdFbKwlghZfn27k,57
355
- metaflow-2.12.32.dist-info/top_level.txt,sha256=v1pDHoWaSaKeuc5fKTRSfsXCKSdW1zvNVmvA-i0if3o,9
356
- metaflow-2.12.32.dist-info/RECORD,,
351
+ metaflow-2.12.34.dist-info/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
352
+ metaflow-2.12.34.dist-info/METADATA,sha256=nyJPMX2oVYbAC9lY9KuNPMngDomft-j5DJoeHGAnJuY,5907
353
+ metaflow-2.12.34.dist-info/WHEEL,sha256=pxeNX5JdtCe58PUSYP9upmc7jdRPgvT0Gm9kb1SHlVw,109
354
+ metaflow-2.12.34.dist-info/entry_points.txt,sha256=IKwTN1T3I5eJL3uo_vnkyxVffcgnRdFbKwlghZfn27k,57
355
+ metaflow-2.12.34.dist-info/top_level.txt,sha256=v1pDHoWaSaKeuc5fKTRSfsXCKSdW1zvNVmvA-i0if3o,9
356
+ metaflow-2.12.34.dist-info/RECORD,,