Skip to content

Commit 8e331a8

Browse files
📝 Add docstrings to Feature/config_check
Docstrings generation was requested by @vstars1. * #15 (comment) The following files were modified: * `gitlab_integration/gitlab_fetcher.py` * `gitlab_integration/webhook_listener.py` * `response_module/abstract_response.py` * `response_module/response_controller.py` * `response_module/response_factory.py` * `response_module/response_target/msg_response/dingtalk_response.py` * `response_module/response_target/msg_response/gitlab_response.py` * `response_module/response_target/other_type_response/template_response.py` * `review_engine/abstract_handler.py` * `review_engine/handler/default_handler.py` * `review_engine/review_engine.py` * `utils/args_check.py` * `utils/gitlab_parser.py` * `utils/tools.py`
1 parent a97193d commit 8e331a8

File tree

14 files changed

+545
-77
lines changed

14 files changed

+545
-77
lines changed

gitlab_integration/gitlab_fetcher.py

Lines changed: 117 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@
1313

1414
class GitlabMergeRequestFetcher:
1515
def __init__(self, project_id, merge_request_iid):
16+
"""
17+
Initialize a GitLab merge request fetcher.
18+
19+
Assigns the project identifier and merge request IID, and sets up caches for
20+
tracking changes, file content, and merge request information.
21+
22+
Parameters:
23+
project_id: The unique identifier for the GitLab project.
24+
merge_request_iid: The internal identifier for the merge request.
25+
"""
1626
self.project_id = project_id
1727
self.iid = merge_request_iid
1828
self._changes_cache = None
@@ -22,17 +32,23 @@ def __init__(self, project_id, merge_request_iid):
2232
@retry(stop_max_attempt_number=3, wait_fixed=2000)
2333
def get_changes(self, force=False):
2434
"""
25-
Get the changes of the merge request
26-
:return: changes
35+
Retrieve merge request changes via GitLab API.
36+
37+
If cached changes are available and force is False, returns the cached data.
38+
Otherwise, performs a GET request to fetch the latest changes, caches them on success,
39+
and returns the list of changes. Returns None if the API request fails.
40+
41+
Args:
42+
force (bool): If True, bypasses the cache to fetch fresh changes.
2743
"""
2844
if self._changes_cache and not force:
2945
return self._changes_cache
3046
# URL for the GitLab API endpoint
31-
url = f"{GITLAB_SERVER_URL}/api/v4/projects/{self.project_id}/merge_requests/{self.iid}/changes"
47+
url = f"{gitlab_server_url}/api/v4/projects/{self.project_id}/merge_requests/{self.iid}/changes"
3248

3349
# Headers for the request
3450
headers = {
35-
"PRIVATE-TOKEN": GITLAB_PRIVATE_TOKEN
51+
"PRIVATE-TOKEN": gitlab_private_token
3652
}
3753

3854
# Make the GET request
@@ -49,20 +65,31 @@ def get_changes(self, force=False):
4965
# 获取文件内容
5066
def get_file_content(self, file_path, branch_name='main', force=False):
5167
"""
52-
Get the content of the file
53-
:param file_path: The path of the file
54-
:return: The content of the file
68+
Fetch the raw content of a repository file via the GitLab API.
69+
70+
This method retrieves the file content from the specified branch by making a GET
71+
request to the GitLab API. The provided file path is URL-encoded for proper API
72+
endpoint formatting. Cached content is returned if available, unless the force
73+
flag is set to True.
74+
75+
Args:
76+
file_path: The repository path of the file; forward slashes are URL-encoded.
77+
branch_name: The branch to fetch the file from (default is 'main').
78+
force: If True, bypasses the cache to retrieve fresh content.
79+
80+
Returns:
81+
The raw file content as a string if the request is successful; otherwise, None.
5582
"""
5683
# 对file_path中的'/'转换为'%2F'
5784
file_path = file_path.replace('/', '%2F')
5885
if file_path in self._file_content_cache and not force:
5986
return self._file_content_cache[file_path]
6087
# URL for the GitLab API endpoint
61-
url = f"{GITLAB_SERVER_URL}/api/v4/projects/{self.project_id}/repository/files/{file_path}/raw?ref={branch_name}"
88+
url = f"{gitlab_server_url}/api/v4/projects/{self.project_id}/repository/files/{file_path}/raw?ref={branch_name}"
6289

