ob-metaflow-extensions 1.1.86__tar.gz → 1.1.88__tar.gz

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-extensions might be problematic. Click here for more details.

Files changed (44) hide show
  1. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/PKG-INFO +1 -1
  2. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/__init__.py +2 -2
  3. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/fast_bakery/docker_environment.py +92 -36
  4. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery.py +14 -0
  5. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/ob_metaflow_extensions.egg-info/PKG-INFO +1 -1
  6. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/setup.py +1 -1
  7. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/README.md +0 -0
  8. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/__init__.py +0 -0
  9. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/config/__init__.py +0 -0
  10. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/auth_server.py +0 -0
  11. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/fast_bakery/__init__.py +0 -0
  12. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery_cli.py +0 -0
  13. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery_decorator.py +0 -0
  14. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/kubernetes/__init__.py +0 -0
  15. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/kubernetes/kubernetes_client.py +0 -0
  16. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/nim/__init__.py +0 -0
  17. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/nim/nim_manager.py +0 -0
  18. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/nvcf/__init__.py +0 -0
  19. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/nvcf/heartbeat_store.py +0 -0
  20. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/nvcf/nvcf.py +0 -0
  21. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/nvcf/nvcf_cli.py +0 -0
  22. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/nvcf/nvcf_decorator.py +0 -0
  23. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/perimeters.py +0 -0
  24. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/snowpark/__init__.py +0 -0
  25. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark.py +0 -0
  26. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_cli.py +0 -0
  27. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_client.py +0 -0
  28. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_decorator.py +0 -0
  29. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_exceptions.py +0 -0
  30. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_job.py +0 -0
  31. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_service_spec.py +0 -0
  32. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/profilers/__init__.py +0 -0
  33. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/profilers/gpu.py +0 -0
  34. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/remote_config.py +0 -0
  35. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/toplevel/__init__.py +0 -0
  36. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/toplevel/global_aliases_for_metaflow_package.py +0 -0
  37. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/toplevel/plugins/azure/__init__.py +0 -0
  38. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/toplevel/plugins/gcp/__init__.py +0 -0
  39. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/metaflow_extensions/outerbounds/toplevel/plugins/kubernetes/__init__.py +0 -0
  40. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/ob_metaflow_extensions.egg-info/SOURCES.txt +0 -0
  41. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/ob_metaflow_extensions.egg-info/dependency_links.txt +0 -0
  42. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/ob_metaflow_extensions.egg-info/requires.txt +0 -0
  43. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/ob_metaflow_extensions.egg-info/top_level.txt +0 -0
  44. {ob-metaflow-extensions-1.1.86 → ob-metaflow-extensions-1.1.88}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ob-metaflow-extensions
3
- Version: 1.1.86
3
+ Version: 1.1.88
4
4
  Summary: Outerbounds Platform Extensions for Metaflow
5
5
  Author: Outerbounds, Inc.
6
6
  License: Commercial
@@ -104,7 +104,7 @@ def get_boto3_session(role_arn=None, session_vars=None):
104
104
  tmp_aws_config_file = f.name
105
105
  os.rename(tmp_aws_config_file, aws_config_file)
106
106
  os.environ["AWS_CONFIG_FILE"] = aws_config_file
107
- os.environ["AWS_DEFAULT_PROFILE"] = "cspr"
107
+ os.environ["AWS_PROFILE"] = "cspr"
108
108
  else:
109
109
  os.environ["AWS_WEB_IDENTITY_TOKEN_FILE"] = token_file
110
110
  os.environ["AWS_ROLE_ARN"] = token_info["role_arn"]
@@ -122,7 +122,7 @@ def get_boto3_session(role_arn=None, session_vars=None):
122
122
  # AWS_CONFIG_FILE environment variable above.
123
123
  if role_arn == USE_CSPR_ROLE_ARN_IF_SET:
124
124
  # Otherwise start from the default profile, assuming CSPR role
