Skip to content

Commit 72eb1b7

Browse files
nicoddemuspytestbot
authored andcommitted
[8.0.x] Improve GitHub release workflow
1 parent 8381516 commit 72eb1b7

File tree

9 files changed

+73
-75
lines changed

9 files changed

+73
-75
lines changed

.github/workflows/deploy.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,14 @@ jobs:
8282
python -m pip install --upgrade pip
8383
pip install --upgrade tox
8484
85-
- name: Publish GitHub release notes
86-
env:
87-
GH_RELEASE_NOTES_TOKEN: ${{ github.token }}
85+
- name: Generate release notes
8886
run: |
8987
sudo apt-get install pandoc
90-
tox -e publish-gh-release-notes
88+
tox -e generate-gh-release-notes -- ${{ github.event.inputs.version }} scripts/latest-release-notes.md
89+
90+
- name: Publish GitHub Release
91+
uses: softprops/action-gh-release@v1
92+
with:
93+
body_path: scripts/latest-release-notes.md
94+
files: dist/*
95+
tag_name: ${{ github.event.inputs.version }}

.pre-commit-config.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,15 @@ repos:
5959
rev: v1.8.0
6060
hooks:
6161
- id: mypy
62-
files: ^(src/|testing/)
62+
files: ^(src/|testing/|scripts/)
6363
args: []
6464
additional_dependencies:
6565
- iniconfig>=1.1.0
6666
- attrs>=19.2.0
6767
- packaging
6868
- tomli
6969
- types-pkg_resources
70+
- types-tabulate
7071
# for mypy running on python>=3.11 since exceptiongroup is only a dependency
7172
# on <3.11
7273
- exceptiongroup>=1.0.0rc8

scripts/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
latest-release-notes.md

scripts/publish-gh-release-notes.py renamed to scripts/generate-gh-release-notes.py

Lines changed: 18 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# mypy: disallow-untyped-defs
12
"""
23
Script used to publish GitHub release notes extracted from CHANGELOG.rst.
34
@@ -19,27 +20,19 @@
1920
2021
Requires Python3.6+.
2122
"""
22-
import os
2323
import re
2424
import sys
2525
from pathlib import Path
26+
from typing import Sequence
2627

27-
import github3
2828
import pypandoc
2929

3030

31-
def publish_github_release(slug, token, tag_name, body):
32-
github = github3.login(token=token)
33-
owner, repo = slug.split("/")
34-
repo = github.repository(owner, repo)
35-
return repo.create_release(tag_name=tag_name, body=body)
36-
37-
38-
def parse_changelog(tag_name):
31+
def parse_changelog(tag_name: str) -> str:
3932
p = Path(__file__).parent.parent / "doc/en/changelog.rst"
4033
changelog_lines = p.read_text(encoding="UTF-8").splitlines()
4134

42-
title_regex = re.compile(r"pytest (\d\.\d+\.\d+) \(\d{4}-\d{2}-\d{2}\)")
35+
title_regex = re.compile(r"pytest (\d\.\d+\.\d+\w*) \(\d{4}-\d{2}-\d{2}\)")
4336
consuming_version = False
4437
version_lines = []
4538
for line in changelog_lines:
@@ -57,43 +50,26 @@ def parse_changelog(tag_name):
5750
return "\n".join(version_lines)
5851

5952

60-
def convert_rst_to_md(text):
61-
return pypandoc.convert_text(
53+
def convert_rst_to_md(text: str) -> str:
54+
result = pypandoc.convert_text(
6255
text, "md", format="rst", extra_args=["--wrap=preserve"]
6356
)
57+
assert isinstance(result, str), repr(result)
58+
return result
6459

6560

66-
def main(argv):
67-
if len(argv) > 1:
68-
tag_name = argv[1]
69-
else:
70-
tag_name = os.environ.get("GITHUB_REF")
71-
if not tag_name:
72-
print("tag_name not given and $GITHUB_REF not set", file=sys.stderr)
73-
return 1
74-
if tag_name.startswith("refs/tags/"):
75-
tag_name = tag_name[len("refs/tags/") :]
76-
77-
token = os.environ.get("GH_RELEASE_NOTES_TOKEN")
78-
if not token:
79-
print("GH_RELEASE_NOTES_TOKEN not set", file=sys.stderr)
80-
return 1
81-
82-
slug = os.environ.get("GITHUB_REPOSITORY")
83-
if not slug:
84-
print("GITHUB_REPOSITORY not set", file=sys.stderr)
85-
return 1
86-
87-
rst_body = parse_changelog(tag_name)
88-
md_body = convert_rst_to_md(rst_body)
89-
if not publish_github_release(slug, token, tag_name, md_body):
90-
print("Could not publish release notes:", file=sys.stderr)
91-
print(md_body, file=sys.stderr)
92-
return 5
61+
def main(argv: Sequence[str]) -> int:
62+
if len(argv) != 3:
63+
print("Usage: generate-gh-release-notes VERSION FILE")
64+
return 2
9365

66+
version, filename = argv[1:3]
67+
print(f"Generating GitHub release notes for version {version}")
68+
rst_body = parse_changelog(version)
69+
md_body = convert_rst_to_md(rst_body)
70+
Path(filename).write_text(md_body, encoding="UTF-8")
9471
print()
95-
print(f"Release notes for {tag_name} published successfully:")
96-
print(f"https://github.com/{slug}/releases/tag/{tag_name}")
72+
print(f"Done: {filename}")
9773
print()
9874
return 0
9975

scripts/prepare-release-pr.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# mypy: disallow-untyped-defs
12
"""
23
This script is part of the pytest release process which is triggered manually in the Actions
34
tab of the repository.

scripts/release.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# mypy: disallow-untyped-defs
12
"""Invoke development tasks."""
23
import argparse
34
import os
@@ -10,15 +11,15 @@
1011
from colorama import init
1112

1213

13-
def announce(version, template_name, doc_version):
14+
def announce(version: str, template_name: str, doc_version: str) -> None:
1415
"""Generates a new release announcement entry in the docs."""
1516
# Get our list of authors
16-
stdout = check_output(["git", "describe", "--abbrev=0", "--tags"])
17-
stdout = stdout.decode("utf-8")
17+
stdout = check_output(["git", "describe", "--abbrev=0", "--tags"], encoding="UTF-8")
1818
last_version = stdout.strip()
1919

20-
stdout = check_output(["git", "log", f"{last_version}..HEAD", "--format=%aN"])
21-
stdout = stdout.decode("utf-8")
20+
stdout = check_output(
21+
["git", "log", f"{last_version}..HEAD", "--format=%aN"], encoding="UTF-8"
22+
)
2223

2324
contributors = {
2425
name
@@ -61,7 +62,7 @@ def announce(version, template_name, doc_version):
6162
check_call(["git", "add", str(target)])
6263

6364

64-
def regen(version):
65+
def regen(version: str) -> None:
6566
"""Call regendoc tool to update examples and pytest output in the docs."""
6667
print(f"{Fore.CYAN}[generate.regen] {Fore.RESET}Updating docs")
6768
check_call(
@@ -70,21 +71,23 @@ def regen(version):
7071
)
7172

7273

73-
def fix_formatting():
74+
def fix_formatting() -> None:
7475
"""Runs pre-commit in all files to ensure they are formatted correctly"""
7576
print(
7677
f"{Fore.CYAN}[generate.fix linting] {Fore.RESET}Fixing formatting using pre-commit"
7778
)
7879
call(["pre-commit", "run", "--all-files"])
7980

8081

81-
def check_links():
82+
def check_links() -> None:
8283
"""Runs sphinx-build to check links"""
8384
print(f"{Fore.CYAN}[generate.check_links] {Fore.RESET}Checking links")
8485
check_call(["tox", "-e", "docs-checklinks"])
8586

8687

87-
def pre_release(version, template_name, doc_version, *, skip_check_links):
88+
def pre_release(
89+
version: str, template_name: str, doc_version: str, *, skip_check_links: bool
90+
) -> None:
8891
"""Generates new docs, release announcements and creates a local tag."""
8992
announce(version, template_name, doc_version)
9093
regen(version)
@@ -102,12 +105,12 @@ def pre_release(version, template_name, doc_version, *, skip_check_links):
102105
print("Please push your branch and open a PR.")
103106

104107

105-
def changelog(version, write_out=False):
108+
def changelog(version: str, write_out: bool = False) -> None:
106109
addopts = [] if write_out else ["--draft"]
107110
check_call(["towncrier", "--yes", "--version", version] + addopts)
108111

109112

110-
def main():
113+
def main() -> None:
111114
init(autoreset=True)
112115
parser = argparse.ArgumentParser()
113116
parser.add_argument("version", help="Release version")

scripts/towncrier-draft-to-file.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
# mypy: disallow-untyped-defs
12
import sys
23
from subprocess import call
34

45

5-
def main():
6+
def main() -> int:
67
"""
7-
Platform agnostic wrapper script for towncrier.
8-
Fixes the issue (#7251) where windows users are unable to natively run tox -e docs to build pytest docs.
8+
Platform-agnostic wrapper script for towncrier.
9+
Fixes the issue (#7251) where Windows users are unable to natively run tox -e docs to build pytest docs.
910
"""
1011
with open(
1112
"doc/en/_changelog_towncrier_draft.rst", "w", encoding="utf-8"

scripts/update-plugin-list.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
# mypy: disallow-untyped-defs
12
import datetime
23
import pathlib
34
import re
45
from textwrap import dedent
56
from textwrap import indent
7+
from typing import Any
8+
from typing import Iterable
9+
from typing import Iterator
10+
from typing import TypedDict
611

712
import packaging.version
813
import platformdirs
@@ -109,7 +114,17 @@ def pytest_plugin_projects_from_pypi(session: CachedSession) -> dict[str, int]:
109114
}
110115

111116

112-
def iter_plugins():
117+
class PluginInfo(TypedDict):
118+
"""Relevant information about a plugin to generate the summary."""
119+
120+
name: str
121+
summary: str
122+
last_release: str
123+
status: str
124+
requires: str
125+
126+
127+
def iter_plugins() -> Iterator[PluginInfo]:
113128
session = get_session()
114129
name_2_serial = pytest_plugin_projects_from_pypi(session)
115130

@@ -136,7 +151,7 @@ def iter_plugins():
136151
requires = requirement
137152
break
138153

139-
def version_sort_key(version_string):
154+
def version_sort_key(version_string: str) -> Any:
140155
"""
141156
Return the sort key for the given version string
142157
returned by the API.
@@ -162,20 +177,20 @@ def version_sort_key(version_string):
162177
yield {
163178
"name": name,
164179
"summary": summary.strip(),
165-
"last release": last_release,
180+
"last_release": last_release,
166181
"status": status,
167182
"requires": requires,
168183
}
169184

170185

171-
def plugin_definitions(plugins):
186+
def plugin_definitions(plugins: Iterable[PluginInfo]) -> Iterator[str]:
172187
"""Return RST for the plugin list that fits better on a vertical page."""
173188

174189
for plugin in plugins:
175190
yield dedent(
176191
f"""
177192
{plugin['name']}
178-
*last release*: {plugin["last release"]},
193+
*last release*: {plugin["last_release"]},
179194
*status*: {plugin["status"]},
180195
*requires*: {plugin["requires"]}
181196
@@ -184,7 +199,7 @@ def plugin_definitions(plugins):
184199
)
185200

186201

187-
def main():
202+
def main() -> None:
188203
plugins = [*iter_plugins()]
189204

190205
reference_dir = pathlib.Path("doc", "en", "reference")

tox.ini

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -177,18 +177,13 @@ passenv = {[testenv:release]passenv}
177177
deps = {[testenv:release]deps}
178178
commands = python scripts/prepare-release-pr.py {posargs}
179179

180-
[testenv:publish-gh-release-notes]
181-
description = create GitHub release after deployment
180+
[testenv:generate-gh-release-notes]
181+
description = generate release notes that can be published as GitHub Release
182182
basepython = python3
183183
usedevelop = True
184-
passenv =
185-
GH_RELEASE_NOTES_TOKEN
186-
GITHUB_REF
187-
GITHUB_REPOSITORY
188184
deps =
189-
github3.py
190185
pypandoc
191-
commands = python scripts/publish-gh-release-notes.py {posargs}
186+
commands = python scripts/generate-gh-release-notes.py {posargs}
192187

193188
[flake8]
194189
max-line-length = 120

0 commit comments

Comments
 (0)