claude-dev-env 8.54.1 → 8.54.2
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.
package/package.json
CHANGED
|
@@ -30,10 +30,16 @@ CHECK_RUN_PAGE_SIZE: int = 100
|
|
|
30
30
|
"""How many check runs one request asks for."""
|
|
31
31
|
|
|
32
32
|
REVIEW_THREAD_QUERY: str = """
|
|
33
|
-
query(
|
|
33
|
+
query(
|
|
34
|
+
$owner: String!, $name: String!, $number: Int!, $pageSize: Int!, $after: String
|
|
35
|
+
) {
|
|
34
36
|
repository(owner: $owner, name: $name) {
|
|
35
37
|
pullRequest(number: $number) {
|
|
36
|
-
reviewThreads(first: $pageSize) {
|
|
38
|
+
reviewThreads(first: $pageSize, after: $after) {
|
|
39
|
+
pageInfo {
|
|
40
|
+
hasNextPage
|
|
41
|
+
endCursor
|
|
42
|
+
}
|
|
37
43
|
nodes {
|
|
38
44
|
path
|
|
39
45
|
isResolved
|
|
@@ -52,14 +58,13 @@ query($owner: String!, $name: String!, $number: Int!, $pageSize: Int!) {
|
|
|
52
58
|
"""
|
|
53
59
|
"""GraphQL query for the review threads on one pull request."""
|
|
54
60
|
|
|
55
|
-
|
|
61
|
+
ALL_THREAD_CONNECTION_KEYS: tuple[str, ...] = (
|
|
56
62
|
"data",
|
|
57
63
|
"repository",
|
|
58
64
|
"pullRequest",
|
|
59
65
|
"reviewThreads",
|
|
60
|
-
"nodes",
|
|
61
66
|
)
|
|
62
|
-
"""Path from the GraphQL answer down to the review-thread
|
|
67
|
+
"""Path from the GraphQL answer down to the review-thread connection."""
|
|
63
68
|
|
|
64
69
|
ALL_RESOLVED_KEYS: tuple[str, ...] = ("isResolved", "resolved", "is_resolved")
|
|
65
70
|
"""Field names either route uses for a resolved review thread."""
|
|
@@ -174,6 +179,29 @@ NUMBER_VARIABLE: str = "number"
|
|
|
174
179
|
PAGE_SIZE_VARIABLE: str = "pageSize"
|
|
175
180
|
"""GraphQL variable naming the page size."""
|
|
176
181
|
|
|
182
|
+
AFTER_VARIABLE: str = "after"
|
|
183
|
+
"""GraphQL variable naming the cursor a page starts after."""
|
|
184
|
+
|
|
185
|
+
NODES_KEY: str = "nodes"
|
|
186
|
+
"""GraphQL connection field carrying one page of records."""
|
|
187
|
+
|
|
188
|
+
PAGE_INFO_KEY: str = "pageInfo"
|
|
189
|
+
"""GraphQL connection field describing the page boundaries."""
|
|
190
|
+
|
|
191
|
+
HAS_NEXT_PAGE_KEY: str = "hasNextPage"
|
|
192
|
+
"""Page-info field that is true while more records follow."""
|
|
193
|
+
|
|
194
|
+
END_CURSOR_KEY: str = "endCursor"
|
|
195
|
+
"""Page-info field naming the cursor the next page starts after."""
|
|
196
|
+
|
|
197
|
+
MAX_REVIEW_THREAD_PAGES: int = 20
|
|
198
|
+
"""How many review-thread pages one read follows before it reports an error."""
|
|
199
|
+
|
|
200
|
+
TOO_MANY_THREAD_PAGES_TEMPLATE: str = (
|
|
201
|
+
"review threads on {slug}#{number} span more than {page_count} pages"
|
|
202
|
+
)
|
|
203
|
+
"""Error raised when the review threads outrun the page limit."""
|
|
204
|
+
|
|
177
205
|
ALL_TOKEN_ENVIRONMENT_VARIABLES: tuple[str, ...] = (
|
|
178
206
|
"GITHUB_TOKEN",
|
|
179
207
|
"GH_TOKEN",
|
|
@@ -26,20 +26,26 @@ import urllib.request
|
|
|
26
26
|
from collections.abc import Mapping, Sequence
|
|
27
27
|
|
|
28
28
|
from dev_env_scripts_constants.review_closure_constants import (
|
|
29
|
-
|
|
29
|
+
AFTER_VARIABLE,
|
|
30
|
+
ALL_THREAD_CONNECTION_KEYS,
|
|
30
31
|
ALL_TOKEN_ENVIRONMENT_VARIABLES,
|
|
31
32
|
CHECK_RUN_PAGE_SIZE,
|
|
32
33
|
CHECK_RUNS_ENDPOINT_TEMPLATE,
|
|
33
34
|
CHECK_RUNS_KEY,
|
|
34
35
|
COMMENT_PAGE_SIZE,
|
|
36
|
+
END_CURSOR_KEY,
|
|
35
37
|
GET_METHOD,
|
|
36
38
|
GITHUB_API_ROOT,
|
|
37
39
|
GITHUB_GRAPHQL_ENDPOINT,
|
|
40
|
+
HAS_NEXT_PAGE_KEY,
|
|
38
41
|
MAX_COMMENT_PAGES,
|
|
42
|
+
MAX_REVIEW_THREAD_PAGES,
|
|
39
43
|
NAME_VARIABLE,
|
|
40
44
|
NO_SIGN_IN_MESSAGE,
|
|
45
|
+
NODES_KEY,
|
|
41
46
|
NUMBER_VARIABLE,
|
|
42
47
|
OWNER_VARIABLE,
|
|
48
|
+
PAGE_INFO_KEY,
|
|
43
49
|
PAGE_SIZE_VARIABLE,
|
|
44
50
|
POST_METHOD,
|
|
45
51
|
PULL_REQUEST_ENDPOINT_TEMPLATE,
|
|
@@ -51,6 +57,7 @@ from dev_env_scripts_constants.review_closure_constants import (
|
|
|
51
57
|
REVIEW_THREAD_QUERY,
|
|
52
58
|
REVIEW_THREADS_ENDPOINT_TEMPLATE,
|
|
53
59
|
SLUG_SEPARATOR,
|
|
60
|
+
TOO_MANY_THREAD_PAGES_TEMPLATE,
|
|
54
61
|
TOP_LEVEL_COMMENTS_ENDPOINT_TEMPLATE,
|
|
55
62
|
UNREADABLE_TOP_LEVEL_COMMENT_TEMPLATE,
|
|
56
63
|
VARIABLES_KEY,
|
|
@@ -372,21 +379,46 @@ def _thread_records_over_rest(
|
|
|
372
379
|
|
|
373
380
|
|
|
374
381
|
def _thread_records_over_graphql(slug: str, number: int, token: str) -> list[object]:
|
|
382
|
+
all_records: list[object] = []
|
|
383
|
+
after_cursor: object = None
|
|
384
|
+
for _ in range(MAX_REVIEW_THREAD_PAGES):
|
|
385
|
+
page_records, after_cursor = _thread_page_over_graphql(
|
|
386
|
+
slug, number, token, after_cursor
|
|
387
|
+
)
|
|
388
|
+
all_records.extend(page_records)
|
|
389
|
+
if after_cursor is None:
|
|
390
|
+
return all_records
|
|
391
|
+
raise GitHubError(
|
|
392
|
+
TOO_MANY_THREAD_PAGES_TEMPLATE.format(
|
|
393
|
+
slug=slug, number=number, page_count=MAX_REVIEW_THREAD_PAGES
|
|
394
|
+
)
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _thread_page_over_graphql(
|
|
399
|
+
slug: str, number: int, token: str, after_cursor: object
|
|
400
|
+
) -> tuple[list[object], object]:
|
|
375
401
|
document = request_json(
|
|
376
402
|
POST_METHOD,
|
|
377
403
|
GITHUB_GRAPHQL_ENDPOINT,
|
|
378
404
|
token,
|
|
379
|
-
_thread_query_payload(slug, number),
|
|
405
|
+
_thread_query_payload(slug, number, after_cursor),
|
|
380
406
|
)
|
|
381
|
-
|
|
382
|
-
functools.partial(_field_at, document),
|
|
407
|
+
connection = functools.reduce(
|
|
408
|
+
functools.partial(_field_at, document), ALL_THREAD_CONNECTION_KEYS, document
|
|
383
409
|
)
|
|
384
|
-
|
|
410
|
+
page_records = _field_at(document, connection, NODES_KEY)
|
|
411
|
+
if not isinstance(page_records, list):
|
|
385
412
|
raise GitHubError(str(document))
|
|
386
|
-
|
|
413
|
+
page_info = _field_at(document, connection, PAGE_INFO_KEY)
|
|
414
|
+
if not isinstance(page_info, Mapping) or not page_info.get(HAS_NEXT_PAGE_KEY):
|
|
415
|
+
return page_records, None
|
|
416
|
+
return page_records, page_info.get(END_CURSOR_KEY)
|
|
387
417
|
|
|
388
418
|
|
|
389
|
-
def _thread_query_payload(
|
|
419
|
+
def _thread_query_payload(
|
|
420
|
+
slug: str, number: int, after_cursor: object
|
|
421
|
+
) -> dict[str, object]:
|
|
390
422
|
owner, _, name = slug.partition(SLUG_SEPARATOR)
|
|
391
423
|
return {
|
|
392
424
|
QUERY_KEY: REVIEW_THREAD_QUERY,
|
|
@@ -395,6 +427,7 @@ def _thread_query_payload(slug: str, number: int) -> dict[str, object]:
|
|
|
395
427
|
NAME_VARIABLE: name,
|
|
396
428
|
NUMBER_VARIABLE: number,
|
|
397
429
|
PAGE_SIZE_VARIABLE: REVIEW_THREAD_PAGE_SIZE,
|
|
430
|
+
AFTER_VARIABLE: after_cursor,
|
|
398
431
|
},
|
|
399
432
|
}
|
|
400
433
|
|
|
@@ -192,6 +192,76 @@ def should_fall_back_to_the_graphql_query(monkeypatch: pytest.MonkeyPatch) -> No
|
|
|
192
192
|
assert any("graphql" in each_url for each_url in all_requested_urls)
|
|
193
193
|
|
|
194
194
|
|
|
195
|
+
def _thread_page(path: str, end_cursor: str | None) -> dict[str, object]:
|
|
196
|
+
return {
|
|
197
|
+
"data": {
|
|
198
|
+
"repository": {
|
|
199
|
+
"pullRequest": {
|
|
200
|
+
"reviewThreads": {
|
|
201
|
+
"pageInfo": {
|
|
202
|
+
"hasNextPage": end_cursor is not None,
|
|
203
|
+
"endCursor": end_cursor,
|
|
204
|
+
},
|
|
205
|
+
"nodes": [
|
|
206
|
+
{
|
|
207
|
+
"path": path,
|
|
208
|
+
"isResolved": False,
|
|
209
|
+
"isOutdated": False,
|
|
210
|
+
"comments": {"nodes": []},
|
|
211
|
+
}
|
|
212
|
+
],
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def should_read_every_graphql_page_of_review_threads(
|
|
221
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
222
|
+
) -> None:
|
|
223
|
+
all_pages_by_cursor = {
|
|
224
|
+
None: _thread_page("scripts/first.py", "cursor-1"),
|
|
225
|
+
"cursor-1": _thread_page("scripts/second.py", None),
|
|
226
|
+
}
|
|
227
|
+
all_requested_cursors: list[object] = []
|
|
228
|
+
|
|
229
|
+
@contextmanager
|
|
230
|
+
def fake_urlopen(request: object, timeout: int = 0) -> Iterator[FakeReply]:
|
|
231
|
+
if "ccr/review_threads" in request.full_url:
|
|
232
|
+
yield FakeReply(404, {})
|
|
233
|
+
return
|
|
234
|
+
cursor = json.loads(request.data)["variables"].get("after")
|
|
235
|
+
all_requested_cursors.append(cursor)
|
|
236
|
+
yield FakeReply(200, all_pages_by_cursor[cursor])
|
|
237
|
+
|
|
238
|
+
monkeypatch.setattr(reader.urllib.request, "urlopen", fake_urlopen)
|
|
239
|
+
|
|
240
|
+
all_threads = reader.read_review_threads("jl-cmd/claude-dev-env", 7, TOKEN)
|
|
241
|
+
|
|
242
|
+
assert [each.subject for each in all_threads] == [
|
|
243
|
+
"scripts/first.py",
|
|
244
|
+
"scripts/second.py",
|
|
245
|
+
]
|
|
246
|
+
assert all_requested_cursors == [None, "cursor-1"]
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def should_report_review_threads_that_outrun_the_page_limit(
|
|
250
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
251
|
+
) -> None:
|
|
252
|
+
@contextmanager
|
|
253
|
+
def fake_urlopen(request: object, timeout: int = 0) -> Iterator[FakeReply]:
|
|
254
|
+
if "ccr/review_threads" in request.full_url:
|
|
255
|
+
yield FakeReply(404, {})
|
|
256
|
+
return
|
|
257
|
+
yield FakeReply(200, _thread_page("scripts/endless.py", "cursor-next"))
|
|
258
|
+
|
|
259
|
+
monkeypatch.setattr(reader.urllib.request, "urlopen", fake_urlopen)
|
|
260
|
+
|
|
261
|
+
with pytest.raises(GitHubError):
|
|
262
|
+
reader.read_review_threads("jl-cmd/claude-dev-env", 7, TOKEN)
|
|
263
|
+
|
|
264
|
+
|
|
195
265
|
def should_report_a_graphql_answer_carrying_no_threads(
|
|
196
266
|
monkeypatch: pytest.MonkeyPatch,
|
|
197
267
|
) -> None:
|