ob-metaflow 2.12.30.2__py2.py3-none-any.whl → 2.13.6.1__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.

Potentially problematic release.


This version of ob-metaflow might be problematic. Click here for more details.

Files changed (96) hide show
  1. metaflow/__init__.py +3 -0
  2. metaflow/cards.py +1 -0
  3. metaflow/cli.py +185 -717
  4. metaflow/cli_args.py +17 -0
  5. metaflow/cli_components/__init__.py +0 -0
  6. metaflow/cli_components/dump_cmd.py +96 -0
  7. metaflow/cli_components/init_cmd.py +51 -0
  8. metaflow/cli_components/run_cmds.py +362 -0
  9. metaflow/cli_components/step_cmd.py +176 -0
  10. metaflow/cli_components/utils.py +140 -0
  11. metaflow/cmd/develop/stub_generator.py +9 -2
  12. metaflow/datastore/flow_datastore.py +2 -2
  13. metaflow/decorators.py +63 -2
  14. metaflow/exception.py +8 -2
  15. metaflow/extension_support/plugins.py +42 -27
  16. metaflow/flowspec.py +176 -23
  17. metaflow/graph.py +28 -27
  18. metaflow/includefile.py +50 -22
  19. metaflow/lint.py +35 -20
  20. metaflow/metadata_provider/heartbeat.py +23 -8
  21. metaflow/metaflow_config.py +10 -1
  22. metaflow/multicore_utils.py +31 -14
  23. metaflow/package.py +17 -3
  24. metaflow/parameters.py +97 -25
  25. metaflow/plugins/__init__.py +22 -0
  26. metaflow/plugins/airflow/airflow.py +18 -17
  27. metaflow/plugins/airflow/airflow_cli.py +1 -0
  28. metaflow/plugins/argo/argo_client.py +0 -2
  29. metaflow/plugins/argo/argo_workflows.py +195 -132
  30. metaflow/plugins/argo/argo_workflows_cli.py +1 -1
  31. metaflow/plugins/argo/argo_workflows_decorator.py +2 -4
  32. metaflow/plugins/argo/argo_workflows_deployer_objects.py +51 -9
  33. metaflow/plugins/argo/jobset_input_paths.py +0 -1
  34. metaflow/plugins/aws/aws_utils.py +6 -1
  35. metaflow/plugins/aws/batch/batch_client.py +1 -3
  36. metaflow/plugins/aws/batch/batch_decorator.py +13 -13
  37. metaflow/plugins/aws/secrets_manager/aws_secrets_manager_secrets_provider.py +13 -10
  38. metaflow/plugins/aws/step_functions/dynamo_db_client.py +0 -3
  39. metaflow/plugins/aws/step_functions/production_token.py +1 -1
  40. metaflow/plugins/aws/step_functions/step_functions.py +33 -1
  41. metaflow/plugins/aws/step_functions/step_functions_cli.py +1 -1
  42. metaflow/plugins/aws/step_functions/step_functions_decorator.py +0 -1
  43. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +7 -9
  44. metaflow/plugins/cards/card_cli.py +7 -2
  45. metaflow/plugins/cards/card_creator.py +1 -0
  46. metaflow/plugins/cards/card_decorator.py +79 -8
  47. metaflow/plugins/cards/card_modules/basic.py +56 -5
  48. metaflow/plugins/cards/card_modules/card.py +16 -1
  49. metaflow/plugins/cards/card_modules/components.py +64 -16
  50. metaflow/plugins/cards/card_modules/main.js +27 -25
  51. metaflow/plugins/cards/card_modules/test_cards.py +4 -4
  52. metaflow/plugins/cards/component_serializer.py +1 -1
  53. metaflow/plugins/datatools/s3/s3.py +12 -4
  54. metaflow/plugins/datatools/s3/s3op.py +3 -3
  55. metaflow/plugins/events_decorator.py +338 -186
  56. metaflow/plugins/kubernetes/kube_utils.py +84 -1
  57. metaflow/plugins/kubernetes/kubernetes.py +40 -92
  58. metaflow/plugins/kubernetes/kubernetes_cli.py +32 -7
  59. metaflow/plugins/kubernetes/kubernetes_decorator.py +76 -4
  60. metaflow/plugins/kubernetes/kubernetes_job.py +23 -20
  61. metaflow/plugins/kubernetes/kubernetes_jobsets.py +41 -20
  62. metaflow/plugins/kubernetes/spot_metadata_cli.py +69 -0
  63. metaflow/plugins/kubernetes/spot_monitor_sidecar.py +109 -0
  64. metaflow/plugins/parallel_decorator.py +4 -1
  65. metaflow/plugins/project_decorator.py +33 -5
  66. metaflow/plugins/pypi/bootstrap.py +249 -81
  67. metaflow/plugins/pypi/conda_decorator.py +20 -10
  68. metaflow/plugins/pypi/conda_environment.py +83 -27
  69. metaflow/plugins/pypi/micromamba.py +82 -37
  70. metaflow/plugins/pypi/pip.py +9 -6
  71. metaflow/plugins/pypi/pypi_decorator.py +11 -9
  72. metaflow/plugins/pypi/utils.py +4 -2
  73. metaflow/plugins/timeout_decorator.py +2 -2
  74. metaflow/runner/click_api.py +240 -50
  75. metaflow/runner/deployer.py +1 -1
  76. metaflow/runner/deployer_impl.py +12 -11
  77. metaflow/runner/metaflow_runner.py +68 -34
  78. metaflow/runner/nbdeploy.py +2 -0
  79. metaflow/runner/nbrun.py +1 -1
  80. metaflow/runner/subprocess_manager.py +61 -10
  81. metaflow/runner/utils.py +208 -44
  82. metaflow/runtime.py +216 -112
  83. metaflow/sidecar/sidecar_worker.py +1 -1
  84. metaflow/tracing/tracing_modules.py +4 -1
  85. metaflow/user_configs/__init__.py +0 -0
  86. metaflow/user_configs/config_decorators.py +563 -0
  87. metaflow/user_configs/config_options.py +548 -0
  88. metaflow/user_configs/config_parameters.py +436 -0
  89. metaflow/util.py +22 -0
  90. metaflow/version.py +1 -1
  91. {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/METADATA +12 -3
  92. {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/RECORD +96 -84
  93. {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/WHEEL +1 -1
  94. {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/LICENSE +0 -0
  95. {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/entry_points.txt +0 -0
  96. {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,15 @@
1
+ import functools
1
2
  import json
2
3
  import os
4
+ import re
3
5
  import subprocess
4
6
  import tempfile
7
+ import time
5
8
 
6
9
  from metaflow.exception import MetaflowException
7
10
  from metaflow.util import which
8
11
 
9
- from .utils import conda_platform
12
+ from .utils import MICROMAMBA_MIRROR_URL, MICROMAMBA_URL, conda_platform
10
13
 
11
14
 
12
15
  class MicromambaException(MetaflowException):
@@ -19,8 +22,13 @@ class MicromambaException(MetaflowException):
19
22
  super(MicromambaException, self).__init__(msg)
20
23
 
21
24
 
25
+ GLIBC_VERSION = os.environ.get("CONDA_OVERRIDE_GLIBC", "2.38")
26
+
27
+ _double_equal_match = re.compile("==(?=[<=>!~])")
28
+
29
+
22
30
  class Micromamba(object):
23
- def __init__(self):
31
+ def __init__(self, logger=None):
24
32
  # micromamba is a tiny version of the mamba package manager and comes with
25
33
  # metaflow specific performance enhancements.
26
34
 
@@ -33,6 +41,12 @@ class Micromamba(object):
33
41
  os.path.expanduser(_home),
34
42
  "micromamba",
35
43
  )
44
+
45
+ if logger:
46
+ self.logger = logger
47
+ else:
48
+ self.logger = lambda *args, **kwargs: None # No-op logger if not provided
49
+
36
50
  self.bin = (
37
51
  which(os.environ.get("METAFLOW_PATH_TO_MICROMAMBA") or "micromamba")
38
52
  or which("./micromamba") # to support remote execution
@@ -70,6 +84,9 @@ class Micromamba(object):
70
84
  "MAMBA_ADD_PIP_AS_PYTHON_DEPENDENCY": "true",
71
85
  "CONDA_SUBDIR": platform,
72
86
  # "CONDA_UNSATISFIABLE_HINTS_CHECK_DEPTH": "0" # https://github.com/conda/conda/issues/9862
87
+ # Add a default glibc version for linux-64 environments (ignored for other platforms)
88
+ # TODO: Make the version configurable
89
+ "CONDA_OVERRIDE_GLIBC": GLIBC_VERSION,
73
90
  }
74
91
  cmd = [
75
92
  "create",
@@ -78,6 +95,7 @@ class Micromamba(object):
78
95
  "--dry-run",
79
96
  "--no-extra-safety-checks",
80
97
  "--repodata-ttl=86400",
98
+ "--safety-checks=disabled",
81
99
  "--retry-clean-cache",
82
100
  "--prefix=%s/prefix" % tmp_dir,
83
101
  ]
@@ -86,15 +104,17 @@ class Micromamba(object):
86
104
  cmd.append("--channel=%s" % channel)
87
105
 
88
106
  for package, version in packages.items():
89
- cmd.append("%s==%s" % (package, version))
107
+ version_string = "%s==%s" % (package, version)
108
+ cmd.append(_double_equal_match.sub("", version_string))
90
109
  if python:
91
110
  cmd.append("python==%s" % python)
92
111
  # TODO: Ensure a human readable message is returned when the environment
93
112
  # can't be resolved for any and all reasons.
94
- return [
113
+ solved_packages = [
95
114
  {k: v for k, v in item.items() if k in ["url"]}
96
115
  for item in self._call(cmd, env)["actions"]["LINK"]
97
116
  ]
117
+ return solved_packages
98
118
 
99
119
  def download(self, id_, packages, python, platform):
100
120
  # Unfortunately all the packages need to be catalogued in package cache
@@ -103,8 +123,6 @@ class Micromamba(object):
103
123
  # Micromamba is painfully slow in determining if many packages are infact
104
124
  # already cached. As a perf heuristic, we check if the environment already
105
125
  # exists to short circuit package downloads.
106
- if self.path_to_environment(id_, platform):
107
- return
108
126
 
109
127
  prefix = "{env_dirs}/{keyword}/{platform}/{id}".format(
110
128
  env_dirs=self.info()["envs_dirs"][0],
@@ -113,13 +131,18 @@ class Micromamba(object):
113
131
  id=id_,
114
132
  )
115
133
 
116
- # Another forced perf heuristic to skip cross-platform downloads.
134
+ # cheap check
117
135
  if os.path.exists(f"{prefix}/fake.done"):
118
136
  return
119
137
 
138
+ # somewhat expensive check
139
+ if self.path_to_environment(id_, platform):
140
+ return
141
+
120
142
  with tempfile.TemporaryDirectory() as tmp_dir:
121
143
  env = {
122
144
  "CONDA_SUBDIR": platform,
145
+ "CONDA_OVERRIDE_GLIBC": GLIBC_VERSION,
123
146
  }
124
147
  cmd = [
125
148
  "create",
@@ -159,6 +182,7 @@ class Micromamba(object):
159
182
  # use hardlinks when possible, otherwise copy files
160
183
  # disabled for now since it adds to environment creation latencies
161
184
  "CONDA_ALLOW_SOFTLINKS": "0",
185
+ "CONDA_OVERRIDE_GLIBC": GLIBC_VERSION,
162
186
  }
163
187
  cmd = [
164
188
  "create",
@@ -174,6 +198,7 @@ class Micromamba(object):
174
198
  cmd.append("{url}".format(**package))
175
199
  self._call(cmd, env)
176
200
 
201
+ @functools.lru_cache(maxsize=None)
177
202
  def info(self):
178
203
  return self._call(["config", "list", "-a"])
179
204
 
@@ -198,18 +223,24 @@ class Micromamba(object):
198
223
  }
199
224
  directories = self.info()["pkgs_dirs"]
200
225
  # search all package caches for packages
201
- metadata = {
202
- url: os.path.join(d, file)
226
+
227
+ file_to_path = {}
228
+ for d in directories:
229
+ if os.path.isdir(d):
230
+ try:
231
+ with os.scandir(d) as entries:
232
+ for entry in entries:
233
+ if entry.is_file():
234
+ # Prefer the first occurrence if the file exists in multiple directories
235
+ file_to_path.setdefault(entry.name, entry.path)
236
+ except OSError:
237
+ continue
238
+ ret = {
239
+ # set package tarball local paths to None if package tarballs are missing
240
+ url: file_to_path.get(file)
203
241
  for url, file in packages_to_filenames.items()
204
- for d in directories
205
- if os.path.isdir(d)
206
- and file in os.listdir(d)
207
- and os.path.isfile(os.path.join(d, file))
208
242
  }
209
- # set package tarball local paths to None if package tarballs are missing
210
- for url in packages_to_filenames:
211
- metadata.setdefault(url, None)
212
- return metadata
243
+ return ret
213
244
 
214
245
  def interpreter(self, id_):
215
246
  return os.path.join(self.path_to_environment(id_), "bin/python")
@@ -296,7 +327,7 @@ class Micromamba(object):
296
327
  stderr="\n".join(err),
297
328
  )
298
329
  )
299
- except (TypeError, ValueError) as ve:
330
+ except (TypeError, ValueError):
300
331
  pass
301
332
  raise MicromambaException(
302
333
  msg.format(
@@ -312,23 +343,37 @@ def _install_micromamba(installation_location):
312
343
  # Unfortunately no 32bit binaries are available for micromamba, which ideally
313
344
  # shouldn't be much of a problem in today's world.
314
345
  platform = conda_platform()
315
- try:
316
- subprocess.Popen(f"mkdir -p {installation_location}", shell=True).wait()
317
- # https://mamba.readthedocs.io/en/latest/micromamba-installation.html#manual-installation
318
- # requires bzip2
319
- result = subprocess.Popen(
320
- f"curl -Ls https://micro.mamba.pm/api/micromamba/{platform}/1.5.7 | tar -xvj -C {installation_location} bin/micromamba",
321
- shell=True,
322
- stderr=subprocess.PIPE,
323
- stdout=subprocess.PIPE,
324
- )
325
- _, err = result.communicate()
326
- if result.returncode != 0:
327
- raise MicromambaException(
328
- f"Micromamba installation '{result.args}' failed:\n{err.decode()}"
329
- )
346
+ url = MICROMAMBA_URL.format(platform=platform, version="1.5.7")
347
+ mirror_url = MICROMAMBA_MIRROR_URL.format(platform=platform, version="1.5.7")
348
+ os.makedirs(installation_location, exist_ok=True)
330
349
 
331
- except subprocess.CalledProcessError as e:
332
- raise MicromambaException(
333
- "Micromamba installation failed:\n{}".format(e.stderr.decode())
334
- )
350
+ def _download_and_extract(url):
351
+ max_retries = 3
352
+ for attempt in range(max_retries):
353
+ try:
354
+ # https://mamba.readthedocs.io/en/latest/micromamba-installation.html#manual-installation
355
+ # requires bzip2
356
+ result = subprocess.Popen(
357
+ f"curl -Ls {url} | tar -xvj -C {installation_location} bin/micromamba",
358
+ shell=True,
359
+ stderr=subprocess.PIPE,
360
+ stdout=subprocess.PIPE,
361
+ )
362
+ _, err = result.communicate()
363
+ if result.returncode != 0:
364
+ raise MicromambaException(
365
+ f"Micromamba installation '{result.args}' failed:\n{err.decode()}"
366
+ )
367
+ except subprocess.CalledProcessError as e:
368
+ if attempt == max_retries - 1:
369
+ raise MicromambaException(
370
+ "Micromamba installation failed:\n{}".format(e.stderr.decode())
371
+ )
372
+ time.sleep(2**attempt)
373
+
374
+ try:
375
+ # prioritize downloading from mirror
376
+ _download_and_extract(mirror_url)
377
+ except Exception:
378
+ # download from official source as a fallback
379
+ _download_and_extract(url)
@@ -50,10 +50,14 @@ INSTALLATION_MARKER = "{prefix}/.pip/id"
50
50
 
51
51
 
52
52
  class Pip(object):
53
- def __init__(self, micromamba=None):
53
+ def __init__(self, micromamba=None, logger=None):
54
54
  # pip is assumed to be installed inside a conda environment managed by
55
55
  # micromamba. pip commands are executed using `micromamba run --prefix`
56
- self.micromamba = micromamba or Micromamba()
56
+ self.micromamba = micromamba or Micromamba(logger)
57
+ if logger:
58
+ self.logger = logger
59
+ else:
60
+ self.logger = lambda *args, **kwargs: None # No-op logger if not provided
57
61
 
58
62
  def solve(self, id_, packages, python, platform):
59
63
  prefix = self.micromamba.path_to_environment(id_)
@@ -102,9 +106,8 @@ class Pip(object):
102
106
  except PipPackageNotFound as ex:
103
107
  # pretty print package errors
104
108
  raise PipException(
105
- "Could not find a binary distribution for %s \n"
106
- "for the platform %s\n\n"
107
- "Note that ***@pypi*** does not currently support source distributions"
109
+ "Unable to find a binary distribution compatible with %s for %s.\n\n"
110
+ "Note: ***@pypi*** does not currently support source distributions"
108
111
  % (ex.package_spec, platform)
109
112
  )
110
113
 
@@ -123,7 +126,7 @@ class Pip(object):
123
126
  **res,
124
127
  subdir_str=(
125
128
  "#subdirectory=%s" % subdirectory if subdirectory else ""
126
- )
129
+ ),
127
130
  )
128
131
  # used to deduplicate the storage location in case wheel does not
129
132
  # build with enough unique identifiers.
@@ -25,9 +25,10 @@ class PyPIStepDecorator(StepDecorator):
25
25
  defaults = {"packages": {}, "python": None, "disabled": None} # wheels
26
26
 
27
27
  def __init__(self, attributes=None, statically_defined=False):
28
- self._user_defined_attributes = (
29
- attributes.copy() if attributes is not None else {}
28
+ self._attributes_with_user_values = (
29
+ set(attributes.keys()) if attributes is not None else set()
30
30
  )
31
+
31
32
  super().__init__(attributes, statically_defined)
32
33
 
33
34
  def step_init(self, flow, graph, step, decos, environment, flow_datastore, logger):
@@ -42,10 +43,9 @@ class PyPIStepDecorator(StepDecorator):
42
43
  if "pypi_base" in self.flow._flow_decorators:
43
44
  pypi_base = self.flow._flow_decorators["pypi_base"][0]
44
45
  super_attributes = pypi_base.attributes
45
- self._user_defined_attributes = {
46
- **self._user_defined_attributes,
47
- **pypi_base._user_defined_attributes,
48
- }
46
+ self._attributes_with_user_values.update(
47
+ pypi_base._attributes_with_user_values
48
+ )
49
49
  self.attributes["packages"] = {
50
50
  **super_attributes["packages"],
51
51
  **self.attributes["packages"],
@@ -106,7 +106,7 @@ class PyPIStepDecorator(StepDecorator):
106
106
  environment.set_local_root(LocalStorage.get_datastore_root_from_config(logger))
107
107
 
108
108
  def is_attribute_user_defined(self, name):
109
- return name in self._user_defined_attributes
109
+ return name in self._attributes_with_user_values
110
110
 
111
111
 
112
112
  class PyPIFlowDecorator(FlowDecorator):
@@ -129,9 +129,10 @@ class PyPIFlowDecorator(FlowDecorator):
129
129
  defaults = {"packages": {}, "python": None, "disabled": None}
130
130
 
131
131
  def __init__(self, attributes=None, statically_defined=False):
132
- self._user_defined_attributes = (
133
- attributes.copy() if attributes is not None else {}
132
+ self._attributes_with_user_values = (
133
+ set(attributes.keys()) if attributes is not None else set()
134
134
  )
135
+
135
136
  super().__init__(attributes, statically_defined)
136
137
 
137
138
  def flow_init(
@@ -140,6 +141,7 @@ class PyPIFlowDecorator(FlowDecorator):
140
141
  from metaflow import decorators
141
142
 
142
143
  decorators._attach_decorators(flow, ["pypi"])
144
+ decorators._init(flow)
143
145
 
144
146
  # @pypi uses a conda environment to create a virtual environment.
145
147
  # The conda environment can be created through micromamba.
@@ -1,4 +1,3 @@
1
- import os
2
1
  import platform
3
2
  import sys
4
3
 
@@ -17,10 +16,13 @@ else:
17
16
  from metaflow._vendor.packaging import tags
18
17
  from metaflow._vendor.packaging.utils import parse_wheel_filename
19
18
 
20
- from urllib.parse import unquote, urlparse
19
+ from urllib.parse import unquote
21
20
 
22
21
  from metaflow.exception import MetaflowException
23
22
 
23
+ MICROMAMBA_URL = "https://micro.mamba.pm/api/micromamba/{platform}/{version}"
24
+ MICROMAMBA_MIRROR_URL = "https://micromamba.outerbounds.sh/{platform}/{version}.tar.bz2"
25
+
24
26
 
25
27
  def conda_platform():
26
28
  # Returns the conda platform for the Python interpreter
@@ -37,8 +37,8 @@ class TimeoutDecorator(StepDecorator):
37
37
  name = "timeout"
38
38
  defaults = {"seconds": 0, "minutes": 0, "hours": 0}
39
39
 
40
- def __init__(self, *args, **kwargs):
41
- super(TimeoutDecorator, self).__init__(*args, **kwargs)
40
+ def init(self):
41
+ super().init()
42
42
  # Initialize secs in __init__ so other decorators could safely use this
43
43
  # value without worrying about decorator order.
44
44
  # Convert values in attributes to type:int since they can be type:str