ob-metaflow-extensions 1.1.91__py2.py3-none-any.whl → 1.1.93__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-extensions might be problematic. Click here for more details.

@@ -116,15 +116,6 @@ class DockerEnvironment(MetaflowEnvironment):
116
116
 
117
117
  self.datastore = [d for d in DATASTORES if d.TYPE == self.datastore_type][0]
118
118
 
119
- # Mixing @pypi/@conda in a single step is not supported yet
120
- for step in self.flow:
121
- if sum(1 for deco in step.decorators if _is_env_deco(deco)) > 1:
122
- raise MetaflowException(
123
- "Mixing and matching PyPI packages and Conda packages within a\n"
124
- "step is not yet supported. Use one of @pypi or @conda only for the *%s* step."
125
- % step.name
126
- )
127
-
128
119
  def init_environment(self, echo):
129
120
  self.skipped_steps = {
130
121
  step.name for step in self.flow if not _step_executes_remotely(step)
@@ -132,35 +123,45 @@ class DockerEnvironment(MetaflowEnvironment):
132
123
  # Attach environment decorator as needed. This is done on a step-by-step basis
133
124
  # as we require a conda decorator for fallback steps, but prefer pypi for the baked ones.
134
125
  for step in self.flow:
135
- if not _step_has_environment_deco(step):
136
- if step.name in self.skipped_steps:
137
- # Conda fallback requires a conda decorator as the default for a step
138
- decorators._attach_decorators_to_step(step, ["conda"])
139
- else:
126
+ # Mixing @pypi/@conda in a single step is not supported yet.
127
+ # We validate this before attaching any new ones as the OSS Conda environment requires an implicit conda decorator for pypi environments which would fail the validation.
128
+ if sum(1 for deco in step.decorators if _is_env_deco(deco)) > 1:
129
+ raise MetaflowException(
130
+ "Mixing and matching PyPI packages and Conda packages within a\n"
131
+ "step is not yet supported. Use one of @pypi or @conda only for the *%s* step."
132
+ % step.name
133
+ )
134
+ if step.name in self.skipped_steps:
135
+ # Conda fallback requires a conda decorator as the default for a step
136
+ decorators._attach_decorators_to_step(step, ["conda"])
137
+ else:
138
+ if not _step_has_environment_deco(step):
140
139
  # We default to PyPI for steps that are going to be baked.
141
140
  decorators._attach_decorators_to_step(step, ["pypi"])
142
- # Initialize the decorator we attached.
143
- # This is crucial for the conda decorator to work properly in the fallback environment
144
- for deco in step.decorators:
145
- if _is_env_deco(deco):
146
- deco.step_init(
147
- self.flow,
148
- None, # not passing graph as it is not available, and not required by conda/pypi decorators
149
- step.name,
150
- step.decorators,
151
- self,
152
- self.datastore,
153
- echo,
154
- )
141
+ # Initialize the decorator we attached.
142
+ # This is crucial for the conda decorator to work properly in the fallback environment
143
+ for deco in step.decorators:
144
+ if _is_env_deco(deco):
145
+ deco.step_init(
146
+ self.flow,
147
+ None, # not passing graph as it is not available, and not required by conda/pypi decorators
148
+ step.name,
149
+ step.decorators,
150
+ self,
151
+ self.datastore,
152
+ echo,
153
+ )
155
154
 
156
155
  steps_to_bake = [
157
- step for step in self.flow if step.name not in self.skipped_steps
156
+ step
157
+ for step in self.flow
158
+ if step.name not in self.skipped_steps and not self.is_disabled(step)
158
159
  ]
159
160
  if steps_to_bake:
160
161
  self.logger("🚀 Baking container image(s) ...")
161
162
  start_time = time.time()
162
163
  self.results = self._bake(steps_to_bake)
163
- for step in self.flow:
164
+ for step in steps_to_bake:
164
165
  for d in step.decorators:
165
166
  if _is_remote_deco(d):
166
167
  d.attributes["image"] = self.results[step.name].container_image
@@ -1,8 +1,10 @@
1
+ from concurrent.futures import ThreadPoolExecutor
1
2
  import os
2
3
  import sys
3
4
  import time
4
5
 
5
6
  from metaflow.exception import MetaflowException
7
+ from metaflow.metaflow_config import KUBERNETES_NAMESPACE
6
8
 
7
9
 
8
10
  CLIENT_REFRESH_INTERVAL_SECONDS = 300
@@ -27,6 +29,7 @@ class KubernetesClient(object):
27
29
  % sys.executable
28
30
  )
