Skip to content

Commit 4945fb0

Browse files
committed
Fix: Refactor fix_uninstallable function for improved error handling and path management
1 parent f9c3dbb commit 4945fb0

1 file changed

Lines changed: 58 additions & 37 deletions

File tree

scripts/fix_outdated.py

Lines changed: 58 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
import logging
33
import sys
44
import time
5+
from pathlib import Path
56
from concurrent.futures import ThreadPoolExecutor, as_completed
7+
from collections import defaultdict
68

79
import yaml
810
from bioblend import toolshed
@@ -16,13 +18,11 @@
1618
def retry_with_backoff(func, *args, **kwargs):
1719
MAX_RETRIES = 5
1820
backoff = 2
19-
last_exception = None
2021

2122
for attempt in range(MAX_RETRIES):
2223
try:
2324
return func(*args, **kwargs)
2425
except Exception as e:
25-
last_exception = e
2626
error_msg = str(e)
2727
if any(
2828
code in error_msg
@@ -34,14 +34,9 @@ def retry_with_backoff(func, *args, **kwargs):
3434
)
3535
time.sleep(backoff)
3636
backoff = min(backoff * 2, 60)
37-
else:
38-
logger.error(f"All {MAX_RETRIES} attempts failed")
39-
else:
40-
raise
41-
42-
if last_exception:
43-
raise last_exception
44-
raise Exception("Retry failed with no exception captured")
37+
continue
38+
raise e
39+
raise Exception("Retry failed after max attempts")
4540

4641

4742
def get_tool_versions(ts, name, owner, revision):
@@ -80,12 +75,28 @@ def fetch_versions_parallel(ts, name, owner, revisions, max_workers=10):
8075

8176
def fix_uninstallable(lockfile_name, toolshed_url):
8277
ts = toolshed.ToolShedInstance(url=toolshed_url)
83-
with open(lockfile_name) as f:
78+
lockfile_path = Path(lockfile_name)
79+
with open(lockfile_path) as f:
8480
lockfile = yaml.safe_load(f) or {}
8581
locked_tools = lockfile.get("tools", [])
8682
total = len(locked_tools)
8783

88-
logger.info(f"Processing {total} tools from {lockfile_name}...")
84+
uninstallable_file = lockfile_path.with_name(
85+
lockfile_path.name.replace(".yaml.lock", ".uninstallable_revisions.yaml")
86+
)
87+
88+
removed_map = defaultdict(set)
89+
try:
90+
with open(uninstallable_file) as f:
91+
uninstallable_data = yaml.safe_load(f) or {}
92+
for t in uninstallable_data.get("tools", []):
93+
removed_map[(t["name"], t["owner"])] = set(
94+
t.get("removed_revisions", [])
95+
)
96+
except FileNotFoundError:
97+
pass
98+
99+
logger.info(f"Processing {total} tools from {lockfile_path.name}...")
89100
changed, skipped = 0, 0
90101

91102
for i, tool in enumerate(locked_tools):
@@ -95,38 +106,40 @@ def fix_uninstallable(lockfile_name, toolshed_url):
95106
)
96107

97108
name, owner = tool.get("name"), tool.get("owner")
98-
revisions = tool.get("revisions", [])
109+
current_revisions = set(tool.get("revisions", []))
99110
try:
100-
installable = retry_with_backoff(
111+
installable_list = retry_with_backoff(
101112
ts.repositories.get_ordered_installable_revisions, name, owner
102113
)
103114
except Exception as e:
104115
logger.warning(f"{name},{owner}: could not get installable revisions ({e})")
105116
continue
106117

107-
uninstallable = set(revisions) - set(installable)
118+
uninstallable = current_revisions - set(installable_list)
108119
if not uninstallable:
109120
skipped += 1
110121
continue
111122

112-
all_revs = list(uninstallable) + list(installable)
123+
all_revs = list(uninstallable) + installable_list
113124
version_cache = fetch_versions_parallel(ts, name, owner, all_revs)
114125

115-
to_remove = []
126+
installable_signatures = {}
127+
for rev in installable_list:
128+
sig = frozenset(version_cache.get(rev, []))
129+
if sig:
130+
installable_signatures[sig] = rev
131+
to_remove = set()
132+
116133
for cur in uninstallable:
117-
cur_versions = version_cache.get(cur, set())
118-
if not cur_versions:
119-
if installable:
120-
nxt = installable[0]
134+
cur_sig = frozenset(version_cache.get(cur, []))
135+
if not cur_sig:
136+
if installable_list:
137+
nxt = installable_list[-1]
121138
logger.info(f"{name},{owner}: unverifiable {cur}, keeping {nxt}")
122-
to_remove.append(cur)
123-
continue
139+
to_remove.add(cur)
140+
continue
124141

125-
nxt = None
126-
for cand in reversed(installable):
127-
if version_cache.get(cand) == cur_versions:
128-
nxt = cand
129-
break
142+
nxt = installable_signatures.get(cur_sig)
130143

131144
if not nxt:
132145
logger.warning(
@@ -135,30 +148,38 @@ def fix_uninstallable(lockfile_name, toolshed_url):
135148
sys.exit(1)
136149

137150
logger.info(f"{name},{owner}: removing {cur} in favor of {nxt}")
138-
if nxt not in revisions:
139-
revisions.append(nxt)
140-
to_remove.append(cur)
151+
if nxt not in current_revisions:
152+
tool["revisions"].append(nxt)
153+
to_remove.add(cur)
141154

142155
if to_remove:
143156
changed += 1
144-
tool["revisions"] = sorted(set(revisions) - set(to_remove))
157+
tool["revisions"] = sorted(set(tool["revisions"]) - to_remove)
158+
removed_map[(name, owner)].update(to_remove)
145159

146160
logger.info(
147161
f"Completed: {total} tools processed, {skipped} skipped, {changed} changed"
148162
)
149163

150-
with open(lockfile_name, "w") as f:
164+
with open(lockfile_path, "w") as f:
151165
yaml.dump(lockfile, f, sort_keys=False, default_flow_style=False)
152166

167+
uninstallable_output = {
168+
"tools": [
169+
{"name": n, "owner": o, "removed_revisions": sorted(revs)}
170+
for (n, o), revs in removed_map.items()
171+
]
172+
}
173+
with open(uninstallable_file, "w") as f:
174+
yaml.dump(uninstallable_output, f, sort_keys=False, default_flow_style=False)
175+
153176

154177
if __name__ == "__main__":
155178
parser = argparse.ArgumentParser()
156-
parser.add_argument(
157-
"lockfile", type=argparse.FileType("r"), help="Tool.yaml.lock file"
158-
)
179+
parser.add_argument("lockfile", help="Tool.yaml.lock file path")
159180
parser.add_argument(
160181
"--toolshed", default="https://toolshed.g2.bx.psu.edu", help="Toolshed base URL"
161182
)
162183
args = parser.parse_args()
163184

164-
fix_uninstallable(args.lockfile.name, args.toolshed)
185+
fix_uninstallable(args.lockfile, args.toolshed)

0 commit comments

Comments
 (0)