c2087cb0ed189d946439a090d43d2dba5d71a9eb lrnassar Wed Aug 5 17:28:22 2026 -0700 Fix error containment and a wasted retry sleep in the VCEP version notifier per CR feedback. refs #37795 ClinGen's version field is a free-form string, so an unparseable value raised ValueError past the per-VCEP except RuntimeError in checkVcep. That aborted the whole run, skipping the remaining VCEPs and mailing a traceback instead of the report. normalizeVersion now reports the offending string as a RuntimeError, and the version comparison moved inside the try so it is caught, keeping the damage to a single failure line. fetchUrl also slept 30 seconds after its final failed attempt, which could not help. It now only sleeps between attempts. diff --git src/hg/utils/otto/vcepVersions/checkVcepVersions.py src/hg/utils/otto/vcepVersions/checkVcepVersions.py index be80d370ca2..8af0f3c687e 100755 --- src/hg/utils/otto/vcepVersions/checkVcepVersions.py +++ src/hg/utils/otto/vcepVersions/checkVcepVersions.py @@ -43,45 +43,53 @@ }, "InSiGHT Lynch Syndrome VCEP": { "hubUrl": "https://hgdownload.soe.ucsc.edu/hubs/insight/insight.html", "hubRegex": r"<h1>InSiGHT specs\s+(\d+(?:\.\d+)+)</h1>", "affiliation": "50099", "genes": ["MLH1", "MSH2", "MSH6", "PMS2"], }, } cspecUrl = "https://cspec.genome.network/cspec/ui/svi/affiliation/" def fetchUrl(url): """Fetch url and return its text. Retries a few times so a transient network blip does not turn into a false alarm, then raises.""" + attempts = 5 lastErr = None - for attempt in range(5): + for attempt in range(attempts): try: with urllib.request.urlopen(url, timeout=120) as resp: return resp.read().decode("utf-8", "replace") except (urllib.error.URLError, OSError) as e: lastErr = e + if attempt < attempts - 1: time.sleep(30) - raise RuntimeError("could not fetch " + url + " after 5 attempts: " + str(lastErr)) + raise RuntimeError("could not fetch " + url + " after " + str(attempts) + + " attempts: " + str(lastErr)) def normalizeVersion(version): """Turn a dotted version into a tuple for comparison, dropping trailing - zeroes so ClinGen's '2.0' matches our page's '2.0.0'.""" + zeroes so ClinGen's '2.0' matches our page's '2.0.0'. ClinGen's version is a + free-form string, so an unparseable one is reported rather than raising past + the per-VCEP error handling and killing the rest of the run.""" + try: parts = [int(p) for p in version.split(".")] + except ValueError: + raise RuntimeError("could not parse version string '" + version + "'") while len(parts) > 1 and parts[-1] == 0: parts.pop() return tuple(parts) def getOurVersion(config): """Scrape the version we publish from a hub description page.""" html = fetchUrl(config["hubUrl"]) match = re.search(config["hubRegex"], html) if match is None: raise RuntimeError("no version found on " + config["hubUrl"] + " (page wording changed? regex: " + config["hubRegex"] + ")") return match.group(1) @@ -105,36 +113,36 @@ if gene.get("label") in config["genes"]: versions[gene["label"]] = svi["version"] missing = [g for g in config["genes"] if g not in versions] if missing: raise RuntimeError("no released CSpec spec for " + ", ".join(missing) + " at " + url) return versions def checkVcep(name, config, report): """Compare one VCEP and append any mismatch to report. Returns True on success, False if the check itself could not be completed.""" try: ourVersion = getOurVersion(config) clinGenVersions = getClinGenVersions(config) + stale = {g: v for g, v in clinGenVersions.items() + if normalizeVersion(v) != normalizeVersion(ourVersion)} except RuntimeError as e: report.append(name + ": check failed: " + str(e)) return False - stale = {g: v for g, v in clinGenVersions.items() - if normalizeVersion(v) != normalizeVersion(ourVersion)} if stale: geneList = ", ".join(g + "=" + stale[g] for g in sorted(stale)) report.append(name + " is out of date.") report.append(" our hub page: " + ourVersion + " (" + config["hubUrl"] + ")") report.append(" ClinGen CSpec: " + geneList + " (" + cspecUrl + config["affiliation"] + ")") return True def main(): report = [] ok = True for name in sorted(vcepConfig): if not checkVcep(name, vcepConfig[name], report): ok = False