Skip to content

Commit

Permalink
Merge pull request #142 from stfc/extending_classes_no_dir
Browse files Browse the repository at this point in the history
Extending classes no dir
  • Loading branch information
khalford committed Sep 2, 2024
2 parents 93388b8 + 75ffad8 commit d1186a6
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 196 deletions.
16 changes: 10 additions & 6 deletions cloud_chatops/src/custom_exceptions.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
"""This module contains custom exceptions to handle errors for the Application."""


class RepoNotFound(Exception):
class RepoNotFound(LookupError):
"""Error: The requested repository does not exist on GitHub."""


class UnknownHTTPError(Exception):
class UnknownHTTPError(RuntimeError):
"""Error: The received HTTP response is unexpected."""


class RepositoriesNotGiven(Exception):
class RepositoriesNotGiven(RuntimeError):
"""Error: repos.csv does not contain any repositories."""


class TokensNotGiven(Exception):
class TokensNotGiven(RuntimeError):
"""Error: Token values are either empty or not given."""


class UserMapNotGiven(Exception):
class UserMapNotGiven(RuntimeError):
"""Error: User map is empty."""


class BadGitHubToken(Exception):
class BadGitHubToken(RuntimeError):
"""Error: GitHub REST Api token is invalid."""


class ChannelNotFound(LookupError):
"""Error: The channel was not found."""
126 changes: 67 additions & 59 deletions cloud_chatops/src/get_github_prs.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
"""
This module handles the HTTP requests and formatting to the GitHub REST Api.
It will get all open pull requests in provided STFC owned repositories.
It will get all open pull requests in provided repositories.
"""

from typing import List, Dict, Union
from typing import List, Dict
import requests
from src.read_data import get_token
from src.custom_exceptions import RepoNotFound, UnknownHTTPError, BadGitHubToken
from read_data import get_token
from pr_dataclass import PrData
from custom_exceptions import RepoNotFound, UnknownHTTPError, BadGitHubToken


