diff --git a/issues_parser.py b/issues_parser.py index 2ec86d75..887f3ac5 100644 --- a/issues_parser.py +++ b/issues_parser.py @@ -3,7 +3,6 @@ from datetime import datetime from time import sleep from typing import Generator - import pytz import requests @@ -43,80 +42,134 @@ class IssueDataWithComment(IssueData): comment_author_email: str = '' -def get_connected_pulls(issue_number, repo_owner, repo_name, token): - # TODO как-то заменить - return - access_token = token - repo_owner = repo_owner.login - # Формирование запроса GraphQL - query = """ - { - repository(owner: "%s", name: "%s") { - issue(number: %d) { - timelineItems(first: 50, itemTypes:[CONNECTED_EVENT,CROSS_REFERENCED_EVENT]) { - filteredCount - nodes { - ... on ConnectedEvent { - ConnectedEvent: subject { - ... on PullRequest { - number - title - url +def get_connected_pulls( + token: str, + issue_number: int, + repo_owner: str, + repo_name: str, + base_url: str | None = None +) -> str: + + if base_url: # Forgejo + headers = { + "Authorization": f"token {token}", + "Accept": "application/json" + } + + connected_prs = set() + api_base = f"{base_url}/api/v1/repos/{repo_owner}/{repo_name}" + + try: + comments_response = requests.get( + f"{api_base}/issues/{issue_number}/comments", + headers=headers + ) + comments_response.raise_for_status() + + for comment in comments_response.json(): + body = comment.get("body", "") + if not body: + continue + + for word in body.split(): + clean_word = word.strip(".,:;!?()[]{}") + if len(clean_word) > 1 and clean_word[1:].isdigit(): + if clean_word.startswith('#'): + pr_num = clean_word[1:] + connected_prs.add(f"{base_url}/{repo_owner}/{repo_name}/pulls/{pr_num}") + elif clean_word.startswith('!'): + pr_num = clean_word[1:] + connected_prs.add(f"{base_url}/{repo_owner}/{repo_name}/pulls/{pr_num}") + + prs_response = requests.get( + f"{api_base}/pulls?state=all", + headers=headers + ) + prs_response.raise_for_status() + + for pr in prs_response.json(): + if f"#{issue_number}" in pr.get("body", ""): + connected_prs.add(pr.get("html_url")) + + except requests.exceptions.RequestException as e: + print(f"[Warning] Failed to fetch connected PRs: {str(e)}") + return 'Empty field' + + return ';'.join(sorted(connected_prs)) if connected_prs else 'Empty field' + + else: # PyGithub + repo_owner = repo_owner.login + # Формирование запроса GraphQL + query = """ + { + repository(owner: "%s", name: "%s") { + issue(number: %d) { + timelineItems(first: 50, itemTypes:[CONNECTED_EVENT,CROSS_REFERENCED_EVENT]) { + filteredCount + nodes { + ... on ConnectedEvent { + ConnectedEvent: subject { + ... on PullRequest { + number + title + url + } + } } - } - } - ... on CrossReferencedEvent { - CrossReferencedEvent: source { - ... on PullRequest { - number - title - url + ... on CrossReferencedEvent { + CrossReferencedEvent: source { + ... on PullRequest { + number + title + url + } + } } } } } } + }""" % ( + repo_owner, + repo_name, + issue_number, + ) + + # Формирование заголовков запроса + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", } - } - }""" % ( - repo_owner, - repo_name, - issue_number, - ) - - # Формирование заголовков запроса - headers = { - "Authorization": f"Bearer {access_token}", - "Content-Type": "application/json", - } - - # Отправка запроса GraphQL - response = requests.post( - "https://api.github.com/graphql", - headers=headers, - data=json.dumps({"query": query}), - ) - response_data = response.json() - # Обработка полученных данных - pull_request_data = response_data["data"]["repository"]["issue"] - list_url = [] - if pull_request_data is not None: - issues_data = pull_request_data["timelineItems"]["nodes"] - for pulls in issues_data: - if ( - pulls.get("CrossReferencedEvent") is not None - and pulls.get("CrossReferencedEvent").get("url") not in list_url - ): - list_url.append(pulls.get("CrossReferencedEvent").get("url")) - if ( - pulls.get("ConnectedEvent") is not None - and pulls.get("ConnectedEvent").get("url") not in list_url - ): - list_url.append(pulls.get("ConnectedEvent").get("url")) - if list_url == []: - return 'Empty field' - else: - return ';'.join(list_url) + + # Отправка запроса GraphQL + response = requests.post( + "https://api.github.com/graphql", + headers=headers, + data=json.dumps({"query": query}), + ) + response_data = response.json() + # Обработка полученных данных + pull_request_data = response_data["data"]["repository"]["issue"] + list_url = [] + if pull_request_data is not None: + issues_data = pull_request_data["timelineItems"]["nodes"] + for pulls in issues_data: + if ( + pulls.get("CrossReferencedEvent") is not None + and pulls.get("CrossReferencedEvent").get("url") is not None + and pulls.get("CrossReferencedEvent").get("url") not in list_url + ): + list_url.append(pulls.get("CrossReferencedEvent").get("url")) + if ( + pulls.get("ConnectedEvent") is not None + and pulls.get("ConnectedEvent").get("url") is not None + and pulls.get("ConnectedEvent").get("url") not in list_url + ): + list_url.append(pulls.get("ConnectedEvent").get("url")) + if list_url == []: + return 'Empty field' + else: + return ';'.join(list_url) return 'Empty field' @@ -154,7 +207,7 @@ def get_info(obj, attr): closer_email=issue.closed_by.email if issue.closed_by else None, assignee_story=get_assignee_story(issue), connected_pull_requests=( - get_connected_pulls(issue._id, repository.owner, repository.name, token) + get_connected_pulls(token, issue._id, repository.owner, repository.name) if issue._id is not None else EMPTY_FIELD ), @@ -199,18 +252,14 @@ def log_issues( logger.log_to_csv(csv_name, list(info.keys())) for client, repo, token in binded_repos: - try: - logger.log_title(repo.name) - log_repository_issues(client, repo, csv_name, token, start, finish) - if fork_flag: - forked_repos = client.get_forks(repo) - for forked_repo in forked_repos: - logger.log_title(f"FORKED: {forked_repo.name}") - log_repository_issues( - client, forked_repo, csv_name, token, start, finish - ) - sleep(TIMEDELTA) - sleep(TIMEDELTA) - except Exception as e: - print("log_issues exception:", e) - exit(1) + logger.log_title(repo.name) + log_repository_issues(client, repo, csv_name, token, start, finish) + if fork_flag: + forked_repos = client.get_forks(repo) + for forked_repo in forked_repos: + logger.log_title(f"FORKED: {forked_repo.name}") + log_repository_issues( + client, forked_repo, csv_name, token, start, finish + ) + sleep(TIMEDELTA) + sleep(TIMEDELTA) diff --git a/utils.py b/utils.py index 62ba46a8..1a1b8532 100644 --- a/utils.py +++ b/utils.py @@ -21,11 +21,11 @@ def log_title(title: str, title_len: int = TITLE_LEN): @staticmethod def log_to_csv(csv_name: str, field_names: tuple[str], row: dict | None = None): if isinstance(row, dict): - with open(csv_name, 'a', newline='') as file: + with open(csv_name, 'a', encoding='utf-8', newline='') as file: writer = csv.DictWriter(file, fieldnames=field_names) writer.writerow(row) elif row is None: - with open(csv_name, 'w', newline='') as file: + with open(csv_name, 'w', encoding='utf-8', newline='') as file: writer = csv.writer(file) writer.writerow(field_names) else: