github-rest-api 0.45.0__tar.gz → 0.46.0__tar.gz
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.
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/.github/workflows/lint.yaml +8 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/.github/workflows/test.yaml +1 -1
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/PKG-INFO +1 -1
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/pr_content.py +18 -7
- github_rest_api-0.46.0/github_rest_api/scripts/github/auto_merge_pull_request.py +109 -0
- github_rest_api-0.45.0/github_rest_api/scripts/github/add_github_repo.py → github_rest_api-0.46.0/github_rest_api/scripts/github/create_github_repo.py +5 -5
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/pyproject.toml +3 -2
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/tests/test_pr_content.py +48 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/uv.lock +1 -1
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/.devcontainer/devcontainer.json +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/.github/workflows/create_pr_to_main.yaml +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/.github/workflows/release.yaml +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/.github/workflows/remove_branch.yaml +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/.gitignore +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/GEMINI.md +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/README.md +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/__init__.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/github.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/__init__.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/cargo/__init__.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/cargo/benchmark.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/cargo/profiling.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/cargo/utils.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/container/__init__.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/container/build_container_images.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/container/config_container.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/container/update_version_containerfile.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/github/__init__.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/github/create_pull_request.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/github/release_on_github.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/github/remove_branch.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/github/workflows/create_pr_dev_to_main.yaml +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/github/workflows/create_pr_to_dev.yaml +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/github/workflows/create_pr_to_main.yaml +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/github/workflows/python/lint.yaml +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/github/workflows/python/test.yaml +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/github/workflows/remove_branch.yaml +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/utils.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/utils.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/memory/MEMORY.md +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/memory/feedback_test_runner.md +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/tests/__init__.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/tests/test_build_container_images.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/tests/test_create_pull_request.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/tests/test_github.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/tests/test_release_on_github.py +0 -0
- {github_rest_api-0.45.0 → github_rest_api-0.46.0}/tests/test_utils.py +0 -0
|
@@ -24,3 +24,11 @@ jobs:
|
|
|
24
24
|
run: uv run ty check
|
|
25
25
|
- name: Analyze Dependencies
|
|
26
26
|
run: uv run deptry .
|
|
27
|
+
- name: Check workflow templates use .yaml extension
|
|
28
|
+
run: |
|
|
29
|
+
yml_files=$(find github_rest_api/scripts/github/workflows/ -name '*.yml')
|
|
30
|
+
if [ -n "$yml_files" ]; then
|
|
31
|
+
echo "The following workflow templates must use the .yaml extension instead of .yml:"
|
|
32
|
+
echo "$yml_files"
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
@@ -83,9 +83,15 @@ def deterministic_title(compare: dict[str, Any]) -> str:
|
|
|
83
83
|
"""Derive a Conventional-Commits title from a comparison result.
|
|
84
84
|
|
|
85
85
|
The type is the most significant type present across the commits
|
|
86
|
-
(``feat`` > ``fix`` > the most frequent parsed type
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
(``feat`` > ``fix`` > the most frequent parsed type). When no commit follows
|
|
87
|
+
the Conventional-Commits grammar, the type is ``ci`` for changes confined to
|
|
88
|
+
``.github`` and ``chore`` otherwise. The scope is the common top-level
|
|
89
|
+
directory of the changed files; ``!`` is appended for breaking changes.
|
|
90
|
+
|
|
91
|
+
The description reuses a matching commit's description when available and
|
|
92
|
+
otherwise falls back to the first commit's subject line, so a genuinely
|
|
93
|
+
descriptive (if non-conventional) subject is preserved rather than replaced
|
|
94
|
+
by a generic placeholder.
|
|
89
95
|
|
|
90
96
|
:param compare: The comparison result from `Repository.compare`.
|
|
91
97
|
"""
|
|
@@ -94,17 +100,22 @@ def deterministic_title(compare: dict[str, Any]) -> str:
|
|
|
94
100
|
parsed = [parse_conventional(subject) for subject in subjects]
|
|
95
101
|
types = [item[0] for item in parsed if item]
|
|
96
102
|
scope = _common_scope(compare)
|
|
97
|
-
prefix_scope = f"({scope})" if scope else ""
|
|
98
|
-
if not subjects:
|
|
99
|
-
return f"chore{prefix_scope}: update"
|
|
100
103
|
if "feat" in types:
|
|
101
104
|
type_ = "feat"
|
|
102
105
|
elif "fix" in types:
|
|
103
106
|
type_ = "fix"
|
|
104
107
|
elif types:
|
|
105
108
|
type_ = Counter(types).most_common(1)[0][0]
|
|
109
|
+
elif scope == ".github":
|
|
110
|
+
# Changes confined to `.github` (e.g. workflow files) are conventionally
|
|
111
|
+
# `ci`; the type already conveys the location, so drop the (redundant)
|
|
112
|
+
# `.github` scope.
|
|
113
|
+
type_, scope = "ci", None
|
|
106
114
|
else:
|
|
107
115
|
type_ = "chore"
|
|
116
|
+
prefix_scope = f"({scope})" if scope else ""
|
|
117
|
+
if not subjects:
|
|
118
|
+
return f"{type_}{prefix_scope}: update"
|
|
108
119
|
breaking = any(item[2] for item in parsed if item) or any(
|
|
109
120
|
_BREAKING_CHANGE_PATTERN.search(message) for message in messages
|
|
110
121
|
)
|
|
@@ -113,7 +124,7 @@ def deterministic_title(compare: dict[str, Any]) -> str:
|
|
|
113
124
|
else:
|
|
114
125
|
description = next(
|
|
115
126
|
(item[3] for item in parsed if item and item[0] == type_),
|
|
116
|
-
|
|
127
|
+
subjects[0],
|
|
117
128
|
)
|
|
118
129
|
return f"{type_}{prefix_scope}{'!' if breaking else ''}: {description}"
|
|
119
130
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Auto-merge eligible open pull requests in the repository.
|
|
2
|
+
|
|
3
|
+
Every open PR is evaluated by ``Repository.auto_merge_pull_requests`` and merged
|
|
4
|
+
only when it passes the eligibility gate (author allowlist, title type, head
|
|
5
|
+
commit age, mergeable state and an approving review or marker comment).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from argparse import ArgumentParser, Namespace
|
|
12
|
+
|
|
13
|
+
from github_rest_api import MergeMethod, Repository
|
|
14
|
+
from github_rest_api.github import (
|
|
15
|
+
DEFAULT_AUTO_MERGE_MARKER,
|
|
16
|
+
DEFAULT_AUTO_MERGE_TYPES,
|
|
17
|
+
DEFAULT_MIN_AGE_MINUTES,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def parse_args(args=None, namespace=None) -> Namespace:
|
|
22
|
+
"""Parse command-line arguments.
|
|
23
|
+
:param args: The arguments to parse.
|
|
24
|
+
If None, the arguments from command-line are parsed.
|
|
25
|
+
:param namespace: An inital Namespace object.
|
|
26
|
+
:return: A namespace object containing parsed options.
|
|
27
|
+
"""
|
|
28
|
+
parser = ArgumentParser(description="Auto-merge eligible open pull requests.")
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--token",
|
|
31
|
+
dest="token",
|
|
32
|
+
required=True,
|
|
33
|
+
help="The personal access token for authentication.",
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--authors",
|
|
37
|
+
dest="authors",
|
|
38
|
+
nargs="*",
|
|
39
|
+
default=[],
|
|
40
|
+
help="Logins whose PRs are eligible for auto-merge. An empty allowlist "
|
|
41
|
+
"makes nothing eligible (fail-safe).",
|
|
42
|
+
)
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"--approvers",
|
|
45
|
+
dest="approvers",
|
|
46
|
+
nargs="*",
|
|
47
|
+
default=[],
|
|
48
|
+
help="Logins whose reviews or marker comments grant approval.",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--allowed-types",
|
|
52
|
+
dest="allowed_types",
|
|
53
|
+
nargs="*",
|
|
54
|
+
default=list(DEFAULT_AUTO_MERGE_TYPES),
|
|
55
|
+
help="Conventional-Commits title types eligible for auto-merge.",
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"--min-age-minutes",
|
|
59
|
+
dest="min_age_minutes",
|
|
60
|
+
type=int,
|
|
61
|
+
default=DEFAULT_MIN_AGE_MINUTES,
|
|
62
|
+
help="The minimum head-commit age in minutes; set to 0 to disable the guard.",
|
|
63
|
+
)
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"--marker",
|
|
66
|
+
dest="marker",
|
|
67
|
+
default=DEFAULT_AUTO_MERGE_MARKER,
|
|
68
|
+
help="The marker substring an approver may comment to approve.",
|
|
69
|
+
)
|
|
70
|
+
parser.add_argument(
|
|
71
|
+
"--merge-method",
|
|
72
|
+
dest="merge_method",
|
|
73
|
+
choices=[m.value for m in MergeMethod],
|
|
74
|
+
default=MergeMethod.MERGE.value,
|
|
75
|
+
help="The merge method to use.",
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument(
|
|
78
|
+
"--dry-run",
|
|
79
|
+
dest="dry_run",
|
|
80
|
+
action="store_true",
|
|
81
|
+
help="Log the PRs that would be merged without merging them.",
|
|
82
|
+
)
|
|
83
|
+
return parser.parse_args(args=args, namespace=namespace)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def main() -> int:
|
|
87
|
+
"""Main entrance of the script,
|
|
88
|
+
which auto-merges every eligible open pull request in the repository.
|
|
89
|
+
"""
|
|
90
|
+
args = parse_args()
|
|
91
|
+
# auto_merge_pull_requests reports its outcomes (including the dry-run
|
|
92
|
+
# listing) via logging.info, which the root logger suppresses by default;
|
|
93
|
+
# configure logging so those messages are actually emitted.
|
|
94
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
95
|
+
repo = Repository(args.token, os.environ["GITHUB_REPOSITORY"])
|
|
96
|
+
repo.auto_merge_pull_requests(
|
|
97
|
+
authors=args.authors,
|
|
98
|
+
approvers=args.approvers,
|
|
99
|
+
allowed_types=args.allowed_types,
|
|
100
|
+
min_age_minutes=args.min_age_minutes,
|
|
101
|
+
marker=args.marker,
|
|
102
|
+
merge_method=args.merge_method,
|
|
103
|
+
dry_run=args.dry_run,
|
|
104
|
+
)
|
|
105
|
+
return 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
sys.exit(main())
|
|
@@ -80,7 +80,7 @@ def _init_local_repo(
|
|
|
80
80
|
_add_workflow(path, language)
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
def
|
|
83
|
+
def create_github_repo(
|
|
84
84
|
repo: str,
|
|
85
85
|
private: bool,
|
|
86
86
|
language: str,
|
|
@@ -117,7 +117,7 @@ def _add_workflow(path: Path, language: str, workflow_dir: Path | None = None) -
|
|
|
117
117
|
workflow_dir = Path(__file__).parent / "workflows"
|
|
118
118
|
dir_dest = path / ".github" / "workflows"
|
|
119
119
|
dir_dest.mkdir(parents=True, exist_ok=True)
|
|
120
|
-
for yaml in workflow_dir.glob("*.
|
|
120
|
+
for yaml in workflow_dir.glob("*.yaml"):
|
|
121
121
|
if not (dir_dest / yaml.name).exists():
|
|
122
122
|
shutil.copy2(yaml, dir_dest)
|
|
123
123
|
if not language:
|
|
@@ -125,13 +125,13 @@ def _add_workflow(path: Path, language: str, workflow_dir: Path | None = None) -
|
|
|
125
125
|
lang_dir = workflow_dir / language
|
|
126
126
|
if not lang_dir.exists():
|
|
127
127
|
return
|
|
128
|
-
for yaml in lang_dir.glob("*.
|
|
128
|
+
for yaml in lang_dir.glob("*.yaml"):
|
|
129
129
|
if not (dir_dest / yaml.name).exists():
|
|
130
130
|
shutil.copy2(yaml, dir_dest)
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
def parse_args(args=None, namespace=None):
|
|
134
|
-
parser = argparse.ArgumentParser(description="
|
|
134
|
+
parser = argparse.ArgumentParser(description="Create a GitHub repository.")
|
|
135
135
|
parser.add_argument(
|
|
136
136
|
"repo",
|
|
137
137
|
help="The GitHub repo (in the format of owner/repo) to be created.",
|
|
@@ -203,7 +203,7 @@ def parse_args(args=None, namespace=None):
|
|
|
203
203
|
def main() -> int:
|
|
204
204
|
args = parse_args()
|
|
205
205
|
try:
|
|
206
|
-
|
|
206
|
+
create_github_repo(
|
|
207
207
|
repo=args.repo,
|
|
208
208
|
private=args.private,
|
|
209
209
|
language=args.language,
|
|
@@ -4,7 +4,7 @@ requires = [ "hatchling" ]
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "github-rest-api"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.46.0"
|
|
8
8
|
description = "Simple wrapper of GitHub REST APIs."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [ { name = "Ben Du", email = "longendu@yahoo.com" } ]
|
|
@@ -27,9 +27,10 @@ dependencies = [
|
|
|
27
27
|
optional-dependencies.ai = [
|
|
28
28
|
"litellm>=1.50",
|
|
29
29
|
]
|
|
30
|
-
scripts.
|
|
30
|
+
scripts.auto_merge_pull_request = "github_rest_api.scripts.github.auto_merge_pull_request:main"
|
|
31
31
|
scripts.build_container_images = "github_rest_api.scripts.container.build_container_images:main"
|
|
32
32
|
scripts.config_container = "github_rest_api.scripts.container.config_container:main"
|
|
33
|
+
scripts.create_github_repo = "github_rest_api.scripts.github.create_github_repo:main"
|
|
33
34
|
scripts.create_pull_request = "github_rest_api.scripts.github.create_pull_request:main"
|
|
34
35
|
scripts.release_on_github = "github_rest_api.scripts.github.release_on_github:main"
|
|
35
36
|
scripts.remove_branch = "github_rest_api.scripts.github.remove_branch:main"
|
|
@@ -136,6 +136,54 @@ def test_deterministic_title_no_scope_for_root_files():
|
|
|
136
136
|
assert deterministic_title(compare) == "chore: bump"
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
def test_deterministic_title_non_conventional_multi_uses_first_subject():
|
|
140
|
+
compare = {
|
|
141
|
+
"commits": [
|
|
142
|
+
_commit("1", "did some work"),
|
|
143
|
+
_commit("2", "did more work"),
|
|
144
|
+
],
|
|
145
|
+
"files": [_file("pkg/a.py"), _file("pkg/b.py")],
|
|
146
|
+
}
|
|
147
|
+
# No conventional commits: fall back to the first subject rather than the
|
|
148
|
+
# generic "update N commits" placeholder.
|
|
149
|
+
assert deterministic_title(compare) == "chore(pkg): did some work"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_deterministic_title_github_scope_maps_to_ci():
|
|
153
|
+
compare = {
|
|
154
|
+
"commits": [
|
|
155
|
+
_commit("1", "Claude PR Assistant workflow"),
|
|
156
|
+
_commit("2", "Claude Code Review workflow"),
|
|
157
|
+
],
|
|
158
|
+
"files": [
|
|
159
|
+
_file(".github/workflows/claude.yml", "added"),
|
|
160
|
+
_file(".github/workflows/claude-review.yml", "added"),
|
|
161
|
+
],
|
|
162
|
+
}
|
|
163
|
+
# `.github`-only changes become `ci` (with the redundant scope dropped), and
|
|
164
|
+
# the first subject supplies the description.
|
|
165
|
+
assert deterministic_title(compare) == "ci: Claude PR Assistant workflow"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def test_deterministic_title_github_scope_respects_conventional_type():
|
|
169
|
+
compare = {
|
|
170
|
+
"commits": [_commit("1", "docs: update issue template")],
|
|
171
|
+
"files": [_file(".github/ISSUE_TEMPLATE/bug.md")],
|
|
172
|
+
}
|
|
173
|
+
# An explicit conventional type wins over the `.github` -> `ci` fallback.
|
|
174
|
+
assert deterministic_title(compare) == "docs(.github): update issue template"
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_deterministic_title_github_ci_not_applied_when_mixed_with_other_dir():
|
|
178
|
+
compare = {
|
|
179
|
+
"commits": [_commit("1", "tweak things")],
|
|
180
|
+
"files": [_file(".github/workflows/ci.yml"), _file("pkg/a.py")],
|
|
181
|
+
}
|
|
182
|
+
# Changes span `.github` and another top-level dir, so no common scope and no
|
|
183
|
+
# `ci` upgrade -- the generic `chore` fallback applies.
|
|
184
|
+
assert deterministic_title(compare) == "chore: tweak things"
|
|
185
|
+
|
|
186
|
+
|
|
139
187
|
def test_deterministic_body_sections():
|
|
140
188
|
body = deterministic_body(COMPARE)
|
|
141
189
|
assert "## Summary" in body
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/cargo/benchmark.py
RENAMED
|
File without changes
|
{github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/cargo/profiling.py
RENAMED
|
File without changes
|
|
File without changes
|
{github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/container/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/github/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{github_rest_api-0.45.0 → github_rest_api-0.46.0}/github_rest_api/scripts/github/remove_branch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|