Primakov Alexandr Alexandrovich 09cdd06307 init
2025-10-12 23:15:09 +03:00

182 lines
7.1 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Bitbucket API service"""
import httpx
from typing import List, Dict, Any
from app.services.base import BaseGitService, FileChange, PRInfo
class BitbucketService(BaseGitService):
"""Service for interacting with Bitbucket API"""
def __init__(self, base_url: str, token: str, repo_owner: str, repo_name: str):
# Bitbucket Cloud uses api.bitbucket.org
super().__init__("https://api.bitbucket.org/2.0", token, repo_owner, repo_name)
def _get_headers(self) -> Dict[str, str]:
"""Get headers for API requests"""
return {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json"
}
def _get_repo_path(self) -> str:
"""Get repository API path"""
return f"{self.base_url}/repositories/{self.repo_owner}/{self.repo_name}"
async def get_pull_request(self, pr_number: int) -> PRInfo:
"""Get pull request information from Bitbucket"""
url = f"{self._get_repo_path()}/pullrequests/{pr_number}"
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=self._get_headers())
response.raise_for_status()
data = response.json()
return PRInfo(
number=data["id"],
title=data["title"],
description=data.get("description", ""),
author=data["author"]["display_name"],
source_branch=data["source"]["branch"]["name"],
target_branch=data["destination"]["branch"]["name"],
url=data["links"]["html"]["href"],
state=data["state"]
)
async def get_pr_files(self, pr_number: int) -> List[FileChange]:
"""Get list of changed files in PR"""
url = f"{self._get_repo_path()}/pullrequests/{pr_number}/diffstat"
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=self._get_headers())
response.raise_for_status()
data = response.json()
changes = []
for file in data.get("values", []):
status = file.get("status", "modified")
changes.append(FileChange(
filename=file["new"]["path"] if file.get("new") else file["old"]["path"],
status=status,
additions=file.get("lines_added", 0),
deletions=file.get("lines_removed", 0)
))
return changes
async def get_file_content(self, file_path: str, ref: str) -> str:
"""Get file content at specific ref"""
url = f"{self._get_repo_path()}/src/{ref}/{file_path}"
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=self._get_headers())
response.raise_for_status()
return response.text
async def create_review_comment(
self,
pr_number: int,
file_path: str,
line_number: int,
comment: str,
commit_id: str
) -> Dict[str, Any]:
"""Create a review comment on PR"""
url = f"{self._get_repo_path()}/pullrequests/{pr_number}/comments"
payload = {
"content": {
"raw": comment
},
"inline": {
"path": file_path,
"to": line_number
}
}
async with httpx.AsyncClient() as client:
response = await client.post(
url,
headers=self._get_headers(),
json=payload
)
response.raise_for_status()
return response.json()
async def create_review(
self,
pr_number: int,
comments: List[Dict[str, Any]],
body: str = "",
event: str = "COMMENT"
) -> Dict[str, Any]:
"""Create a review with separate comment for each issue
Args:
pr_number: PR number
comments: List of comments
body: Overall review summary (markdown supported)
event: Review event (не используется, для совместимости)
"""
print(f"\n📤 Публикация ревью в Bitbucket PR #{pr_number}")
print(f" Комментариев для публикации: {len(comments)}")
url = f"{self._get_repo_path()}/pullrequests/{pr_number}/comments"
# 1. Сначала публикуем общий summary
if body:
print(f"\n 📝 Публикация общего summary ({len(body)} символов)...")
payload = {
"content": {
"raw": body
}
}
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
url,
headers=self._get_headers(),
json=payload
)
response.raise_for_status()
print(f" ✅ Summary опубликован!")
# 2. Затем публикуем каждую проблему отдельным комментарием
if comments:
print(f"\n 💬 Публикация {len(comments)} отдельных комментариев...")
for i, comment in enumerate(comments, 1):
severity_emoji = {
"ERROR": "",
"WARNING": "⚠️",
"INFO": ""
}.get(comment.get("severity", "INFO").upper(), "💬")
# Bitbucket ссылка на строку
file_url = f"https://bitbucket.org/{self.repo_owner}/{self.repo_name}/pull-requests/{pr_number}/diff#{comment['file_path']}T{comment['line_number']}"
# Форматируем комментарий
comment_body = f"{severity_emoji} **[`{comment['file_path']}:{comment['line_number']}`]({file_url})**\n\n"
comment_body += f"**{comment.get('severity', 'INFO').upper()}**: {comment['content']}"
payload = {
"content": {
"raw": comment_body
}
}
try:
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
url,
headers=self._get_headers(),
json=payload
)
response.raise_for_status()
print(f"{i}/{len(comments)}: {comment['file_path']}:{comment['line_number']}")
except Exception as e:
print(f"{i}/{len(comments)}: Ошибка - {e}")
print(f"\n 🎉 Все комментарии опубликованы!")
return {"summary": "posted", "comments_count": len(comments)}