arcade-github 0.0.13__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.
File without changes
@@ -0,0 +1,55 @@
1
+ from unittest.mock import AsyncMock, patch
2
+
3
+ import pytest
4
+ from arcade_github.tools.activity import set_starred
5
+ from httpx import Response
6
+
7
+ from arcade.core.errors import ToolExecutionError
8
+
9
+
10
+ @pytest.fixture
11
+ def mock_context():
12
+ context = AsyncMock()
13
+ context.authorization.token = "mock_token" # noqa: S105
14
+ return context
15
+
16
+
17
+ @pytest.fixture
18
+ def mock_client():
19
+ with patch("arcade_github.tools.activity.httpx.AsyncClient") as client:
20
+ yield client.return_value.__aenter__.return_value
21
+
22
+
23
+ @pytest.mark.asyncio
24
+ @pytest.mark.parametrize(
25
+ "starred,expected_message",
26
+ [
27
+ (True, "Successfully starred the repository owner/repo"),
28
+ (False, "Successfully unstarred the repository owner/repo"),
29
+ ],
30
+ )
31
+ async def test_set_starred_success(mock_context, mock_client, starred, expected_message):
32
+ mock_client.put.return_value = mock_client.delete.return_value = Response(204)
33
+
34
+ result = await set_starred(mock_context, "owner", "repo", starred)
35
+ assert result == expected_message
36
+
37
+
38
+ @pytest.mark.asyncio
39
+ @pytest.mark.parametrize(
40
+ "status_code,error_message,expected_error",
41
+ [
42
+ (403, "Forbidden", "Error accessing.*: Forbidden"),
43
+ (404, "Not Found", "Error accessing.*: Resource not found"),
44
+ (500, "Internal Server Error", "Error accessing.*: Failed to process request"),
45
+ ],
46
+ )
47
+ async def test_set_starred_errors(
48
+ mock_context, mock_client, status_code, error_message, expected_error
49
+ ):
50
+ mock_client.put.return_value = mock_client.delete.return_value = Response(
51
+ status_code, json={"message": error_message}
52
+ )
53
+
54
+ with pytest.raises(ToolExecutionError, match=expected_error):
55
+ await set_starred(mock_context, "owner", "repo", True)
@@ -0,0 +1,110 @@
1
+ from unittest.mock import AsyncMock, patch
2
+
3
+ import pytest
4
+ from arcade_github.tools.issues import create_issue, create_issue_comment
5
+ from httpx import Response
6
+
7
+ from arcade.core.errors import ToolExecutionError
8
+
9
+
10
+ @pytest.fixture
11
+ def mock_context():
12
+ context = AsyncMock()
13
+ context.authorization.token = "mock_token" # noqa: S105
14
+ return context
15
+
16
+
17
+ @pytest.fixture
18
+ def mock_client():
19
+ with patch("arcade_github.tools.issues.httpx.AsyncClient") as client:
20
+ yield client.return_value.__aenter__.return_value
21
+
22
+
23
+ @pytest.mark.asyncio
24
+ @pytest.mark.parametrize(
25
+ "status_code,error_message,expected_error,func,args",
26
+ [
27
+ (
28
+ 422,
29
+ "Validation Failed",
30
+ "Error accessing.*: Validation failed",
31
+ create_issue,
32
+ ("owner", "repo", "title"),
33
+ ),
34
+ (
35
+ 401,
36
+ "Unauthorized",
37
+ "Error accessing.*: Failed to process request",
38
+ create_issue_comment,
39
+ ("owner", "repo", 1, "body"),
40
+ ),
41
+ (
42
+ 403,
43
+ "API rate limit exceeded",
44
+ "Error accessing.*: Forbidden",
45
+ create_issue_comment,
46
+ ("owner", "repo", 1, "body"),
47
+ ),
48
+ (
49
+ 401,
50
+ "Bad credentials",
51
+ "Error accessing.*: Failed to process request",
52
+ create_issue,
53
+ ("owner", "repo", "title"),
54
+ ),
55
+ ],
56
+ )
57
+ async def test_issue_errors(
58
+ mock_context, mock_client, status_code, error_message, expected_error, func, args
59
+ ):
60
+ mock_client.post.return_value = Response(status_code, json={"message": error_message})
61
+
62
+ with pytest.raises(ToolExecutionError, match=expected_error):
63
+ await func(mock_context, *args)
64
+
65
+
66
+ @pytest.mark.asyncio
67
+ @pytest.mark.parametrize(
68
+ "func,args,response_json,expected_assertions",
69
+ [
70
+ (
71
+ create_issue,
72
+ ("owner", "repo", "Test Issue", "This is a test issue"),
73
+ {
74
+ "id": 1,
75
+ "url": "https://api.github.com/repos/owner/repo/issues/1",
76
+ "title": "Test Issue",
77
+ "body": "This is a test issue",
78
+ "state": "open",
79
+ "html_url": "https://github.com/owner/repo/issues/1",
80
+ "created_at": "2023-05-01T12:00:00Z",
81
+ "updated_at": "2023-05-01T12:00:00Z",
82
+ "user": {"login": "testuser"},
83
+ "assignees": [],
84
+ "labels": [],
85
+ },
86
+ ["Test Issue", "https://github.com/owner/repo/issues/1"],
87
+ ),
88
+ (
89
+ create_issue_comment,
90
+ ("owner", "repo", 1, "This is a test comment"),
91
+ {
92
+ "id": 1,
93
+ "url": "https://api.github.com/repos/owner/repo/issues/comments/1",
94
+ "body": "This is a test comment",
95
+ "user": {"login": "testuser"},
96
+ "created_at": "2023-05-01T12:00:00Z",
97
+ "updated_at": "2023-05-01T12:00:00Z",
98
+ },
99
+ ["This is a test comment", "https://api.github.com/repos/owner/repo/issues/comments/1"],
100
+ ),
101
+ ],
102
+ )
103
+ async def test_issue_success(
104
+ mock_context, mock_client, func, args, response_json, expected_assertions
105
+ ):
106
+ mock_client.post.return_value = Response(201, json=response_json)
107
+
108
+ result = await func(mock_context, *args)
109
+ for assertion in expected_assertions:
110
+ assert assertion in result
@@ -0,0 +1,359 @@
1
+ from unittest.mock import AsyncMock, patch
2
+
3
+ import pytest
4
+ from arcade_github.tools.models import (
5
+ DiffSide,
6
+ ReviewCommentSubjectType,
7
+ )
8
+ from arcade_github.tools.pull_requests import (
9
+ create_reply_for_review_comment,
10
+ create_review_comment,
11
+ get_pull_request,
12
+ list_pull_request_commits,
13
+ list_pull_requests,
14
+ list_review_comments_on_pull_request,
15
+ update_pull_request,
16
+ )
17
+ from httpx import Response
18
+
19
+ from arcade.core.errors import RetryableToolError, ToolExecutionError
20
+
21
+
22
+ @pytest.fixture
23
+ def mock_context():
24
+ context = AsyncMock()
25
+ context.authorization.token = "mock_token" # noqa: S105
26
+ return context
27
+
28
+
29
+ @pytest.fixture
30
+ def mock_client():
31
+ with patch("arcade_github.tools.pull_requests.httpx.AsyncClient") as client:
32
+ yield client.return_value.__aenter__.return_value
33
+
34
+
35
+ @pytest.mark.asyncio
36
+ @pytest.mark.parametrize(
37
+ "func,args,status_code,json_response,expected_result,error_message",
38
+ [
39
+ (list_pull_requests, ("owner", "repo"), 200, [], '{"pull_requests": []}', None),
40
+ (
41
+ get_pull_request,
42
+ ("owner", "repo", 1),
43
+ 404,
44
+ {"message": "Not Found"},
45
+ None,
46
+ "Error accessing.*: Resource not found",
47
+ ),
48
+ (
49
+ update_pull_request,
50
+ ("owner", "repo", 1, "New Title"),
51
+ 409,
52
+ {"message": "Conflict"},
53
+ None,
54
+ "Error accessing.*: Failed to process request",
55
+ ),
56
+ (
57
+ list_pull_request_commits,
58
+ ("owner", "repo", 1),
59
+ 500,
60
+ {"message": "Internal Server Error"},
61
+ None,
62
+ "Error accessing.*: Failed to process request",
63
+ ),
64
+ (
65
+ list_review_comments_on_pull_request,
66
+ ("owner", "repo", 1),
67
+ 403,
68
+ {"message": "API rate limit exceeded"},
69
+ None,
70
+ "Error accessing.*: Forbidden",
71
+ ),
72
+ ],
73
+ )
74
+ async def test_pull_request_functions(
75
+ mock_context,
76
+ mock_client,
77
+ func,
78
+ args,
79
+ status_code,
80
+ json_response,
81
+ expected_result,
82
+ error_message,
83
+ ):
84
+ mock_client.get.return_value = mock_client.post.return_value = (
85
+ mock_client.patch.return_value
86
+ ) = Response(status_code, json=json_response)
87
+
88
+ if error_message:
89
+ with pytest.raises(ToolExecutionError, match=error_message):
90
+ await func(mock_context, *args)
91
+ else:
92
+ result = await func(mock_context, *args)
93
+ assert result == expected_result
94
+
95
+
96
+ @pytest.mark.asyncio
97
+ @pytest.mark.parametrize(
98
+ "func,args,json_response,expected_assertions",
99
+ [
100
+ (
101
+ list_pull_requests,
102
+ ("owner", "repo"),
103
+ [
104
+ {
105
+ "number": 1,
106
+ "title": "Test PR",
107
+ "body": "This is a test PR",
108
+ "state": "open",
109
+ "html_url": "https://github.com/owner/repo/pull/1",
110
+ "created_at": "2023-05-01T12:00:00Z",
111
+ "updated_at": "2023-05-01T12:00:00Z",
112
+ "user": {"login": "testuser"},
113
+ "base": {"ref": "main"},
114
+ "head": {"ref": "feature-branch"},
115
+ }
116
+ ],
117
+ ["Test PR", "https://github.com/owner/repo/pull/1"],
118
+ ),
119
+ (
120
+ update_pull_request,
121
+ ("owner", "repo", 1, "Updated PR Title", "Updated PR body"),
122
+ {
123
+ "number": 1,
124
+ "title": "Updated PR Title",
125
+ "body": "Updated PR body",
126
+ "state": "open",
127
+ "html_url": "https://github.com/owner/repo/pull/1",
128
+ "created_at": "2023-05-01T12:00:00Z",
129
+ "updated_at": "2023-05-02T12:00:00Z",
130
+ "user": {"login": "testuser"},
131
+ },
132
+ ["Updated PR Title", "Updated PR body"],
133
+ ),
134
+ (
135
+ list_pull_request_commits,
136
+ ("owner", "repo", 1),
137
+ [
138
+ {
139
+ "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
140
+ "commit": {
141
+ "author": {
142
+ "name": "Test Author",
143
+ "email": "author@example.com",
144
+ "date": "2023-05-01T12:00:00Z",
145
+ },
146
+ "message": "Test commit message",
147
+ },
148
+ }
149
+ ],
150
+ ["6dcb09b5b57875f334f61aebed695e2e4193db5e", "Test commit message"],
151
+ ),
152
+ (
153
+ create_reply_for_review_comment,
154
+ ("owner", "repo", 1, 42, "Thanks for the suggestion."),
155
+ {
156
+ "id": 123,
157
+ "body": "Thanks for the suggestion.",
158
+ "user": {"login": "testuser"},
159
+ "created_at": "2023-05-02T12:00:00Z",
160
+ "updated_at": "2023-05-02T12:00:00Z",
161
+ },
162
+ ["Thanks for the suggestion.", "testuser"],
163
+ ),
164
+ (
165
+ list_review_comments_on_pull_request,
166
+ ("owner", "repo", 1),
167
+ [
168
+ {
169
+ "id": 1,
170
+ "body": "Great changes!",
171
+ "user": {"login": "reviewer1"},
172
+ "created_at": "2023-05-01T12:00:00Z",
173
+ "updated_at": "2023-05-01T12:00:00Z",
174
+ "path": "file1.txt",
175
+ "line": 5,
176
+ }
177
+ ],
178
+ ["Great changes!", "reviewer1", "file1.txt"],
179
+ ),
180
+ (
181
+ get_pull_request,
182
+ ("owner", "repo", 1, False, False),
183
+ {
184
+ "number": 1,
185
+ "title": "Test PR",
186
+ "body": "This is a test PR",
187
+ "state": "open",
188
+ "html_url": "https://github.com/owner/repo/pull/1",
189
+ "created_at": "2023-05-01T12:00:00Z",
190
+ "updated_at": "2023-05-01T12:00:00Z",
191
+ "user": {"login": "testuser"},
192
+ "base": {"ref": "main"},
193
+ "head": {"ref": "feature-branch"},
194
+ },
195
+ ["Test PR", "https://github.com/owner/repo/pull/1"],
196
+ ),
197
+ (
198
+ get_pull_request,
199
+ ("owner", "repo", 1, True, False),
200
+ {
201
+ "number": 1,
202
+ "title": "Test PR",
203
+ "body": "This is a test PR",
204
+ "state": "open",
205
+ "html_url": "https://github.com/owner/repo/pull/1",
206
+ "created_at": "2023-05-01T12:00:00Z",
207
+ "updated_at": "2023-05-01T12:00:00Z",
208
+ "user": {"login": "testuser"},
209
+ "base": {"ref": "main"},
210
+ "head": {"ref": "feature-branch"},
211
+ "diff_content": "Sample diff content",
212
+ },
213
+ ["Test PR", "https://github.com/owner/repo/pull/1", "diff_content"],
214
+ ),
215
+ (
216
+ create_review_comment,
217
+ (
218
+ "owner",
219
+ "repo",
220
+ 1,
221
+ "Great changes!",
222
+ "file1.txt",
223
+ "6dcb09b5b57875f334f61aebed695e2e4193db5e",
224
+ 1,
225
+ 2,
226
+ DiffSide.RIGHT,
227
+ None,
228
+ ReviewCommentSubjectType.LINE,
229
+ ),
230
+ {
231
+ "id": 1,
232
+ "body": "Great changes!",
233
+ "path": "file1.txt",
234
+ "line": 2,
235
+ "side": "RIGHT",
236
+ "commit_id": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
237
+ "user": {"login": "testuser"},
238
+ "created_at": "2023-05-01T12:00:00Z",
239
+ "updated_at": "2023-05-01T12:00:00Z",
240
+ "html_url": "https://github.com/owner/repo/pull/1#discussion_r1",
241
+ },
242
+ ["Great changes!", "file1.txt", "6dcb09b5b57875f334f61aebed695e2e4193db5e"],
243
+ ),
244
+ ],
245
+ )
246
+ async def test_pull_request_functions_success(
247
+ mock_context, mock_client, func, args, json_response, expected_assertions
248
+ ):
249
+ mock_client.get.return_value = mock_client.post.return_value = (
250
+ mock_client.patch.return_value
251
+ ) = Response(200, json=json_response)
252
+
253
+ result = await func(mock_context, *args)
254
+ for assertion in expected_assertions:
255
+ assert assertion in result
256
+
257
+
258
+ @pytest.mark.asyncio
259
+ async def test_create_review_comment_file_subject_type(mock_context, mock_client):
260
+ mock_client.post.return_value = Response(
261
+ 200,
262
+ json={
263
+ "id": 1,
264
+ "body": "File comment",
265
+ "path": "file1.txt",
266
+ "commit_id": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
267
+ "user": {"login": "testuser"},
268
+ "created_at": "2023-05-01T12:00:00Z",
269
+ "updated_at": "2023-05-01T12:00:00Z",
270
+ "html_url": "https://github.com/owner/repo/pull/1#discussion_r1",
271
+ },
272
+ )
273
+
274
+ result = await create_review_comment(
275
+ mock_context,
276
+ "owner",
277
+ "repo",
278
+ 1,
279
+ "File comment",
280
+ "file1.txt",
281
+ "6dcb09b5b57875f334f61aebed695e2e4193db5e",
282
+ subject_type=ReviewCommentSubjectType.FILE,
283
+ )
284
+
285
+ assert "File comment" in result
286
+ assert "file1.txt" in result
287
+ assert "6dcb09b5b57875f334f61aebed695e2e4193db5e" in result
288
+ assert "start_line" not in mock_client.post.call_args[1]["json"]
289
+ assert "end_line" not in mock_client.post.call_args[1]["json"]
290
+
291
+
292
+ @pytest.mark.asyncio
293
+ async def test_create_review_comment_missing_commit_id(mock_context, mock_client):
294
+ mock_client.get.return_value = Response(
295
+ 200,
296
+ json=[{"sha": "latest_commit_sha"}],
297
+ )
298
+ mock_client.post.return_value = Response(
299
+ 200,
300
+ json={
301
+ "id": 1,
302
+ "body": "Comment with auto-fetched commit ID",
303
+ "path": "file1.txt",
304
+ "commit_id": "latest_commit_sha",
305
+ "user": {"login": "testuser"},
306
+ "created_at": "2023-05-01T12:00:00Z",
307
+ "updated_at": "2023-05-01T12:00:00Z",
308
+ "html_url": "https://github.com/owner/repo/pull/1#discussion_r1",
309
+ },
310
+ )
311
+
312
+ result = await create_review_comment(
313
+ mock_context,
314
+ "owner",
315
+ "repo",
316
+ 1,
317
+ "Comment with auto-fetched commit ID",
318
+ "file1.txt",
319
+ start_line=1,
320
+ end_line=2,
321
+ )
322
+
323
+ assert "Comment with auto-fetched commit ID" in result
324
+ assert "latest_commit_sha" in result
325
+ assert mock_client.get.called
326
+ assert mock_client.post.called
327
+
328
+
329
+ @pytest.mark.asyncio
330
+ async def test_create_review_comment_invalid_input(mock_context, mock_client):
331
+ with pytest.raises(
332
+ RetryableToolError, match="'start_line' and 'end_line' parameters are required"
333
+ ):
334
+ await create_review_comment(
335
+ mock_context,
336
+ "owner",
337
+ "repo",
338
+ 1,
339
+ "Invalid comment",
340
+ "file1.txt",
341
+ subject_type=ReviewCommentSubjectType.LINE,
342
+ )
343
+
344
+
345
+ @pytest.mark.asyncio
346
+ async def test_create_review_comment_no_commits(mock_context, mock_client):
347
+ mock_client.get.return_value = Response(200, json=[])
348
+
349
+ with pytest.raises(RetryableToolError, match="Failed to get the latest commit SHA"):
350
+ await create_review_comment(
351
+ mock_context,
352
+ "owner",
353
+ "repo",
354
+ 1,
355
+ "Comment with no commits",
356
+ "file1.txt",
357
+ start_line=1,
358
+ end_line=2,
359
+ )
@@ -0,0 +1,73 @@
1
+ from unittest.mock import AsyncMock, patch
2
+
3
+ import pytest
4
+ from arcade_github.tools.models import RepoType
5
+ from arcade_github.tools.repositories import (
6
+ count_stargazers,
7
+ get_repository,
8
+ list_org_repositories,
9
+ list_repository_activities,
10
+ list_review_comments_in_a_repository,
11
+ )
12
+ from httpx import Response
13
+
14
+ from arcade.core.errors import ToolExecutionError
15
+
16
+
17
+ @pytest.fixture
18
+ def mock_context():
19
+ context = AsyncMock()
20
+ context.authorization.token = "mock_token" # noqa: S105
21
+ return context
22
+
23
+
24
+ @pytest.fixture
25
+ def mock_client():
26
+ with patch("arcade_github.tools.repositories.httpx.AsyncClient") as client:
27
+ yield client.return_value.__aenter__.return_value
28
+
29
+
30
+ @pytest.mark.asyncio
31
+ @pytest.mark.parametrize(
32
+ "status_code,error_message,expected_error",
33
+ [
34
+ (422, "Validation Failed", "Error accessing.*: Validation failed"),
35
+ (301, "Moved Permanently", "Error accessing.*: Moved permanently"),
36
+ (404, "Not Found", "Error accessing.*: Resource not found"),
37
+ (503, "Service Unavailable", "Error accessing.*: Service unavailable"),
38
+ (410, "Gone", "Error accessing.*: Gone"),
39
+ ],
40
+ )
41
+ async def test_error_responses(
42
+ mock_context, mock_client, status_code, error_message, expected_error
43
+ ):
44
+ mock_client.get.return_value = Response(status_code, json={"message": error_message})
45
+ mock_client.post.return_value = Response(status_code, json={"message": error_message})
46
+
47
+ with pytest.raises(ToolExecutionError, match=expected_error):
48
+ if status_code == 422:
49
+ await list_org_repositories(mock_context, "org", repo_type=RepoType.ALL)
50
+ elif status_code == 301:
51
+ await count_stargazers(mock_context, "owner", "repo")
52
+ elif status_code == 404:
53
+ await list_org_repositories(mock_context, "non_existent_org")
54
+ elif status_code == 503:
55
+ await get_repository(mock_context, "owner", "repo")
56
+ elif status_code == 410:
57
+ await list_review_comments_in_a_repository(mock_context, "owner", "repo")
58
+
59
+
60
+ @pytest.mark.asyncio
61
+ async def test_list_repository_activities_invalid_cursor(mock_context, mock_client):
62
+ mock_client.get.return_value = Response(422, json={"message": "Validation Failed"})
63
+
64
+ with pytest.raises(ToolExecutionError, match="Error accessing.*: Validation failed"):
65
+ await list_repository_activities(mock_context, "owner", "repo", before="invalid_cursor")
66
+
67
+
68
+ @pytest.mark.asyncio
69
+ async def test_count_stargazers_success(mock_context, mock_client):
70
+ mock_client.get.return_value = Response(200, json={"stargazers_count": 42})
71
+
72
+ result = await count_stargazers(mock_context, "owner", "repo")
73
+ assert result == 42
File without changes
@@ -0,0 +1,41 @@
1
+ from typing import Annotated
2
+
3
+ import httpx
4
+
5
+ from arcade.core.schema import ToolContext
6
+ from arcade.sdk import tool
7
+ from arcade.sdk.auth import GitHub
8
+ from arcade_github.tools.utils import get_github_json_headers, get_url, handle_github_response
9
+
10
+
11
+ # Implements https://docs.github.com/en/rest/activity/starring?apiVersion=2022-11-28#star-a-repository-for-the-authenticated-user and https://docs.github.com/en/rest/activity/starring?apiVersion=2022-11-28#unstar-a-repository-for-the-authenticated-user
12
+ # Example `arcade chat` usage: "star the vscode repo owned by microsoft"
13
+ @tool(requires_auth=GitHub())
14
+ async def set_starred(
15
+ context: ToolContext,
16
+ owner: Annotated[str, "The owner of the repository"],
17
+ name: Annotated[str, "The name of the repository"],
18
+ starred: Annotated[bool, "Whether to star the repository or not"],
19
+ ) -> Annotated[
20
+ str, "A message indicating whether the repository was successfully starred or unstarred"
21
+ ]:
22
+ """
23
+ Star or un-star a GitHub repository.
24
+ For example, to star microsoft/vscode, you would use:
25
+ ```
26
+ set_starred(owner="microsoft", name="vscode", starred=True)
27
+ ```
28
+ """
29
+ url = get_url("user_starred", owner=owner, repo=name)
30
+ headers = get_github_json_headers(context.authorization.token)
31
+
32
+ async with httpx.AsyncClient() as client:
33
+ if starred:
34
+ response = await client.put(url, headers=headers)
35
+ else:
36
+ response = await client.delete(url, headers=headers)
37
+
38
+ handle_github_response(response, url)
39
+
40
+ action = "starred" if starred else "unstarred"
41
+ return f"Successfully {action} the repository {owner}/{name}"
@@ -0,0 +1,19 @@
1
+ # Base URL for GitHub API
2
+ GITHUB_API_BASE_URL = "https://api.github.com"
3
+
4
+ # Endpoint patterns
5
+ ENDPOINTS = {
6
+ "repo": "/repos/{owner}/{repo}",
7
+ "org_repos": "/orgs/{org}/repos",
8
+ "repo_activity": "/repos/{owner}/{repo}/activity",
9
+ "repo_pulls_comments": "/repos/{owner}/{repo}/pulls/comments",
10
+ "repo_issues": "/repos/{owner}/{repo}/issues",
11
+ "repo_issue_comments": "/repos/{owner}/{repo}/issues/{issue_number}/comments",
12
+ "repo_pulls": "/repos/{owner}/{repo}/pulls",
13
+ "repo_pull": "/repos/{owner}/{repo}/pulls/{pull_number}",
14
+ "repo_pull_commits": "/repos/{owner}/{repo}/pulls/{pull_number}/commits",
15
+ "repo_pull_comments": "/repos/{owner}/{repo}/pulls/{pull_number}/comments",
16
+ "repo_pull_comment_replies": "/repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies",
17
+ "user_starred": "/user/starred/{owner}/{repo}",
18
+ "repo_stargazers": "/repos/{owner}/{repo}/stargazers",
19
+ }