ob-metaflow-extensions 1.1.122__py2.py3-none-any.whl → 1.1.123rc0__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.
- metaflow_extensions/outerbounds/plugins/__init__.py +1 -0
- metaflow_extensions/outerbounds/plugins/apps/__init__.py +0 -0
- metaflow_extensions/outerbounds/plugins/apps/app_utils.py +170 -0
- metaflow_extensions/outerbounds/plugins/apps/deploy_decorator.py +90 -0
- metaflow_extensions/outerbounds/plugins/apps/supervisord_utils.py +167 -0
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark_job.py +1 -1
- {ob_metaflow_extensions-1.1.122.dist-info → ob_metaflow_extensions-1.1.123rc0.dist-info}/METADATA +1 -1
- {ob_metaflow_extensions-1.1.122.dist-info → ob_metaflow_extensions-1.1.123rc0.dist-info}/RECORD +10 -6
- {ob_metaflow_extensions-1.1.122.dist-info → ob_metaflow_extensions-1.1.123rc0.dist-info}/WHEEL +0 -0
- {ob_metaflow_extensions-1.1.122.dist-info → ob_metaflow_extensions-1.1.123rc0.dist-info}/top_level.txt +0 -0
|
@@ -319,6 +319,7 @@ STEP_DECORATORS_DESC = [
|
|
|
319
319
|
("snowpark", ".snowpark.snowpark_decorator.SnowparkDecorator"),
|
|
320
320
|
("tensorboard", ".tensorboard.TensorboardDecorator"),
|
|
321
321
|
("gpu_profile", ".profilers.gpu_profile_decorator.GPUProfileDecorator"),
|
|
322
|
+
("app_deploy", ".apps.deploy_decorator.WorkstationAppDeployDecorator"),
|
|
322
323
|
]
|
|
323
324
|
FLOW_DECORATORS_DESC = [("nim", ".nim.NimDecorator")]
|
|
324
325
|
TOGGLE_STEP_DECORATOR = [
|
|
File without changes
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from metaflow.exception import MetaflowException
|
|
2
|
+
import os
|
|
3
|
+
from metaflow.metaflow_config_funcs import init_config
|
|
4
|
+
import requests
|
|
5
|
+
import time
|
|
6
|
+
import random
|
|
7
|
+
|
|
8
|
+
# IMPORTANT: Currently contents of this file are mostly duplicated from the outerbounds package.
|
|
9
|
+
# This is purely due to the time rush of having to deliver this feature. As a fast forward, we
|
|
10
|
+
# will reorganize things in a way that the amount of duplication in minimum.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
APP_READY_POLL_TIMEOUT_SECONDS = 300
|
|
14
|
+
# Even after our backend validates that the app routes are ready, it takes a few seconds for
|
|
15
|
+
# the app to be accessible via the browser. Till we hunt down this delay, add an extra buffer.
|
|
16
|
+
APP_READY_EXTRA_BUFFER_SECONDS = 30
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def start_app(port=-1, name=""):
|
|
20
|
+
if len(name) == 0 or len(name) >= 20:
|
|
21
|
+
raise MetaflowException("App name should not be more than 20 characters long.")
|
|
22
|
+
elif not name.isalnum() or not name.islower():
|
|
23
|
+
raise MetaflowException(
|
|
24
|
+
"App name can only contain lowercase alphanumeric characters."
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if "WORKSTATION_ID" not in os.environ:
|
|
28
|
+
raise MetaflowException(
|
|
29
|
+
"All outerbounds app commands can only be run from a workstation."
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
workstation_id = os.environ["WORKSTATION_ID"]
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
try:
|
|
36
|
+
conf = init_config()
|
|
37
|
+
metaflow_token = conf["METAFLOW_SERVICE_AUTH_KEY"]
|
|
38
|
+
api_url = conf["OBP_API_SERVER"]
|
|
39
|
+
|
|
40
|
+
workstations_response = requests.get(
|
|
41
|
+
f"https://{api_url}/v1/workstations",
|
|
42
|
+
headers={"x-api-key": metaflow_token},
|
|
43
|
+
)
|
|
44
|
+
workstations_response.raise_for_status()
|
|
45
|
+
except:
|
|
46
|
+
raise MetaflowException("Failed to list workstations!")
|
|
47
|
+
|
|
48
|
+
workstations_json = workstations_response.json()["workstations"]
|
|
49
|
+
for workstation in workstations_json:
|
|
50
|
+
if workstation["instance_id"] == os.environ["WORKSTATION_ID"]:
|
|
51
|
+
if "named_ports" in workstation["spec"]:
|
|
52
|
+
try:
|
|
53
|
+
ensure_app_start_request_is_valid(
|
|
54
|
+
workstation["spec"]["named_ports"], port, name
|
|
55
|
+
)
|
|
56
|
+
except ValueError as e:
|
|
57
|
+
raise MetaflowException(str(e))
|
|
58
|
+
|
|
59
|
+
for named_port in workstation["spec"]["named_ports"]:
|
|
60
|
+
if int(named_port["port"]) == port:
|
|
61
|
+
if named_port["enabled"] and named_port["name"] == name:
|
|
62
|
+
print(f"App {name} started on port {port}!")
|
|
63
|
+
print(
|
|
64
|
+
f"Browser URL: https://{api_url.replace('api', 'ui')}/apps/{os.environ['WORKSTATION_ID']}/{name}/"
|
|
65
|
+
)
|
|
66
|
+
print(
|
|
67
|
+
f"API URL: https://{api_url}/apps/{os.environ['WORKSTATION_ID']}/{name}/"
|
|
68
|
+
)
|
|
69
|
+
return
|
|
70
|
+
else:
|
|
71
|
+
try:
|
|
72
|
+
response = requests.put(
|
|
73
|
+
f"https://{api_url}/v1/workstations/update/{workstation_id}/namedports",
|
|
74
|
+
headers={"x-api-key": metaflow_token},
|
|
75
|
+
json={
|
|
76
|
+
"port": port,
|
|
77
|
+
"name": name,
|
|
78
|
+
"enabled": True,
|
|
79
|
+
},
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
response.raise_for_status()
|
|
83
|
+
poll_success = wait_for_app_port_to_be_accessible(
|
|
84
|
+
api_url,
|
|
85
|
+
metaflow_token,
|
|
86
|
+
workstation_id,
|
|
87
|
+
name,
|
|
88
|
+
APP_READY_POLL_TIMEOUT_SECONDS,
|
|
89
|
+
)
|
|
90
|
+
if poll_success:
|
|
91
|
+
print(f"App {name} started on port {port}!")
|
|
92
|
+
print(
|
|
93
|
+
f"Browser URL: https://{api_url.replace('api', 'ui')}/apps/{os.environ['WORKSTATION_ID']}/{name}/"
|
|
94
|
+
)
|
|
95
|
+
print(
|
|
96
|
+
f"API URL: https://{api_url}/apps/{os.environ['WORKSTATION_ID']}/{name}/"
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
raise MetaflowException(
|
|
100
|
+
f"The app could not be deployed in {APP_READY_POLL_TIMEOUT_SECONDS / 60} minutes. Please try again later."
|
|
101
|
+
)
|
|
102
|
+
except Exception:
|
|
103
|
+
raise MetaflowException(
|
|
104
|
+
f"Failed to start app {name} on port {port}!"
|
|
105
|
+
)
|
|
106
|
+
except Exception as e:
|
|
107
|
+
raise MetaflowException(f"Failed to start app {name} on port {port}!")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def ensure_app_start_request_is_valid(existing_named_ports, port: int, name: str):
|
|
111
|
+
existing_apps_by_port = {np["port"]: np for np in existing_named_ports}
|
|
112
|
+
|
|
113
|
+
if port not in existing_apps_by_port:
|
|
114
|
+
raise MetaflowException(f"Port {port} not found on workstation")
|
|
115
|
+
|
|
116
|
+
for existing_named_port in existing_named_ports:
|
|
117
|
+
if (
|
|
118
|
+
name == existing_named_port["name"]
|
|
119
|
+
and existing_named_port["port"] != port
|
|
120
|
+
and existing_named_port["enabled"]
|
|
121
|
+
):
|
|
122
|
+
raise MetaflowException(
|
|
123
|
+
f"App with name '{name}' is already deployed on port {existing_named_port['port']}"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def wait_for_app_port_to_be_accessible(
|
|
128
|
+
api_url, metaflow_token, workstation_id, app_name, poll_timeout_seconds
|
|
129
|
+
) -> bool:
|
|
130
|
+
num_retries_per_request = 3
|
|
131
|
+
start_time = time.time()
|
|
132
|
+
retry_delay = 1.0
|
|
133
|
+
poll_interval = 10
|
|
134
|
+
wait_message = f"App {app_name} is currently being deployed..."
|
|
135
|
+
while time.time() - start_time < poll_timeout_seconds:
|
|
136
|
+
for _ in range(num_retries_per_request):
|
|
137
|
+
try:
|
|
138
|
+
workstations_response = requests.get(
|
|
139
|
+
f"https://{api_url}/v1/workstations",
|
|
140
|
+
headers={"x-api-key": metaflow_token},
|
|
141
|
+
)
|
|
142
|
+
workstations_response.raise_for_status()
|
|
143
|
+
if is_app_ready(workstations_response.json(), workstation_id, app_name):
|
|
144
|
+
print(wait_message)
|
|
145
|
+
time.sleep(APP_READY_EXTRA_BUFFER_SECONDS)
|
|
146
|
+
return True
|
|
147
|
+
else:
|
|
148
|
+
print(wait_message)
|
|
149
|
+
time.sleep(poll_interval)
|
|
150
|
+
except (
|
|
151
|
+
requests.exceptions.ConnectionError,
|
|
152
|
+
requests.exceptions.ReadTimeout,
|
|
153
|
+
):
|
|
154
|
+
time.sleep(retry_delay)
|
|
155
|
+
retry_delay *= 2 # Double the delay for the next attempt
|
|
156
|
+
retry_delay += random.uniform(0, 1) # Add jitter
|
|
157
|
+
retry_delay = min(retry_delay, 10)
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def is_app_ready(response_json: dict, workstation_id: str, app_name: str) -> bool:
|
|
162
|
+
"""Checks if the app is ready in the given workstation's response."""
|
|
163
|
+
workstations = response_json.get("workstations", [])
|
|
164
|
+
for workstation in workstations:
|
|
165
|
+
if workstation.get("instance_id") == workstation_id:
|
|
166
|
+
hosted_apps = workstation.get("status", {}).get("hosted_apps", [])
|
|
167
|
+
for hosted_app in hosted_apps:
|
|
168
|
+
if hosted_app.get("name") == app_name:
|
|
169
|
+
return bool(hosted_app.get("ready"))
|
|
170
|
+
return False
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from metaflow.exception import MetaflowException
|
|
2
|
+
from metaflow.decorators import StepDecorator
|
|
3
|
+
from metaflow import current
|
|
4
|
+
from .app_utils import start_app
|
|
5
|
+
from .supervisord_utils import SupervisorClient, SupervisorClientException
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
DEFAULT_WAIT_TIME_SECONDS_FOR_PROCESS_TO_START = 10
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class WorkstationAppDeployDecorator(StepDecorator):
|
|
12
|
+
"""
|
|
13
|
+
Specifies that this step is used to deploy an instance of the app.
|
|
14
|
+
Requires that self.app_name, self.app_port, self.entrypoint and self.deployDir is set.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
app_port : int
|
|
19
|
+
Number of GPUs to use.
|
|
20
|
+
app_name : str
|
|
21
|
+
Name of the app to deploy.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
name = "app_deploy"
|
|
25
|
+
defaults = {"app_port": 8080, "app_name": "app"}
|
|
26
|
+
|
|
27
|
+
def step_init(self, flow, graph, step, decos, environment, flow_datastore, logger):
|
|
28
|
+
if any([deco.name == "kubernetes" for deco in decos]):
|
|
29
|
+
raise MetaflowException(
|
|
30
|
+
"Step *{step}* is marked for execution both on Kubernetes and "
|
|
31
|
+
"Nvidia. Please use one or the other.".format(step=step)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
app_port = self.attributes["app_port"]
|
|
35
|
+
app_name = self.attributes["app_name"]
|
|
36
|
+
|
|
37
|
+
# Currently this decorator is expected to only execute on workstation.
|
|
38
|
+
if app_port is None or app_port < 6000 or app_port > 6002:
|
|
39
|
+
raise MetaflowException(
|
|
40
|
+
"AppDeployDecorator requires app_port to be between 6000 and 6002."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if app_name is None:
|
|
44
|
+
raise MetaflowException("AppDeployDecorator requires app_name to be set.")
|
|
45
|
+
|
|
46
|
+
def task_post_step(
|
|
47
|
+
self, step_name, flow, graph, retry_count, max_user_code_retries
|
|
48
|
+
):
|
|
49
|
+
entrypoint = getattr(flow, "entrypoint", None)
|
|
50
|
+
wait_time_for_app_start = getattr(
|
|
51
|
+
flow,
|
|
52
|
+
"wait_time_for_app_start",
|
|
53
|
+
DEFAULT_WAIT_TIME_SECONDS_FOR_PROCESS_TO_START,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if entrypoint is None or not isinstance(entrypoint, str):
|
|
57
|
+
raise MetaflowException(
|
|
58
|
+
f"@app_deploy requires entrypoint to be set to a string. The current value of entrypoint {entrypoint} is not valid."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
launch_dir = getattr(flow, "launch_dir", None)
|
|
62
|
+
if launch_dir is None or launch_dir == "":
|
|
63
|
+
raise MetaflowException("@app_deploy requires launch_dir to be set.")
|
|
64
|
+
elif not isinstance(launch_dir, str) or not os.path.exists(launch_dir):
|
|
65
|
+
raise MetaflowException(
|
|
66
|
+
f"@app_deploy requires launch_dir to be set to a valid directory. The current value of launch_dir {launch_dir} is not valid."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
supervisor_client = SupervisorClient(
|
|
71
|
+
wait_time_seconds_for_app_start=wait_time_for_app_start
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# First, let's deploy the app.
|
|
75
|
+
start_app(
|
|
76
|
+
port=self.attributes["app_port"], name=self.attributes["app_name"]
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Now, let's add the app to supervisor.
|
|
80
|
+
supervisor_client.start_process_with_supervisord(
|
|
81
|
+
self.attributes["app_name"],
|
|
82
|
+
entrypoint,
|
|
83
|
+
launch_dir,
|
|
84
|
+
)
|
|
85
|
+
except SupervisorClientException as e:
|
|
86
|
+
raise MetaflowException(str(e))
|
|
87
|
+
except Exception as e:
|
|
88
|
+
raise MetaflowException(
|
|
89
|
+
f"Failed to start {self.attributes['app_name']}! Cause: {str(e)}"
|
|
90
|
+
) from e
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import configparser
|
|
4
|
+
import tempfile
|
|
5
|
+
import sys
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import shutil
|
|
9
|
+
from enum import Enum
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SupervisorClientException(Exception):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SupervisorClient:
|
|
18
|
+
class SupervisodProcessCodes(Enum):
|
|
19
|
+
STOPPED = 0
|
|
20
|
+
STARTING = 10
|
|
21
|
+
RUNNING = 20
|
|
22
|
+
BACKOFF = 30
|
|
23
|
+
STOPPING = 40
|
|
24
|
+
EXITED = 100
|
|
25
|
+
FATAL = 200
|
|
26
|
+
UNKNOWN = 1000
|
|
27
|
+
|
|
28
|
+
def __init__(self, wait_time_seconds_for_app_start: int):
|
|
29
|
+
self.supervisor_conf_loc = os.environ.get("SUPERVISOR_CONF_PATH")
|
|
30
|
+
|
|
31
|
+
self.wait_time_seconds_for_app_start = wait_time_seconds_for_app_start
|
|
32
|
+
if self.supervisor_conf_loc is None or not os.path.exists(
|
|
33
|
+
self.supervisor_conf_loc
|
|
34
|
+
):
|
|
35
|
+
raise SupervisorClientException(
|
|
36
|
+
"This workstation does not support deploying apps! Please reach out to Outerbounds for support."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
self.metaflow_envs_persistent_path = os.environ.get(
|
|
40
|
+
"SUPERVISOR_PYTHON_ENVS_PATH"
|
|
41
|
+
)
|
|
42
|
+
if self.metaflow_envs_persistent_path is None:
|
|
43
|
+
raise SupervisorClientException(
|
|
44
|
+
"This workstation does not support deploying apps! Please reach out to Outerbounds for support."
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Check if supervisorctl is installed
|
|
48
|
+
if not shutil.which("supervisorctl"):
|
|
49
|
+
raise SupervisorClientException(
|
|
50
|
+
"This workstation does not support deploying apps! Please reach out to Outerbounds for support."
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def start_process_with_supervisord(self, app_name, entrypoint, directory=None):
|
|
54
|
+
"""Add a new program entry to supervisor configuration."""
|
|
55
|
+
|
|
56
|
+
persistent_path_for_executable = (
|
|
57
|
+
self.persist_metaflow_generated_python_environment()
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
command = f"{persistent_path_for_executable} {entrypoint}"
|
|
61
|
+
|
|
62
|
+
entry = {
|
|
63
|
+
"command": command,
|
|
64
|
+
"directory": directory or os.getcwd(),
|
|
65
|
+
"autostart": "true",
|
|
66
|
+
"autorestart": "true",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
supervisor_config = configparser.ConfigParser()
|
|
70
|
+
supervisor_config.read(self.supervisor_conf_loc)
|
|
71
|
+
|
|
72
|
+
supervisor_config[f"program:{app_name}"] = entry
|
|
73
|
+
|
|
74
|
+
with tempfile.NamedTemporaryFile(
|
|
75
|
+
"w", dir=os.path.dirname(self.supervisor_conf_loc), delete=False
|
|
76
|
+
) as f:
|
|
77
|
+
supervisor_config.write(f)
|
|
78
|
+
tmp_file = f.name
|
|
79
|
+
os.rename(tmp_file, self.supervisor_conf_loc)
|
|
80
|
+
|
|
81
|
+
# Execute supervisorctl reload
|
|
82
|
+
# Capture the exit code
|
|
83
|
+
exit_code = subprocess.run(
|
|
84
|
+
["supervisorctl", "reload"],
|
|
85
|
+
stdout=subprocess.DEVNULL,
|
|
86
|
+
stderr=subprocess.DEVNULL,
|
|
87
|
+
).returncode
|
|
88
|
+
if exit_code != 0:
|
|
89
|
+
print("Failed to reload supervisor configuration!", file=sys.stderr)
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
print(
|
|
93
|
+
f"Waiting for {self.wait_time_seconds_for_app_start} seconds for {app_name} to start..."
|
|
94
|
+
)
|
|
95
|
+
time.sleep(self.wait_time_seconds_for_app_start)
|
|
96
|
+
status = self._get_launched_prcoess_status(app_name)
|
|
97
|
+
|
|
98
|
+
if status not in [
|
|
99
|
+
self.SupervisodProcessCodes.RUNNING,
|
|
100
|
+
self.SupervisodProcessCodes.STARTING,
|
|
101
|
+
]:
|
|
102
|
+
raise SupervisorClientException(
|
|
103
|
+
f"Failed to start {app_name}! Try running {command} manually to debug."
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def _get_launched_prcoess_status(self, app_name):
|
|
107
|
+
status_cmd_output = subprocess.run(
|
|
108
|
+
["supervisorctl", "status", app_name],
|
|
109
|
+
stdout=subprocess.PIPE,
|
|
110
|
+
stderr=subprocess.PIPE,
|
|
111
|
+
).stdout.decode("utf-8")
|
|
112
|
+
|
|
113
|
+
status_cmd_output_parts = [
|
|
114
|
+
x.strip() for x in status_cmd_output.split(" ") if x.strip()
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
status_str = status_cmd_output_parts[1]
|
|
118
|
+
|
|
119
|
+
if status_str == "RUNNING":
|
|
120
|
+
return self.SupervisodProcessCodes.RUNNING
|
|
121
|
+
elif status_str == "STOPPED":
|
|
122
|
+
return self.SupervisodProcessCodes.STOPPED
|
|
123
|
+
elif status_str == "STARTING":
|
|
124
|
+
return self.SupervisodProcessCodes.STARTING
|
|
125
|
+
elif status_str == "BACKOFF":
|
|
126
|
+
return self.SupervisodProcessCodes.BACKOFF
|
|
127
|
+
elif status_str == "STOPPING":
|
|
128
|
+
return self.SupervisodProcessCodes.STOPPING
|
|
129
|
+
elif status_str == "EXITED":
|
|
130
|
+
return self.SupervisodProcessCodes.EXITED
|
|
131
|
+
elif status_str == "FATAL":
|
|
132
|
+
return self.SupervisodProcessCodes.FATAL
|
|
133
|
+
else:
|
|
134
|
+
return self.SupervisodProcessCodes.UNKNOWN
|
|
135
|
+
|
|
136
|
+
# By default, an environment generated by metaflow will end up in a path like: /root/micromamba/envs/metaflow/linux-64/02699a4d2d50cfc/bin/python
|
|
137
|
+
# However, on a workstation these environments are not persisted, so we need to copy them over to /home/ob-workspace
|
|
138
|
+
def persist_metaflow_generated_python_environment(self):
|
|
139
|
+
current_executable = sys.executable
|
|
140
|
+
environment_path = Path(current_executable).parent.parent
|
|
141
|
+
|
|
142
|
+
persistent_path_for_this_environment = os.path.join(
|
|
143
|
+
self.metaflow_envs_persistent_path,
|
|
144
|
+
environment_path.parent.name,
|
|
145
|
+
environment_path.name,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
final_executable_path = os.path.join(
|
|
149
|
+
persistent_path_for_this_environment,
|
|
150
|
+
Path(current_executable).parent.name,
|
|
151
|
+
Path(current_executable).name,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if os.path.exists(final_executable_path):
|
|
155
|
+
return final_executable_path
|
|
156
|
+
|
|
157
|
+
os.makedirs(persistent_path_for_this_environment, exist_ok=True)
|
|
158
|
+
|
|
159
|
+
for item in os.listdir(environment_path):
|
|
160
|
+
src_path = os.path.join(environment_path, item)
|
|
161
|
+
dst_path = os.path.join(persistent_path_for_this_environment, item)
|
|
162
|
+
if os.path.isdir(src_path):
|
|
163
|
+
shutil.copytree(src_path, dst_path, dirs_exist_ok=True)
|
|
164
|
+
else:
|
|
165
|
+
shutil.copy2(src_path, dst_path)
|
|
166
|
+
|
|
167
|
+
return final_executable_path
|
{ob_metaflow_extensions-1.1.122.dist-info → ob_metaflow_extensions-1.1.123rc0.dist-info}/RECORD
RENAMED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
metaflow_extensions/outerbounds/__init__.py,sha256=TRGvIUMjkfneWtYUFSWoubu_Kf2ekAL4WLbV3IxOj9k,499
|
|
2
2
|
metaflow_extensions/outerbounds/remote_config.py,sha256=Zpfpjgz68_ZgxlXezjzlsDLo4840rkWuZgwDB_5H57U,4059
|
|
3
3
|
metaflow_extensions/outerbounds/config/__init__.py,sha256=JsQGRuGFz28fQWjUvxUgR8EKBLGRdLUIk_buPLJplJY,1225
|
|
4
|
-
metaflow_extensions/outerbounds/plugins/__init__.py,sha256=
|
|
4
|
+
metaflow_extensions/outerbounds/plugins/__init__.py,sha256=0M_MbFmDcHRYs9mJ7SFDT1hmXkVEhzEEWRgUU7mEt2w,12844
|
|
5
5
|
metaflow_extensions/outerbounds/plugins/auth_server.py,sha256=_Q9_2EL0Xy77bCRphkwT1aSu8gQXRDOH-Z-RxTUO8N4,2202
|
|
6
6
|
metaflow_extensions/outerbounds/plugins/perimeters.py,sha256=QXh3SFP7GQbS-RAIxUOPbhPzQ7KDFVxZkTdKqFKgXjI,2697
|
|
7
|
+
metaflow_extensions/outerbounds/plugins/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
metaflow_extensions/outerbounds/plugins/apps/app_utils.py,sha256=JrVKlbRx8-nSmI4cRrB7F8BQGDHleABIYZudK4P-XFE,7905
|
|
9
|
+
metaflow_extensions/outerbounds/plugins/apps/deploy_decorator.py,sha256=goD50XtFOrTl8jsHazdYbu6owVzReOL_1YeukOgX75E,3439
|
|
10
|
+
metaflow_extensions/outerbounds/plugins/apps/supervisord_utils.py,sha256=1ImWHiQ4GNIyognIg-IT-cpInS1-CKWCEy2h9upZufo,5975
|
|
7
11
|
metaflow_extensions/outerbounds/plugins/fast_bakery/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
12
|
metaflow_extensions/outerbounds/plugins/fast_bakery/docker_environment.py,sha256=i6F3FXwvEhkmUCTHDJ4VmSoL6vKyQhC_YRCtY6F4EkA,14209
|
|
9
13
|
metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery.py,sha256=cj63FrdioggipQFP8GwgxU3FYe6IyzjGSUGYxLQZ4nQ,5189
|
|
@@ -33,7 +37,7 @@ metaflow_extensions/outerbounds/plugins/snowpark/snowpark_cli.py,sha256=ezJ2Jr8J
|
|
|
33
37
|
metaflow_extensions/outerbounds/plugins/snowpark/snowpark_client.py,sha256=JEW0EUxj_mNZXo9OFkJFmWfg-P7_CEgvNbgsMTCBTAE,4273
|
|
34
38
|
metaflow_extensions/outerbounds/plugins/snowpark/snowpark_decorator.py,sha256=a7LqSKULVh8IrR1StrVPbemHOLojR0nEqh-mMX-M1i4,9904
|
|
35
39
|
metaflow_extensions/outerbounds/plugins/snowpark/snowpark_exceptions.py,sha256=FTfYlJu-sn9DkPOs2R1V1ChWb1vZthOgeq0BZdT1ucY,296
|
|
36
|
-
metaflow_extensions/outerbounds/plugins/snowpark/snowpark_job.py,sha256=
|
|
40
|
+
metaflow_extensions/outerbounds/plugins/snowpark/snowpark_job.py,sha256=aQphxX6jqYgfa83w387pEWl0keuLm38V53I8P8UL2ck,6887
|
|
37
41
|
metaflow_extensions/outerbounds/plugins/snowpark/snowpark_service_spec.py,sha256=AI_kcm1hZV3JRxJkookcH6twiGnAYjk9Dx-MeoYz60Y,8511
|
|
38
42
|
metaflow_extensions/outerbounds/plugins/tensorboard/__init__.py,sha256=9lUM4Cqi5RjrHBRfG6AQMRz8-R96eZC8Ih0KD2lv22Y,1858
|
|
39
43
|
metaflow_extensions/outerbounds/profilers/__init__.py,sha256=wa_jhnCBr82TBxoS0e8b6_6sLyZX0fdHicuGJZNTqKw,29
|
|
@@ -44,7 +48,7 @@ metaflow_extensions/outerbounds/toplevel/plugins/azure/__init__.py,sha256=WUuhz2
|
|
|
44
48
|
metaflow_extensions/outerbounds/toplevel/plugins/gcp/__init__.py,sha256=BbZiaH3uILlEZ6ntBLKeNyqn3If8nIXZFq_Apd7Dhco,70
|
|
45
49
|
metaflow_extensions/outerbounds/toplevel/plugins/kubernetes/__init__.py,sha256=5zG8gShSj8m7rgF4xgWBZFuY3GDP5n1T0ktjRpGJLHA,69
|
|
46
50
|
metaflow_extensions/outerbounds/toplevel/plugins/snowflake/__init__.py,sha256=LptpH-ziXHrednMYUjIaosS1SXD3sOtF_9_eRqd8SJw,50
|
|
47
|
-
ob_metaflow_extensions-1.1.
|
|
48
|
-
ob_metaflow_extensions-1.1.
|
|
49
|
-
ob_metaflow_extensions-1.1.
|
|
50
|
-
ob_metaflow_extensions-1.1.
|
|
51
|
+
ob_metaflow_extensions-1.1.123rc0.dist-info/METADATA,sha256=jVJeDkUJvaWZ3B2Xkor72wjgNKwd1u3LThYzdotjQYs,523
|
|
52
|
+
ob_metaflow_extensions-1.1.123rc0.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
|
|
53
|
+
ob_metaflow_extensions-1.1.123rc0.dist-info/top_level.txt,sha256=NwG0ukwjygtanDETyp_BUdtYtqIA_lOjzFFh1TsnxvI,20
|
|
54
|
+
ob_metaflow_extensions-1.1.123rc0.dist-info/RECORD,,
|
{ob_metaflow_extensions-1.1.122.dist-info → ob_metaflow_extensions-1.1.123rc0.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|