metaflow 2.12.20__py2.py3-none-any.whl → 2.12.22__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/__init__.py CHANGED
@@ -42,14 +42,8 @@ If you have any questions, feel free to post a bug report/question on the
42
42
  Metaflow GitHub page.
43
43
  """
44
44
 
45
- import importlib
45
+ import os
46
46
  import sys
47
- import types
48
-
49
- from os import path
50
-
51
- CURRENT_DIRECTORY = path.dirname(path.abspath(__file__))
52
- INFO_FILE = path.join(path.dirname(CURRENT_DIRECTORY), "INFO")
53
47
 
54
48
  from metaflow.extension_support import (
55
49
  alias_submodules,
@@ -61,7 +55,6 @@ from metaflow.extension_support import (
61
55
  _ext_debug,
62
56
  )
63
57
 
64
-
65
58
  # We load the module overrides *first* explicitly. Non overrides can be loaded
66
59
  # in toplevel as well but these can be loaded first if needed. Note that those
67
60
  # modules should be careful not to include anything in Metaflow at their top-level
@@ -79,9 +72,14 @@ try:
79
72
  )
80
73
  tl_module = m.module.__dict__.get("toplevel", None)
81
74
  if tl_module is not None:
82
- _tl_modules.append(".".join([EXT_PKG, m.tl_package, "toplevel", tl_module]))
75
+ _tl_modules.append(
76
+ (
77
+ m.package_name,
78
+ ".".join([EXT_PKG, m.tl_package, "toplevel", tl_module]),
79
+ )
80
+ )
83
81
  _ext_debug("Got overrides to load: %s" % _override_modules)
84
- _ext_debug("Got top-level imports: %s" % _tl_modules)
82
+ _ext_debug("Got top-level imports: %s" % str(_tl_modules))
85
83
  except Exception as e:
86
84
  _ext_debug("Error in importing toplevel/overrides: %s" % e)
87
85
 
@@ -153,9 +151,9 @@ if sys.version_info >= (3, 7):
153
151
  from .runner.deployer import Deployer
154
152
  from .runner.nbdeploy import NBDeployer
155
153
 
156
- __version_addl__ = []
154
+ __ext_tl_modules__ = []
157
155
  _ext_debug("Loading top-level modules")
158
- for m in _tl_modules:
156
+ for pkg_name, m in _tl_modules:
159
157
  extension_module = load_module(m)
160
158
  if extension_module:
161
159
  tl_package = m.split(".")[1]
@@ -163,15 +161,7 @@ for m in _tl_modules:
163
161
  lazy_load_aliases(
164
162
  alias_submodules(extension_module, tl_package, None, extra_indent=True)
165
163
  )
166
- version_info = getattr(extension_module, "__mf_extensions__", "<unk>")
167
- if extension_module.__version__:
168
- version_info = "%s(%s)" % (version_info, extension_module.__version__)
169
- __version_addl__.append(version_info)
170
-
171
- if __version_addl__:
172
- __version_addl__ = ";".join(__version_addl__)
173
- else:
174
- __version_addl__ = None
164
+ __ext_tl_modules__.append((pkg_name, extension_module))
175
165
 
176
166
  # Erase all temporary names to avoid leaking things
177
167
  for _n in [
metaflow/client/core.py CHANGED
@@ -34,7 +34,7 @@ from metaflow.plugins import ENVIRONMENTS, METADATA_PROVIDERS
34
34
  from metaflow.unbounded_foreach import CONTROL_TASK_TAG
35
35
  from metaflow.util import cached_property, is_stringish, resolve_identity, to_unicode
36
36
 
37
- from .. import INFO_FILE
37
+ from ..info_file import INFO_FILE
38
38
  from .filecache import FileCache
39
39
 
40
40
  try:
metaflow/cmd/main_cli.py CHANGED
@@ -84,12 +84,13 @@ def start(ctx):
84
84
 
85
85
  import metaflow
86
86
 
87
+ version = get_version()
87
88
  echo("Metaflow ", fg="magenta", bold=True, nl=False)
88
89
 
89
90
  if ctx.invoked_subcommand is None:
90
- echo("(%s): " % get_version(), fg="magenta", bold=False, nl=False)
91
+ echo("(%s): " % version, fg="magenta", bold=False, nl=False)
91
92
  else:
92
- echo("(%s)\n" % get_version(), fg="magenta", bold=False)
93
+ echo("(%s)\n" % version, fg="magenta", bold=False)
93
94
 
94
95
  if ctx.invoked_subcommand is None:
95
96
  echo("More data science, less engineering\n", fg="magenta")
@@ -1,7 +1,6 @@
1
1
  from __future__ import print_function
2
2
 
3
3
  import importlib
4
- import json
5
4
  import os
6
5
  import re
7
6
  import sys
@@ -11,6 +10,10 @@ from collections import defaultdict, namedtuple
11
10
 
12
11
  from importlib.abc import MetaPathFinder, Loader
13
12
  from itertools import chain
13
+ from pathlib import Path
14
+
15
+ from metaflow.info_file import read_info_file
16
+
14
17
 
15
18
  #
16
19
  # This file provides the support for Metaflow's extension mechanism which allows
@@ -59,6 +62,9 @@ __all__ = (
59
62
  "load_module",
60
63
  "get_modules",
61
64
  "dump_module_info",
65
+ "get_extensions_in_dir",
66
+ "extension_info",
67
+ "update_package_info",
62
68
  "get_aliased_modules",
63
69
  "package_mfext_package",
64
70
  "package_mfext_all",
@@ -80,9 +86,14 @@ EXT_EXCLUDE_SUFFIXES = [".pyc"]
80
86
  # To get verbose messages, set METAFLOW_DEBUG_EXT to 1
81
87
  DEBUG_EXT = os.environ.get("METAFLOW_DEBUG_EXT", False)
82
88
 
89
+ # This is extracted only from environment variable and here separately from
90
+ # metaflow_config to prevent nasty circular dependencies
91
+ EXTENSIONS_SEARCH_DIRS = os.environ.get("METAFLOW_EXTENSIONS_SEARCH_DIRS", "").split(
92
+ os.pathsep
93
+ )
83
94
 
84
95
  MFExtPackage = namedtuple("MFExtPackage", "package_name tl_package config_module")
85
- MFExtModule = namedtuple("MFExtModule", "tl_package module")
96
+ MFExtModule = namedtuple("MFExtModule", "package_name tl_package module")
86
97
 
87
98
 
88
99
  def load_module(module_name):
@@ -113,17 +124,64 @@ def get_modules(extension_point):
113
124
  return modules_to_load
114
125
 
115
126
 
116
- def dump_module_info():
117
- _filter_files_all()
127
+ def dump_module_info(all_packages=None, pkgs_per_extension_point=None):
128
+ if all_packages is None:
129
+ all_packages = _all_packages
130
+ if pkgs_per_extension_point is None:
131
+ pkgs_per_extension_point = _pkgs_per_extension_point
132
+
133
+ _filter_files_all(all_packages)
118
134
  sanitized_all_packages = dict()
119
135
  # Strip out root_paths (we don't need it and no need to expose user's dir structure)
120
- for k, v in _all_packages.items():
136
+ for k, v in all_packages.items():
121
137
  sanitized_all_packages[k] = {
122
138
  "root_paths": None,
123
139
  "meta_module": v["meta_module"],
124
140
  "files": v["files"],
141
+ "version": v["version"],
142
+ "package_version": v.get("package_version", "<unk>"),
143
+ "extension_name": v.get("extension_name", "<unk>"),
125
144
  }
126
- return "ext_info", [sanitized_all_packages, _pkgs_per_extension_point]
145
+ return "ext_info", [sanitized_all_packages, pkgs_per_extension_point]
146
+
147
+
148
+ def get_extensions_in_dir(d):
149
+ if not _mfext_supported:
150
+ _ext_debug("Not supported for your Python version -- 3.4+ is needed")
151
+ return None, None
152
+ return _get_extension_packages(ignore_info_file=True, restrict_to_directories=[d])
153
+
154
+
155
+ def extension_info(packages=None):
156
+ if packages is None:
157
+ packages = _all_packages
158
+ # Returns information about installed extensions so it it can be stored in
159
+ # _graph_info.
160
+ return {
161
+ "installed": {
162
+ k: {
163
+ "dist_version": v["version"],
164
+ "package_version": v.get("package_version", "<unk>"),
165
+ "extension_name": v.get("extension_name", "<unk>"),
166
+ }
167
+ for k, v in packages.items()
168
+ },
169
+ }
170
+
171
+
172
+ def update_package_info(pkg_to_update=None, package_name=None, **kwargs):
173
+ pkg = None
174
+ if pkg_to_update:
175
+ pkg = pkg_to_update
176
+ elif package_name:
177
+ pkg = _all_packages.get(package_name)
178
+ for k, v in kwargs.items():
179
+ if k in pkg:
180
+ raise ValueError(
181
+ "Trying to overwrite existing key '%s' for package %s" % (k, str(pkg))
182
+ )
183
+ pkg[k] = v
184
+ return pkg
127
185
 
128
186
 
129
187
  def get_aliased_modules():
@@ -134,8 +192,8 @@ def package_mfext_package(package_name):
134
192
  from metaflow.util import to_unicode
135
193
 
136
194
  _ext_debug("Packaging '%s'" % package_name)
137
- _filter_files_package(package_name)
138
195
  pkg_info = _all_packages.get(package_name, None)
196
+ _filter_files_package(pkg_info)
139
197
  if pkg_info and pkg_info.get("root_paths", None):
140
198
  single_path = len(pkg_info["root_paths"]) == 1
141
199
  for p in pkg_info["root_paths"]:
@@ -296,7 +354,7 @@ def _ext_debug(*args, **kwargs):
296
354
  print(init_str, *args, **kwargs)
297
355
 
298
356
 
299
- def _get_extension_packages():
357
+ def _get_extension_packages(ignore_info_file=False, restrict_to_directories=None):
300
358
  if not _mfext_supported:
301
359
  _ext_debug("Not supported for your Python version -- 3.4+ is needed")
302
360
  return {}, {}
@@ -304,20 +362,20 @@ def _get_extension_packages():
304
362
  # If we have an INFO file with the appropriate information (if running from a saved
305
363
  # code package for example), we use that directly
306
364
  # Pre-compute on _extension_points
307
- from metaflow import INFO_FILE
308
-
309
- try:
310
- with open(INFO_FILE, encoding="utf-8") as contents:
311
- all_pkg, ext_to_pkg = json.load(contents).get("ext_info", (None, None))
312
- if all_pkg is not None and ext_to_pkg is not None:
313
- _ext_debug("Loading pre-computed information from INFO file")
314
- # We need to properly convert stuff in ext_to_pkg
315
- for k, v in ext_to_pkg.items():
316
- v = [MFExtPackage(*d) for d in v]
317
- ext_to_pkg[k] = v
318
- return all_pkg, ext_to_pkg
319
- except IOError:
320
- pass
365
+ info_content = read_info_file()
366
+ if not ignore_info_file and info_content:
367
+ all_pkg, ext_to_pkg = info_content.get("ext_info", (None, None))
368
+ if all_pkg is not None and ext_to_pkg is not None:
369
+ _ext_debug("Loading pre-computed information from INFO file")
370
+ # We need to properly convert stuff in ext_to_pkg
371
+ for k, v in ext_to_pkg.items():
372
+ v = [MFExtPackage(*d) for d in v]
373
+ ext_to_pkg[k] = v
374
+ return all_pkg, ext_to_pkg
375
+
376
+ # Late import to prevent some circular nastiness
377
+ if restrict_to_directories is None and EXTENSIONS_SEARCH_DIRS != [""]:
378
+ restrict_to_directories = EXTENSIONS_SEARCH_DIRS
321
379
 
322
380
  # Check if we even have extensions
323
381
  try:
@@ -331,6 +389,11 @@ def _get_extension_packages():
331
389
  raise
332
390
  return {}, {}
333
391
 
392
+ if restrict_to_directories:
393
+ restrict_to_directories = [
394
+ Path(p).resolve().as_posix() for p in restrict_to_directories
395
+ ]
396
+
334
397
  # There are two "types" of packages:
335
398
  # - those installed on the system (distributions)
336
399
  # - those present in the PYTHONPATH
@@ -343,8 +406,12 @@ def _get_extension_packages():
343
406
  # At this point, we look at all the paths and create a set. As we find distributions
344
407
  # that match it, we will remove from the set and then will be left with any
345
408
  # PYTHONPATH "packages"
346
- all_paths = set(extensions_module.__path__)
409
+ all_paths = set(Path(p).resolve().as_posix() for p in extensions_module.__path__)
347
410
  _ext_debug("Found packages present at %s" % str(all_paths))
411
+ if restrict_to_directories:
412
+ _ext_debug(
413
+ "Processed packages will be restricted to %s" % str(restrict_to_directories)
414
+ )
348
415
 
349
416
  list_ext_points = [x.split(".") for x in _extension_points]
350
417
  init_ext_points = [x[0] for x in list_ext_points]
@@ -391,9 +458,20 @@ def _get_extension_packages():
391
458
  # This is not 100% accurate because it is possible that at the same
392
459
  # location there is a package and a non-package, but this is extremely
393
460
  # unlikely so we are going to ignore this case.
394
- dist_root = dist.locate_file(EXT_PKG).as_posix()
461
+ dist_root = dist.locate_file(EXT_PKG).resolve().as_posix()
395
462
  all_paths.discard(dist_root)
396
463
  dist_name = dist.metadata["Name"]
464
+ dist_version = dist.metadata["Version"]
465
+ if restrict_to_directories:
466
+ parent_dirs = list(
467
+ p.as_posix() for p in Path(dist_root).resolve().parents
468
+ )
469
+ if all(p not in parent_dirs for p in restrict_to_directories):
470
+ _ext_debug(
471
+ "Ignoring package at %s as it is not in the considered directories"
472
+ % dist_root
473
+ )
474
+ continue
397
475
  if dist_name in mf_ext_packages:
398
476
  _ext_debug(
399
477
  "Ignoring duplicate package '%s' (duplicate paths in sys.path? (%s))"
@@ -537,6 +615,7 @@ def _get_extension_packages():
537
615
  "root_paths": [dist_root],
538
616
  "meta_module": meta_module,
539
617
  "files": files_to_include,
618
+ "version": dist_version,
540
619
  }
541
620
  # At this point, we have all the packages that contribute to EXT_PKG,
542
621
  # we now check to see if there is an order to respect based on dependencies. We will
@@ -605,6 +684,16 @@ def _get_extension_packages():
605
684
  if len(all_paths_list) > 0:
606
685
  _ext_debug("Non installed packages present at %s" % str(all_paths))
607
686
  for package_count, package_path in enumerate(all_paths_list):
687
+ if restrict_to_directories:
688
+ parent_dirs = list(
689
+ p.as_posix() for p in Path(package_path).resolve().parents
690
+ )
691
+ if all(p not in parent_dirs for p in restrict_to_directories):
692
+ _ext_debug(
693
+ "Ignoring non-installed package at %s as it is not in "
694
+ "the considered directories" % package_path
695
+ )
696
+ continue
608
697
  # We give an alternate name for the visible package name. It is
609
698
  # not exposed to the end user but used to refer to the package, and it
610
699
  # doesn't provide much additional information to have the full path
@@ -740,6 +829,7 @@ def _get_extension_packages():
740
829
  "root_paths": [package_path],
741
830
  "meta_module": meta_module,
742
831
  "files": files_to_include,
832
+ "version": "_local_",
743
833
  }
744
834
 
745
835
  # Sanity check that we only have one package per configuration file.
@@ -868,12 +958,13 @@ def _get_extension_config(distribution_name, tl_pkg, extension_point, config_mod
868
958
  _ext_debug("Package '%s' is rooted at %s" % (distribution_name, root_paths))
869
959
  _all_packages[distribution_name]["root_paths"] = root_paths
870
960
 
871
- return MFExtModule(tl_package=tl_pkg, module=extension_module)
961
+ return MFExtModule(
962
+ package_name=distribution_name, tl_package=tl_pkg, module=extension_module
963
+ )
872
964
  return None
873
965
 
874
966
 
875
- def _filter_files_package(package_name):
876
- pkg = _all_packages.get(package_name)
967
+ def _filter_files_package(pkg):
877
968
  if pkg and pkg["root_paths"] and pkg["meta_module"]:
878
969
  meta_module = _attempt_load_module(pkg["meta_module"])
879
970
  if meta_module:
@@ -902,8 +993,8 @@ def _filter_files_package(package_name):
902
993
  pkg["files"] = new_files
903
994
 
904
995
 
905
- def _filter_files_all():
906
- for p in _all_packages:
996
+ def _filter_files_all(all_packages):
997
+ for p in all_packages.values():
907
998
  _filter_files_package(p)
908
999
 
909
1000
 
metaflow/flowspec.py CHANGED
@@ -15,6 +15,9 @@ from .exception import (
15
15
  MissingInMergeArtifactsException,
16
16
  UnhandledInMergeArtifactsException,
17
17
  )
18
+
19
+ from .extension_support import extension_info
20
+
18
21
  from .graph import FlowGraph
19
22
  from .unbounded_foreach import UnboundedForeachInput
20
23
  from .util import to_pod
@@ -208,6 +211,7 @@ class FlowSpec(metaclass=_FlowSpecMeta):
208
211
  for deco in flow_decorators(self)
209
212
  if not deco.name.startswith("_")
210
213
  ],
214
+ "extensions": extension_info(),
211
215
  }
212
216
  self._graph_info = graph_info
213
217
 
metaflow/info_file.py ADDED
@@ -0,0 +1,25 @@
1
+ import json
2
+
3
+ from os import path
4
+
5
+ CURRENT_DIRECTORY = path.dirname(path.abspath(__file__))
6
+ INFO_FILE = path.join(path.dirname(CURRENT_DIRECTORY), "INFO")
7
+
8
+ _info_file_content = None
9
+ _info_file_present = None
10
+
11
+
12
+ def read_info_file():
13
+ global _info_file_content
14
+ global _info_file_present
15
+ if _info_file_present is None:
16
+ _info_file_present = path.exists(INFO_FILE)
17
+ if _info_file_present:
18
+ try:
19
+ with open(INFO_FILE, "r", encoding="utf-8") as contents:
20
+ _info_file_content = json.load(contents)
21
+ except IOError:
22
+ pass
23
+ if _info_file_present:
24
+ return _info_file_content
25
+ return None
@@ -25,7 +25,6 @@ DATASTORE_LOCAL_DIR = ".metaflow"
25
25
  # Local configuration file (in .metaflow) containing overrides per-project
26
26
  LOCAL_CONFIG_FILE = "config.json"
27
27
 
28
-
29
28
  ###
30
29
  # Default configuration
31
30
  ###
@@ -9,8 +9,6 @@ from metaflow.extension_support import dump_module_info
9
9
  from metaflow.mflog import BASH_MFLOG
10
10
  from . import R
11
11
 
12
- version_cache = None
13
-
14
12
 
15
13
  class InvalidEnvironmentException(MetaflowException):
16
14
  headline = "Incompatible environment"
@@ -180,10 +178,6 @@ class MetaflowEnvironment(object):
180
178
  return cmds
181
179
 
182
180
  def get_environment_info(self, include_ext_info=False):
183
- global version_cache
184
- if version_cache is None:
185
- version_cache = metaflow_version.get_version()
186
-
187
181
  # note that this dict goes into the code package
188
182
  # so variables here should be relatively stable (no
189
183
  # timestamps) so the hash won't change all the time
@@ -197,7 +191,7 @@ class MetaflowEnvironment(object):
197
191
  "use_r": R.use_r(),
198
192
  "python_version": sys.version,
199
193
  "python_version_code": "%d.%d.%d" % sys.version_info[:3],
200
- "metaflow_version": version_cache,
194
+ "metaflow_version": metaflow_version.get_version(),
201
195
  "script": os.path.basename(os.path.abspath(sys.argv[0])),
202
196
  }
203
197
  if R.use_r():
@@ -7,11 +7,15 @@ See the documentation of get_version for more information
7
7
 
8
8
  # This file is adapted from https://github.com/aebrahim/python-git-version
9
9
 
10
- from subprocess import check_output, CalledProcessError
11
- from os import path, name, devnull, environ, listdir
12
- import json
10
+ import subprocess
11
+ from os import path, name, environ, listdir
13
12
 
14
- from metaflow import CURRENT_DIRECTORY, INFO_FILE
13
+ from metaflow.extension_support import update_package_info
14
+ from metaflow.info_file import CURRENT_DIRECTORY, read_info_file
15
+
16
+
17
+ # True/False correspond to the value `public`` in get_version
18
+ _version_cache = {True: None, False: None}
15
19
 
16
20
  __all__ = ("get_version",)
17
21
 
@@ -57,87 +61,149 @@ if name == "nt":
57
61
  GIT_COMMAND = find_git_on_windows()
58
62
 
59
63
 
60
- def call_git_describe(abbrev=7):
64
+ def call_git_describe(file_to_check, abbrev=7):
61
65
  """return the string output of git describe"""
62
66
  try:
63
-
64
- # first, make sure we are actually in a Metaflow repo,
65
- # not some other repo
66
- with open(devnull, "w") as fnull:
67
- arguments = [GIT_COMMAND, "rev-parse", "--show-toplevel"]
68
- reponame = (
69
- check_output(arguments, cwd=CURRENT_DIRECTORY, stderr=fnull)
70
- .decode("ascii")
71
- .strip()
72
- )
73
- if path.basename(reponame) != "metaflow":
74
- return None
75
-
76
- with open(devnull, "w") as fnull:
77
- arguments = [GIT_COMMAND, "describe", "--tags", "--abbrev=%d" % abbrev]
78
- return (
79
- check_output(arguments, cwd=CURRENT_DIRECTORY, stderr=fnull)
80
- .decode("ascii")
81
- .strip()
82
- )
83
-
84
- except (OSError, CalledProcessError):
67
+ wd = path.dirname(file_to_check)
68
+ filename = path.basename(file_to_check)
69
+
70
+ # First check if the file is tracked in the GIT repository we are in
71
+ # We do this because in some setups and for some bizarre reason, python files
72
+ # are installed directly into a git repository (I am looking at you brew). We
73
+ # don't want to consider this a GIT install in that case.
74
+ args = [GIT_COMMAND, "ls-files", "--error-unmatch", filename]
75
+ git_return_code = subprocess.run(
76
+ args,
77
+ cwd=wd,
78
+ stderr=subprocess.DEVNULL,
79
+ stdout=subprocess.DEVNULL,
80
+ check=False,
81
+ ).returncode
82
+
83
+ if git_return_code != 0:
84
+ return None
85
+
86
+ args = [
87
+ GIT_COMMAND,
88
+ "describe",
89
+ "--tags",
90
+ "--dirty",
91
+ "--long",
92
+ "--abbrev=%d" % abbrev,
93
+ ]
94
+ return (
95
+ subprocess.check_output(args, cwd=wd, stderr=subprocess.DEVNULL)
96
+ .decode("ascii")
97
+ .strip()
98
+ )
99
+
100
+ except (OSError, subprocess.CalledProcessError):
85
101
  return None
86
102
 
87
103
 
88
- def format_git_describe(git_str, pep440=False):
104
+ def format_git_describe(git_str, public=False):
89
105
  """format the result of calling 'git describe' as a python version"""
90
106
  if git_str is None:
91
107
  return None
92
- if "-" not in git_str: # currently at a tag
93
- return git_str
108
+ splits = git_str.split("-")
109
+ if len(splits) == 4:
110
+ # Formatted as <tag>-<post>-<hash>-dirty
111
+ tag, post, h = splits[:3]
112
+ dirty = "-" + splits[3]
94
113
  else:
95
- # formatted as version-N-githash
96
- # want to convert to version.postN-githash
97
- git_str = git_str.replace("-", ".post", 1)
98
- if pep440: # does not allow git hash afterwards
99
- return git_str.split("-")[0]
100
- else:
101
- return git_str.replace("-g", "+git")
114
+ # Formatted as <tag>-<post>-<hash>
115
+ tag, post, h = splits
116
+ dirty = ""
117
+ if post == "0":
118
+ if public:
119
+ return tag
120
+ return tag + dirty
121
+
122
+ if public:
123
+ return "%s.post%s" % (tag, post)
124
+
125
+ return "%s.post%s-git%s%s" % (tag, post, h[1:], dirty)
102
126
 
103
127
 
104
128
  def read_info_version():
105
129
  """Read version information from INFO file"""
106
- try:
107
- with open(INFO_FILE, "r") as contents:
108
- return json.load(contents).get("metaflow_version")
109
- except IOError:
110
- return None
130
+ info_file = read_info_file()
131
+ if info_file:
132
+ return info_file.get("metaflow_version")
133
+ return None
111
134
 
112
135
 
113
- def get_version(pep440=False):
136
+ def get_version(public=False):
114
137
  """Tracks the version number.
