From eb96b7dbc8fa490b3a01bcbe5fd06a3c44900106 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Sun, 27 Jul 2025 13:49:00 +0200 Subject: [PATCH] stubsabot: Handle errors when determining action (#14473) Use colors for output and move processed distribution name to the front --- scripts/stubsabot.py | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/scripts/stubsabot.py b/scripts/stubsabot.py index 516383e64..71e0d0b04 100755 --- a/scripts/stubsabot.py +++ b/scripts/stubsabot.py @@ -133,7 +133,7 @@ class Update: diff_analysis: DiffAnalysis | None def __str__(self) -> str: - return f"Updating {self.distribution} from '{self.old_version_spec}' to '{self.new_version_spec}'" + return f"{colored('updating', 'yellow')} from '{self.old_version_spec}' to '{self.new_version_spec}'" @property def new_version(self) -> str: @@ -151,7 +151,7 @@ class Obsolete: links: dict[str, str] def __str__(self) -> str: - return f"Marking {self.distribution} as obsolete since {self.obsolete_since_version!r}" + return f"{colored('marking as obsolete', 'yellow')} since {self.obsolete_since_version!r}" @dataclass @@ -161,7 +161,7 @@ class Remove: links: dict[str, str] def __str__(self) -> str: - return f"Removing {self.distribution} as {self.reason}" + return f"{colored('removing', 'yellow')} ({self.reason})" @dataclass @@ -170,7 +170,16 @@ class NoUpdate: reason: str def __str__(self) -> str: - return f"Skipping {self.distribution}: {self.reason}" + return f"{colored('skipping', 'green')} ({self.reason})" + + +@dataclass +class Error: + distribution: str + message: str + + def __str__(self) -> str: + return f"{colored('error', 'red')} ({self.message})" _T = TypeVar("_T") @@ -542,7 +551,16 @@ async def has_no_longer_updated_release(release_to_download: PypiReleaseDownload return await with_extracted_archive(release_to_download, session=session, handler=parse_no_longer_updated_from_archive) -async def determine_action(distribution: str, session: aiohttp.ClientSession) -> Update | NoUpdate | Obsolete | Remove: +async def determine_action(distribution: str, session: aiohttp.ClientSession) -> Update | NoUpdate | Obsolete | Remove | Error: + try: + return await determine_action_no_error_handling(distribution, session) + except Exception as exc: + return Error(distribution, str(exc)) + + +async def determine_action_no_error_handling( + distribution: str, session: aiohttp.ClientSession +) -> Update | NoUpdate | Obsolete | Remove: stub_info = read_metadata(distribution) if stub_info.is_obsolete: assert type(stub_info.obsolete) is ObsoleteMetadata @@ -861,7 +879,7 @@ async def suggest_typeshed_remove(remove: Remove, session: aiohttp.ClientSession await create_or_update_pull_request(title=title, body=body, branch_name=branch_name, session=session) -async def main() -> None: +async def main() -> int: parser = argparse.ArgumentParser() parser.add_argument( "--action-level", @@ -890,11 +908,11 @@ async def main() -> None: print("Unexpected exception!") print(diff_result.stdout) print(diff_result.stderr) - sys.exit(diff_result.returncode) + return diff_result.returncode if diff_result.stdout: changed_files = ", ".join(repr(line) for line in diff_result.stdout.split("\n") if line) print(f"Cannot run stubsabot, as uncommitted changes are present in {changed_files}!") - sys.exit(1) + return 1 if args.action_level > ActionLevel.fork: if os.environ.get("GITHUB_TOKEN") is None: @@ -909,6 +927,8 @@ async def main() -> None: if args.action_level >= ActionLevel.local: subprocess.check_call(["git", "fetch", "--prune", "--all"]) + error = False + try: conn = aiohttp.TCPConnector(limit_per_host=10) async with aiohttp.ClientSession(connector=conn) as session: @@ -921,10 +941,14 @@ async def main() -> None: action_count = 0 for task in asyncio.as_completed(tasks): update = await task + print(f"{update.distribution}... ", end="") print(update) if isinstance(update, NoUpdate): continue + if isinstance(update, Error): + error = True + continue if args.action_count_limit is not None and action_count >= args.action_count_limit: print(colored("... but we've reached action count limit", "red")) @@ -952,6 +976,8 @@ async def main() -> None: if args.action_level >= ActionLevel.local and original_branch: subprocess.check_call(["git", "checkout", original_branch]) + return 1 if error else 0 + if __name__ == "__main__": - asyncio.run(main()) + sys.exit(asyncio.run(main()))