Skip to content

Commit

Permalink
[FEAT] Non-destructive sync (#143)
Browse files Browse the repository at this point in the history
* feat (gh api): query all issues

* feat (setup github labels): display labels that are unsafe to remove

* fix: types
  • Loading branch information
seyLu authored Mar 30, 2024
1 parent 2733bac commit 373aadb
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 24 deletions.
10 changes: 10 additions & 0 deletions src/ghlabel/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
41 changes: 34 additions & 7 deletions src/ghlabel/utils/github_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down Expand Up @@ -166,18 +173,21 @@ 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, label_names: set[str] | None = None, state: str = "all"
) -> tuple[list[GithubIssue], StatusCode]:
"""
Issue queried include PRs. PR has "pull_request" key.
"""

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}"
if label_names:
params["labels"] = ",".join(label_name for label_name in label_names)

if state:
params["state"] = state

page: int = 1
per_page: int = 100
Expand All @@ -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()
Expand All @@ -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)
16 changes: 16 additions & 0 deletions src/ghlabel/utils/github_api_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -11,4 +16,15 @@ class GithubLabel(TypedDict):


class GithubIssue(TypedDict):
html_url: str
pull_request: NotRequired[dict[str, str]]
labels: list[GithubLabel]


class GithubIssueParams(GithubParams):
labels: NotRequired[str]
state: NotRequired[str]


class GithubPullRequest(TypedDict):
labels: list[GithubLabel]
2 changes: 2 additions & 0 deletions src/ghlabel/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
66 changes: 49 additions & 17 deletions src/ghlabel/utils/setup_github_label.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,33 @@
from dotenv import load_dotenv

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.github_api_types import GithubIssue, GithubLabel
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__(
self,
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:
Expand All @@ -70,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:
Expand All @@ -90,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] = []
Expand Down Expand Up @@ -223,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(
Expand Down

0 comments on commit 373aadb

Please sign in to comment.