repo-review 0.12.1__tar.gz → 0.12.3__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.
Files changed (68) hide show
  1. repo_review-0.12.3/.github/release.yml +5 -0
  2. {repo_review-0.12.1 → repo_review-0.12.3}/.github/workflows/cd.yml +2 -2
  3. {repo_review-0.12.1 → repo_review-0.12.3}/.github/workflows/ci.yml +14 -11
  4. {repo_review-0.12.1 → repo_review-0.12.3}/.pre-commit-config.yaml +8 -8
  5. {repo_review-0.12.1 → repo_review-0.12.3}/PKG-INFO +3 -2
  6. {repo_review-0.12.1 → repo_review-0.12.3}/README.md +1 -1
  7. {repo_review-0.12.1 → repo_review-0.12.3}/action.yml +2 -2
  8. {repo_review-0.12.1 → repo_review-0.12.3}/docs/index.html +5 -5
  9. {repo_review-0.12.1 → repo_review-0.12.3}/docs/webapp.js +185 -22
  10. {repo_review-0.12.1 → repo_review-0.12.3}/docs/webapp.md +1 -1
  11. {repo_review-0.12.1 → repo_review-0.12.3}/pyproject.toml +4 -1
  12. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/__main__.py +33 -12
  13. repo_review-0.12.3/src/repo_review/_version.py +34 -0
  14. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/ghpath.py +2 -2
  15. {repo_review-0.12.1 → repo_review-0.12.3}/tests/test_package.py +2 -1
  16. repo_review-0.12.1/.github/release.yml +0 -5
  17. repo_review-0.12.1/src/repo_review/_version.py +0 -16
  18. {repo_review-0.12.1 → repo_review-0.12.3}/.devcontainer/devcontainer.json +0 -0
  19. {repo_review-0.12.1 → repo_review-0.12.3}/.git_archival.txt +0 -0
  20. {repo_review-0.12.1 → repo_review-0.12.3}/.gitattributes +0 -0
  21. {repo_review-0.12.1 → repo_review-0.12.3}/.github/CONTRIBUTING.md +0 -0
  22. {repo_review-0.12.1 → repo_review-0.12.3}/.github/ISSUE_TEMPLATE/new-issue.md +0 -0
  23. {repo_review-0.12.1 → repo_review-0.12.3}/.github/dependabot.yml +0 -0
  24. {repo_review-0.12.1 → repo_review-0.12.3}/.gitignore +0 -0
  25. {repo_review-0.12.1 → repo_review-0.12.3}/.pre-commit-hooks.yaml +0 -0
  26. {repo_review-0.12.1 → repo_review-0.12.3}/.readthedocs.yaml +0 -0
  27. {repo_review-0.12.1 → repo_review-0.12.3}/LICENSE +0 -0
  28. {repo_review-0.12.1 → repo_review-0.12.3}/docs/.nojekyll +0 -0
  29. {repo_review-0.12.1 → repo_review-0.12.3}/docs/api/repo_review.resources.rst +1 -1
  30. {repo_review-0.12.1 → repo_review-0.12.3}/docs/api/repo_review.rst +9 -9
  31. {repo_review-0.12.1 → repo_review-0.12.3}/docs/changelog.md +0 -0
  32. {repo_review-0.12.1 → repo_review-0.12.3}/docs/checks.md +0 -0
  33. {repo_review-0.12.1 → repo_review-0.12.3}/docs/cli.md +0 -0
  34. {repo_review-0.12.1 → repo_review-0.12.3}/docs/conf.py +0 -0
  35. {repo_review-0.12.1 → repo_review-0.12.3}/docs/families.md +0 -0
  36. {repo_review-0.12.1 → repo_review-0.12.3}/docs/fixtures.md +0 -0
  37. {repo_review-0.12.1 → repo_review-0.12.3}/docs/index.md +0 -0
  38. {repo_review-0.12.1 → repo_review-0.12.3}/docs/intro.md +0 -0
  39. {repo_review-0.12.1 → repo_review-0.12.3}/docs/plugins.md +0 -0
  40. {repo_review-0.12.1 → repo_review-0.12.3}/docs/programmatic.md +0 -0
  41. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/__init__.py +0 -0
  42. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/_compat/__init__.py +0 -0
  43. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/_compat/importlib/__init__.py +0 -0
  44. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/_compat/importlib/resources/__init__.py +0 -0
  45. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/_compat/importlib/resources/abc.py +0 -0
  46. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/_compat/tomllib.py +0 -0
  47. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/_compat/typing.py +0 -0
  48. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/_version.pyi +0 -0
  49. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/checks.py +0 -0
  50. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/families.py +0 -0
  51. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/fixtures.py +0 -0
  52. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/html.py +0 -0
  53. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/processor.py +0 -0
  54. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/py.typed +0 -0
  55. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/resources/__init__.py +0 -0
  56. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/resources/repo-review.schema.json +0 -0
  57. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/schema.py +0 -0
  58. {repo_review-0.12.1 → repo_review-0.12.3}/src/repo_review/testing.py +0 -0
  59. {repo_review-0.12.1 → repo_review-0.12.3}/tests/conftest.py +0 -0
  60. {repo_review-0.12.1 → repo_review-0.12.3}/tests/test_checks.py +0 -0
  61. {repo_review-0.12.1 → repo_review-0.12.3}/tests/test_cmd.py +0 -0
  62. {repo_review-0.12.1 → repo_review-0.12.3}/tests/test_depends.py +0 -0
  63. {repo_review-0.12.1 → repo_review-0.12.3}/tests/test_families.py +0 -0
  64. {repo_review-0.12.1 → repo_review-0.12.3}/tests/test_fixtures.py +0 -0
  65. {repo_review-0.12.1 → repo_review-0.12.3}/tests/test_multi.py +0 -0
  66. {repo_review-0.12.1 → repo_review-0.12.3}/tests/test_self.py +0 -0
  67. {repo_review-0.12.1 → repo_review-0.12.3}/tests/test_utilities/pyproject.py +0 -0
  68. {repo_review-0.12.1 → repo_review-0.12.3}/tests/test_utilities/pyproject.toml +0 -0