6390
# Headers for the request
6491
headers = {
65-
"PRIVATE-TOKEN": GITLAB_PRIVATE_TOKEN
92+
"PRIVATE-TOKEN": gitlab_private_token
6693
}
6794

6895
# Make the GET request
@@ -78,17 +105,26 @@ def get_file_content(self, file_path, branch_name='main', force=False):
78105
@retry(stop_max_attempt_number=3, wait_fixed=2000)
79106
def get_info(self, force=False):
80107
"""
81-
Get the merge request information
82-
:return: Merge request information
108+
Retrieve merge request information.
109+
110+
If cached data is available and force is False, the cached merge request details
111+
are returned. Otherwise, the method calls the GitLab API to fetch fresh information,
112+
caches the result, and returns it. If the API request fails, None is returned.
113+
114+
Args:
115+
force (bool): If True, bypass the cache and retrieve fresh data.
116+
117+
Returns:
118+
dict or None: The merge request information if successful; otherwise, None.
83119
"""
84120
if self._info_cache and not force:
85121
return self._info_cache
86122
# URL for the GitLab API endpoint
87-
url = f"{GITLAB_SERVER_URL}/api/v4/projects/{self.project_id}/merge_requests/{self.iid}"
123+
url = f"{gitlab_server_url}/api/v4/projects/{self.project_id}/merge_requests/{self.iid}"
88124

89125
# Headers for the request
90126
headers = {
91-
"PRIVATE-TOKEN": GITLAB_PRIVATE_TOKEN
127+
"PRIVATE-TOKEN": gitlab_private_token
92128
}
93129

94130
# Make the GET request
@@ -104,22 +140,37 @@ def get_info(self, force=False):
104140
# gitlab仓库clone和管理
105141
class GitlabRepoManager:
106142
def __init__(self, project_id, branch_name = ""):
143+
"""
144+
Initialize a GitlabRepoManager instance.
145+
146+
Creates a unique repository path by combining the provided project ID with the current
147+
timestamp. The repository is initially marked as not cloned. The optional branch name
148+
parameter is accepted for potential branch-related operations, although it is not used
149+
during initialization.
150+
151+
Args:
152+
project_id: Identifier for the GitLab project.
153+
branch_name: Optional branch name for repository operations.
154+
"""
107155
self.project_id = project_id
108156
self.timestamp = int(time.time() * 1000)
109157
self.repo_path = f"./repo/{self.project_id}_{self.timestamp}"
110158
self.has_cloned = False
111159

112160
def get_info(self):
113161
"""
114-
Get the project information
115-
:return: Project information
162+
Retrieve project information from GitLab.
163+
164+
Makes a GET request to the GitLab API to fetch details for the project identified by the
165+
instance's project_id. Returns the JSON-decoded response if the request is successful (HTTP 200);
166+
otherwise, returns None.
116167
"""
117168
# URL for the GitLab API endpoint
118-
url = f"{GITLAB_SERVER_URL}/api/v4/projects/{self.project_id}"
169+
url = f"{gitlab_server_url}/api/v4/projects/{self.project_id}"
119170

120171
# Headers for the request
121172
headers = {
122-
"PRIVATE-TOKEN": GITLAB_PRIVATE_TOKEN
173+
"PRIVATE-TOKEN": gitlab_private_token
123174
}
124175

125176
# Make the GET request
@@ -129,14 +180,20 @@ def get_info(self):
129180
if response.status_code == 200:
130181
return response.json()
131182
else:
132-
log.error(f"获取项目信息失败: {response.status_code} {response.text}")
133183
return None
134184

