Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ gets wrong:
`pybamm.X`) and get a `docs/source/api/*.rst` entry.
- Every feature or fix adds a `CHANGELOG.md` bullet under `# [Unreleased]` (Keep a Changelog
format), ending with the PR link, e.g. `([#1234](https://github.com/pybamm-team/PyBaMM/pull/1234))`.
Get a prediction from `uv run --no-project scripts/next_pr_number.py`. If another issue, pull
request, or discussion takes that number before the PR opens, update the entry manually.

### Tests

Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ If your PR is internal-only (refactor, docs typo, CI tweak, tests), no changelog

Each entry is a single bullet ending in a PR link, e.g. `([#1234](https://github.com/pybamm-team/PyBaMM/pull/1234))`. Use the full URL so the rendered markdown on docs.pybamm.org links correctly.

To write the entry before opening the PR, run `uv run --no-project scripts/next_pr_number.py`. It prints the likely next number using the [GitHub CLI](https://cli.github.com). This is only a prediction: if another issue, pull request, or discussion takes the number first, update the entry manually.

For the full release policy — what counts as "public API", deprecation horizon, version scheme — see [`RELEASE.md`](https://github.com/pybamm-team/PyBaMM/blob/main/RELEASE.md).

## Coding style guidelines
Expand Down
41 changes: 41 additions & 0 deletions packages/pybamm/tests/unit/test_next_pr_number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import importlib.util
import json
import subprocess
from pathlib import Path

import pytest

SCRIPT = Path(__file__).parents[4] / "scripts" / "next_pr_number.py"
spec = importlib.util.spec_from_file_location("next_pr_number", SCRIPT)
next_pr_number = importlib.util.module_from_spec(spec)
spec.loader.exec_module(next_pr_number)


class TestNextPrNumber:
def test_returns_one_more_than_the_newest_number(self, monkeypatch):
response = {
"data": {
"repository": {
"issues": {"nodes": [{"number": 40}]},
"pullRequests": {"nodes": [{"number": 42}]},
"discussions": {"nodes": [{"number": 41}]},
}
}
}
completed = subprocess.CompletedProcess(
args=[], returncode=0, stdout=json.dumps(response), stderr=""
)
monkeypatch.setattr(subprocess, "run", lambda *args, **kwargs: completed)

assert next_pr_number.next_pr_number() == 43

def test_reports_github_cli_failure(self, monkeypatch):
def fail(*args, **kwargs):
raise subprocess.CalledProcessError(
1, "gh", stderr="authentication required"
)

monkeypatch.setattr(subprocess, "run", fail)

with pytest.raises(SystemExit, match="authentication required"):
next_pr_number.next_pr_number()
63 changes: 63 additions & 0 deletions scripts/next_pr_number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Print the likely number of the next PyBaMM pull request.

The result is a prediction. An issue, pull request, or discussion created before
the pull request will take the predicted number first.
"""

from __future__ import annotations

import json
import subprocess

REPO = "pybamm-team/PyBaMM"
QUERY = """
query ($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
issues(orderBy: {field: CREATED_AT, direction: DESC}, first: 1) {
nodes { number }
}
pullRequests(orderBy: {field: CREATED_AT, direction: DESC}, first: 1) {
nodes { number }
}
discussions(orderBy: {field: CREATED_AT, direction: DESC}, first: 1) {
nodes { number }
}
}
}
"""


def next_pr_number() -> int:
"""Return the likely number of the next PyBaMM pull request."""
owner, name = REPO.split("/")
try:
result = subprocess.run(
[
"gh",
"api",
"graphql",
"-f",
f"query={QUERY}",
"-f",
f"owner={owner}",
"-f",
f"name={name}",
],
check=True,
capture_output=True,
text=True,
encoding="utf-8",
)
except FileNotFoundError as error:
raise SystemExit("gh is not installed; see https://cli.github.com") from error
except subprocess.CalledProcessError as error:
message = error.stderr.strip() or "unknown error"
raise SystemExit(f"`gh api graphql` failed: {message}") from error

connections = json.loads(result.stdout)["data"]["repository"].values()
newest = [item["nodes"][0]["number"] for item in connections if item["nodes"]]
return max(newest, default=0) + 1


if __name__ == "__main__":
print(next_pr_number())
Loading