@@ -0,0 +1,5 @@
1
+ changelog:
2
+ exclude:
3
+ authors:
4
+ - dependabot[bot]
5
+ - pre-commit-ci[bot]
@@ -14,7 +14,7 @@ jobs:
14
14
  runs-on: ubuntu-latest
15
15
 
16
16
  steps:
17
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v5
18
18
  with:
19
19
  fetch-depth: 0
20
20
  persist-credentials: false
@@ -30,7 +30,7 @@ jobs:
30
30
  runs-on: ubuntu-latest
31
31
  if: github.event_name == 'release' && github.event.action == 'published'
32
32
  steps:
33
- - uses: actions/download-artifact@v4
33
+ - uses: actions/download-artifact@v5
34
34
  with:
35
35
  name: Packages
36
36
  path: dist
@@ -24,10 +24,10 @@ jobs:
24
24
  name: Format
25
25
  runs-on: ubuntu-latest
26
26
  steps:
27
- - uses: actions/checkout@v4
27
+ - uses: actions/checkout@v5
28
28
  with:
29
29
  persist-credentials: false
30
- - uses: actions/setup-python@v5
30
+ - uses: actions/setup-python@v6
31
31
  with:
32
32
  python-version: "3.x"
33
33
  - uses: pre-commit/action@v3.0.1
@@ -43,22 +43,25 @@ jobs:
43
43
  runs-on: [ubuntu-latest, macos-latest, windows-latest]
44
44
 
45
45
  steps:
46
- - uses: actions/checkout@v4
46
+ - uses: actions/checkout@v5
47
47
  with:
48
48
  fetch-depth: 0
49
49
  persist-credentials: false
50
50
 
51
- - uses: actions/setup-python@v5
51
+ # Last one is activated
52
+ # yaml circular import issue on 3.14t on ubuntu
53
+ - uses: actions/setup-python@v6
52
54
  with:
53
55
  python-version: |
54
56
  3.10
55
57
  3.11
56
58
  3.12
59
+ 3.14
57
60
  3.13
58
61
  allow-prereleases: true
59
62
 
60
63
  - name: Setup uv
61
- uses: yezz123/setup-uv@v4
64
+ uses: astral-sh/setup-uv@v6
62
65
 
63
66
  - name: Install hatch
64
67
  run: uv pip install --system hatch
@@ -71,7 +74,7 @@ jobs:
71
74
  runs-on: ubuntu-latest
72
75
 
73
76
  steps:
74
- - uses: actions/checkout@v4
77
+ - uses: actions/checkout@v5
75
78
  with:
76
79
  fetch-depth: 0
77
80
  persist-credentials: false
@@ -82,15 +85,15 @@ jobs:
82
85
  name: Docs
83
86
  runs-on: ubuntu-latest
84
87
  steps:
85
- - uses: actions/checkout@v4
88
+ - uses: actions/checkout@v5
86
89
  with:
87
90
  fetch-depth: 0
88
91
  persist-credentials: false
89
92
 
90
93
  - name: Setup uv
91
- uses: yezz123/setup-uv@v4
94
+ uses: astral-sh/setup-uv@v6
92
95
 
93
- - uses: actions/setup-python@v5
96
+ - uses: actions/setup-python@v6
94
97
  with:
95
98
  python-version: "3.12"
96
99
 
@@ -112,7 +115,7 @@ jobs:
112
115
  name: Action