135185
@retry(stop_max_attempt_number=3, wait_fixed=2000)
136186
def shallow_clone(self, branch_name = "main"):
137187
"""
138-
Perform a shallow clone of the repository
139-
param branch_name: The name of the branch to clone
188+
Shallow clones the repository to a local directory.
189+
190+
Deletes any existing local clone, constructs an authenticated Git URL using
191+
repository information, and executes a shallow clone (depth of 1) for the specified
192+
branch. If cloning fails, an error is logged; otherwise, the repository is marked
193+
as cloned.
194+
195+
Args:
196+
branch_name (str): The branch to clone (default "main").
140197
"""
141198
# If the target directory exists, remove it
142199
self.delete_repo()
@@ -159,6 +216,17 @@ def shallow_clone(self, branch_name = "main"):
159216
# 切换分支
160217
def checkout_branch(self, branch_name, force=False):
161218
# Build the Git command
219+
"""
220+
Checks out the specified branch by performing a shallow clone if necessary.
221+
222+
If the repository has not been cloned already, the method executes a shallow clone for the target branch.
223+
If the repository is already cloned, it verifies whether the branch is already checked out (unless forced)
224+
and performs a shallow clone if the branch differs or if force is True.
225+
226+
Args:
227+
branch_name: The name of the branch to check out.
228+
force: If True, forces re-cloning of the branch even if it appears to be already checked out.
229+
"""
162230
if not self.has_cloned:
163231
self.shallow_clone(branch_name)
164232
else:
@@ -170,11 +238,30 @@ def checkout_branch(self, branch_name, force=False):
170238

171239
# 删除库
172240
def delete_repo(self):
241+
"""
242+
Deletes the cloned repository directory if it exists.
243+
244+
This method checks whether the repository path exists on the filesystem and removes it along with its contents. If the directory is not present, no action is taken.
245+
"""
173246
if os.path.exists(self.repo_path):
174247
shutil.rmtree(self.repo_path)
175248

176249
# 查找相关文件列表
177250
def find_files_by_keyword(self, keyword, branch_name="main"):
251+
"""
252+
Search for files whose content matches a regex pattern.
253+
254+
Checks out the specified branch and recursively searches for files whose content
255+
matches the provided regular expression. Files that cannot be read due to encoding,
256+
permission, or existence issues are skipped.
257+
258+
Args:
259+
keyword: Regular expression pattern to search for in file contents.
260+
branch_name: Branch to search in; defaults to "main".
261+
262+
Returns:
263+
A list of file paths for files containing a match to the keyword.
264+
"""
178265
matching_files = []
179266
regex = re.compile(keyword)
180267
self.checkout_branch(branch_name)
@@ -196,23 +283,19 @@ def find_files_by_keyword(self, keyword, branch_name="main"):
196283
# 构建带有身份验证信息的 URL
197284
def _build_authenticated_url(self, repo_url):
198285
# 如果 URL 使用 https
199-
token = GITLAB_PRIVATE_TOKEN
286+
"""
287+
Builds an authenticated URL for repository access.
288+
289+
This method embeds an OAuth2 token into the provided repository URL, supporting only
290+
URLs beginning with "http://" or "https://". For HTTPS URLs, it returns a URL in the
291+
format "https://oauth2:{token}@<rest_of_url>" and similarly for HTTP URLs. If the URL
292+
scheme is unsupported, a ValueError is raised.
293+
"""
294+
token = gitlab_private_token
200295
if repo_url.startswith("https://"):
201296
return f"https://oauth2:{token}@{repo_url[8:]}"
202297
# 如果 URL 使用 http
203298
elif repo_url.startswith("http://"):
204299
return f"http://oauth2:{token}@{repo_url[7:]}"
205300
else:
206-
raise ValueError("Unsupported URL scheme")
207-
208-
def is_merge_request_opened(gitlab_payload) -> bool:
209-
"""
210-
判断是否是merge request打开事件
211-
"""
212-
try:
213-
gitlab_merge_request_old = gitlab_payload.get("object_attributes").get("state") == "opened" and gitlab_payload.get("object_attributes").get("merge_status") == "preparing"
214-
gitlab_merge_request_new = gitlab_payload.get("object_attributes").get("state") == "merged" and gitlab_payload.get("object_attributes").get("merge_status") == "can_be_merged"
215-
return gitlab_merge_request_old or gitlab_merge_request_new
216-
except Exception as e:
217-
log.error(f"判断是否是merge request打开事件失败: {e}")
218-
return False
301+
raise ValueError("Unsupported URL scheme")

gitlab_integration/webhook_listener.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from response_module.response_controller import ReviewResponse
88
from review_engine.review_engine import ReviewEngine
99
from utils.logger import log
10-
from gitlab_integration.gitlab_fetcher import is_merge_request_opened
10+
1111