125
- session = boto3.session.Session(profile_name="default")
125
+ session = boto3.session.Session(profile_name="cspr")
126
126
  else:
127
127
  session = boto3.session.Session(profile_name="task")
128
128
  else:
@@ -1,30 +1,30 @@
1
1
  import hashlib
2
2
  import json
3
3
  import os
4
-
4
+ import threading
5
+ import time
6
+ import uuid
5
7
  from concurrent.futures import ThreadPoolExecutor
6
8
  from typing import Dict
9
+
7
10
  from metaflow.exception import MetaflowException
8
- from metaflow.metaflow_config import (
9
- FAST_BAKERY_URL,
10
- get_pinned_conda_libs,
11
- )
11
+ from metaflow.metaflow_config import FAST_BAKERY_URL, get_pinned_conda_libs
12
12
  from metaflow.metaflow_environment import MetaflowEnvironment
13
- from metaflow.plugins.pypi.conda_environment import CondaEnvironment
14
- from .fast_bakery import FastBakery, FastBakeryApiResponse, FastBakeryException
15
13
  from metaflow.plugins.aws.batch.batch_decorator import BatchDecorator
16
14
  from metaflow.plugins.kubernetes.kubernetes_decorator import KubernetesDecorator
17
15
  from metaflow.plugins.pypi.conda_decorator import CondaStepDecorator
16
+ from metaflow.plugins.pypi.conda_environment import CondaEnvironment
18
17
  from metaflow.plugins.pypi.pypi_decorator import PyPIStepDecorator
19
18
 
19
+ from .fast_bakery import FastBakery, FastBakeryApiResponse, FastBakeryException
20
+
20
21
  BAKERY_METAFILE = ".imagebakery-cache"
21
22
 
23
+ import fcntl
22
24
  import json
23
25
  import os
24
- import fcntl
25
- from functools import wraps
26
26
  from concurrent.futures import ThreadPoolExecutor
27
-
27
+ from functools import wraps
28
28
 
29
29
  # TODO - ensure that both @conda/@pypi are not assigned to the same step
30
30
 
@@ -36,6 +36,9 @@ def cache_request(cache_file):
36
36
  call_args = kwargs.copy()
37
37
  call_args.update(zip(func.__code__.co_varnames, args))
38
38
  call_args.pop("self", None)
39
+ call_args.pop("ref", None)
40
+ # invalidate cache when moving from one deployment to another
41
+ call_args.update({"fast_bakery_url": FAST_BAKERY_URL})
39
42
  cache_key = hashlib.md5(
40
43
  json.dumps(call_args, sort_keys=True).encode("utf-8")
41
44
  ).hexdigest()
@@ -79,7 +82,7 @@ def cache_request(cache_file):
79
82
 
80
83
 
81
84
  class DockerEnvironmentException(MetaflowException):
82
- headline = "Ran into an error while setting up the environment"
85
+ headline = "Ran into an error while baking image"
83
86
 
84
87
  def __init__(self, msg):
85
88
  super(DockerEnvironmentException, self).__init__(msg)
@@ -93,8 +96,8 @@ class DockerEnvironment(MetaflowEnvironment):
93
96
  self.skipped_steps = set()
94
97
  self.flow = flow
95
98
 
96
- self.bakery = FastBakery(url=FAST_BAKERY_URL)
97
99
  self.results = {}
100
+ self.images_baked = 0
98
101
 
99
102
  def set_local_root(self, local_root):
100
103
  self.local_root = local_root
@@ -102,15 +105,31 @@ class DockerEnvironment(MetaflowEnvironment):
102
105
  def decospecs(self):
103
106
  return ("conda", "fast_bakery_internal") + super().decospecs()
104
107
 
105
- def validate_environment(self, echo, datastore_type):
108
+ def validate_environment(self, logger, datastore_type):
106
109
  self.datastore_type = datastore_type
107
- self.echo = echo
110
+ self.logger = logger
108
111
 