113
116
  runs-on: ubuntu-latest
114
117
  steps:
115
- - uses: actions/checkout@v4
118
+ - uses: actions/checkout@v5
116
119
  with:
117
120
  fetch-depth: 0
118
121
  persist-credentials: false
@@ -120,4 +123,4 @@ jobs:
120
123
  - name: Run repo-review action
121
124
  uses: ./
122
125
  with:
123
- plugins: sp-repo-review==2025.01.22
126
+ plugins: sp-repo-review==2025.05.02
@@ -11,20 +11,20 @@ repos:
11
11
  additional_dependencies: [black==24.*]
12
12
 
13
13
  - repo: https://github.com/astral-sh/ruff-pre-commit
14
- rev: "v0.9.4"
14
+ rev: "v0.12.11"
15
15
  hooks:
16
- - id: ruff
16
+ - id: ruff-check
17
17
  args: ["--fix", "--show-fixes"]
18
18
  - id: ruff-format
19
19
 
20
20
  - repo: https://github.com/rbubley/mirrors-prettier
21
- rev: "v3.4.2"
21
+ rev: "v3.6.2"
22
22
  hooks:
23
23
  - id: prettier
24
24
  types_or: [yaml, markdown, html, css, scss, javascript, json]
25
25
 
26
26
  - repo: https://github.com/pre-commit/pre-commit-hooks
27
- rev: v5.0.0
27
+ rev: v6.0.0
28
28
  hooks:
29
29
  - id: check-added-large-files
30
30
  - id: check-case-conflict
@@ -45,7 +45,7 @@ repos:
45
45
  - id: rst-inline-touching-normal
46
46
 
47
47
  - repo: https://github.com/pre-commit/mirrors-mypy
48
- rev: v1.14.1
48
+ rev: v1.17.1
49
49
  hooks:
50
50
  - id: mypy
51
51
  files: (src|web|tests)
@@ -66,7 +66,7 @@ repos:
66
66
  args: ["-Lhist,absense", "-w"]
67
67
 
68
68
  - repo: https://github.com/shellcheck-py/shellcheck-py
69
- rev: v0.10.0.1
69
+ rev: v0.11.0.1
70
70
  hooks:
71
71
  - id: shellcheck
72
72
 
@@ -79,12 +79,12 @@ repos:
79
79
  exclude: .pre-commit-config.yaml
80
80
 
81
81
  - repo: https://github.com/henryiii/validate-pyproject-schema-store
82
- rev: 2025.02.03
82
+ rev: 2025.08.29
83
83
  hooks:
84
84
  - id: validate-pyproject
85
85
 
86
86
  - repo: https://github.com/python-jsonschema/check-jsonschema
87
- rev: 0.31.1
87
+ rev: 0.33.3
88
88
  hooks:
89
89
  - id: check-dependabot
90
90
  - id: check-github-workflows
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: repo_review
3
- Version: 0.12.1
3
+ Version: 0.12.3
4
4
  Summary: Framework that can run checks on repos
5
5
  Project-URL: Changelog, https://github.com/scientific-python/repo-review/releases
6
6
  Project-URL: Demo, https://scientific-python.github.io/repo-review
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3.10
22
22
  Classifier: Programming Language :: Python :: 3.11
23
23
  Classifier: Programming Language :: Python :: 3.12
24
24
  Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
25
26
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
27
  Classifier: Topic :: Software Development :: Quality Assurance
27
28
  Classifier: Typing :: Typed
@@ -128,7 +129,7 @@ repos have some [pre-commit][] check.
128
129
  ## Development of repo-review and plugins
129
130
 
130
131
  This project is intended to be fun and easy to develop and design checks for -
131
- it requires and uses Python 3.10, and uses a lot of the new features in 3.9 and
132
+ it requires and uses Python 3.10+, and uses a lot of the new features in 3.9 and
132
133
  3.10. It's maybe not entirely conventional, but it enables very simple plugin
133
134
  development. It works locally, remotely, and in WebAssembly (using
134
135
  [Pyodide][]). [See the docs][writing-a-plugin].
@@ -90,7 +90,7 @@ repos have some [pre-commit][] check.
90
90
  ## Development of repo-review and plugins
91
91
 
92
92
  This project is intended to be fun and easy to develop and design checks for -
93
- it requires and uses Python 3.10, and uses a lot of the new features in 3.9 and
93
+ it requires and uses Python 3.10+, and uses a lot of the new features in 3.9 and
94
94
  3.10. It's maybe not entirely conventional, but it enables very simple plugin
95
95
  development. It works locally, remotely, and in WebAssembly (using
96
96
  [Pyodide][]). [See the docs][writing-a-plugin].
@@ -23,10 +23,10 @@ inputs:
23
23
  runs:
24
24
  using: composite
25
25
  steps:
26
- - uses: actions/setup-python@v5
26
+ - uses: actions/setup-python@v6
27
27
  id: python
28
28
  with:
29
- python-version: "3.12"
29
+ python-version: "3.13"
30
30
  update-environment: false
31
31
 
32
32
  - name: Install repo-review and plugins
@@ -6,7 +6,7 @@
6
6
  content="initial-scale=1, width=device-width"
7
7
  />
8
8
  <script
9
- src="https://cdn.jsdelivr.net/pyodide/v0.27.1/full/pyodide.js"
9
+ src="https://cdn.jsdelivr.net/pyodide/v0.28.1/full/pyodide.js"
10
10
  crossorigin
11
11
  ></script>
12
12
  <!-- Production -->
@@ -64,10 +64,10 @@
64
64
  <App
65
65
  header={true}
66
66
  deps={[
67
- "repo-review~=0.12.0",
68
- "sp-repo-review==2025.01.22",
69
- "validate-pyproject-schema-store==2025.02.03",
70
- "validate-pyproject[all]~=0.22.0",
67
+ "repo-review~=0.12.2",
68
+ "sp-repo-review==2025.05.02",
69
+ "validate-pyproject[all]~=0.24.0",
70
+ "validate-pyproject-schema-store==2025.05.12",
71
71
  ]}
72
72
  />,
73
73
  );
@@ -1,5 +1,5 @@
1
1
  const DEFAULT_MSG =
2
- "Enter a GitHub repo and branch to review. Runs Python entirely in your browser using WebAssembly. Built with React, MaterialUI, and Pyodide.";
2
+ "Enter a GitHub repo and branch/tag to review. Runs Python entirely in your browser using WebAssembly. Built with React, MaterialUI, and Pyodide.";
3
3
 
4
4
  const urlParams = new URLSearchParams(window.location.search);
5
5
  const baseurl = window.location.pathname;
@@ -154,6 +154,39 @@ function Results(props) {
154
154
  );
155
155
  }
156
156
 