29
31
  self._refresh_client()
32
+ self._namespace = KUBERNETES_NAMESPACE
30
33
 
31
34
  def _refresh_client(self):
32
35
  from metaflow_extensions.outerbounds.plugins.auth_server import get_token
@@ -50,6 +53,99 @@ class KubernetesClient(object):
50
53
 
51
54
  return self._client
52
55
 
56
+ def _find_active_pods(self, flow_name, run_id=None, user=None):
57
+ def _request(_continue=None):
58
+ # handle paginated responses
59
+ return self._client.CoreV1Api().list_namespaced_pod(
60
+ namespace=self._namespace,
61
+ # limited selector support for K8S api. We want to cover multiple statuses: Running / Pending / Unknown
62
+ field_selector="status.phase!=Succeeded,status.phase!=Failed",
63
+ limit=1000,
64
+ _continue=_continue,
65
+ )
66
+
67
+ results = _request()
68
+
69
+ if run_id is not None:
70
+ # handle argo prefixes in run_id
71
+ run_id = run_id[run_id.startswith("argo-") and len("argo-") :]
72
+
73
+ while results.metadata._continue or results.items:
74
+ for pod in results.items:
75
+ match = (
76
+ # arbitrary pods might have no annotations at all.
77
+ pod.metadata.annotations
78
+ and pod.metadata.labels
79
+ and (
80
+ run_id is None
81
+ or (pod.metadata.annotations.get("metaflow/run_id") == run_id)
82
+ # we want to also match pods launched by argo-workflows
83
+ or (
84
+ pod.metadata.labels.get("workflows.argoproj.io/workflow")
85
+ == run_id
86
+ )
87
+ )
88
+ and (
89
+ user is None
90
+ or pod.metadata.annotations.get("metaflow/user") == user
91
+ )
92
+ and (
93
+ pod.metadata.annotations.get("metaflow/flow_name") == flow_name
94
+ )
95
+ )
96
+ if match:
97
+ yield pod
98
+ if not results.metadata._continue:
99
+ break
100
+ results = _request(results.metadata._continue)
101
+
102
+ def list(self, flow_name, run_id, user):
103
+ results = self._find_active_pods(flow_name, run_id, user)
104
+
105
+ return list(results)
106
+
107
+ def kill_pods(self, flow_name, run_id, user, echo):
108
+ from kubernetes.stream import stream
109
+
110
+ api_instance = self._client.CoreV1Api()
111
+ job_api = self._client.BatchV1Api()
112
+ pods = self._find_active_pods(flow_name, run_id, user)
113
+
114
+ def _kill_pod(pod):
115
+ echo("Killing Kubernetes pod %s\n" % pod.metadata.name)
116
+ try:
117
+ stream(
118
+ api_instance.connect_get_namespaced_pod_exec,
119
+ name=pod.metadata.name,
120
+ namespace=pod.metadata.namespace,
121
+ command=[
122
+ "/bin/sh",
123
+ "-c",
124
+ "/sbin/killall5",
125
+ ],
126
+ stderr=True,
127
+ stdin=False,
128
+ stdout=True,
129
+ tty=False,
130
+ )
131
+ except Exception:
132
+ # best effort kill for pod can fail.
133
+ try:
134
+ job_name = pod.metadata.labels.get("job-name", None)
135
+ if job_name is None:
136
+ raise Exception("Could not determine job name")
137
+ job_api.patch_namespaced_job(
138
+ name=job_name,
139
+ namespace=pod.metadata.namespace,
140
+ field_manager="metaflow",
141
+ body={"spec": {"parallelism": 0}},
142
+ )
143
+ except Exception as e:
144
+ echo("failed to kill pod %s - %s" % (pod.metadata.name, str(e)))
145
+
146
+ with ThreadPoolExecutor() as executor:
147
+ executor.map(_kill_pod, list(pods))
148
+
53
149
  def job(self, **kwargs):
54
150
  from metaflow.plugins.kubernetes.kubernetes_job import KubernetesJob
55
151
 
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ob-metaflow-extensions
3
- Version: 1.1.91
3
+ Version: 1.1.93
4
4
  Summary: Outerbounds Platform Extensions for Metaflow
5
5
  Author: Outerbounds, Inc.
6
6
  License: Commercial
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: boto3
9
9
  Requires-Dist: kubernetes
10
- Requires-Dist: ob-metaflow (==2.12.19.1)
10
+ Requires-Dist: ob-metaflow (==2.12.20.1)
11
11
 
12
12
  # Outerbounds platform package
13
13
 