109
112
  # Avoiding circular imports.
110
113
  from metaflow.plugins import DATASTORES
111
114
 
112
115
  self.datastore = [d for d in DATASTORES if d.TYPE == self.datastore_type][0]
113
116
 
117
+ # Mixing @pypi/@conda in a single step is not supported yet
118
+ for step in self.flow:
119
+ if (
120
+ sum(
121
+ 1
122
+ for deco in step.decorators
123
+ if isinstance(deco, (PyPIStepDecorator, CondaStepDecorator))
124
+ )
125
+ > 1
126
+ ):
127
+ raise MetaflowException(
128
+ "Mixing and matching PyPI packages and Conda packages within a\n"
129
+ "step is not yet supported. Use one of @pypi or @conda only for the *%s* step."
130
+ % step.name
131
+ )
132
+
114
133
  def init_environment(self, echo):
115
134
  self.skipped_steps = {
116
135
  step.name
@@ -125,14 +144,21 @@ class DockerEnvironment(MetaflowEnvironment):
125
144
  step for step in self.flow if step.name not in self.skipped_steps
126
145
  ]
127
146
  if steps_to_bake:
128
- echo("Baking container image(s) ...")
129
- self.results = self._bake(steps_to_bake, echo)
147
+ self.logger("🚀 Baking container image(s) ...")
148
+ start_time = time.time()
149
+ self.results = self._bake(steps_to_bake)
130
150
  for step in self.flow:
131
151
  for d in step.decorators:
132
152
  if isinstance(d, (BatchDecorator, KubernetesDecorator)):
133
153
  d.attributes["image"] = self.results[step.name].container_image
134
154
  d.attributes["executable"] = self.results[step.name].python_path
135
- echo("Container image(s) baked!")
155
+ if self.images_baked > 0:
156
+ bake_time = time.time() - start_time
157
+ self.logger(
158
+ f"🎉 All container image(s) baked in {bake_time:.2f} seconds!"
159
+ )
160
+ else:
161
+ self.logger("🎉 All container image(s) baked!")
136
162
 
137
163
  if self.skipped_steps:
138
164
  self.delegate = CondaEnvironment(self.flow)
@@ -140,29 +166,54 @@ class DockerEnvironment(MetaflowEnvironment):
140
166
  self.delegate.validate_environment(echo, self.datastore_type)
141
167
  self.delegate.init_environment(echo, self.skipped_steps)
142
168
 
143
- def _bake(self, steps, echo) -> Dict[str, FastBakeryApiResponse]:
169
+ def _bake(self, steps) -> Dict[str, FastBakeryApiResponse]:
144
170
  metafile_path = get_fastbakery_metafile_path(self.local_root, self.flow.name)
171
+ logger_lock = threading.Lock()
145
172
 
146
173
  @cache_request(metafile_path)
147
174
  def _cached_bake(
148
- python=None, pypi_packages=None, conda_packages=None, base_image=None
175
+ ref=None,
176
+ python=None,
177
+ pypi_packages=None,
178
+ conda_packages=None,
179
+ base_image=None,
149
180
  ):
150
- self.bakery._reset_payload()
151
- self.bakery.python_version(python)
152
- self.bakery.pypi_packages(pypi_packages)
153
- self.bakery.conda_packages(conda_packages)
154
- self.bakery.base_image(base_image)
155
- # self.bakery.ignore_cache()
181
+ bakery = FastBakery(url=FAST_BAKERY_URL)
182
+ bakery._reset_payload()
183
+ bakery.python_version(python)
184
+ bakery.pypi_packages(pypi_packages)
185
+ bakery.conda_packages(conda_packages)
186
+ bakery.base_image(base_image)
187
+ # bakery.ignore_cache()
188
+
189
+ with logger_lock:
190
+ self.logger(f"🍳 Baking [{ref}] ...")
191
+ self.logger(f" 🐍 Python: {python}")
192
+
193
+ if pypi_packages:
194
+ self.logger(f" 📦 PyPI packages:")
195
+ for package, version in pypi_packages.items():
196
+ self.logger(f" 🔧 {package}: {version}")
197
+
198
+ if conda_packages:
199
+ self.logger(f" 📦 Conda packages:")
200
+ for package, version in conda_packages.items():
201
+ self.logger(f" 🔧 {package}: {version}")
202
+
203
+ self.logger(f" 🏗️ Base image: {base_image}")
204
+
205
+ start_time = time.time()
156
206
  try:
157
- res = self.bakery.bake()
158
- if res.baking_stats:
159
- echo(
160
- "baked image in: %s milliseconds"
161
- % res.baking_stats.solver_stats.duration_ms
162
- )
207
+ res = bakery.bake()
208
+ # TODO: Get actual bake time from bakery
209
+ bake_time = time.time() - start_time
210
+
211
+ with logger_lock:
212
+ self.logger(f"🏁 Baked [{ref}] in {bake_time:.2f} seconds!")
213
+ self.images_baked += 1
163
214
  return res
164
215
  except FastBakeryException as ex:
165
- raise DockerEnvironmentException(str(ex))
216
+ raise DockerEnvironmentException(f"Bake [{ref}] failed: {str(ex)}")
166
217
 
167
218
  def prepare_step(step):
168
219
  base_image = next(
@@ -216,10 +267,15 @@ class DockerEnvironment(MetaflowEnvironment):
216
267
  }
217
268
 
218
269
  with ThreadPoolExecutor() as executor:
219
- return {
220
- step.name: _cached_bake(**args)
221
- for step, args in zip(steps, executor.map(prepare_step, steps))
222
- }
270
+ prepared_args = list(executor.map(prepare_step, steps))
271
+ for i, args in enumerate(prepared_args, 1):
272
+ args["ref"] = f"#{i:02d}"
273
+ futures = [executor.submit(_cached_bake, **args) for args in prepared_args]
274
+ results = {}
275
+ for step, future in zip(steps, futures):
276
+ results[step.name] = future.result()
277
+
278
+ return results
223
279
 
224
280
  def executable(self, step_name, default=None):
225
281
  if step_name in self.skipped_steps:
@@ -1,5 +1,6 @@
1
1
  from typing import Dict, Optional
2
2
  import requests
3
+ import time
3
4
 
4
5
 
5
6
  class FastBakeryException(Exception):
@@ -140,6 +141,19 @@ class FastBakery:
140
141
  headers = {**self.headers, **(SERVICE_HEADERS or {})}
141
142
  except ImportError:
142
143
  headers = self.headers
144
+
145
+ retryable_status_codes = [409]
146
+
147
+ for attempt in range(2): # 0 = initial attempt, 1-2 = retries
148
+ response = requests.post(self.url, json=payload, headers=headers)
149
+
150
+ if response.status_code not in retryable_status_codes:
151
+ break
152
+
153
+ if attempt < 2: # Don't sleep after the last attempt
154
+ sleep_time = 0.5 * (attempt + 1)
155
+ time.sleep(sleep_time)
156
+
143
157
  response = requests.post(self.url, json=payload, headers=headers)
144
158
  self._handle_error_response(response)
145
159
  return FastBakeryApiResponse(response.json())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ob-metaflow-extensions
3
- Version: 1.1.86
3
+ Version: 1.1.88
4
4
  Summary: Outerbounds Platform Extensions for Metaflow
5
5
  Author: Outerbounds, Inc.
6
6
  License: Commercial
@@ -2,7 +2,7 @@ from setuptools import setup, find_namespace_packages
2
2
  from pathlib import Path
3
3
 
4
4
 
5
- version = "1.1.86"
5
+ version = "1.1.88"
6
6
  this_directory = Path(__file__).parent
7
7
  long_description = (this_directory / "README.md").read_text()
8
8