1212
class WebhookListener:
1313
def __init__(self):
@@ -25,6 +25,19 @@ def handle_webhook(self):
2525
return self.call_handle(gitlab_payload, event_type)
2626

2727
def call_handle(self, gitlab_payload, event_type):
28+
"""
29+
Dispatches a GitLab webhook payload to the corresponding event handler.
30+
31+
Determines the event type and builds a configuration dictionary for a ReviewResponse,
32+
which is then passed to the appropriate handler—merge request, push, or other events.
33+
34+
Args:
35+
gitlab_payload: A dictionary containing data from a GitLab webhook.
36+
event_type: A string specifying the event type (e.g., 'merge_request', 'push').
37+
38+
Returns:
39+
The response object returned by the invoked event-specific handler.
40+
"""
2841
if event_type == 'merge_request':
2942
config = {
3043
'type': 'merge_request',
@@ -51,9 +64,22 @@ def call_handle(self, gitlab_payload, event_type):
5164

5265
def handle_merge_request(self, gitlab_payload, reply):
5366
"""
54-
处理合并请求事件
67+
Process a GitLab merge request event.
68+
69+
When the merge request is in the "opened" state and its merge status is "preparing", this
70+
function logs the event, extracts the project and merge request identifiers, and initializes
71+
a ReviewEngine to process the merge asynchronously in a new thread using a GitlabMergeRequestFetcher
72+
and a GitlabRepoManager. It returns a JSON response with a status of "success". If the event does
73+
not meet these criteria, a JSON response indicating that no further check is needed is returned.
74+
75+
Args:
76+
gitlab_payload: A dictionary containing GitLab merge request event details.
77+
reply: A ReviewResponse configuration object used for initializing the ReviewEngine.
78+
79+
Returns:
80+
A tuple with a JSON response and an HTTP status code (200).
5581
"""
56-
if is_merge_request_opened(gitlab_payload):
82+
if gitlab_payload.get("object_attributes").get("state") == "opened" and gitlab_payload.get("object_attributes").get("merge_status") == "preparing":
5783
log.info("首次merge_request ", gitlab_payload)
5884
project_id = gitlab_payload.get('project')['id']
5985
merge_request_iid = gitlab_payload.get("object_attributes")["iid"]

response_module/abstract_response.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,66 @@
33
class AbstractResponse(ABC):
44
@abstractmethod
55
def __init__(self, config):
6+
"""
7+
Initialize the response instance with a configuration.
8+
9+
The provided configuration is stored for use by subclasses.
10+
"""
611
self.config = config
712

813

914
class AbstractResponseMessage(AbstractResponse):
1015
@abstractmethod
1116
def __init__(self, config):
17+
"""
18+
Initialize the instance with the provided configuration.
19+
20+
Delegates to the superclass constructor to set up the instance.
21+
"""
1222
super().__init__(config)
1323

1424
@abstractmethod
1525
def send(self, message):
26+
"""
27+
Sends a message.
28+
29+
Subclasses must override this method to deliver the provided message using the
30+
appropriate communication mechanism.
31+
32+
Args:
33+
message: The content or payload of the message to be sent.
34+
"""
1635
pass
1736

1837

1938
class AbstractResponseOther(AbstractResponse):
2039
@abstractmethod
2140
def __init__(self, config):
41+
"""
42+
Initialize the response instance with the specified configuration.
43+
44+
This constructor passes the configuration to the parent initializer to establish
45+
the necessary settings for the response object.
46+
"""
2247
super().__init__(config)
2348

2449
@abstractmethod
2550
def set_state(self, *args, **kwargs):
51+
"""
52+
Set the state of the response.
53+
54+
Subclasses must override this method to update the internal state based on the
55+
provided positional and keyword arguments.
56+
"""
2657
pass
2758

2859
@abstractmethod
2960
def send(self, *args, **kwargs):
61+
"""
62+
Sends a response using provided arguments.
63+
64+
Subclasses must override this method to handle sending a response
65+
or triggering appropriate actions based on supplied positional and keyword
66+
arguments.
67+
"""
3068
pass

0 commit comments

Comments
 (0)