@@ -5,12 +5,12 @@ metaflow_extensions/outerbounds/plugins/__init__.py,sha256=WwvFcN5kserbPwhrE4hXp
5
5
  metaflow_extensions/outerbounds/plugins/auth_server.py,sha256=1v2GBqoMBxp5E7Lejz139w-jxJtPnLDvvHXP0HhEIHI,2361
6
6
  metaflow_extensions/outerbounds/plugins/perimeters.py,sha256=QXh3SFP7GQbS-RAIxUOPbhPzQ7KDFVxZkTdKqFKgXjI,2697
7
7
  metaflow_extensions/outerbounds/plugins/fast_bakery/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- metaflow_extensions/outerbounds/plugins/fast_bakery/docker_environment.py,sha256=gFeXt8nLv7SzZ2zEoAKMpdxAePN4jZclSsmQAm6hB4w,13846
8
+ metaflow_extensions/outerbounds/plugins/fast_bakery/docker_environment.py,sha256=jpEBbdRLq7826LqUKIHqo3cYei2CNeVAxggw6vFmYso,13996
9
9
  metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery.py,sha256=MAPRQsfqeEkL1LXqgwPrUJOzZ3kY3C00QjdDgQ7wdIg,5160
10
10
  metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery_cli.py,sha256=kqFyu2bJSnc9_9aYfBpz5xK6L6luWFZK_NMuh8f1eVk,1494
11
11
  metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery_decorator.py,sha256=EZDbyrfZ7fgcU-P9dMS_hpCxsdDeUE0K5VU3uNM4aW4,1506
12
12
  metaflow_extensions/outerbounds/plugins/kubernetes/__init__.py,sha256=5zG8gShSj8m7rgF4xgWBZFuY3GDP5n1T0ktjRpGJLHA,69
13
- metaflow_extensions/outerbounds/plugins/kubernetes/kubernetes_client.py,sha256=gj6Iaz26bGbZm3aQuNS18Mqh_80iJp5PgFwFSlJRcn8,1968
13
+ metaflow_extensions/outerbounds/plugins/kubernetes/kubernetes_client.py,sha256=Vbchj4D9HbMBQy7Te2QbFnQqEkfABeGxrWrvBWy5TTY,5858
14
14
  metaflow_extensions/outerbounds/plugins/nim/__init__.py,sha256=GVnvSTjqYVj5oG2yh8KJFt7iZ33cEadDD5HbdmC9hJ0,1457
15
15
  metaflow_extensions/outerbounds/plugins/nim/nim_manager.py,sha256=SWieODDxtIaeZwdMYtObDi57Kjyfw2DUuE6pJtU750w,9206
16
16
  metaflow_extensions/outerbounds/plugins/nvcf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -33,7 +33,7 @@ metaflow_extensions/outerbounds/toplevel/global_aliases_for_metaflow_package.py,
33
33
  metaflow_extensions/outerbounds/toplevel/plugins/azure/__init__.py,sha256=WUuhz2YQfI4fz7nIcipwwWq781eaoHEk7n4GAn1npDg,63
34
34
  metaflow_extensions/outerbounds/toplevel/plugins/gcp/__init__.py,sha256=BbZiaH3uILlEZ6ntBLKeNyqn3If8nIXZFq_Apd7Dhco,70
35
35
  metaflow_extensions/outerbounds/toplevel/plugins/kubernetes/__init__.py,sha256=5zG8gShSj8m7rgF4xgWBZFuY3GDP5n1T0ktjRpGJLHA,69
36
- ob_metaflow_extensions-1.1.91.dist-info/METADATA,sha256=rTZDEBQQ5bWEZnqLoralSTjginYiJ-Q81f054qg3vLs,520
37
- ob_metaflow_extensions-1.1.91.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
38
- ob_metaflow_extensions-1.1.91.dist-info/top_level.txt,sha256=NwG0ukwjygtanDETyp_BUdtYtqIA_lOjzFFh1TsnxvI,20
39
- ob_metaflow_extensions-1.1.91.dist-info/RECORD,,
36
+ ob_metaflow_extensions-1.1.93.dist-info/METADATA,sha256=yWB9qUR6LTg61tnxvkMqcdGw3SRZjcSnSIhn6J6u5aQ,520
37
+ ob_metaflow_extensions-1.1.93.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
38
+ ob_metaflow_extensions-1.1.93.dist-info/top_level.txt,sha256=NwG0ukwjygtanDETyp_BUdtYtqIA_lOjzFFh1TsnxvI,20
39
+ ob_metaflow_extensions-1.1.93.dist-info/RECORD,,