auto-coder 0.1.398__py3-none-any.whl → 0.1.400__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of auto-coder might be problematic. Click here for more details.
- auto_coder-0.1.400.dist-info/METADATA +396 -0
- {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info}/RECORD +82 -29
- {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info}/WHEEL +1 -1
- {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info}/entry_points.txt +2 -0
- autocoder/agent/base_agentic/base_agent.py +2 -2
- autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +1 -1
- autocoder/agent/entry_command_agent/__init__.py +29 -0
- autocoder/agent/entry_command_agent/auto_tool.py +61 -0
- autocoder/agent/entry_command_agent/chat.py +475 -0
- autocoder/agent/entry_command_agent/designer.py +53 -0
- autocoder/agent/entry_command_agent/generate_command.py +50 -0
- autocoder/agent/entry_command_agent/project_reader.py +58 -0
- autocoder/agent/entry_command_agent/voice2text.py +71 -0
- autocoder/auto_coder.py +23 -548
- autocoder/auto_coder_runner.py +511 -8
- autocoder/chat/rules_command.py +1 -1
- autocoder/chat_auto_coder.py +6 -1
- autocoder/common/ac_style_command_parser/__init__.py +15 -0
- autocoder/common/ac_style_command_parser/example.py +7 -0
- autocoder/{command_parser.py → common/ac_style_command_parser/parser.py} +28 -45
- autocoder/common/ac_style_command_parser/test_parser.py +516 -0
- autocoder/common/auto_coder_lang.py +78 -0
- autocoder/common/command_completer_v2.py +1 -1
- autocoder/common/command_file_manager/examples.py +22 -8
- autocoder/common/command_file_manager/manager.py +37 -6
- autocoder/common/conversations/get_conversation_manager.py +143 -0
- autocoder/common/conversations/manager.py +122 -11
- autocoder/common/conversations/storage/index_manager.py +89 -0
- autocoder/common/pull_requests/__init__.py +256 -0
- autocoder/common/pull_requests/base_provider.py +191 -0
- autocoder/common/pull_requests/config.py +66 -0
- autocoder/common/pull_requests/example.py +1 -0
- autocoder/common/pull_requests/exceptions.py +46 -0
- autocoder/common/pull_requests/manager.py +201 -0
- autocoder/common/pull_requests/models.py +164 -0
- autocoder/common/pull_requests/providers/__init__.py +23 -0
- autocoder/common/pull_requests/providers/gitcode_provider.py +19 -0
- autocoder/common/pull_requests/providers/gitee_provider.py +20 -0
- autocoder/common/pull_requests/providers/github_provider.py +214 -0
- autocoder/common/pull_requests/providers/gitlab_provider.py +29 -0
- autocoder/common/pull_requests/test_module.py +1 -0
- autocoder/common/pull_requests/utils.py +344 -0
- autocoder/common/tokens/__init__.py +62 -0
- autocoder/common/tokens/counter.py +211 -0
- autocoder/common/tokens/file_detector.py +105 -0
- autocoder/common/tokens/filters.py +111 -0
- autocoder/common/tokens/models.py +28 -0
- autocoder/common/v2/agent/agentic_edit.py +312 -85
- autocoder/common/v2/agent/agentic_edit_types.py +11 -0
- autocoder/common/v2/code_auto_generate_editblock.py +10 -2
- autocoder/dispacher/__init__.py +10 -0
- autocoder/rags.py +0 -27
- autocoder/run_context.py +1 -0
- autocoder/sdk/__init__.py +188 -0
- autocoder/sdk/cli/__init__.py +15 -0
- autocoder/sdk/cli/__main__.py +26 -0
- autocoder/sdk/cli/completion_wrapper.py +38 -0
- autocoder/sdk/cli/formatters.py +211 -0
- autocoder/sdk/cli/handlers.py +175 -0
- autocoder/sdk/cli/install_completion.py +301 -0
- autocoder/sdk/cli/main.py +286 -0
- autocoder/sdk/cli/options.py +73 -0
- autocoder/sdk/constants.py +102 -0
- autocoder/sdk/core/__init__.py +20 -0
- autocoder/sdk/core/auto_coder_core.py +880 -0
- autocoder/sdk/core/bridge.py +500 -0
- autocoder/sdk/example.py +0 -0
- autocoder/sdk/exceptions.py +72 -0
- autocoder/sdk/models/__init__.py +19 -0
- autocoder/sdk/models/messages.py +209 -0
- autocoder/sdk/models/options.py +196 -0
- autocoder/sdk/models/responses.py +311 -0
- autocoder/sdk/session/__init__.py +32 -0
- autocoder/sdk/session/session.py +106 -0
- autocoder/sdk/session/session_manager.py +56 -0
- autocoder/sdk/utils/__init__.py +24 -0
- autocoder/sdk/utils/formatters.py +216 -0
- autocoder/sdk/utils/io_utils.py +302 -0
- autocoder/sdk/utils/validators.py +287 -0
- autocoder/version.py +2 -1
- auto_coder-0.1.398.dist-info/METADATA +0 -111
- autocoder/common/conversations/compatibility.py +0 -303
- autocoder/common/conversations/conversation_manager.py +0 -502
- autocoder/common/conversations/example.py +0 -152
- {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info/licenses}/LICENSE +0 -0
- {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pull Request 统一管理模块
|
|
3
|
+
|
|
4
|
+
统一的 Pull Request 创建和管理模块,支持 GitHub、GitLab、Gitee、GitCode 四大代码托管平台的 PR 操作。
|
|
5
|
+
"""
|
|
6
|
+
from typing import Optional, List, Dict, Any
|
|
7
|
+
from loguru import logger
|
|
8
|
+
|
|
9
|
+
from .models import (
|
|
10
|
+
PRConfig, PRResult, PRInfo, PRData, RepoInfo, PlatformType,
|
|
11
|
+
DEFAULT_TEMPLATES
|
|
12
|
+
)
|
|
13
|
+
from .manager import PullRequestManager, get_global_manager, set_global_config
|
|
14
|
+
from .exceptions import (
|
|
15
|
+
PRError, AuthenticationError, RepositoryNotFoundError,
|
|
16
|
+
BranchNotFoundError, NetworkError, RateLimitError,
|
|
17
|
+
ValidationError, PlatformNotSupportedError, ConfigurationError
|
|
18
|
+
)
|
|
19
|
+
from .config import get_config
|
|
20
|
+
from .utils import (
|
|
21
|
+
parse_git_url, detect_platform_from_repo, get_repo_info_from_path,
|
|
22
|
+
get_current_branch, branch_exists, is_git_repo, get_default_remote_branch,
|
|
23
|
+
ensure_branch_exists_remotely, is_main_branch, create_and_checkout_branch,
|
|
24
|
+
generate_auto_branch_name
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_pull_request(
|
|
29
|
+
repo_path: str,
|
|
30
|
+
title: str,
|
|
31
|
+
source_branch: Optional[str] = None,
|
|
32
|
+
target_branch: Optional[str] = None,
|
|
33
|
+
description: str = "",
|
|
34
|
+
labels: Optional[List[str]] = None,
|
|
35
|
+
assignees: Optional[List[str]] = None,
|
|
36
|
+
reviewers: Optional[List[str]] = None,
|
|
37
|
+
draft: bool = False,
|
|
38
|
+
template_type: Optional[str] = None,
|
|
39
|
+
template_vars: Optional[Dict[str, str]] = None,
|
|
40
|
+
platform: Optional[str] = None,
|
|
41
|
+
token: Optional[str] = None,
|
|
42
|
+
**kwargs
|
|
43
|
+
) -> PRResult:
|
|
44
|
+
"""
|
|
45
|
+
创建 Pull Request(主要接口函数)
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
repo_path: 仓库路径
|
|
49
|
+
title: PR标题
|
|
50
|
+
source_branch: 源分支(可选,默认为当前分支)
|
|
51
|
+
target_branch: 目标分支(可选,默认为远程默认分支或main)
|
|
52
|
+
description: PR描述
|
|
53
|
+
labels: 标签列表
|
|
54
|
+
assignees: 负责人列表
|
|
55
|
+
reviewers: 审查者列表
|
|
56
|
+
draft: 是否为草稿PR
|
|
57
|
+
template_type: 模板类型
|
|
58
|
+
template_vars: 模板变量
|
|
59
|
+
platform: 平台类型(可选,会自动检测)
|
|
60
|
+
token: 访问令牌(可选)
|
|
61
|
+
**kwargs: 其他配置参数
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
PR创建结果
|
|
65
|
+
"""
|
|
66
|
+
# 处理可选的分支参数
|
|
67
|
+
if source_branch is None:
|
|
68
|
+
source_branch = get_current_branch(repo_path)
|
|
69
|
+
if not source_branch:
|
|
70
|
+
raise ValidationError("无法获取当前分支,请指定source_branch参数")
|
|
71
|
+
|
|
72
|
+
# 从当前分支,自动创建新分支
|
|
73
|
+
auto_branch_name = generate_auto_branch_name()
|
|
74
|
+
logger.info(f"检测到当前分支 '{source_branch}' 为主分支,自动创建新分支: {auto_branch_name}")
|
|
75
|
+
|
|
76
|
+
if not create_and_checkout_branch(repo_path, auto_branch_name):
|
|
77
|
+
raise ValidationError(f"无法从主分支 '{source_branch}' 创建新分支 '{auto_branch_name}'")
|
|
78
|
+
|
|
79
|
+
source_branch = auto_branch_name
|
|
80
|
+
logger.info(f"已切换到新分支: {source_branch}")
|
|
81
|
+
|
|
82
|
+
if target_branch is None:
|
|
83
|
+
target_branch = get_default_remote_branch(repo_path)
|
|
84
|
+
if not target_branch:
|
|
85
|
+
target_branch = "main" # 默认值
|
|
86
|
+
|
|
87
|
+
# 验证仓库是否为Git仓库
|
|
88
|
+
if not is_git_repo(repo_path):
|
|
89
|
+
raise ValidationError(f"路径 {repo_path} 不是一个有效的Git仓库")
|
|
90
|
+
|
|
91
|
+
# 检查源分支和目标分支是否相同
|
|
92
|
+
if source_branch == target_branch:
|
|
93
|
+
raise ValidationError(f"源分支和目标分支不能相同: {source_branch}")
|
|
94
|
+
|
|
95
|
+
# 确保源分支存在于远程仓库,如果不存在则自动推送
|
|
96
|
+
if not ensure_branch_exists_remotely(repo_path, source_branch):
|
|
97
|
+
raise BranchNotFoundError(
|
|
98
|
+
f"源分支 '{source_branch}' 在本地不存在或推送失败。"
|
|
99
|
+
f"请检查分支是否存在或网络连接是否正常。"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# 检查目标分支是否存在于远程仓库
|
|
103
|
+
if not branch_exists(repo_path, target_branch, remote=True):
|
|
104
|
+
raise BranchNotFoundError(
|
|
105
|
+
f"目标分支 '{target_branch}' 在远程仓库中不存在。"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# 如果提供了token,创建临时配置
|
|
109
|
+
manager = get_global_manager()
|
|
110
|
+
|
|
111
|
+
if token:
|
|
112
|
+
# 自动检测平台或使用指定平台
|
|
113
|
+
if not platform:
|
|
114
|
+
detected_platform = detect_platform_from_repo(repo_path)
|
|
115
|
+
if detected_platform:
|
|
116
|
+
platform = detected_platform.value
|
|
117
|
+
else:
|
|
118
|
+
raise ValidationError("无法检测平台类型,请指定platform参数")
|
|
119
|
+
|
|
120
|
+
# 创建临时配置
|
|
121
|
+
temp_config = PRConfig(platform=PlatformType(platform), token=token)
|
|
122
|
+
temp_manager = PullRequestManager(temp_config)
|
|
123
|
+
|
|
124
|
+
return temp_manager.create_pull_request(
|
|
125
|
+
repo_path=repo_path,
|
|
126
|
+
source_branch=source_branch,
|
|
127
|
+
target_branch=target_branch,
|
|
128
|
+
title=title,
|
|
129
|
+
description=description,
|
|
130
|
+
labels=labels,
|
|
131
|
+
assignees=assignees,
|
|
132
|
+
reviewers=reviewers,
|
|
133
|
+
draft=draft,
|
|
134
|
+
template_type=template_type,
|
|
135
|
+
template_vars=template_vars,
|
|
136
|
+
platform=platform
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
# 使用全局管理器
|
|
140
|
+
return manager.create_pull_request(
|
|
141
|
+
repo_path=repo_path,
|
|
142
|
+
source_branch=source_branch,
|
|
143
|
+
target_branch=target_branch,
|
|
144
|
+
title=title,
|
|
145
|
+
description=description,
|
|
146
|
+
labels=labels,
|
|
147
|
+
assignees=assignees,
|
|
148
|
+
reviewers=reviewers,
|
|
149
|
+
draft=draft,
|
|
150
|
+
template_type=template_type,
|
|
151
|
+
template_vars=template_vars,
|
|
152
|
+
platform=platform
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def get_pull_request(
|
|
157
|
+
repo_path: str,
|
|
158
|
+
pr_number: int,
|
|
159
|
+
platform: Optional[str] = None,
|
|
160
|
+
token: Optional[str] = None,
|
|
161
|
+
**kwargs
|
|
162
|
+
) -> PRResult:
|
|
163
|
+
"""获取 Pull Request 信息"""
|
|
164
|
+
manager = get_global_manager()
|
|
165
|
+
|
|
166
|
+
if token:
|
|
167
|
+
if not platform:
|
|
168
|
+
detected_platform = detect_platform_from_repo(repo_path)
|
|
169
|
+
if detected_platform:
|
|
170
|
+
platform = detected_platform.value
|
|
171
|
+
else:
|
|
172
|
+
raise ValidationError("无法检测平台类型,请指定platform参数")
|
|
173
|
+
|
|
174
|
+
config_data = {"token": token}
|
|
175
|
+
config_data.update(kwargs)
|
|
176
|
+
temp_config = PRConfig(platform=platform, **config_data)
|
|
177
|
+
temp_manager = PullRequestManager(temp_config)
|
|
178
|
+
|
|
179
|
+
return temp_manager.get_pull_request(repo_path, pr_number, platform)
|
|
180
|
+
else:
|
|
181
|
+
return manager.get_pull_request(repo_path, pr_number, platform)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def list_pull_requests(
|
|
185
|
+
repo_path: str,
|
|
186
|
+
state: str = "open",
|
|
187
|
+
per_page: int = 30,
|
|
188
|
+
page: int = 1,
|
|
189
|
+
platform: Optional[str] = None,
|
|
190
|
+
token: Optional[str] = None,
|
|
191
|
+
**kwargs
|
|
192
|
+
) -> List[PRInfo]:
|
|
193
|
+
"""列出 Pull Requests"""
|
|
194
|
+
manager = get_global_manager()
|
|
195
|
+
|
|
196
|
+
if token:
|
|
197
|
+
if not platform:
|
|
198
|
+
detected_platform = detect_platform_from_repo(repo_path)
|
|
199
|
+
if detected_platform:
|
|
200
|
+
platform = detected_platform.value
|
|
201
|
+
else:
|
|
202
|
+
raise ValidationError("无法检测平台类型,请指定platform参数")
|
|
203
|
+
|
|
204
|
+
config_data = {"token": token}
|
|
205
|
+
config_data.update(kwargs)
|
|
206
|
+
temp_config = PRConfig(platform=platform, **config_data)
|
|
207
|
+
temp_manager = PullRequestManager(temp_config)
|
|
208
|
+
|
|
209
|
+
return temp_manager.list_pull_requests(repo_path, state, per_page, page, platform)
|
|
210
|
+
else:
|
|
211
|
+
return manager.list_pull_requests(repo_path, state, per_page, page, platform)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# 导出所有公共接口
|
|
215
|
+
__all__ = [
|
|
216
|
+
# 主要函数
|
|
217
|
+
'create_pull_request',
|
|
218
|
+
'get_pull_request',
|
|
219
|
+
'list_pull_requests',
|
|
220
|
+
|
|
221
|
+
# 类和模型
|
|
222
|
+
'PullRequestManager',
|
|
223
|
+
'PRConfig',
|
|
224
|
+
'PRResult',
|
|
225
|
+
'PRInfo',
|
|
226
|
+
'PRData',
|
|
227
|
+
'RepoInfo',
|
|
228
|
+
'PlatformType',
|
|
229
|
+
|
|
230
|
+
# 异常
|
|
231
|
+
'PRError',
|
|
232
|
+
'AuthenticationError',
|
|
233
|
+
'RepositoryNotFoundError',
|
|
234
|
+
'BranchNotFoundError',
|
|
235
|
+
'NetworkError',
|
|
236
|
+
'RateLimitError',
|
|
237
|
+
'ValidationError',
|
|
238
|
+
'PlatformNotSupportedError',
|
|
239
|
+
'ConfigurationError',
|
|
240
|
+
|
|
241
|
+
# 工具函数
|
|
242
|
+
'parse_git_url',
|
|
243
|
+
'detect_platform_from_repo',
|
|
244
|
+
'get_repo_info_from_path',
|
|
245
|
+
'get_current_branch',
|
|
246
|
+
'branch_exists',
|
|
247
|
+
'is_git_repo',
|
|
248
|
+
|
|
249
|
+
# 配置函数
|
|
250
|
+
'get_config',
|
|
251
|
+
'set_global_config',
|
|
252
|
+
'get_global_manager',
|
|
253
|
+
|
|
254
|
+
# 模板
|
|
255
|
+
'DEFAULT_TEMPLATES'
|
|
256
|
+
]
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pull Request 平台提供者基类
|
|
3
|
+
"""
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Optional, Dict, Any, List
|
|
6
|
+
import requests
|
|
7
|
+
import time
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
from .models import RepoInfo, PRData, PRResult, PRInfo, PRConfig
|
|
11
|
+
from .exceptions import (
|
|
12
|
+
PRError, NetworkError, AuthenticationError, RateLimitError,
|
|
13
|
+
RepositoryNotFoundError, ValidationError
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BasePlatformProvider(ABC):
|
|
18
|
+
"""平台提供者基类,定义统一接口"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, config: PRConfig):
|
|
21
|
+
self.config = config
|
|
22
|
+
self.session = self._create_session()
|
|
23
|
+
|
|
24
|
+
def _create_session(self) -> requests.Session:
|
|
25
|
+
"""创建HTTP会话"""
|
|
26
|
+
session = requests.Session()
|
|
27
|
+
session.headers.update({
|
|
28
|
+
'User-Agent': 'AutoCoder-PR/1.0',
|
|
29
|
+
'Authorization': self._get_auth_header(),
|
|
30
|
+
'Accept': 'application/json',
|
|
31
|
+
'Content-Type': 'application/json'
|
|
32
|
+
})
|
|
33
|
+
session.verify = self.config.verify_ssl
|
|
34
|
+
return session
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def _get_auth_header(self) -> str:
|
|
38
|
+
"""获取认证头"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def _make_request(
|
|
42
|
+
self,
|
|
43
|
+
method: str,
|
|
44
|
+
url: str,
|
|
45
|
+
data: Optional[Dict[str, Any]] = None,
|
|
46
|
+
params: Optional[Dict[str, str]] = None,
|
|
47
|
+
retry_count: Optional[int] = None
|
|
48
|
+
) -> requests.Response:
|
|
49
|
+
"""发送HTTP请求,包含重试逻辑"""
|
|
50
|
+
|
|
51
|
+
if retry_count is None:
|
|
52
|
+
retry_count = self.config.retry_count
|
|
53
|
+
|
|
54
|
+
last_exception = None
|
|
55
|
+
|
|
56
|
+
for attempt in range(retry_count + 1):
|
|
57
|
+
try:
|
|
58
|
+
response = self.session.request(
|
|
59
|
+
method=method,
|
|
60
|
+
url=url,
|
|
61
|
+
json=data,
|
|
62
|
+
params=params,
|
|
63
|
+
timeout=self.config.timeout
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if response.status_code == 401:
|
|
67
|
+
raise AuthenticationError("认证失败,请检查token是否正确")
|
|
68
|
+
elif response.status_code == 404:
|
|
69
|
+
raise RepositoryNotFoundError("仓库或资源不存在")
|
|
70
|
+
elif response.status_code == 429:
|
|
71
|
+
retry_after = int(response.headers.get('Retry-After', 60))
|
|
72
|
+
raise RateLimitError(f"API限流,请等待{retry_after}秒后重试", retry_after=retry_after)
|
|
73
|
+
elif response.status_code == 422:
|
|
74
|
+
# 422 通常是验证错误,尝试解析响应内容获取详细错误信息
|
|
75
|
+
try:
|
|
76
|
+
error_data = response.json()
|
|
77
|
+
if 'errors' in error_data:
|
|
78
|
+
errors = error_data['errors']
|
|
79
|
+
error_msgs = []
|
|
80
|
+
for error in errors:
|
|
81
|
+
if isinstance(error, dict):
|
|
82
|
+
field = error.get('field', '')
|
|
83
|
+
code = error.get('code', '')
|
|
84
|
+
message = error.get('message', str(error))
|
|
85
|
+
if field and code:
|
|
86
|
+
error_msgs.append(f"{field}: {message} (code: {code})")
|
|
87
|
+
else:
|
|
88
|
+
error_msgs.append(message)
|
|
89
|
+
else:
|
|
90
|
+
error_msgs.append(str(error))
|
|
91
|
+
raise ValidationError(f"请求验证失败: {'; '.join(error_msgs)}")
|
|
92
|
+
elif 'message' in error_data:
|
|
93
|
+
raise ValidationError(f"请求验证失败: {error_data['message']}")
|
|
94
|
+
else:
|
|
95
|
+
raise ValidationError(f"请求验证失败: {response.text}")
|
|
96
|
+
except (ValueError, KeyError):
|
|
97
|
+
raise ValidationError(f"请求验证失败: HTTP 422 - {response.text}")
|
|
98
|
+
elif response.status_code >= 400:
|
|
99
|
+
try:
|
|
100
|
+
error_data = response.json()
|
|
101
|
+
if 'message' in error_data:
|
|
102
|
+
raise PRError(f"API请求失败: HTTP {response.status_code} - {error_data['message']}")
|
|
103
|
+
else:
|
|
104
|
+
raise PRError(f"API请求失败: HTTP {response.status_code} - {response.text}")
|
|
105
|
+
except (ValueError, KeyError):
|
|
106
|
+
raise PRError(f"API请求失败: HTTP {response.status_code} - {response.text}")
|
|
107
|
+
|
|
108
|
+
return response
|
|
109
|
+
|
|
110
|
+
except (requests.exceptions.RequestException, ConnectionError) as e:
|
|
111
|
+
last_exception = NetworkError(f"网络请求失败: {str(e)}")
|
|
112
|
+
|
|
113
|
+
if attempt < retry_count:
|
|
114
|
+
delay = 2 ** attempt
|
|
115
|
+
logger.warning(f"请求失败,{delay}秒后重试")
|
|
116
|
+
time.sleep(delay)
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
except RateLimitError as e:
|
|
120
|
+
if attempt < retry_count:
|
|
121
|
+
logger.warning(f"遇到限流,等待{e.retry_after}秒后重试")
|
|
122
|
+
time.sleep(e.retry_after)
|
|
123
|
+
continue
|
|
124
|
+
raise
|
|
125
|
+
|
|
126
|
+
if last_exception:
|
|
127
|
+
raise last_exception
|
|
128
|
+
|
|
129
|
+
raise PRError("请求失败,已达到最大重试次数")
|
|
130
|
+
|
|
131
|
+
def _validate_pr_data(self, pr_data: PRData) -> None:
|
|
132
|
+
"""验证PR数据"""
|
|
133
|
+
if not pr_data.title.strip():
|
|
134
|
+
raise ValidationError("PR标题不能为空")
|
|
135
|
+
if not pr_data.source_branch.strip():
|
|
136
|
+
raise ValidationError("源分支不能为空")
|
|
137
|
+
if not pr_data.target_branch.strip():
|
|
138
|
+
raise ValidationError("目标分支不能为空")
|
|
139
|
+
if pr_data.source_branch == pr_data.target_branch:
|
|
140
|
+
raise ValidationError("源分支和目标分支不能相同")
|
|
141
|
+
|
|
142
|
+
def _validate_repo_info(self, repo_info: RepoInfo) -> None:
|
|
143
|
+
"""验证仓库信息"""
|
|
144
|
+
if not repo_info.owner.strip():
|
|
145
|
+
raise ValidationError("仓库所有者不能为空")
|
|
146
|
+
if not repo_info.name.strip():
|
|
147
|
+
raise ValidationError("仓库名称不能为空")
|
|
148
|
+
|
|
149
|
+
@abstractmethod
|
|
150
|
+
def create_pr(self, repo_info: RepoInfo, pr_data: PRData) -> PRResult:
|
|
151
|
+
"""创建 Pull Request"""
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
@abstractmethod
|
|
155
|
+
def get_pr(self, repo_info: RepoInfo, pr_number: int) -> PRResult:
|
|
156
|
+
"""获取 PR 信息"""
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
@abstractmethod
|
|
160
|
+
def update_pr(self, repo_info: RepoInfo, pr_number: int, **kwargs) -> PRResult:
|
|
161
|
+
"""更新 PR"""
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
@abstractmethod
|
|
165
|
+
def close_pr(self, repo_info: RepoInfo, pr_number: int) -> PRResult:
|
|
166
|
+
"""关闭 PR"""
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
@abstractmethod
|
|
170
|
+
def merge_pr(self, repo_info: RepoInfo, pr_number: int, **kwargs) -> PRResult:
|
|
171
|
+
"""合并 PR"""
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
@abstractmethod
|
|
175
|
+
def list_prs(
|
|
176
|
+
self,
|
|
177
|
+
repo_info: RepoInfo,
|
|
178
|
+
state: str = "open",
|
|
179
|
+
per_page: int = 30,
|
|
180
|
+
page: int = 1
|
|
181
|
+
) -> List[PRInfo]:
|
|
182
|
+
"""列出仓库的PR"""
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
def health_check(self) -> bool:
|
|
186
|
+
"""检查连接和认证状态"""
|
|
187
|
+
try:
|
|
188
|
+
response = self._make_request('GET', f"{self.config.base_url}/user")
|
|
189
|
+
return response.status_code == 200
|
|
190
|
+
except Exception:
|
|
191
|
+
return False
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pull Request 配置管理
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
from typing import Dict, Any, Optional
|
|
6
|
+
from .models import PRConfig, PlatformType
|
|
7
|
+
from .exceptions import ConfigurationError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_config(platform: str, **overrides) -> PRConfig:
|
|
11
|
+
"""
|
|
12
|
+
获取平台配置
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
platform: 平台名称
|
|
16
|
+
**overrides: 配置覆盖参数
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
配置对象
|
|
20
|
+
"""
|
|
21
|
+
# 从环境变量加载配置
|
|
22
|
+
env_config = _load_from_env(platform)
|
|
23
|
+
|
|
24
|
+
# 合并配置
|
|
25
|
+
merged_config = {}
|
|
26
|
+
if env_config:
|
|
27
|
+
merged_config.update(env_config)
|
|
28
|
+
merged_config.update(overrides)
|
|
29
|
+
|
|
30
|
+
# 验证必需的配置
|
|
31
|
+
if 'token' not in merged_config:
|
|
32
|
+
raise ConfigurationError(f"平台 {platform} 缺少必需的 token 配置")
|
|
33
|
+
|
|
34
|
+
return PRConfig(platform=PlatformType(platform), **merged_config)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _load_from_env(platform: str) -> Dict[str, Any]:
|
|
38
|
+
"""从环境变量加载配置"""
|
|
39
|
+
env_mappings = {
|
|
40
|
+
'github': {
|
|
41
|
+
'token': 'GITHUB_TOKEN',
|
|
42
|
+
'base_url': 'GITHUB_BASE_URL'
|
|
43
|
+
},
|
|
44
|
+
'gitlab': {
|
|
45
|
+
'token': 'GITLAB_TOKEN',
|
|
46
|
+
'base_url': 'GITLAB_BASE_URL'
|
|
47
|
+
},
|
|
48
|
+
'gitee': {
|
|
49
|
+
'token': 'GITEE_TOKEN',
|
|
50
|
+
'base_url': 'GITEE_BASE_URL'
|
|
51
|
+
},
|
|
52
|
+
'gitcode': {
|
|
53
|
+
'token': 'GITCODE_TOKEN',
|
|
54
|
+
'base_url': 'GITCODE_BASE_URL'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
mapping = env_mappings.get(platform, {})
|
|
59
|
+
config = {}
|
|
60
|
+
|
|
61
|
+
for key, env_var in mapping.items():
|
|
62
|
+
value = os.getenv(env_var)
|
|
63
|
+
if value:
|
|
64
|
+
config[key] = value
|
|
65
|
+
|
|
66
|
+
return config
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pull Request 模块自定义异常类
|
|
3
|
+
"""
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
class PRError(Exception):
|
|
7
|
+
"""Pull Request 操作基础异常"""
|
|
8
|
+
def __init__(self, message: str, error_code: Optional[str] = None, platform: Optional[str] = None):
|
|
9
|
+
self.message = message
|
|
10
|
+
self.error_code = error_code
|
|
11
|
+
self.platform = platform
|
|
12
|
+
super().__init__(message)
|
|
13
|
+
|
|
14
|
+
class AuthenticationError(PRError):
|
|
15
|
+
"""认证失败异常"""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
class RepositoryNotFoundError(PRError):
|
|
19
|
+
"""仓库不存在异常"""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
class BranchNotFoundError(PRError):
|
|
23
|
+
"""分支不存在异常"""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
class NetworkError(PRError):
|
|
27
|
+
"""网络错误异常"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
class RateLimitError(PRError):
|
|
31
|
+
"""API 限流异常"""
|
|
32
|
+
def __init__(self, message: str, retry_after: int = 60, **kwargs):
|
|
33
|
+
self.retry_after = retry_after
|
|
34
|
+
super().__init__(message, **kwargs)
|
|
35
|
+
|
|
36
|
+
class ValidationError(PRError):
|
|
37
|
+
"""参数验证错误异常"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
class PlatformNotSupportedError(PRError):
|
|
41
|
+
"""平台不支持异常"""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
class ConfigurationError(PRError):
|
|
45
|
+
"""配置错误异常"""
|
|
46
|
+
pass
|