From 93f2accb6d6ec1dd43a6ad3cf3368305ecfa43d1 Mon Sep 17 00:00:00 2001 From: seylu <98249191+seyLu@users.noreply.github.com> Date: Sat, 30 Mar 2024 23:56:31 +0800 Subject: [PATCH 1/3] feat (gh api): query all issues --- src/ghlabel/utils/github_api.py | 39 +++++++++++++++++++++---- src/ghlabel/utils/github_api_types.py | 14 +++++++++ src/ghlabel/utils/helpers.py | 2 ++ src/ghlabel/utils/setup_github_label.py | 4 +-- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/ghlabel/utils/github_api.py b/src/ghlabel/utils/github_api.py index e4f0105..5fd5424 100644 --- a/src/ghlabel/utils/github_api.py +++ b/src/ghlabel/utils/github_api.py @@ -8,7 +8,14 @@ from requests.exceptions import HTTPError, Timeout from requests.models import Response -from ghlabel.utils.github_api_types import GithubIssue, GithubLabel, StatusCode +from ghlabel.utils.github_api_types import ( + GithubIssue, + GithubIssueParams, + GithubLabel, + GithubPullRequest, + StatusCode, +) +from ghlabel.utils.helpers import STATUS_OK, validate_env Path("logs").mkdir(exist_ok=True) fileConfig(os.path.join(os.path.dirname(__file__), "../logging.ini")) @@ -166,7 +173,7 @@ def delete_label(self, label_name: str) -> tuple[None, StatusCode]: return None, res.status_code def list_issues( - self, labels: list[GithubLabel] | None = None + self, labels: list[GithubLabel] | None = None, state: str = "all" ) -> tuple[list[GithubIssue], StatusCode]: """ Issue queried include PRs. PR has "pull_request" key. @@ -174,10 +181,13 @@ def list_issues( url: str = f"{self.base_url}/issues" res: Response + params: GithubIssueParams = {} if labels: - labels_str: str = ",".join(label["name"] for label in labels) - url += f"?labels={labels_str}" + params["labels"] = ",".join(label["name"] for label in labels) + + if state: + params["state"] = state page: int = 1 per_page: int = 100 @@ -187,13 +197,14 @@ def list_issues( ) github_issues: list[GithubIssue] = [] while True: - params: dict[str, int] = {"page": page, "per_page": per_page} + params["page"] = page + params["per_page"] = per_page logging.info(f"Fetching page {page}.") try: res = requests.get( url, headers=self.headers, - params=params, + params=params, # type: ignore[arg-type] timeout=10, ) res.raise_for_status() @@ -215,3 +226,19 @@ def list_issues( page += 1 return github_issues, res.status_code + + +if __name__ == "__main__": + gh_api = GithubApi( + validate_env("GITHUB_TOKEN"), + validate_env("GITHUB_REPO_OWNER"), + validate_env("GITHUB_REPO_NAME"), + ) + gh_issues, status_code = gh_api.list_issues() + if status_code != STATUS_OK: + sys.exit() + gh_pull_requests: list[GithubPullRequest] = [] + for issue in gh_issues: + if "pull_request" in issue: + gh_pull_requests.append(issue) + print(gh_pull_requests) diff --git a/src/ghlabel/utils/github_api_types.py b/src/ghlabel/utils/github_api_types.py index ccbed4a..9b458ba 100644 --- a/src/ghlabel/utils/github_api_types.py +++ b/src/ghlabel/utils/github_api_types.py @@ -3,6 +3,11 @@ StatusCode = int +class GithubParams(TypedDict): + page: NotRequired[int] + per_page: NotRequired[int] + + class GithubLabel(TypedDict): name: str new_name: NotRequired[str] @@ -12,3 +17,12 @@ class GithubLabel(TypedDict): class GithubIssue(TypedDict): labels: list[GithubLabel] + + +class GithubIssueParams(GithubParams): + labels: NotRequired[str] + state: NotRequired[str] + + +class GithubPullRequest(TypedDict): + labels: list[GithubLabel] diff --git a/src/ghlabel/utils/helpers.py b/src/ghlabel/utils/helpers.py index e8e94d1..589050e 100644 --- a/src/ghlabel/utils/helpers.py +++ b/src/ghlabel/utils/helpers.py @@ -10,6 +10,8 @@ Path("logs").mkdir(exist_ok=True) fileConfig(os.path.join(os.path.dirname(__file__), "../logging.ini")) +STATUS_OK: int = 200 + def validate_env(env: str) -> str: _env: str | None = os.getenv(env) diff --git a/src/ghlabel/utils/setup_github_label.py b/src/ghlabel/utils/setup_github_label.py index 4f588e2..434fb9e 100644 --- a/src/ghlabel/utils/setup_github_label.py +++ b/src/ghlabel/utils/setup_github_label.py @@ -25,14 +25,12 @@ from ghlabel.utils.github_api import GithubApi from ghlabel.utils.github_api_types import GithubLabel -from ghlabel.utils.helpers import validate_env +from ghlabel.utils.helpers import STATUS_OK, validate_env load_dotenv() Path("logs").mkdir(exist_ok=True) fileConfig(os.path.join(os.path.dirname(__file__), "../logging.ini")) -STATUS_OK: int = 200 - class SetupGithubLabel: def __init__( From 2f42ebc454f8d791f4d92efeb3e07c5f8f5fee20 Mon Sep 17 00:00:00 2001 From: seylu <98249191+seyLu@users.noreply.github.com> Date: Sun, 31 Mar 2024 01:07:09 +0800 Subject: [PATCH 2/3] feat (setup github labels): display labels that are unsafe to remove --- src/ghlabel/cli.py | 10 ++++ src/ghlabel/utils/github_api.py | 6 +-- src/ghlabel/utils/setup_github_label.py | 62 +++++++++++++++++++------ 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/ghlabel/cli.py b/src/ghlabel/cli.py index 02615a1..1d51f7c 100644 --- a/src/ghlabel/cli.py +++ b/src/ghlabel/cli.py @@ -195,6 +195,16 @@ def setup_labels( # noqa: PLR0913 gh_label.add_labels(labels=parse_add_labels(add_labels), preview=preview) + if gh_label.labels_unsafe_to_remove: + if not preview: + rich.print() + rich.print(" The following labels are not [red]removed[/red]:") + for label_name in gh_label.labels_unsafe_to_remove: + rich.print( + f' - {label_name} \[{", ".join(url for url in gh_label.label_name_urls_map[label_name])}]' + ) + rich.print() + @app.command("dump", help="Generate starter labels config files.") # type: ignore[misc] def app_dump( diff --git a/src/ghlabel/utils/github_api.py b/src/ghlabel/utils/github_api.py index 5fd5424..d3da2b7 100644 --- a/src/ghlabel/utils/github_api.py +++ b/src/ghlabel/utils/github_api.py @@ -173,7 +173,7 @@ def delete_label(self, label_name: str) -> tuple[None, StatusCode]: return None, res.status_code def list_issues( - self, labels: list[GithubLabel] | None = None, state: str = "all" + self, label_names: set[str] | None = None, state: str = "all" ) -> tuple[list[GithubIssue], StatusCode]: """ Issue queried include PRs. PR has "pull_request" key. @@ -183,8 +183,8 @@ def list_issues( res: Response params: GithubIssueParams = {} - if labels: - params["labels"] = ",".join(label["name"] for label in labels) + if label_names: + params["labels"] = ",".join(label_name for label_name in label_names) if state: params["state"] = state diff --git a/src/ghlabel/utils/setup_github_label.py b/src/ghlabel/utils/setup_github_label.py index 434fb9e..0433497 100644 --- a/src/ghlabel/utils/setup_github_label.py +++ b/src/ghlabel/utils/setup_github_label.py @@ -24,7 +24,7 @@ from dotenv import load_dotenv from ghlabel.utils.github_api import GithubApi -from ghlabel.utils.github_api_types import GithubLabel +from ghlabel.utils.github_api_types import GithubIssue, GithubLabel from ghlabel.utils.helpers import STATUS_OK, validate_env load_dotenv() @@ -38,18 +38,19 @@ def __init__( gh_api: GithubApi, labels_dir: str = "labels", ) -> None: - self._labels_dir: str = labels_dir - self._gh_api: GithubApi = gh_api - self._github_labels = self._fetch_formatted_github_labels() - self._github_label_names: list[ - str - ] = [ # requires index, so using list index instead of set - github_label["name"] for github_label in self.github_labels + self._labels_dir = labels_dir + self._gh_api = gh_api + + self._github_labels: list[GithubLabel] = self._fetch_formatted_github_labels() + self._github_label_names: list[str] = [ + # requires index, so using list instead of set + github_label["name"] + for github_label in self.github_labels ] self._labels: list[GithubLabel] = self._load_labels_from_config() or [] - self._labels_to_remove: set[str] = set( - self._load_labels_to_remove_from_config() or [] - ) + self._label_name_urls_map: dict[str, set[str]] = {} + self._labels_unsafe_to_remove: set[str] = set() + self._labels_safe_to_remove: set[str] = self._list_labels_safe_to_remove() @property def labels_dir(self) -> str: @@ -68,8 +69,16 @@ def labels(self) -> list[GithubLabel]: return self._labels @property - def labels_to_remove(self) -> set[str]: - return self._labels_to_remove + def label_name_urls_map(self) -> dict[str, set[str]]: + return self._label_name_urls_map + + @property + def labels_unsafe_to_remove(self) -> set[str]: + return self._labels_unsafe_to_remove + + @property + def labels_safe_to_remove(self) -> set[str]: + return self._labels_safe_to_remove @property def gh_api(self) -> GithubApi: @@ -88,6 +97,31 @@ def _format_github_label(self, github_label: GithubLabel) -> GithubLabel: if key not in ["id", "node_id", "url", "default"] } + def _list_labels_safe_to_remove(self) -> set[str]: + all_labels_to_remove: set[str] = set( + self._load_labels_to_remove_from_config() or [] + ) + labels_unsafe_to_remove: set[str] = set() + + gh_issues: list[GithubIssue] + gh_issues, status_code = self.gh_api.list_issues() + if status_code != STATUS_OK: + sys.exit() + for issue in gh_issues: + url = issue["html_url"] + if "pull_request" in issue: + url = issue["pull_request"]["html_url"] + + for label in issue["labels"]: + labels_unsafe_to_remove.add(label["name"]) + if label["name"] not in self._label_name_urls_map: + self._label_name_urls_map[label["name"]] = set([url]) + else: + self._label_name_urls_map[label["name"]].add(url) + + self._labels_unsafe_to_remove = labels_unsafe_to_remove + return all_labels_to_remove - labels_unsafe_to_remove + def _load_labels_from_config(self) -> list[GithubLabel]: use_labels: list[GithubLabel] = [] labels: list[GithubLabel] = [] @@ -221,7 +255,7 @@ def remove_labels( strict: bool = False, preview: bool = False, ) -> None: - labels_to_remove: set[str] = self.labels_to_remove + labels_to_remove: set[str] = self.labels_safe_to_remove if strict: labels_to_remove.update( From e43b5c9a2ba3165aa651af1cabd8ea0afbfec11a Mon Sep 17 00:00:00 2001 From: seylu <98249191+seyLu@users.noreply.github.com> Date: Sun, 31 Mar 2024 01:08:40 +0800 Subject: [PATCH 3/3] fix: types --- src/ghlabel/utils/github_api_types.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ghlabel/utils/github_api_types.py b/src/ghlabel/utils/github_api_types.py index 9b458ba..32b74df 100644 --- a/src/ghlabel/utils/github_api_types.py +++ b/src/ghlabel/utils/github_api_types.py @@ -16,6 +16,8 @@ class GithubLabel(TypedDict): class GithubIssue(TypedDict): + html_url: str + pull_request: NotRequired[dict[str, str]] labels: list[GithubLabel]