218 lines
7.0 KiB
Python
218 lines
7.0 KiB
Python
"""Gitea API客户端,用于与Gitea服务器通信"""
|
||
|
||
import requests
|
||
import json
|
||
import logging
|
||
from typing import Optional, Dict, Any, List
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class GiteaClient:
|
||
"""Gitea API客户端类"""
|
||
|
||
def __init__(self, server_url: str, access_token: str):
|
||
"""初始化Gitea客户端
|
||
|
||
Args:
|
||
server_url: Gitea服务器URL
|
||
access_token: Gitea访问令牌
|
||
"""
|
||
self.server_url = server_url.rstrip('/')
|
||
self.access_token = access_token
|
||
self.api_url = f"{self.server_url}/api/v1"
|
||
self.headers = {
|
||
"Authorization": f"token {self.access_token}",
|
||
"Content-Type": "application/json"
|
||
}
|
||
|
||
def _request(self, method: str, endpoint: str, data: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
|
||
"""发送API请求
|
||
|
||
Args:
|
||
method: HTTP方法(GET, POST, PUT, DELETE)
|
||
endpoint: API端点
|
||
data: 请求数据
|
||
|
||
Returns:
|
||
API响应数据
|
||
"""
|
||
url = f"{self.api_url}/{endpoint}"
|
||
|
||
try:
|
||
if method == "GET":
|
||
response = requests.get(url, headers=self.headers, params=data)
|
||
elif method == "POST":
|
||
response = requests.post(url, headers=self.headers, json=data)
|
||
elif method == "PUT":
|
||
response = requests.put(url, headers=self.headers, json=data)
|
||
elif method == "PATCH":
|
||
response = requests.patch(url, headers=self.headers, json=data)
|
||
elif method == "DELETE":
|
||
response = requests.delete(url, headers=self.headers)
|
||
else:
|
||
raise ValueError(f"Unsupported HTTP method: {method}")
|
||
|
||
response.raise_for_status()
|
||
|
||
if response.content:
|
||
return response.json()
|
||
return None
|
||
|
||
except requests.RequestException as e:
|
||
logger.error(f"Gitea API request failed: {str(e)}")
|
||
return None
|
||
|
||
def create_repository(self, owner: str, name: str, description: str = "", private: bool = False) -> Optional[Dict[str, Any]]:
|
||
"""创建仓库
|
||
|
||
Args:
|
||
owner: 所有者(用户或组织)
|
||
name: 仓库名称
|
||
description: 仓库描述
|
||
private: 是否私有
|
||
|
||
Returns:
|
||
创建的仓库信息
|
||
"""
|
||
data = {
|
||
"name": name,
|
||
"description": description,
|
||
"private": private,
|
||
"auto_init": True
|
||
}
|
||
|
||
# 优先尝试在指定的 owner(组织)下创建仓库
|
||
logger.info(f"Attempting to create repository for owner: {owner}")
|
||
owner_response = self._request("POST", f"org/{owner}/repos", data)
|
||
if owner_response:
|
||
logger.info(f"Repository created successfully under owner: {owner}")
|
||
return owner_response
|
||
|
||
# 如果组织创建失败,尝试在用户下创建仓库
|
||
logger.info(f"Organization creation failed, trying user account")
|
||
user_response = self._request("POST", f"user/repos", data)
|
||
if user_response:
|
||
logger.info(f"Repository created successfully in user account")
|
||
return user_response
|
||
|
||
logger.error(f"Failed to create repository for owner {owner}")
|
||
return None
|
||
|
||
def get_repository(self, owner: str, repo: str) -> Optional[Dict[str, Any]]:
|
||
"""获取仓库信息
|
||
|
||
Args:
|
||
owner: 所有者(用户或组织)
|
||
repo: 仓库名称
|
||
|
||
Returns:
|
||
仓库信息
|
||
"""
|
||
return self._request("GET", f"repos/{owner}/{repo}")
|
||
|
||
def list_repositories(self, owner: str) -> Optional[List[Dict[str, Any]]]:
|
||
"""列出用户或组织的仓库
|
||
|
||
Args:
|
||
owner: 所有者(用户或组织)
|
||
|
||
Returns:
|
||
仓库列表
|
||
"""
|
||
return self._request("GET", f"users/{owner}/repos")
|
||
|
||
def delete_repository(self, owner: str, repo: str) -> bool:
|
||
"""删除仓库
|
||
|
||
Args:
|
||
owner: 所有者(用户或组织)
|
||
repo: 仓库名称
|
||
|
||
Returns:
|
||
是否删除成功
|
||
"""
|
||
response = self._request("DELETE", f"repos/{owner}/{repo}")
|
||
return response is not None
|
||
|
||
def create_file(self, owner: str, repo: str, path: str, content: str, message: str) -> Optional[Dict[str, Any]]:
|
||
"""创建或更新文件
|
||
|
||
Args:
|
||
owner: 所有者(用户或组织)
|
||
repo: 仓库名称
|
||
path: 文件路径
|
||
content: 文件内容(base64编码)
|
||
message: 提交消息
|
||
|
||
Returns:
|
||
操作结果
|
||
"""
|
||
data = {
|
||
"content": content,
|
||
"message": message
|
||
}
|
||
|
||
return self._request("POST", f"repos/{owner}/{repo}/contents/{path}", data)
|
||
|
||
def get_file(self, owner: str, repo: str, path: str) -> Optional[Dict[str, Any]]:
|
||
"""获取文件内容
|
||
|
||
Args:
|
||
owner: 所有者(用户或组织)
|
||
repo: 仓库名称
|
||
path: 文件路径
|
||
|
||
Returns:
|
||
文件信息和内容
|
||
"""
|
||
return self._request("GET", f"repos/{owner}/{repo}/contents/{path}")
|
||
|
||
def get_repository_files(self, owner: str, repo: str, path: str = "") -> Optional[List[Dict[str, Any]]]:
|
||
"""获取仓库文件列表
|
||
|
||
Args:
|
||
owner: 所有者(用户或组织)
|
||
repo: 仓库名称
|
||
path: 目录路径(默认为根目录)
|
||
|
||
Returns:
|
||
文件列表
|
||
"""
|
||
result = self._request("GET", f"repos/{owner}/{repo}/contents/{path}")
|
||
return result if isinstance(result, list) else None
|
||
|
||
|
||
|
||
def check_connection(self) -> bool:
|
||
"""检查与Gitea服务器的连接
|
||
|
||
Returns:
|
||
是否连接成功
|
||
"""
|
||
response = self._request("GET", "user")
|
||
return response is not None
|
||
|
||
def update_repository(self, owner: str, repo: str, name: Optional[str] = None, description: Optional[str] = None, private: Optional[bool] = None) -> Optional[Dict[str, Any]]:
|
||
"""更新仓库信息
|
||
|
||
Args:
|
||
owner: 所有者(用户或组织)
|
||
repo: 仓库名称
|
||
name: 新的仓库名称(可选)
|
||
description: 新的仓库描述(可选)
|
||
private: 是否私有(可选)
|
||
|
||
Returns:
|
||
更新后的仓库信息
|
||
"""
|
||
data = {}
|
||
if name is not None:
|
||
data["name"] = name
|
||
if description is not None:
|
||
data["description"] = description
|
||
if private is not None:
|
||
data["private"] = private
|
||
|
||
return self._request("PATCH", f"repos/{owner}/{repo}", data)
|