Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] Non-destructive sync #143

Merged
merged 3 commits into from
Mar 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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