44from collections import Counter
55from .models import CodeIssue
66
7- USERNAME = os .environ ["BITBUCKET_USERNAME" ]
8- APP_PASSWORD = os .environ ["BITBUCKET_APP_PASSWORD" ]
9- WORKSPACE = os .environ ["BITBUCKET_WORKSPACE" ]
10- REPO_SLUG = os .environ ["BITBUCKET_REPO_SLUG" ]
11- PR_ID = os .environ ["BITBUCKET_PR_ID" ]
127
13- BASE_URL = f"https://api.bitbucket.org/2.0/repositories/{ WORKSPACE } /{ REPO_SLUG } /pullrequests/{ PR_ID } "
14- AUTH = HTTPBasicAuth (USERNAME , APP_PASSWORD )
15- HEADERS = {
16- "Content-Type" : "application/json"
17- }
8+ def _get_api_details ():
9+ """
10+ Helper function to get all necessary details from environment variables.
11+ This function should only be called when we know we are in a Bitbucket environment.
12+ """
13+ try :
14+ username = os .environ ["BITBUCKET_APP_USERNAME" ]
15+ app_password = os .environ ["BITBUCKET_APP_PASSWORD" ]
16+ workspace = os .environ ["BITBUCKET_WORKSPACE" ]
17+ repo_slug = os .environ ["BITBUCKET_REPO_SLUG" ]
18+ pr_id = os .environ ["BITBUCKET_PR_ID" ]
19+ except KeyError as e :
20+ raise ValueError (f"Required Bitbucket environment variable is not set: { e } " )
21+
22+ base_url = f"https://api.bitbucket.org/2.0/repositories/{ workspace } /{ repo_slug } /pullrequests/{ pr_id } "
23+ auth = HTTPBasicAuth (username , app_password )
24+ headers = {"Content-Type" : "application/json" }
25+
26+ return base_url , auth , headers
1827
1928
2029def post_pr_comment (issue : CodeIssue , file_path : str ):
2130 """Posts a single review comment to a specific line in a Bitbucket Pull Request."""
22- url = f"{ BASE_URL } /comments"
23-
24- comment_body = f"**[{ issue .issue_type } ]**\n \n { issue .comment } "
25- if issue .suggestion :
26- comment_body += f"\n \n **Suggestion:**\n ```\n { issue .suggestion } \n ```"
27-
28- payload = {
29- "content" : {"raw" : comment_body },
30- "inline" : {
31- "path" : file_path ,
32- "to" : issue .line_number
31+ try :
32+ base_url , auth , headers = _get_api_details ()
33+ url = f"{ base_url } /comments"
34+
35+ comment_body = f"**[{ issue .issue_type } ]**\n \n { issue .comment } "
36+ if issue .suggestion :
37+ comment_body += f"\n \n **Suggestion:**\n ```\n { issue .suggestion } \n ```"
38+
39+ payload = {
40+ "content" : {"raw" : comment_body },
41+ "inline" : {
42+ "path" : file_path ,
43+ "to" : issue .line_number
44+ }
3345 }
34- }
3546
36- response = requests .post (url , headers = HEADERS , auth = AUTH , json = payload )
47+ response = requests .post (url , headers = headers , auth = auth , json = payload )
48+ response .raise_for_status ()
3749
38- if response .status_code == 201 :
3950 print (f"✅ Successfully posted comment to PR on file { file_path } ." )
40- else :
41- print (f"❌ Failed to post line comment: { response . status_code } — { response . text } " )
51+ except ( ValueError , requests . exceptions . RequestException ) as e :
52+ print (f"❌ Failed to post line comment: { e } " )
4253
4354
4455def post_summary_comment (all_issues : list [CodeIssue ]):
@@ -47,25 +58,26 @@ def post_summary_comment(all_issues: list[CodeIssue]):
4758 return
4859
4960 print ("📝 Generating and posting summary comment to Bitbucket..." )
61+ try :
62+ total_issues = len (all_issues )
63+ issue_counts = Counter (issue .issue_type for issue in all_issues )
64+
65+ summary_body = f"### 🤖 AI Code Review Summary\n \n Found **{ total_issues } potential issue(s)**.\n \n "
66+ if issue_counts :
67+ summary_body += "**Issue Breakdown:**\n "
68+ for issue_type , count in issue_counts .items ():
69+ summary_body += f"* **{ issue_type } :** { count } issue(s)\n "
70+ summary_body += "\n ---\n *Please see the detailed inline comments on the \" Diff\" tab for more context.*"
71+
72+ base_url , auth , headers = _get_api_details ()
73+ url = f"{ base_url } /comments"
74+ payload = {
75+ "content" : {"raw" : summary_body }
76+ }
5077
51- total_issues = len (all_issues )
52- issue_counts = Counter (issue .issue_type for issue in all_issues )
53-
54- summary_body = f"### 🤖 AI Code Review Summary\n \n Found **{ total_issues } potential issue(s)**.\n \n "
55- if issue_counts :
56- summary_body += "**Issue Breakdown:**\n "
57- for issue_type , count in issue_counts .items ():
58- summary_body += f"* **{ issue_type } :** { count } issue(s)\n "
59- summary_body += "\n ---\n *Please see the detailed inline comments on the \" Diff\" tab for more context.*"
60-
61- url = f"{ BASE_URL } /comments"
62- payload = {
63- "content" : {"raw" : summary_body }
64- }
65-
66- response = requests .post (url , headers = HEADERS , auth = AUTH , json = payload )
78+ response = requests .post (url , headers = headers , auth = auth , json = payload )
79+ response .raise_for_status ()
6780
68- if response .status_code == 201 :
6981 print ("✅ Successfully posted the summary comment to Bitbucket." )
70- else :
71- print (f"❌ Failed to post summary comment: { response . status_code } — { response . text } " )
82+ except ( ValueError , requests . exceptions . RequestException ) as e :
83+ print (f"❌ Failed to post summary comment: { e } " )
0 commit comments