--- /dev/null
+import logging
+from typing import Literal
+
+from github import Github
+from github.PullRequestReview import PullRequestReview
+from pydantic import BaseModel, SecretStr
+from pydantic_settings import BaseSettings
+
+
+class LabelSettings(BaseModel):
+ await_label: str | None = None
+ number: int
+
+
+default_config = {"approved-2": LabelSettings(await_label="awaiting-review", number=2)}
+
+
+class Settings(BaseSettings):
+ github_repository: str
+ token: SecretStr
+ debug: bool | None = False
+ config: dict[str, LabelSettings] | Literal[""] = default_config
+
+
+settings = Settings()
+if settings.debug:
+ logging.basicConfig(level=logging.DEBUG)
+else:
+ logging.basicConfig(level=logging.INFO)
+logging.debug(f"Using config: {settings.json()}")
+g = Github(settings.token.get_secret_value())
+repo = g.get_repo(settings.github_repository)
+for pr in repo.get_pulls(state="open"):
+ logging.info(f"Checking PR: #{pr.number}")
+ pr_labels = list(pr.get_labels())
+ pr_label_by_name = {label.name: label for label in pr_labels}
+ reviews = list(pr.get_reviews())
+ review_by_user: dict[str, PullRequestReview] = {}
+ for review in reviews:
+ if review.user.login in review_by_user:
+ stored_review = review_by_user[review.user.login]
+ if review.submitted_at >= stored_review.submitted_at:
+ review_by_user[review.user.login] = review
+ else:
+ review_by_user[review.user.login] = review
+ approved_reviews = [
+ review for review in review_by_user.values() if review.state == "APPROVED"
+ ]
+ config = settings.config or default_config
+ for approved_label, conf in config.items():
+ logging.debug(f"Processing config: {conf.json()}")
+ if conf.await_label is None or (conf.await_label in pr_label_by_name):
+ logging.debug(f"Processable PR: {pr.number}")
+ if len(approved_reviews) >= conf.number:
+ logging.info(f"Adding label to PR: {pr.number}")
+ pr.add_to_labels(approved_label)
+ if conf.await_label:
+ logging.info(f"Removing label from PR: {pr.number}")
+ pr.remove_from_labels(conf.await_label)
+logging.info("Finished")