#!/usr/bin/env python3 """ Generate a summary of last week's issues tagged with "topic: feature". The summary will include a list of new and changed issues and is sent each Monday at 0200 CE(S)T to the typing-sig mailing list. Due to limitation with GitHub Actions, the mail is sent from a private server, currently maintained by @srittau. """ from __future__ import annotations import datetime from dataclasses import dataclass from typing import Any, Iterable, Sequence import requests ISSUES_API_URL = "https://api.github.com/repos/python/typing/issues" ISSUES_URL = "https://github.com/python/typing/issues?q=label%3A%22topic%3A+feature%22" ISSUES_LABEL = "topic: feature" SENDER_EMAIL = "Typing Bot " RECEIVER_EMAIL = "typing-sig@python.org" @dataclass class Issue: number: int title: str url: str created: datetime.datetime user: str pull_request: bool = False def main() -> None: since = previous_week_start() issues = fetch_issues(since) new, updated = split_issues(issues, since) print_summary(since, new, updated) def previous_week_start() -> datetime.date: today = datetime.date.today() return today - datetime.timedelta(days=today.weekday() + 7) def fetch_issues(since: datetime.date) -> list[Issue]: """Return (new, updated) issues.""" j = requests.get( ISSUES_API_URL, params={ "labels": ISSUES_LABEL, "since": f"{since:%Y-%m-%d}T00:00:00Z", "per_page": "100", "state": "open", }, headers={"Accept": "application/vnd.github.v3+json"}, ).json() assert isinstance(j, list) return [parse_issue(j_i) for j_i in j] def parse_issue(j: Any) -> Issue: number = j["number"] title = j["title"] url = j["html_url"] created_at = datetime.datetime.fromisoformat(j["created_at"][:-1]) user = j["user"]["login"] pull_request = "pull_request" in j assert isinstance(number, int) assert isinstance(title, str) assert isinstance(url, str) assert isinstance(user, str) return Issue(number, title, url, created_at, user, pull_request) def split_issues( issues: Iterable[Issue], since: datetime.date ) -> tuple[list[Issue], list[Issue]]: new = [] updated = [] for issue in issues: if issue.created.date() >= since: new.append(issue) else: updated.append(issue) new.sort(key=lambda i: i.number) updated.sort(key=lambda i: i.number) return new, updated def print_summary( since: datetime.date, new: Sequence[Issue], changed: Sequence[Issue] ) -> None: print(f"From: {SENDER_EMAIL}") print(f"To: {RECEIVER_EMAIL}") print(f"Subject: Opened and changed typing issues week {since:%G-W%V}") print() print(generate_mail(new, changed)) def generate_mail(new: Sequence[Issue], changed: Sequence[Issue]) -> str: if len(new) == 0 and len(changed) == 0: s = ( "No issues or pull requests with the label 'topic: feature' were opened\n" "or updated last week in the typing repository on GitHub.\n\n" ) else: s = ( "The following is an overview of all issues and pull requests in the\n" "typing repository on GitHub with the label 'topic: feature'\n" "that were opened or updated last week, excluding closed issues.\n\n" "---------------------------------------------------\n\n" ) if len(new) > 0: s += "The following issues and pull requests were opened last week: \n\n" s += "".join(generate_issue_text(issue) for issue in new) s += "\n---------------------------------------------------\n\n" if len(changed) > 0: s += "The following issues and pull requests were updated last week: \n\n" s += "".join(generate_issue_text(issue) for issue in changed) s += "\n---------------------------------------------------\n\n" s += ( "All issues and pull requests with the label 'topic: feature'\n" "can be viewed under the following URL:\n\n" ) s += ISSUES_URL return s def generate_issue_text(issue: Issue) -> str: s = f"#{issue.number:<5} " if issue.pull_request: s += "[PR] " s += f"{issue.title}\n" s += f" opened by @{issue.user}\n" s += f" {issue.url}\n" return s if __name__ == "__main__": main()