class GetGitHubPRs:
# pylint: disable=R0903
# Disabling this as in the future there is likely to be more public functions.
"""
This class handles getting the open PRs from the GitHub Rest API.
"""
Expand All @@ -22,29 +25,63 @@ def __init__(self, repos: List[str], owner: str):
"""
self.repos = repos
self.owner = owner
self._http_handler = HTTPHandler()

def run(self) -> Dict[str, List]:
def run(self) -> List[PrData]:
"""
This method is the entry point to the class.
It runs the HTTP request methods and the formatting methods.
It runs the HTTP request methods and returns the responses.
:return: The responses from the HTTP requests.
"""
unformatted_responses = self.request_all_repos_http()
formatted_responses = self.format_http_responses(unformatted_responses)
return formatted_responses
responses = self._request_all_repos_http()
return self._parse_pr_to_dataclass(responses)

def request_all_repos_http(self) -> Dict[str, List[Dict]]:
def _request_all_repos_http(self) -> List[Dict]:
"""
This method starts a request for each repository and returns a list of those PRs.
:return: A dictionary of repos and their PRs.
"""
responses = {}
responses = []
for repo in self.repos:
url = f"https://api.github.com/repos/{self.owner}/{repo}/pulls"
response = self.get_http_response(url)
responses[repo] = response
responses += self._http_handler.make_request(url)
return responses

@staticmethod
def _parse_pr_to_dataclass(responses: List[Dict]) -> List[PrData]:
"""
This module converts the responses from the HTTP request into Dataclasses to be more easily handled.
:param responses: List of responses made from HTTP requests
:return: Responses in dataclasses
"""
responses_dataclasses = []
for pr in responses:
responses_dataclasses.append(
PrData(
pr_title=f"{pr['title']} #{pr['number']}",
user=pr["user"]["login"],
url=pr["html_url"],
created_at=pr["created_at"],
draft=pr["draft"],
)
)
return responses_dataclasses


class HTTPHandler:
"""
This class makes the HTTP requests to the GitHub REST API.
"""

def make_request(self, url: str) -> List[Dict]:
"""
This method gets the HTTP response from the URL given and returns the response as a list.
:param url: The URL to make the HTTP request to.
:return: List of PRs.
"""
response = self.get_http_response(url)
return [response] if isinstance(response, dict) else response

def get_http_response(self, url: str) -> List[Dict]:
"""
This method sends a HTTP request to the GitHub Rest API endpoint and returns all open PRs from that repository.
Expand All @@ -53,57 +90,28 @@ def get_http_response(self, url: str) -> List[Dict]:
"""
headers = {"Authorization": "token " + get_token("GITHUB_TOKEN")}
response = requests.get(url, headers=headers, timeout=60)
self.validate_response(response, url)
self._validate_response(response)
return response.json()

def format_http_responses(
self, responses: Union[Dict[str, List], Dict[str, Dict]]
) -> Dict[str, List]:
"""
This method checks the formats the responses from GitHub are in a consistent format.
:param responses: GitHub's HTTP responses.
:return: Dictionary of responses.
"""
culled_responses = self.remove_empty_response(responses)
for repo, response in culled_responses.items():
if isinstance(response, dict):
responses[repo] = [response]
return responses

@staticmethod
def remove_empty_response(
responses: Union[Dict[str, List], Dict[str, Dict]]
) -> Union[Dict[str, List], Dict[str, Dict]]:
"""
This method removes all empty responses from the Dictionary.
An empty response is the result of no open pull requests.
:param responses: The responses to check.
:return: The responses with no empty values.
"""
to_remove = []
for repo, response in responses.items():
if not response:
to_remove.append(repo)

for i in to_remove:
del responses[i]
return responses

@staticmethod
def validate_response(response: requests.get, url: str) -> None:
def _validate_response(response: requests.get) -> None:
"""
This method checks the status code of the HTTP response and handles exceptions accordingly.
:param response: The response to check.
:param url: The url that was not found.
"""
if response.status_code == 401:
raise BadGitHubToken(
"Your GitHub api token is invalid. Check that it hasn't expired."
)
if response.status_code == 404:
raise RepoNotFound(f'The repo at the url "{url}" could not be found.')
match response.status_code:
case 200:
pass
case 401:
raise BadGitHubToken(
"Your GitHub api token is invalid. Check that it hasn't expired."
)
case 404:
raise RepoNotFound(
f'The repository at the url "{response.url}" could not be found.'
)

if response.status_code != 200:
raise UnknownHTTPError(
f"The HTTP response code is unknown and cannot be handled. Response: {response.status_code}"
)
case _:
raise UnknownHTTPError(
f"The HTTP response code is unknown and cannot be handled. Response: {response.status_code}"
)
2 changes: 1 addition & 1 deletion cloud_chatops/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""

import asyncio
from src.slack_app import run_app
from slack_app import run_app


def main():
Expand Down
2 changes: 1 addition & 1 deletion cloud_chatops/src/online_notif.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""This module handles the Slack Application status notifications."""

from slack_sdk import WebClient
from src.read_data import get_token, get_maintainer
from read_data import get_token, get_maintainer


def online_notif() -> None:
Expand Down
8 changes: 3 additions & 5 deletions cloud_chatops/src/pr_dataclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,17 @@
This module declares the dataclass used to store PR information.
This is preferred over dictionaries as dataclasses make code more readable.
"""

from dataclasses import dataclass

# Disabling this Pylint error as the dataclass needs to hold more than 7 attributes.
# pylint: disable=R0902

@dataclass
class PrData:
"""Class holding information about a single pull request."""

pr_title: str
user: str
url: str
created_at: str
channel: str
thread_ts: str
mention: bool
draft: bool
old: bool = False
Loading

0 comments on commit d1186a6

Please sign in to comment.