From ff4323e2e8b67d34d3cd157737787b710905a9ee Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 20 Aug 2026 18:23:35 -0400 Subject: [PATCH] Minor tweaks to issue-opening bot --- tools/check_stale_wheels.py | 51 ++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/tools/check_stale_wheels.py b/tools/check_stale_wheels.py index d7bdcf2..ecf937b 100755 --- a/tools/check_stale_wheels.py +++ b/tools/check_stale_wheels.py @@ -32,6 +32,7 @@ ANACONDA_API = "https://api.anaconda.org" CHANNEL_URL = f"https://anaconda.org/{ANACONDA_USER}" ACTION_URL = "https://github.com/scientific-python/upload-nightly-action" +BOT_URL = "https://github.com/scientific-python-bot" POLICY_URL = f"{ACTION_URL}#artifact-cleanup-policy-at-the-scientific-python-nightly-wheels-channel" # Hidden markers let us find our own issues and comments again without relying on @@ -62,6 +63,10 @@ SESSION = requests.Session() SESSION.headers["User-Agent"] = "scientific-python-upload-nightly-action" +# What this run did, surfaced as an annotation on the job page at the end so the +# issues are one click away rather than buried in the summary table. +NOTICES = [] + # Ambient run configuration, set once by main(). GH: Github = None LOGIN = "" @@ -230,6 +235,9 @@ def issue_body(stale, age, removal_date): {uploads} +The *Updated* date shown on anaconda.org can be newer than these: it tracks any change to the \ +package, including our own removal of versions that have expired. + Wheels are [removed after {RETENTION_DAYS} days]({POLICY_URL}), so unless a new nightly is \ uploaded before **{removal_date:%Y-%m-%d}** there will be no wheels left on the channel at all, \ and downstream projects that test against nightlies will fail to install them. @@ -243,11 +251,17 @@ def issue_body(stale, age, removal_date): - Has there simply been nothing to build? Consider uploading on a fixed cadence even when \ nothing has changed, to keep the channel populated for downstream users. -This issue was opened automatically from [scientific-python/upload-nightly-action]({ACTION_URL}), \ -and will be closed automatically once a new wheel is uploaded. +*🤖 Opened automatically by [scientific-python-bot]({BOT_URL}) from \ +[scientific-python/upload-nightly-action]({ACTION_URL}), and closed again on its own once a new \ +wheel is uploaded.* """ +def action(done, would): + """Word a summary status as an action taken, or one --dry-run only considered.""" + return would if DRY_RUN else done + + def create_issue(repo, title, body): if DRY_RUN: print(f" [dry run] would open an issue on {repo.full_name}: {title}") @@ -280,8 +294,11 @@ def handle_fresh(packages, issues): newest = max(package.last_upload for package in packages) for issue in open_issues: for package in packages: - package.status = "resolved" + package.status = action("resolved", "would close") package.issue_url = issue.html_url + NOTICES.append( + f"{action('Closed', 'Would close')} for {describe(packages)}: {issue.html_url}" + ) close_issue( issue, f"New nightly wheels were uploaded on {newest:%Y-%m-%d}. Thanks! Closing." ) @@ -310,7 +327,11 @@ def mark(status, url): issue_title(stale, age), issue_body(stale, age, removal_date), ) - mark("opened", url) + mark(action("opened", "would open"), url) + NOTICES.append( + f"{action('Opened', 'Would open')} for {describe(stale)}: " + f"{url or repo.html_url + '/issues'}" + ) return issue = open_issues[0] @@ -319,7 +340,11 @@ def mark(status, url): return if any(FINAL_WARNING_MARKER in (item.body or "") for item in issue.get_comments()): return - mark("final warning", issue.html_url) + mark(action("final warning", "would comment"), issue.html_url) + NOTICES.append( + f"{action('Commented', 'Would comment')} on {describe(stale)} at {age} days: " + f"{issue.html_url}" + ) comment( issue, f"{FINAL_WARNING_MARKER}\nStill no new nightly wheels for {describe(stale)}. The " @@ -348,7 +373,7 @@ def handle_repo(repo, packages, now): def write_summary(packages, now): lines = [ - f"## Nightly wheel freshness ({now:%Y-%m-%d})", + f"## Nightly wheel freshness ({now:%Y-%m-%d}){' — dry run' if DRY_RUN else ''}", "", "| Package | Last upload | Age (days) | Repository | Status |", "| --- | --- | --- | --- | --- |", @@ -376,6 +401,19 @@ def write_summary(packages, now): fid.write(summary + "\n") +def annotate(lines): + """Surface what the run did as an annotation on the GitHub Actions job page.""" + if not lines: + return + body = "\n".join(lines) + if os.environ.get("GITHUB_ACTIONS"): + # Workflow commands are one line, so the newlines have to be encoded + body = body.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A") + print(f"::notice title=Stale wheel check::{body}") + else: + print(f"\n{body}") + + def report_errors(errors): """Open (or update) an issue here about projects we could not reach. @@ -456,6 +494,7 @@ def main(argv=None): handle_repo(group[0].repo, group, now) write_summary(packages, now) + annotate(NOTICES) errors = [package.error for package in packages if package.error] if errors: report_errors(errors)