157
+ async function fetchRepoRefs(repo) {
158
+ if (!repo) return { branches: [], tags: [] };
159
+ try {
160
+ // Fetch both branches and tags from GitHub API
161
+ const [branchesResponse, tagsResponse] = await Promise.all([
162
+ fetch(`https://api.github.com/repos/${repo}/branches`),
163
+ fetch(`https://api.github.com/repos/${repo}/tags`),
164
+ ]);
165
+
166
+ if (!branchesResponse.ok || !tagsResponse.ok) {
167
+ console.error("Error fetching repo data");
168
+ return { branches: [], tags: [] };
169
+ }
170
+
171
+ const branches = await branchesResponse.json();
172
+ const tags = await tagsResponse.json();
173
+
174
+ return {
175
+ branches: branches.map((branch) => ({
176
+ name: branch.name,
177
+ type: "branch",
178
+ })),
179
+ tags: tags.map((tag) => ({
180
+ name: tag.name,
181
+ type: "tag",
182
+ })),
183
+ };
184
+ } catch (error) {
185
+ console.error("Error fetching repo references:", error);
186
+ return { branches: [], tags: [] };
187
+ }
188
+ }
189
+
157
190
  async function prepare_pyodide(deps) {
158
191
  const deps_str = deps.map((i) => `"${i}"`).join(", ");
159
192
  const pyodide = await loadPyodide();
@@ -196,28 +229,58 @@ class App extends React.Component {
196
229
  this.state = {
197
230
  results: [],
198
231
  repo: urlParams.get("repo") || "",
199
- branch: urlParams.get("branch") || "",
232
+ ref: urlParams.get("ref") || "",
233
+ refType: urlParams.get("refType") || "branch",
234
+ refs: { branches: [], tags: [] },
200
235
  msg: `<p>${DEFAULT_MSG}</p><h4>Packages:</h4> ${deps_str}`,
201
236
  progress: false,
237
+ loadingRefs: false,
202
238
  err_msg: "",
203
239
  skip_reason: "",
204
240
  url: "",
205
241
  };
206
242
  this.pyodide_promise = prepare_pyodide(props.deps);
243
+ this.refInputDebounce = null;
244
+ }
245
+
246
+ async fetchRepoReferences(repo) {
247
+ if (!repo) return;
248
+
249
+ this.setState({ loadingRefs: true });
250
+ const refs = await fetchRepoRefs(repo);
251
+ this.setState({
252
+ refs: refs,
253
+ loadingRefs: false,
254
+ });
255
+ }
256
+
257
+ handleRepoChange(repo) {
258
+ this.setState({ repo });
259
+
260
+ // debounce the API call to avoid too many requests
261
+ clearTimeout(this.refInputDebounce);
262
+ this.refInputDebounce = setTimeout(() => {
263
+ this.fetchRepoReferences(repo);
264
+ }, 500);
265
+ }
266
+
267
+ handleRefChange(ref, refType) {
268
+ this.setState({ ref, refType });
207
269
  }
208
270
 
209
271
  handleCompute() {
210
- if (!this.state.repo || !this.state.branch) {
272
+ if (!this.state.repo || !this.state.ref) {
211
273
  this.setState({ results: [], msg: DEFAULT_MSG });
212
274
  window.history.replaceState(null, "", baseurl);
213
275
  alert(
214
- `Please enter a repo (${this.state.repo}) and branch (${this.state.branch})`,
276
+ `Please enter a repo (${this.state.repo}) and branch/tag (${this.state.ref})`,
215
277
  );
216
278
  return;
217
279
  }
218
280
  const local_params = new URLSearchParams({
219
281
  repo: this.state.repo,
220
- branch: this.state.branch,
282
+ ref: this.state.ref,
283
+ refType: this.state.refType,
221
284
  });
222
285
  window.history.replaceState(null, "", `${baseurl}?${local_params}`);
223
286
  this.setState({
@@ -234,13 +297,13 @@ class App extends React.Component {
234
297
  from repo_review.ghpath import GHPath
235
298
  from dataclasses import replace
236
299
 
237
- package = GHPath(repo="${state.repo}", branch="${state.branch}")
300
+ package = GHPath(repo="${state.repo}", branch="${state.ref}")
238
301
  families, checks = process(package)
239
302
 
240
303
  for v in families.values():
241
304
  if v.get("description"):
242
305
  v["description"] = md_as_html(v["description"])
243
- checks = [replace(v, err_msg=md_as_html(v.err_msg), skip_reason=md_as_html(v.skip_reason)) for v in checks]
306
+ checks = [res.md_as_html() for res in checks]
244
307
 
245
308
  (families, checks)
246
309
  `);
@@ -249,7 +312,7 @@ class App extends React.Component {
249
312
  this.setState({
250
313
  msg: DEFAULT_MSG,
251
314
  progress: false,
252
- err_msg: "Invalid repository or branch. Please try again.",
315
+ err_msg: "Invalid repository or branch/tag. Please try again.",
253
316
  });
254
317
  return;
255
318
  }
@@ -288,7 +351,7 @@ class App extends React.Component {
288
351
  this.setState({
289
352
  results: results,
290
353
  families: families,
291
- msg: `Results for ${state.repo}@${state.branch}`,
354
+ msg: `Results for ${state.repo}@${state.ref} (${state.refType})`,
292
355
  progress: false,
293
356
  err_msg: "",
294
357
  url: "",
@@ -300,13 +363,78 @@ class App extends React.Component {
300
363
  }
301
364
 
302
365
  componentDidMount() {
303
- if (urlParams.get("repo") && urlParams.get("branch")) {
304
- this.handleCompute();
366
+ if (urlParams.get("repo")) {
367
+ this.fetchRepoReferences(urlParams.get("repo"));
368
+
369
+ if (urlParams.get("ref")) {
370
+ this.handleCompute();
371
+ }
305
372
  }
306
373
  }
307
374
 
308
375
  render() {
309
- const common_branches = ["main", "master", "develop", "stable"];
376
+ const priorityBranches = ["HEAD", "main", "master", "develop", "stable"];
377
+ const branchMap = new Map(
378
+ this.state.refs.branches.map((branch) => [branch.name, branch]),
379
+ );
380
+
381
+ let availableOptions = [];
382
+
383
+ // If no repo is entered or API hasn't returned any branches/tags yet,
384
+ // show all five priority branches.
385
+ if (
386
+ this.state.repo === "" ||
387
+ (this.state.refs.branches.length === 0 &&
388
+ this.state.refs.tags.length === 0)
389
+ ) {
390
+ availableOptions = [
391
+ { label: "HEAD (default branch)", value: "HEAD", type: "branch" },
392
+ { label: "main (branch)", value: "main", type: "branch" },
393
+ { label: "master (branch)", value: "master", type: "branch" },
394
+ { label: "develop (branch)", value: "develop", type: "branch" },
395
+ { label: "stable (branch)", value: "stable", type: "branch" },
396
+ ];
397
+ } else {
398
+ const prioritizedBranches = [
399
+ { label: "HEAD (default branch)", value: "HEAD", type: "branch" },
400
+ ];
401
+
402
+ priorityBranches.slice(1).forEach((branchName) => {
403
+ if (branchMap.has(branchName)) {
404
+ prioritizedBranches.push({
405
+ label: `${branchName} (branch)`,
406
+ value: branchName,
407
+ type: "branch",
408
+ });
409
+ // Remove from map so it doesn't get added twice.
410
+ branchMap.delete(branchName);
411
+ }
412
+ });
413
+
414
+ const otherBranches = [];
415
+ branchMap.forEach((branch) => {
416
+ otherBranches.push({
417
+ label: `${branch.name} (branch)`,
418
+ value: branch.name,
419
+ type: "branch",
420
+ });
421
+ });
422
+ otherBranches.sort((a, b) => a.value.localeCompare(b.value));
423
+
424
+ const tagOptions = this.state.refs.tags.map((tag) => ({
425
+ label: `${tag.name} (tag)`,
426
+ value: tag.name,
427
+ type: "tag",
428
+ }));
429
+ tagOptions.sort((a, b) => a.value.localeCompare(b.value));
430
+
431
+ availableOptions = [
432
+ ...prioritizedBranches,
433
+ ...otherBranches,
434
+ ...tagOptions,
435
+ ];
436
+ }
437
+
310
438
  return (
311
439
  <MyThemeProvider>
312
440
  <MaterialUI.CssBaseline />
@@ -326,29 +454,64 @@ class App extends React.Component {
326
454
  autoFocus={true}
327
455
  onKeyDown={(e) => {
328
456
  if (e.keyCode === 13)
329
- document.getElementById("branch-select").focus();
457
+ document.getElementById("ref-select").focus();
330
458
  }}
331
- onInput={(e) => this.setState({ repo: e.target.value })}
459
+ onInput={(e) => this.handleRepoChange(e.target.value)}
332
460
  defaultValue={urlParams.get("repo")}
333
461
  sx={{ flexGrow: 3 }}
334
462
  />
335
463
  <MaterialUI.Autocomplete
336
464
  disablePortal
337
- id="branch-select"
338
- options={common_branches}
465
+ id="ref-select"
466
+ options={availableOptions}
467
+ loading={this.state.loadingRefs}
339
468
  freeSolo={true}
340
469
  onKeyDown={(e) => {
341
470
  if (e.keyCode === 13) this.handleCompute();
342
471
  }}
343
- onInputChange={(e, value) => this.setState({ branch: value })}
344
- defaultValue={urlParams.get("branch")}
472
+ getOptionLabel={(option) =>
473
+ typeof option === "string" ? option : option.label
474
+ }
475
+ renderOption={(props, option) => (
476
+ <li {...props}>{option.label}</li>
477
+ )}
478
+ onInputChange={(e, value) => {
479
+ // If the user enters free text, treat it as a branch
480
+ if (typeof value === "string") {
481
+ this.handleRefChange(value, "branch");
482
+ }
483
+ }}
484
+ onChange={(e, option) => {
485
+ if (option) {
486
+ if (typeof option === "object") {
487
+ this.handleRefChange(option.value, option.type);
488
+ } else {
489
+ this.handleRefChange(option, "branch");
490
+ }
491
+ }
492
+ }}
493
+ defaultValue={urlParams.get("ref")}
345
494
  renderInput={(params) => (
346
495
  <MaterialUI.TextField
347
496
  {...params}
348
- label="Branch"
497
+ label="Branch/Tag"
349
498
  variant="outlined"
350
- helperText="e.g. main"
351
- sx={{ flexGrow: 2, minWidth: 130 }}
499
+ helperText="e.g. HEAD, main, or v1.0.0"
500
+ sx={{ flexGrow: 2, minWidth: 200 }}
501
+ InputProps={{
502
+ ...params.InputProps,
503
+ endAdornment: (
504
+ <React.Fragment>
505
+ {this.state.loadingRefs ? (
506
+ <MaterialUI.CircularProgress
507
+ color="inherit"
508
+ size={20}
509
+ />
510
+ ) : null}
511
+ {params.InputProps.endAdornment}
512
+ </React.Fragment>
513
+ ),
514
+ }}
352
515
  />
353
516
  )}
354
517
  />
@@ -358,7 +521,7 @@ class App extends React.Component {
358
521
  variant="contained"
359
522
  size="large"
360
523
  disabled={
361
- this.state.progress || !this.state.repo || !this.state.branch
524
+ this.state.progress || !this.state.repo || !this.state.ref
362
525
  }
363
526
  >
364
527
  <MaterialUI.Icon>start</MaterialUI.Icon>
@@ -20,7 +20,7 @@ You can also use the `html` output and write your own webapp. You need to provid
20
20
 
21
21
  ```html
22
22
  <script
23
- src="https://cdn.jsdelivr.net/pyodide/v0.25.1/full/pyodide.js"
23
+ src="https://cdn.jsdelivr.net/pyodide/v0.27.6/full/pyodide.js"
24
24
  crossorigin
25
25
  ></script>
26
26
  ```
@@ -24,6 +24,7 @@ classifiers = [
24
24
  "Programming Language :: Python :: 3.11",
25
25
  "Programming Language :: Python :: 3.12",
26
26
  "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
27
28
  "Programming Language :: Python",
28
29
  "Topic :: Software Development :: Quality Assurance",
29
30
  "Topic :: Software Development :: Libraries :: Python Modules",
@@ -148,7 +149,7 @@ skip-install = true
148
149
  scripts.serve = "cd docs && echo 'Serving on http://localhost:8080' && python -m http.server 8080"
149
150
 
150
151
  [[tool.hatch.envs.hatch-test.matrix]]
151
- python = ["3.13", "3.12", "3.11", "3.10"]
152
+ python = ["3.14", "3.13", "3.12", "3.11", "3.10"]
152
153
 
153
154
 
154
155
  [tool.pytest.ini_options]
@@ -201,6 +202,7 @@ messages_control.disable = [
201
202
  "used-before-assignment", # False positive on conditional import
202
203
  "unnecessary-ellipsis", # Not correct for typing
203
204
  "import-outside-toplevel", # better handled elsewhere
205
+ "duplicate-code", # Triggers incorrectly
204
206
  ]
205
207
 
206
208
 
@@ -225,6 +227,7 @@ extend-select = [
225
227
  "T20", # flake8-print
226
228
  "UP", # pyupgrade
227
229
  "YTT", # flake8-2020
230
+
228
231
  ]
229
232
  ignore = [
230
233
  "ISC001", # May collide with formatter
@@ -1,11 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import functools
3
4
  import importlib.metadata
4
5
  import itertools
5
6
  import json
6
7
  import os
7
8
  import sys
8
9
  import typing
10
+ import urllib.error
9
11
  from collections.abc import Mapping, Sequence
10
12
  from pathlib import Path
11
13
  from typing import Any, Literal
@@ -17,6 +19,7 @@ if typing.TYPE_CHECKING:
17
19
  else:
18
20
  import rich_click as click
19
21
 
22
+ import rich
20
23
  import rich.console
21
24
  import rich.markdown
22
25
  import rich.syntax
@@ -98,16 +101,8 @@ def all_versions(ctx: click.Context, _param: click.Parameter, value: bool) -> No
98
101
  ctx.exit()
99
102
 
100
103
 
101
- def rich_printer(
102
- families: Mapping[str, Family],
103
- processed: list[Result],
104
- *,
105
- svg: bool = False,
106
- stderr: bool = False,
107
- color: bool = True,
108
- status: Status,
109
- header: str = "",
110
- ) -> None:
104
+ @functools.cache
105
+ def _ensure_unicode_streams() -> None:
111
106
  # Before Python 3.15, this isn't always unicode
112
107
  if (
113
108
  sys.version_info < (3, 15)
@@ -119,6 +114,19 @@ def rich_printer(
119
114
  if sys.stderr.encoding != "utf-8":
120
115
  sys.stderr.reconfigure(encoding="utf-8") # type: ignore[union-attr]
121
116
 
117
+
118
+ def rich_printer(
119
+ families: Mapping[str, Family],
120
+ processed: list[Result],
121
+ *,
122
+ svg: bool = False,
123
+ stderr: bool = False,
124
+ color: bool = True,
125
+ status: Status,
126
+ header: str = "",
127
+ ) -> None:
128
+ _ensure_unicode_streams()
129
+
122
130
  console = rich.console.Console(
123
131
  record=svg, quiet=svg, stderr=stderr, color_system="auto" if color else None
124
132
  )
@@ -265,10 +273,23 @@ def _remote_path_processor(package: Path) -> Path | GHPath:
265
273
  msg = "online repo must be of the form 'gh:org/repo@branch[:path]' (:branch missing)"
266
274
  raise click.BadParameter(msg)
267
275
  org_repo, branch = org_repo_branch.split("@", maxsplit=1)
268
- return GHPath(repo=org_repo, branch=branch, path=p[0] if p else "")
276
+ try:
277
+ return GHPath(repo=org_repo, branch=branch, path=p[0] if p else "")
278
+ except urllib.error.HTTPError as e:
279
+ rich.print(f"[red][bold]Error[/bold] accessing {e.url}", file=sys.stderr)
280
+ rich.print(f"[red]{e}", file=sys.stderr)
281
+ raise SystemExit(1) from None
269
282
 
270
283
 
271
- @click.command(context_settings={"help_option_names": ["-h", "--help"]})
284
+ class UnicodeHelpCommand(click.Command):
285
+ def get_help(self, ctx: click.Context) -> str:
286
+ _ensure_unicode_streams()
287
+ return super().get_help(ctx)
288
+
289
+
290
+ @click.command(
291
+ cls=UnicodeHelpCommand, context_settings={"help_option_names": ["-h", "--help"]}
292
+ )
272
293
  @click.version_option(version=__version__)
273
294
  @click.argument(
274
295
  "packages",
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '0.12.3'
32
+ __version_tuple__ = version_tuple = (0, 12, 3)
33
+
34
+ __commit_id__ = commit_id = None
@@ -50,11 +50,11 @@ class GHPath(Traversable):
50
50
  def open_url(url: str) -> io.StringIO:
51
51
  "This method can be overridden manually for WASM. Supports pyodide currently."
52
52
  if sys.platform == "emscripten":
53
- import pyodide.http
53
+ import pyodide.http # noqa: PLC0415
54
54
 
55
55
  return pyodide.http.open_url(url)
56
56
 
57
- import urllib.request # pylint: disable=import-outside-toplevel
57
+ import urllib.request # noqa: PLC0415
58
58
 
59
59
  with urllib.request.urlopen(url) as response:
60
60
  return io.StringIO(response.read().decode("utf-8"))
@@ -25,7 +25,8 @@ def test_local():
25
25
  assert "BSD License" in results.families["general"]["description"]
26
26
  assert "[tool.repo-review]" in results.families["validate-pyproject"]["description"]
27
27
  for result in results.results:
28
- assert result.result
28
+ if result.result is not None:
29
+ assert result.result
29
30
 
30
31
 
31
32
  def test_broken_validate_pyproject(tmp_path: Path) -> None:
@@ -1,5 +0,0 @@
1
- changelog:
2
- exclude:
3
- authors:
4
- - dependabot
5
- - pre-commit-ci
@@ -1,16 +0,0 @@
1
- # file generated by setuptools_scm
2
- # don't change, don't track in version control
3
- TYPE_CHECKING = False
4
- if TYPE_CHECKING:
5
- from typing import Tuple, Union
6
- VERSION_TUPLE = Tuple[Union[int, str], ...]
7
- else:
8
- VERSION_TUPLE = object
9
-
10
- version: str
11
- __version__: str
12
- __version_tuple__: VERSION_TUPLE
13
- version_tuple: VERSION_TUPLE
14
-
15
- __version__ = version = '0.12.1'
16
- __version_tuple__ = version_tuple = (0, 12, 1)
File without changes
File without changes
@@ -3,5 +3,5 @@ repo\_review.resources package
3
3
 
4
4
  .. automodule:: repo_review.resources
5
5
  :members:
6
- :undoc-members:
7
6
  :show-inheritance:
7
+ :undoc-members:
@@ -3,8 +3,8 @@ repo\_review package
3
3
 
4
4
  .. automodule:: repo_review
5
5
  :members:
6
- :undoc-members:
7
6
  :show-inheritance:
7
+ :undoc-members:
8
8
 
9
9
  Subpackages
10
10
  -----------
@@ -22,61 +22,61 @@ repo\_review.checks module
22
22
 
23
23
  .. automodule:: repo_review.checks
24
24
  :members:
25
- :undoc-members:
26
25
  :show-inheritance:
26
+ :undoc-members:
27
27
 
28
28
  repo\_review.families module
29
29
  ----------------------------
30
30
 
31
31
  .. automodule:: repo_review.families
32
32
  :members:
33
- :undoc-members:
34
33
  :show-inheritance:
34
+ :undoc-members:
35
35
 
36
36
  repo\_review.fixtures module
37
37
  ----------------------------
38
38
 
39
39
  .. automodule:: repo_review.fixtures
40
40
  :members:
41
- :undoc-members:
42
41
  :show-inheritance:
42
+ :undoc-members:
43
43
 
44
44
  repo\_review.ghpath module
45
45
  --------------------------
46
46
 
47
47
  .. automodule:: repo_review.ghpath
48
48
  :members:
49
- :undoc-members:
50
49
  :show-inheritance:
50
+ :undoc-members:
51
51
 
52
52
  repo\_review.html module
53
53
  ------------------------
54
54
 
55
55
  .. automodule:: repo_review.html
56
56
  :members:
57
- :undoc-members:
58
57
  :show-inheritance:
58
+ :undoc-members:
59
59
 
60
60
  repo\_review.processor module
61
61
  -----------------------------
62
62
 
63
63
  .. automodule:: repo_review.processor
64
64
  :members:
65
- :undoc-members:
66
65
  :show-inheritance:
66
+ :undoc-members:
67
67
 
68
68
  repo\_review.schema module
69
69
  --------------------------
70
70
 
71
71
  .. automodule:: repo_review.schema
72
72
  :members:
73
- :undoc-members:
74
73
  :show-inheritance:
74
+ :undoc-members:
75
75
 
76
76
  repo\_review.testing module
77
77
  ---------------------------
78
78
 
79
79
  .. automodule:: repo_review.testing
80
80
  :members:
81
- :undoc-members:
82
81
  :show-inheritance:
82
+ :undoc-members:
File without changes
File without changes
File without changes
File without changes