Skip to content

Commit 2179e0b

Browse files
authored
chore: enhance function documentation and add PR comment automation (#21)
* chore: add pr comment after issue is create Also, update all functions documentations * fix: typo github integration * chore: update package builder * chore: add pr comment function * chore: add uv to build requirements * chore: typo fix on add_pr_comments * chore: removed redundant section
1 parent 7482dc9 commit 2179e0b

File tree

5 files changed

+347
-205
lines changed

5 files changed

+347
-205
lines changed

github_integration.py

Lines changed: 128 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -29,36 +29,69 @@
2929
logging.getLogger(__name__)
3030
logging.basicConfig(level=logging.WARNING)
3131

32-
class GutHubIntegration:
32+
class GitHubIntegration:
3333
def __init__(self):
34-
# Initialize GitHub token
34+
"""
35+
Initialize the GitHubIntegration class.
36+
37+
Returns:
38+
None
39+
40+
Raises:
41+
ValueError: If the GitHub token is not found in environment variables.
42+
"""
3543
self.github_token = GITHUB_TOKEN
3644
if not self.github_token:
3745
raise ValueError("Missing GitHub GITHUB_TOKEN in environment variables")
3846

3947
logging.info("GitHub Integration initialized")
4048

4149
def _get_headers(self):
42-
"""Get headers for GitHub API requests."""
50+
"""
51+
Return the headers required for GitHub API requests.
52+
53+
Returns:
54+
dict: A dictionary containing the required HTTP headers.
55+
56+
Raises:
57+
ValueError: If the GitHub token is missing.
58+
59+
Error Handling:
60+
Raises ValueError if the GitHub token is not set.
61+
"""
62+
if not self.github_token:
63+
raise ValueError("GitHub token is missing for API requests")
4364
return {
4465
'Authorization': f'token {self.github_token}',
4566
'Accept': 'application/vnd.github.v3+json'
4667
}
4768

4869
def _get_pr_url(self, repo_owner: str, repo_name: str, pr_number: int) -> str:
49-
"""Construct the URL for a specific pull request."""
70+
"""
71+
Generates the GitHub API URL for a specific pull request in a given repository.
72+
Args:
73+
repo_owner (str): The owner of the GitHub repository.
74+
repo_name (str): The name of the GitHub repository.
75+
pr_number (int): The pull request number.
76+
Returns:
77+
str: The formatted GitHub API URL for the specified pull request.
78+
Raises:
79+
ValueError: If any of the arguments are empty or if pr_number is not a positive integer.
80+
"""
81+
5082
return f"https://api.github.com/repos/{repo_owner}/{repo_name}/pulls/{pr_number}"
5183

5284
def get_pr_diff(self, repo_owner: str, repo_name: str, pr_number: int) -> str:
53-
"""Fetch the diff/patch of a pull request.
54-
85+
"""
86+
Fetches the diff/patch of a pull request from a GitHub repository.
5587
Args:
56-
repo_owner: The owner of the GitHub repository
57-
repo_name: The name of the GitHub repository
58-
pr_number: The number of the pull request to analyze
59-
88+
repo_owner (str): The owner of the GitHub repository.
89+
repo_name (str): The name of the GitHub repository.
90+
pr_number (int): The pull request number.
6091
Returns:
61-
A string containing the raw patch of the pull request
92+
str: The raw patch/diff text of the pull request if successful, otherwise None.
93+
Error Handling:
94+
Logs an error message and prints the traceback if the request fails or an exception occurs.
6295
"""
6396
logging.info(f"Fetching PR diff for {repo_owner}/{repo_name}#{pr_number}")
6497

@@ -77,15 +110,17 @@ def get_pr_diff(self, repo_owner: str, repo_name: str, pr_number: int) -> str:
77110
return None
78111

79112
def get_pr_content(self, repo_owner: str, repo_name: str, pr_number: int) -> Dict[str, Any]:
80-
"""Fetch the content of a pull request.
81-
113+
"""
114+
Fetches the content of a specific pull request from a GitHub repository.
82115
Args:
83-
repo_owner: The owner of the GitHub repository
84-
repo_name: The name of the GitHub repository
85-
pr_number: The number of the pull request to analyze
86-
116+
repo_owner (str): The owner of the repository.
117+
repo_name (str): The name of the repository.
118+
pr_number (int): The pull request number.
87119
Returns:
88-
A dictionary containing PR metadata and file changes
120+
Dict[str, Any]: A dictionary containing the pull request's title, description, author, creation and update timestamps, and state.
121+
Returns None if an error occurs during the fetch operation.
122+
Error Handling:
123+
Logs an error message and prints the traceback if the request fails or an exception is raised during processing.
89124
"""
90125
logging.info(f"Fetching PR content for {repo_owner}/{repo_name}#{pr_number}")
91126

@@ -117,16 +152,18 @@ def get_pr_content(self, repo_owner: str, repo_name: str, pr_number: int) -> Dic
117152
return None
118153

119154
def add_pr_comments(self, repo_owner: str, repo_name: str, pr_number: int, comment: str) -> Dict[str, Any]:
120-
"""Add a comment to a pull request.
121-
155+
"""
156+
Adds a comment to a specified pull request on GitHub.
122157
Args:
123-
repo_owner: The owner of the GitHub repository
124-
repo_name: The name of the GitHub repository
125-
pr_number: The number of the pull request to comment on
126-
comment: The content of the comment
127-
158+
repo_owner (str): The owner of the repository.
159+
repo_name (str): The name of the repository.
160+
pr_number (int): The pull request number to which the comment will be added.
161+
comment (str): The content of the comment to add.
128162
Returns:
129-
A dictionary containing the added comment's metadata
163+
Dict[str, Any]: The JSON response from the GitHub API containing the comment data if successful.
164+
None: If an error occurs while adding the comment.
165+
Error Handling:
166+
Logs an error message and prints the traceback if the request fails or an exception is raised.
130167
"""
131168
logging.info(f"Adding comment to PR {repo_owner}/{repo_name}#{pr_number}")
132169

@@ -148,17 +185,19 @@ def add_pr_comments(self, repo_owner: str, repo_name: str, pr_number: int, comme
148185
return None
149186

150187
def update_pr_description(self, repo_owner: str, repo_name: str, pr_number: int, new_title: str, new_description: str) -> Dict[str, Any]:
151-
"""Update the description of a pull request.
152-
188+
"""
189+
Updates the title and description of a pull request on GitHub.
153190
Args:
154-
repo_owner: The owner of the GitHub repository
155-
repo
156-
repo_name: The name of the GitHub repository
157-
pr_number: The number of the pull request to update
158-
new_title: The new title for the pull request
159-
new_description: The new description for the pull request
191+
repo_owner (str): The owner of the repository.
192+
repo_name (str): The name of the repository.
193+
pr_number (int): The pull request number to update.
194+
new_title (str): The new title for the pull request.
195+
new_description (str): The new description (body) for the pull request.
160196
Returns:
161-
A dictionary containing the updated pull request's metadata
197+
Dict[str, Any]: The updated pull request data as returned by the GitHub API if the update is successful.
198+
None: If an error occurs during the update process.
199+
Error Handling:
200+
Logs an error message and prints the traceback if the update fails due to an exception (e.g., network issues, invalid credentials, or API errors).
162201
"""
163202
logging.info(f"Updating PR description for {repo_owner}/{repo_name}#{pr_number}")
164203

@@ -181,17 +220,20 @@ def update_pr_description(self, repo_owner: str, repo_name: str, pr_number: int,
181220
return None
182221

183222
def create_issue(self, repo_owner: str, repo_name: str, title: str, body: str, labels: list[str]) -> Dict[str, Any]:
184-
"""Create a new issue in the specified GitHub repository.
185-
223+
"""
224+
Creates a new issue in the specified GitHub repository.
225+
When issue is created add comment to PR with "Resolves: #<issue_number>" using add_pr_comments function.
186226
Args:
187-
repo_owner: The owner of the GitHub repository
188-
repo_name: The name of the GitHub repository
189-
title: The title of the issue
190-
body: The body content of the issue, this should include description, why, details, references
191-
if there is a PR number, add resolve by PR number
192-
labels: A list of labels to apply to the issue
227+
repo_owner (str): The owner of the repository.
228+
repo_name (str): The name of the repository.
229+
title (str): The title of the issue to be created.
230+
body (str): The body content of the issue.
231+
labels (list[str]): A list of labels to assign to the issue. The label 'mcp' will always be included.
193232
Returns:
194-
A dictionary containing the created issue's metadata
233+
Dict[str, Any]: A dictionary containing the created issue's data if successful.
234+
None: If an error occurs during issue creation.
235+
Error Handling:
236+
Logs errors and prints the traceback if the issue creation fails, returning None.
195237
"""
196238
logging.info(f"Creating issue in {repo_owner}/{repo_name}")
197239

@@ -218,18 +260,21 @@ def create_issue(self, repo_owner: str, repo_name: str, title: str, body: str, l
218260
return None
219261

220262
def update_issue(self, repo_owner: str, repo_name: str, issue_number: int, title: str, body: str, labels: list[str] = [], state: str = 'open') -> Dict[str, Any]:
221-
"""Update an existing issue in the specified GitHub repository.
222-
263+
"""
264+
Updates an existing GitHub issue with the specified parameters.
223265
Args:
224-
repo_owner: The owner of the GitHub repository
225-
repo_name: The name of the GitHub repository
226-
issue_number: The number of the issue to update
227-
title: The new title of the issue
228-
body: The new body content of the issue
229-
labels: A list of labels to apply to the issue
230-
state: The new state of the issue (open/closed)
266+
repo_owner (str): The owner of the repository.
267+
repo_name (str): The name of the repository.
268+
issue_number (int): The number of the issue to update.
269+
title (str): The new title for the issue.
270+
body (str): The new body content for the issue.
271+
labels (list[str], optional): A list of labels to assign to the issue. Defaults to an empty list.
272+
state (str, optional): The state of the issue ('open' or 'closed'). Defaults to 'open'.
231273
Returns:
232-
A dictionary containing the updated issue's metadata
274+
Dict[str, Any]: The updated issue data as returned by the GitHub API if the update is successful.
275+
None: If an error occurs during the update process.
276+
Error Handling:
277+
Logs an error message and prints the traceback if the request fails or an exception is raised.
233278
"""
234279
logging.info(f"Updating issue {issue_number} in {repo_owner}/{repo_name}")
235280

@@ -254,13 +299,16 @@ def update_issue(self, repo_owner: str, repo_name: str, issue_number: int, title
254299
return None
255300

256301
def get_latest_sha(self, repo_owner: str, repo_name: str) -> Optional[str]:
257-
"""Fetch the latest commit SHA from the specified GitHub repository.
258-
302+
"""
303+
Fetches the latest commit SHA from a specified GitHub repository.
259304
Args:
260-
repo_owner: The owner of the GitHub repository
261-
repo_name: The name of the GitHub repository
305+
repo_owner (str): The owner of the GitHub repository.
306+
repo_name (str): The name of the GitHub repository.
262307
Returns:
263-
The latest commit SHA as a string, or None if no commits are found
308+
Optional[str]: The SHA string of the latest commit if found, otherwise None.
309+
Error Handling:
310+
Logs errors and warnings if the request fails, the response is invalid, or no commits are found.
311+
Returns None in case of exceptions or if the repository has no commits.
264312
"""
265313
logging.info(f"Fetching latest commit SHA for {repo_owner}/{repo_name}")
266314

@@ -287,15 +335,18 @@ def get_latest_sha(self, repo_owner: str, repo_name: str) -> Optional[str]:
287335
return None
288336

289337
def create_tag(self, repo_owner: str, repo_name: str, tag_name: str, message: str) -> Dict[str, Any]:
290-
"""Create a new tag in the specified GitHub repository.
291-
338+
"""
339+
Creates a new tag in the specified GitHub repository.
292340
Args:
293-
repo_owner: The owner of the GitHub repository
294-
repo_name: The name of the GitHub repository
295-
tag_name: The name of the new tag
296-
message: The message for the tag
341+
repo_owner (str): The owner of the repository.
342+
repo_name (str): The name of the repository.
343+
tag_name (str): The name of the tag to create.
344+
message (str): The message associated with the tag.
297345
Returns:
298-
A dictionary containing the created tag's metadata
346+
Dict[str, Any]: The response data from the GitHub API if the tag is created successfully.
347+
None: If an error occurs during the tag creation process.
348+
Error Handling:
349+
Logs errors and prints the traceback if fetching the latest commit SHA fails or if the GitHub API request fails.
299350
"""
300351
logging.info(f"Creating tag {tag_name} in {repo_owner}/{repo_name}")
301352
# Construct the tags URL
@@ -324,16 +375,19 @@ def create_tag(self, repo_owner: str, repo_name: str, tag_name: str, message: st
324375
return None
325376

326377
def create_release(self, repo_owner: str, repo_name: str, tag_name: str, release_name: str, body: str) -> Dict[str, Any]:
327-
"""Create a new release in the specified GitHub repository.
328-
378+
"""
379+
Creates a new release in the specified GitHub repository.
329380
Args:
330-
repo_owner: The owner of the GitHub repository
331-
repo_name: The name of the GitHub repository
332-
tag_name: The name of the tag for the release
333-
release_name: The name of the release
334-
body: The body content of the release
381+
repo_owner (str): The owner of the repository.
382+
repo_name (str): The name of the repository.
383+
tag_name (str): The tag name for the release.
384+
release_name (str): The name of the release.
385+
body (str): The description or body content of the release.
335386
Returns:
336-
A dictionary containing the created release's metadata
387+
Dict[str, Any]: The JSON response from the GitHub API containing release information if successful.
388+
None: If an error occurs during the release creation process.
389+
Error Handling:
390+
Logs errors and prints the traceback if the release creation fails, returning None.
337391
"""
338392
logging.info(f"Creating release {release_name} in {repo_owner}/{repo_name}")
339393

ip_integration.py

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ class IPIntegration:
3232
def __init__(self, ipv4_api_url: str = None, ipv6_api_url: str = None) -> None:
3333
"""
3434
Initialize the IPIntegration class.
35-
:param ip_api_url: The URL of the IP API to use. If None, defaults to "https://ipinfo.io/json".
35+
36+
:param ipv4_api_url: Optional custom API URL for IPv4 information.
37+
:param ipv6_api_url: Optional custom API URL for IPv6 information.
3638
"""
39+
self.ipv4_api_url = None
40+
self.ipv6_api_url = None
3741
if ipv4_api_url is None or ipv6_api_url is None:
3842
self.ipv4_api_url = "https://ipinfo.io/json"
3943
self.ipv6_api_url = "https://v6.ipinfo.io/json"
@@ -42,19 +46,25 @@ def __init__(self, ipv4_api_url: str = None, ipv6_api_url: str = None) -> None:
4246
self.ipv6_api_url = ipv6_api_url
4347

4448
def get_info(self, url: str) -> Dict[str, Any]:
45-
"""
46-
Get information about an IP address.
47-
:param url: The URL of the IP API to use.
48-
:return: A dictionary containing the IP information.
49-
"""
50-
try:
51-
response = requests.get(url)
52-
response.raise_for_status()
53-
return response.json()
54-
except requests.RequestException as e:
55-
logging.error(f"Error fetching IP info: {e}")
56-
logging.debug(traceback.format_exc())
57-
return {}
49+
"""
50+
Fetches information from the specified URL using an HTTP GET request.
51+
Args:
52+
url (str): The URL to send the GET request to.
53+
Returns:
54+
Dict[str, Any]: The JSON response parsed into a dictionary if the request is successful.
55+
Returns an empty dictionary if the request fails or an exception occurs.
56+
Error Handling:
57+
Logs an error message and stack trace if a requests.RequestException is raised during the HTTP request.
58+
"""
59+
60+
try:
61+
response = requests.get(url)
62+
response.raise_for_status()
63+
return response.json()
64+
except requests.RequestException as e:
65+
logging.error(f"Error fetching IP info: {e}")
66+
logging.debug(traceback.format_exc())
67+
return {}
5868

5969
def get_ipv4_info(self) -> Dict[str, Any]:
6070
"""
@@ -73,21 +83,28 @@ def get_ipv4_info(self) -> Dict[str, Any]:
7383
return {}
7484

7585
def get_ipv6_info(self) -> Dict[str, Any]:
76-
"""
77-
Get information about an IPv6 address.
78-
:return: A dictionary containing the IPv6 information.
79-
"""
80-
# Override the allowed_gai_family method to use IPv6
81-
def allowed_gai_family():
82-
return socket.AF_INET6
83-
urllib3_connection.allowed_gai_family = allowed_gai_family
84-
try:
85-
ipv6 = self.get_info(self.ipv6_api_url)
86-
if not ipv6:
87-
logging.error("No IPv6 information found.")
88-
return {}
89-
return ipv6
90-
except requests.RequestException as e:
91-
logging.error(f"Error fetching IPv6 info: {e}")
92-
logging.debug(traceback.format_exc())
93-
return {}
86+
"""
87+
Retrieves IPv6 information from a specified API endpoint.
88+
This method temporarily overrides the `allowed_gai_family` method to force the use of IPv6 when making network requests.
89+
It then attempts to fetch IPv6-related information from the configured API URL.
90+
Returns:
91+
dict: A dictionary containing IPv6 information if the request is successful.
92+
Returns an empty dictionary if no information is found or if an error occurs.
93+
Error Handling:
94+
Logs an error message and returns an empty dictionary if a `requests.RequestException` is raised during the fetch operation.
95+
Also logs the full traceback at the debug level for troubleshooting.
96+
"""
97+
# Override the allowed_gai_family method to use IPv6
98+
def allowed_gai_family():
99+
return socket.AF_INET6
100+
urllib3_connection.allowed_gai_family = allowed_gai_family
101+
try:
102+
ipv6 = self.get_info(self.ipv6_api_url)
103+
if not ipv6:
104+
logging.error("No IPv6 information found.")
105+
return {}
106+
return ipv6
107+
except requests.RequestException as e:
108+
logging.error(f"Error fetching IPv6 info: {e}")
109+
logging.debug(traceback.format_exc())
110+
return {}

0 commit comments

Comments
 (0)