115
138
 
116
- pep440: bool
117
- When True, this function returns a version string suitable for
118
- a release as defined by PEP 440. When False, the githash (if
119
- available) will be appended to the version string.
139
+ public: bool
140
+ When True, this function returns a *public* version specification which
141
+ doesn't include any local information (dirtiness or hash). See
142
+ https://packaging.python.org/en/latest/specifications/version-specifiers/#version-scheme
120
143
 
121
- If the script is located within an active git repository,
122
- git-describe is used to get the version information.
144
+ We first check the INFO file to see if we recorded a version of Metaflow. If there
145
+ is none, we check if we are in a GIT repository and if so, form the version
146
+ from that.
123
147
 
124
- Otherwise, the version logged by package installer is returned.
125
-
126
- If even that information isn't available (likely when executing on a
127
- remote cloud instance), the version information is returned from INFO file
128
- in the current directory.
148
+ Otherwise, we return the version of Metaflow that was installed.
129
149
 
130
150
  """
131
151
 
132
- version = format_git_describe(call_git_describe(), pep440=pep440)
133
- version_addl = None
134
- if version is None: # not a git repository
135
- import metaflow
136
-
152
+ global _version_cache
153
+
154
+ # To get the version we do the following:
155
+ # - Check if we have a cached version. If so, return that
156
+ # - Then check if we have an INFO file present. If so, use that as it is
157
+ # the most reliable way to get the version. In particular, when running remotely,
158
+ # metaflow is installed in a directory and if any extension is using distutils to
159
+ # determine its version, this would return None and querying the version directly
160
+ # from the extension would fail to produce the correct result
161
+ # - Then if we are in the GIT repository and if so, use the git describe
162
+ # - If we don't have an INFO file, we look at the version information that is
163
+ # populated by metaflow and the extensions.
164
+
165
+ if _version_cache[public] is not None:
166
+ return _version_cache[public]
167
+
168
+ version = (
169
+ read_info_version()
170
+ ) # Version info is cached in INFO file; includes extension info
171
+
172
+ if version:
173
+ _version_cache[public] = version
174
+ return version
175
+
176
+ # Get the version for Metaflow, favor the GIT version
177
+ import metaflow
178
+
179
+ version = format_git_describe(
180
+ call_git_describe(file_to_check=metaflow.__file__), public=public
181
+ )
182
+ if version is None:
137
183
  version = metaflow.__version__
138
- version_addl = metaflow.__version_addl__
139
- if version is None: # not a proper python package
140
- return read_info_version()
141
- if version_addl:
142
- return "+".join([version, version_addl])
184
+
185
+ # Look for extensions and compute their versions. Properly formed extensions have
186
+ # a toplevel file which will contain a __mf_extensions__ value and a __version__
187
+ # value. We already saved the properly formed modules when loading metaflow in
188
+ # __ext_tl_modules__.
189
+ ext_versions = []
190
+ for pkg_name, extension_module in metaflow.__ext_tl_modules__:
191
+ ext_name = getattr(extension_module, "__mf_extensions__", "<unk>")
192
+ ext_version = format_git_describe(
193
+ call_git_describe(file_to_check=extension_module.__file__), public=public
194
+ )
195
+ if ext_version is None:
196
+ ext_version = getattr(extension_module, "__version__", "<unk>")
197
+ # Update the package information about reported version for the extension
198
+ update_package_info(
199
+ package_name=pkg_name,
200
+ extension_name=ext_name,
201
+ package_version=ext_version,
202
+ )
203
+ ext_versions.append("%s(%s)" % (ext_name, ext_version))
204
+
205
+ # We now have all the information about extensions so we can form the final string
206
+ if ext_versions:
207
+ version = version + "+" + ";".join(ext_versions)
208
+ _version_cache[public] = version
143
209
  return version
metaflow/package.py CHANGED
@@ -10,7 +10,8 @@ from .extension_support import EXT_PKG, package_mfext_all
10
10
  from .metaflow_config import DEFAULT_PACKAGE_SUFFIXES
11
11
  from .exception import MetaflowException
12
12
  from .util import to_unicode
13
- from . import R, INFO_FILE
13
+ from . import R
14
+ from .info_file import INFO_FILE
14
15
 
15
16
  DEFAULT_SUFFIXES_LIST = DEFAULT_PACKAGE_SUFFIXES.split(",")
16
17
  METAFLOW_SUFFIXES_LIST = [".py", ".html", ".css", ".js"]
@@ -455,11 +455,17 @@ class ArgoWorkflows(object):
455
455
  )
456
456
  seen.add(norm)
457
457
 
458
- if param.kwargs.get("type") == JSONType or isinstance(
459
- param.kwargs.get("type"), FilePathClass
460
- ):
461
- # Special-case this to avoid touching core
458
+ extra_attrs = {}
459
+ if param.kwargs.get("type") == JSONType:
460
+ param_type = str(param.kwargs.get("type").name)
461
+ elif isinstance(param.kwargs.get("type"), FilePathClass):
462
462
  param_type = str(param.kwargs.get("type").name)
463
+ extra_attrs["is_text"] = getattr(
464
+ param.kwargs.get("type"), "_is_text", True
465
+ )
466
+ extra_attrs["encoding"] = getattr(
467
+ param.kwargs.get("type"), "_encoding", "utf-8"
468
+ )
463
469
  else:
464
470
  param_type = str(param.kwargs.get("type").__name__)
465
471
 
@@ -487,6 +493,7 @@ class ArgoWorkflows(object):
487
493
  type=param_type,
488
494
  description=param.kwargs.get("help"),
489
495
  is_required=is_required,
496
+ **extra_attrs
490
497
  )
491
498
  return parameters
492
499
 
@@ -2515,10 +2522,29 @@ class ArgoWorkflows(object):
2515
2522
  # Use all the affordances available to _parameters task
2516
2523
  executable = self.environment.executable("_parameters")
2517
2524
  run_id = "argo-{{workflow.name}}"
2518
- entrypoint = [executable, "-m metaflow.plugins.argo.daemon"]
2519
- heartbeat_cmds = "{entrypoint} --flow_name {flow_name} --run_id {run_id} {tags} heartbeat".format(
2525
+ script_name = os.path.basename(sys.argv[0])
2526
+ entrypoint = [executable, script_name]
2527
+ # FlowDecorators can define their own top-level options. These might affect run level information
2528
+ # so it is important to pass these to the heartbeat process as well, as it might be the first task to register a run.
2529
+ top_opts_dict = {}
2530
+ for deco in flow_decorators(self.flow):
2531
+ top_opts_dict.update(deco.get_top_level_options())
2532
+
2533
+ top_level = list(dict_to_cli_options(top_opts_dict)) + [
2534
+ "--quiet",
2535
+ "--metadata=%s" % self.metadata.TYPE,
2536
+ "--environment=%s" % self.environment.TYPE,
2537
+ "--datastore=%s" % self.flow_datastore.TYPE,
2538
+ "--datastore-root=%s" % self.flow_datastore.datastore_root,
2539
+ "--event-logger=%s" % self.event_logger.TYPE,
2540
+ "--monitor=%s" % self.monitor.TYPE,
2541
+ "--no-pylint",
2542
+ "--with=argo_workflows_internal:auto-emit-argo-events=%i"
2543
+ % self.auto_emit_argo_events,
2544
+ ]
2545
+ heartbeat_cmds = "{entrypoint} {top_level} argo-workflows heartbeat --run_id {run_id} {tags}".format(
2520
2546
  entrypoint=" ".join(entrypoint),
2521
- flow_name=self.flow.name,
2547
+ top_level=" ".join(top_level) if top_level else "",
2522
2548
  run_id=run_id,
2523
2549
  tags=" ".join(["--tag %s" % t for t in self.tags]) if self.tags else "",
2524
2550
  )
@@ -2569,12 +2595,16 @@ class ArgoWorkflows(object):
2569
2595
  "METAFLOW_SERVICE_URL": SERVICE_INTERNAL_URL,
2570
2596
  "METAFLOW_SERVICE_HEADERS": json.dumps(SERVICE_HEADERS),
2571
2597
  "METAFLOW_USER": "argo-workflows",
2598
+ "METAFLOW_DATASTORE_SYSROOT_S3": DATASTORE_SYSROOT_S3,
2599
+ "METAFLOW_DATATOOLS_S3ROOT": DATATOOLS_S3ROOT,
2572
2600
  "METAFLOW_DEFAULT_DATASTORE": self.flow_datastore.TYPE,
2573
2601
  "METAFLOW_DEFAULT_METADATA": DEFAULT_METADATA,
2602
+ "METAFLOW_CARD_S3ROOT": CARD_S3ROOT,
2574
2603
  "METAFLOW_KUBERNETES_WORKLOAD": 1,
2604
+ "METAFLOW_KUBERNETES_FETCH_EC2_METADATA": KUBERNETES_FETCH_EC2_METADATA,
2575
2605
  "METAFLOW_RUNTIME_ENVIRONMENT": "kubernetes",
2576
2606
  "METAFLOW_OWNER": self.username,
2577
- "METAFLOW_PRODUCTION_TOKEN": self.production_token,
2607
+ "METAFLOW_PRODUCTION_TOKEN": self.production_token, # Used in identity resolving. This affects system tags.
2578
2608
  }
2579
2609
  # support Metaflow sandboxes
2580
2610
  env["METAFLOW_INIT_SCRIPT"] = KUBERNETES_SANDBOX_INIT_SCRIPT
@@ -4,6 +4,7 @@ import platform
4
4
  import re
5
5
  import sys
6
6
  from hashlib import sha1
7
+ from time import sleep
7
8
 
8
9
  from metaflow import JSONType, Run, current, decorators, parameters
9
10
  from metaflow._vendor import click
@@ -959,6 +960,31 @@ def list_workflow_templates(obj, all=None):
959
960
  obj.echo_always(template_name)
960
961
 
961
962
 
963
+ # Internal CLI command to run a heartbeat daemon in an Argo Workflows Daemon container.
964
+ @argo_workflows.command(hidden=True, help="start heartbeat process for a run")
965
+ @click.option("--run_id", required=True)
966
+ @click.option(
967
+ "--tag",
968
+ "tags",
969
+ multiple=True,
970
+ default=None,
971
+ help="Annotate all objects produced by Argo Workflows runs "
972
+ "with the given tag. You can specify this option multiple "
973
+ "times to attach multiple tags.",
974
+ )
975
+ @click.pass_obj
976
+ def heartbeat(obj, run_id, tags=None):
977
+ # Try to register a run in case the start task has not taken care of it yet.
978
+ obj.metadata.register_run_id(run_id, tags)
979
+ # Start run heartbeat
980
+ obj.metadata.start_run_heartbeat(obj.flow.name, run_id)
981
+ # Keepalive loop
982
+ while True:
983
+ # Do not pollute daemon logs with anything unnecessary,
984
+ # as they might be extremely long running.
985
+ sleep(10)
986
+
987
+
962
988
  def validate_run_id(
963
989
  workflow_name, token_prefix, authorize, run_id, instructions_fn=None
964
990
  ):
@@ -12,7 +12,7 @@ from metaflow.metadata import MetaDatum
12
12
  from metaflow.metaflow_environment import InvalidEnvironmentException
13
13
  from metaflow.util import get_metaflow_root
14
14
 
15
- from ... import INFO_FILE
15
+ from ...info_file import INFO_FILE
16
16
 
17
17
 
18
18
  class CondaStepDecorator(StepDecorator):
metaflow/version.py CHANGED
@@ -1 +1 @@
1
- metaflow_version = "2.12.20"
1
+ metaflow_version = "2.12.22"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: metaflow
3
- Version: 2.12.20
3
+ Version: 2.12.22
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.20; extra == "stubs"
29
+ Requires-Dist: metaflow-stubs==2.12.22; 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,5 +1,5 @@
1
1
  metaflow/R.py,sha256=CqVfIatvmjciuICNnoyyNGrwE7Va9iXfLdFbQa52hwA,3958
2
- metaflow/__init__.py,sha256=1TJeSmvQVELJpVGh9s9GoB6aBIK0_zEl9mpW4eSz64Q,5971
2
+ metaflow/__init__.py,sha256=9pnkRH00bbtzaQDSxEspANaB3r6YDC37_EjX7GxtJug,5641
3
3
  metaflow/cards.py,sha256=tP1_RrtmqdFh741pqE4t98S7SA0MtGRlGvRICRZF1Mg,426
4
4
  metaflow/cli.py,sha256=fa6dx6F2PKtq0oeMxRM2efA9LyE108jGZQvYCtiUS94,34274
5
5
  metaflow/cli_args.py,sha256=lcgBGNTvfaiPxiUnejAe60Upt9swG6lRy1_3OqbU6MY,2616
@@ -10,20 +10,21 @@ 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=0-NnCKiX4oJ21Spb5PJkl2IvHoJxBcpi8CQrZEGB2Ag,27178
13
+ metaflow/flowspec.py,sha256=4w7Hm1aFSmVBOq0x9-LtFAq4eDjFb5F6Rdyh_NW41BQ,27270
14
14
  metaflow/graph.py,sha256=HFJ7V_bPSht_NHIm8BejrSqOX2fyBQpVOczRCliRw08,11975
15
15
  metaflow/includefile.py,sha256=yHczcZ_U0SrasxSNhZb3DIBzx8UZnrJCl3FzvpEQLOA,19753
16
+ metaflow/info_file.py,sha256=wtf2_F0M6dgiUu74AFImM8lfy5RrUw5Yj7Rgs2swKRY,686
16
17
  metaflow/integrations.py,sha256=LlsaoePRg03DjENnmLxZDYto3NwWc9z_PtU6nJxLldg,1480
17
18
  metaflow/lint.py,sha256=5rj1MlpluxyPTSINjtMoJ7viotyNzfjtBJSAihlAwMU,10870
18
- metaflow/metaflow_config.py,sha256=4PUd2-JWJs35SaobDgMg4RdpZaKjhEqPUFh2f0pjqnU,22853
19
+ metaflow/metaflow_config.py,sha256=cFd_RaZnxAWQX1kNEA-Y6HlxLvTpL-OM-d1M0CQoz_M,22852
19
20
  metaflow/metaflow_config_funcs.py,sha256=5GlvoafV6SxykwfL8D12WXSfwjBN_NsyuKE_Q3gjGVE,6738
20
21
  metaflow/metaflow_current.py,sha256=pC-EMnAsnvBLvLd61W6MvfiCKcboryeui9f6r8z_sg8,7161
21
- metaflow/metaflow_environment.py,sha256=D7zHYqe8aLZVwJ20nx19vmsNW29Kf3PVE8hbBjVQin8,8115
22
+ metaflow/metaflow_environment.py,sha256=rojFyGdyY56sN1HaEb1-0XX53Q3XPNnl0SaH-8xXZ8w,7987
22
23
  metaflow/metaflow_profile.py,sha256=jKPEW-hmAQO-htSxb9hXaeloLacAh41A35rMZH6G8pA,418
23
- metaflow/metaflow_version.py,sha256=mPQ6g_3XjNdi0NrxDzwlW8ZH0nMyYpwqmJ04P7TIdP0,4774
24
+ metaflow/metaflow_version.py,sha256=lOsrhMfHdKXBPS-673sZb48dK2FJhr34rIwLqMFeG9I,7291
24
25
  metaflow/monitor.py,sha256=T0NMaBPvXynlJAO_avKtk8OIIRMyEuMAyF8bIp79aZU,5323
25
26
  metaflow/multicore_utils.py,sha256=vdTNgczVLODifscUbbveJbuSDOl3Y9pAxhr7sqYiNf4,4760
26
- metaflow/package.py,sha256=sOvRpnvqVaQa6eR8lwcfb5HYCGqmpYFPm-cLgOEdl04,7377
27
+ metaflow/package.py,sha256=QutDP6WzjwGk1UCKXqBfXa9F10Q--FlRr0J7fwlple0,7399
27
28
  metaflow/parameters.py,sha256=l8qnhBG9C4wf_FkXWjq5sapUA6npLdR7pyB0PPQ-KF0,15712
28
29
  metaflow/procpoll.py,sha256=U2tE4iK_Mwj2WDyVTx_Uglh6xZ-jixQOo4wrM9OOhxg,2859
29
30
  metaflow/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -35,7 +36,7 @@ metaflow/tuple_util.py,sha256=_G5YIEhuugwJ_f6rrZoelMFak3DqAR2tt_5CapS1XTY,830
35
36
  metaflow/unbounded_foreach.py,sha256=p184WMbrMJ3xKYHwewj27ZhRUsSj_kw1jlye5gA9xJk,387
36
37
  metaflow/util.py,sha256=olAvJK3y1it_k99MhLulTaAJo7OFVt5rnrD-ulIFLCU,13616
37
38
  metaflow/vendor.py,sha256=FchtA9tH22JM-eEtJ2c9FpUdMn8sSb1VHuQS56EcdZk,5139
38
- metaflow/version.py,sha256=kleNZLjxgidWFnFZyqIbSBKOHUU3wMG2-4RRKGiPktc,29
39
+ metaflow/version.py,sha256=rnbTumj_lXNCrZgYvwtVWNwWj0XG3M0qQhmivZyGWG4,29
39
40
  metaflow/_vendor/__init__.py,sha256=y_CiwUD3l4eAKvTVDZeqgVujMy31cAM1qjAB-HfI-9s,353
40
41
  metaflow/_vendor/typing_extensions.py,sha256=0nUs5p1A_UrZigrAVBoOEM6TxU37zzPDUtiij1ZwpNc,110417
41
42
  metaflow/_vendor/zipp.py,sha256=ajztOH-9I7KA_4wqDYygtHa6xUBVZgFpmZ8FE74HHHI,8425
@@ -110,11 +111,11 @@ metaflow/_vendor/v3_6/importlib_metadata/_meta.py,sha256=_F48Hu_jFxkfKWz5wcYS8vO
110
111
  metaflow/_vendor/v3_6/importlib_metadata/_text.py,sha256=HCsFksZpJLeTP3NEk_ngrAeXVRRtTrtyh9eOABoRP4A,2166
111
112
  metaflow/_vendor/v3_6/importlib_metadata/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
113
  metaflow/client/__init__.py,sha256=1GtQB4Y_CBkzaxg32L1syNQSlfj762wmLrfrDxGi1b8,226
113
- metaflow/client/core.py,sha256=L4COrMyQgSAWkXIPoXFFQ3OqGnoHfePsBZdJQvShU1E,74162
114
+ metaflow/client/core.py,sha256=jnFTP_XLTLJCHRqaxGKYDn1ZwB_v6a1AAkULlLmJGOk,74171
114
115
  metaflow/client/filecache.py,sha256=Wy0yhhCqC1JZgebqi7z52GCwXYnkAqMZHTtxThvwBgM,15229
115
116
  metaflow/cmd/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
116
117
  metaflow/cmd/configure_cmd.py,sha256=o-DKnUf2FBo_HiMVyoyzQaGBSMtpbEPEdFTQZ0hkU-k,33396
117
- metaflow/cmd/main_cli.py,sha256=Yunxh0QFUPgGYMe0eeGrbmxtZGVdJPCgkd4P1x_j8B4,2950
118
+ metaflow/cmd/main_cli.py,sha256=E546zT_jYQKysmjwfpEgzZd5QMsyirs28M2s0OPU93E,2966
118
119
  metaflow/cmd/tutorials_cmd.py,sha256=8FdlKkicTOhCIDKcBR5b0Oz6giDvS-EMY3o9skIrRqw,5156
119
120
  metaflow/cmd/util.py,sha256=jS_0rUjOnGGzPT65fzRLdGjrYAOOLA4jU2S0HJLV0oc,406
120
121
  metaflow/cmd/develop/__init__.py,sha256=p1Sy8yU1MEKSrH5ttOWOZvNcI1qYu6J6jghdTHwPgOw,689
@@ -128,7 +129,7 @@ metaflow/datastore/exceptions.py,sha256=r7Ab5FvHIzyFh6kwiptA1lO5nLqWg0xRBoeYGefv
128
129
  metaflow/datastore/flow_datastore.py,sha256=kbJcOLYnvPHgJfZ_WWkD9LJSX1PHI1K6f9oVUu08A9U,10235
129
130
  metaflow/datastore/inputs.py,sha256=i43dXr2xvgtsgKMO9allgCR18bk80GeayeQFyUTH36w,449
130
131
  metaflow/datastore/task_datastore.py,sha256=-aDvRUUEaWq0chiGzCCSYmsTP8T-gjs0eowvy2Xyaug,36167
131
- metaflow/extension_support/__init__.py,sha256=FF-GdIkxiHSqdwCN5xkW5A0DQn3B8GpnphIFDBpLEr0,49432
132
+ metaflow/extension_support/__init__.py,sha256=2z0c4R8zsVmEFOMGT2Jujsl6xveDVa9KLll7moL58NE,52984
132
133
  metaflow/extension_support/_empty_file.py,sha256=HENjnM4uAfeNygxMB_feCCWORFoSat9n_QwzSx2oXPw,109
133
134
  metaflow/extension_support/cmd.py,sha256=hk8iBUUINqvKCDxInKgWpum8ThiRZtHSJP7qBASHzl8,5711
134
135
  metaflow/extension_support/integrations.py,sha256=AWAh-AZ-vo9IxuAVEjGw3s8p_NMm2DKHYx10oC51gPU,5506
@@ -174,12 +175,11 @@ metaflow/plugins/airflow/sensors/s3_sensor.py,sha256=iDReG-7FKnumrtQg-HY6cCUAAqN
174
175
  metaflow/plugins/argo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
175
176
  metaflow/plugins/argo/argo_client.py,sha256=MKKhMCbWOPzf6z5zQQiyDRHHkAXcO7ipboDZDqAAvOk,15849
176
177
  metaflow/plugins/argo/argo_events.py,sha256=_C1KWztVqgi3zuH57pInaE9OzABc2NnncC-zdwOMZ-w,5909
177
- metaflow/plugins/argo/argo_workflows.py,sha256=8YajGVnvBjGC22L308p8zMXzvfJP0fI9RsxGl46nO1k,170748
178
- metaflow/plugins/argo/argo_workflows_cli.py,sha256=X2j_F0xF8-K30ebM4dSLOTteDKXbr-jMN18oMpl5S6Y,36313
178
+ metaflow/plugins/argo/argo_workflows.py,sha256=W8lrMrtJQUCvJbzNyGcT3UVc1CJyMfGw1mrIm7U5ec4,172389
179
+ metaflow/plugins/argo/argo_workflows_cli.py,sha256=E_PyhOtxuS2F8DwhBANsRZCMxpeZ5rfID8eksfSOPm8,37231
179
180
  metaflow/plugins/argo/argo_workflows_decorator.py,sha256=yprszMdbE3rBTcEA9VR0IEnPjTprUauZBc4SBb-Q7sA,7878
180
181
  metaflow/plugins/argo/argo_workflows_deployer.py,sha256=wSSZtThn_VPvE_Wu6NB1L0Q86LmBJh9g009v_lpvBPM,8125
181
182
  metaflow/plugins/argo/capture_error.py,sha256=Ys9dscGrTpW-ZCirLBU0gD9qBM0BjxyxGlUMKcwewQc,1852
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
@@ -291,7 +291,7 @@ metaflow/plugins/metadata/local.py,sha256=YhLJC5zjVJrvQFIyQ92ZBByiUmhCC762RUX7IT
291
291
  metaflow/plugins/metadata/service.py,sha256=ihq5F7KQZlxvYwzH_-jyP2aWN_I96i2vp92j_d697s8,20204
292
292
  metaflow/plugins/pypi/__init__.py,sha256=0YFZpXvX7HCkyBFglatual7XGifdA1RwC3U4kcizyak,1037
293
293
  metaflow/plugins/pypi/bootstrap.py,sha256=LSRjpDhtpvYqCC0lDPIWvZsrHW7PXx626NDzdYjFeVM,5224
294
- metaflow/plugins/pypi/conda_decorator.py,sha256=sIAfvkjhUfTMK5yLrPOkqxeLUhlC5Buqmc9aITaf22E,15686
294
+ metaflow/plugins/pypi/conda_decorator.py,sha256=fPeXxvmg51oSFTnlguNlcWUIdXHA9OuMnp9ElaxQPFo,15695
295
295
  metaflow/plugins/pypi/conda_environment.py,sha256=--q-8lypKupCdGsASpqABNpNqRxtQi6UCDgq8iHDFe4,19476
296
296
  metaflow/plugins/pypi/micromamba.py,sha256=67FiIZZz0Kig9EcN7bZLObsE6Z1MFyo4Dp93fd3Grcc,12178
297
297
  metaflow/plugins/pypi/pip.py,sha256=7B06mPOs5MvY33xbzPVYZlBr1iKMYaN-n8uulL9zSVg,13649
@@ -345,9 +345,9 @@ metaflow/tutorials/07-worldview/README.md,sha256=5vQTrFqulJ7rWN6r20dhot9lI2sVj9W
345
345
  metaflow/tutorials/07-worldview/worldview.ipynb,sha256=ztPZPI9BXxvW1QdS2Tfe7LBuVzvFvv0AToDnsDJhLdE,2237
346
346
  metaflow/tutorials/08-autopilot/README.md,sha256=GnePFp_q76jPs991lMUqfIIh5zSorIeWznyiUxzeUVE,1039
347
347
  metaflow/tutorials/08-autopilot/autopilot.ipynb,sha256=DQoJlILV7Mq9vfPBGW-QV_kNhWPjS5n6SJLqePjFYLY,3191
348
- metaflow-2.12.20.dist-info/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
349
- metaflow-2.12.20.dist-info/METADATA,sha256=oHu5racxyaljUQCl6cYXqYOHkI02VB5mR298txO9lAk,5906
350
- metaflow-2.12.20.dist-info/WHEEL,sha256=AHX6tWk3qWuce7vKLrj7lnulVHEdWoltgauo8bgCXgU,109
351
- metaflow-2.12.20.dist-info/entry_points.txt,sha256=IKwTN1T3I5eJL3uo_vnkyxVffcgnRdFbKwlghZfn27k,57
352
- metaflow-2.12.20.dist-info/top_level.txt,sha256=v1pDHoWaSaKeuc5fKTRSfsXCKSdW1zvNVmvA-i0if3o,9
353
- metaflow-2.12.20.dist-info/RECORD,,
348
+ metaflow-2.12.22.dist-info/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
349
+ metaflow-2.12.22.dist-info/METADATA,sha256=XI-AUDFfekPRYdOciqofewvcuPCSZx7a5lZd58nU2Rw,5906
350
+ metaflow-2.12.22.dist-info/WHEEL,sha256=AHX6tWk3qWuce7vKLrj7lnulVHEdWoltgauo8bgCXgU,109
351
+ metaflow-2.12.22.dist-info/entry_points.txt,sha256=IKwTN1T3I5eJL3uo_vnkyxVffcgnRdFbKwlghZfn27k,57
352
+ metaflow-2.12.22.dist-info/top_level.txt,sha256=v1pDHoWaSaKeuc5fKTRSfsXCKSdW1zvNVmvA-i0if3o,9
353
+ metaflow-2.12.22.dist-info/RECORD,,
@@ -1,59 +0,0 @@
1
- from collections import namedtuple
2
- from time import sleep
3
- from metaflow.metaflow_config import DEFAULT_METADATA
4
- from metaflow.metaflow_environment import MetaflowEnvironment
5
- from metaflow.plugins import METADATA_PROVIDERS
6
- from metaflow._vendor import click
7
-
8
-
9
- class CliState:
10
- pass
11
-
12
-
13
- @click.group()
14
- @click.option("--flow_name", required=True)
15
- @click.option("--run_id", required=True)
16
- @click.option(
17
- "--tag",
18
- "tags",
19
- multiple=True,
20
- default=None,
21
- help="Annotate all objects produced by Argo Workflows runs "
22
- "with the given tag. You can specify this option multiple "
23
- "times to attach multiple tags.",
24
- )
25
- @click.pass_context
26
- def cli(ctx, flow_name, run_id, tags=None):
27
- ctx.obj = CliState()
28
- ctx.obj.flow_name = flow_name
29
- ctx.obj.run_id = run_id
30
- ctx.obj.tags = tags
31
- # Use a dummy flow to initialize the environment and metadata service,
32
- # as we only need a name for the flow object.
33
- flow = namedtuple("DummyFlow", "name")
34
- dummyflow = flow(flow_name)
35
-
36
- # Initialize a proper metadata service instance
37
- environment = MetaflowEnvironment(dummyflow)
38
-
39
- ctx.obj.metadata = [m for m in METADATA_PROVIDERS if m.TYPE == DEFAULT_METADATA][0](
40
- environment, dummyflow, None, None
41
- )
42
-
43
-
44
- @cli.command(help="start heartbeat process for a run")
45
- @click.pass_obj
46
- def heartbeat(obj):
47
- # Try to register a run in case the start task has not taken care of it yet.
48
- obj.metadata.register_run_id(obj.run_id, obj.tags)
49
- # Start run heartbeat
50
- obj.metadata.start_run_heartbeat(obj.flow_name, obj.run_id)
51
- # Keepalive loop
52
- while True:
53
- # Do not pollute daemon logs with anything unnecessary,
54
- # as they might be extremely long running.
55
- sleep(10)
56
-
57
-
58
- if __name__ == "__main__":
59
- cli()