Skip to content

Commit 0db86f2

Browse files
committed
build: automate updating readthedocs
1 parent 2a89551 commit 0db86f2

File tree

4 files changed

+128
-19
lines changed

4 files changed

+128
-19
lines changed

Makefile

+5-1
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,11 @@ sample_html_beta: _sample_cog_html ## Generate sample HTML report for a beta rel
177177
##@ Kitting: making releases
178178

179179
.PHONY: release_version edit_for_release cheats relbranch relcommit1 relcommit2
180-
.PHONY: kit pypi_upload test_upload kit_local build_kits
180+
.PHONY: kit pypi_upload test_upload kit_local build_kits update_rtd
181181
.PHONY: tag bump_version
182182

183183
REPO_OWNER = nedbat/coveragepy
184+
RTD_PROJECT = coverage
184185

185186
release_version: #: Update the version for a release.
186187
python igor.py release_version
@@ -228,6 +229,9 @@ tag: #: Make a git tag with the version number (see howto.txt).
228229
git tag -s -m "Version $$(python setup.py --version)" $$(python setup.py --version)
229230
git push --follow-tags
230231

232+
update_rtd: #: Update ReadTheDocs with the versions to show
233+
python ci/update-rtfd.py $(RTD_PROJECT)
234+
231235
bump_version: #: Edit sources to bump the version after a release (see howto.txt).
232236
git switch -c nedbat/bump-version
233237
python igor.py bump_version

ci/session.py

+14-12
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,25 @@
88

99
import requests
1010

11-
_SESSION = None
11+
_SESSIONS = {}
1212

13-
def get_session():
14-
"""Get a properly authenticated requests Session."""
13+
def get_session(env="GITHUB_TOKEN"):
14+
"""Get a properly authenticated requests Session.
1515
16-
global _SESSION
16+
Get the token from the `env` environment variable.
17+
"""
1718

18-
if _SESSION is None:
19-
# If GITHUB_TOKEN is in the environment, use it.
20-
token = os.environ.get("GITHUB_TOKEN")
19+
session = _SESSIONS.get(env)
20+
if session is None:
21+
token = os.environ.get(env)
2122
if token is None:
22-
sys.exit("!! Must have a GITHUB_TOKEN")
23+
sys.exit(f"!! Must have {env}")
2324

24-
_SESSION = requests.session()
25-
_SESSION.headers["Authorization"] = f"token {token}"
25+
session = requests.session()
26+
session.headers["Authorization"] = f"token {token}"
2627
# requests.get() will always prefer the .netrc file even if a header
2728
# is already set. This tells it to ignore the .netrc file.
28-
_SESSION.trust_env = False
29+
session.trust_env = False
30+
_SESSIONS[env] = session
2931

30-
return _SESSION
32+
return session

ci/update-rtfd.py

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""
2+
Update ReadTheDocs to show and hide releases.
3+
"""
4+
5+
import re
6+
import sys
7+
8+
from session import get_session
9+
10+
# How many from each level to show.
11+
NUM_MAJORS = 3
12+
NUM_MINORS = 4
13+
OLD_MINORS = 1
14+
NUM_MICROS = 1
15+
OLD_MICROS = 1
16+
17+
18+
def get_all_versions(project):
19+
"""Pull all the versions for a project from ReadTheDocs."""
20+
versions = []
21+
session = get_session("RTFD_TOKEN")
22+
23+
url = f"https://readthedocs.org/api/v3/projects/{project}/versions/"
24+
while url:
25+
resp = session.get(url)
26+
resp.raise_for_status()
27+
data = resp.json()
28+
versions.extend(data["results"])
29+
url = data["next"]
30+
return versions
31+
32+
33+
def version_tuple(vstr):
34+
"""Convert a tag name into a version_info tuple."""
35+
m = re.fullmatch(r"[^\d]*(\d+)\.(\d+)(?:\.(\d+))?(?:([abc])(\d+))?", vstr)
36+
if not m:
37+
return None
38+
return (
39+
int(m[1]),
40+
int(m[2]),
41+
int(m[3] or 0),
42+
(m[4] or "final"),
43+
int(m[5] or 0),
44+
)
45+
46+
47+
def main(project):
48+
"""Update ReadTheDocs for the versions we want to show."""
49+
50+
# Get all the tags. Where there are dupes, keep the shorter tag for a version.
51+
versions = get_all_versions(project)
52+
versions.sort(key=(lambda v: len(v["verbose_name"])), reverse=True)
53+
vdict = {}
54+
for v in versions:
55+
if v["type"] == "tag":
56+
vinfo = version_tuple(v["verbose_name"])
57+
if vinfo and vinfo[3] == "final":
58+
vdict[vinfo] = v
59+
60+
# Decide which to show and update them.
61+
62+
majors = set()
63+
minors = set()
64+
micros = set()
65+
minors_to_show = NUM_MINORS
66+
micros_to_show = NUM_MICROS
67+
68+
session = get_session("RTFD_TOKEN")
69+
version_list = sorted(vdict.items(), reverse=True)
70+
for vi, ver in version_list:
71+
if vi[:1] not in majors:
72+
majors.add(vi[:1])
73+
minors = set()
74+
if len(majors) > 1:
75+
minors_to_show = OLD_MINORS
76+
micros_to_show = OLD_MICROS
77+
if vi[:2] not in minors:
78+
minors.add(vi[:2])
79+
micros = set()
80+
if vi[:3] not in micros:
81+
micros.add(vi[:3])
82+
83+
show_it = (
84+
len(majors) <= NUM_MAJORS
85+
and len(minors) <= minors_to_show
86+
and len(micros) <= micros_to_show
87+
)
88+
active = ver["active"] or (len(majors) <= NUM_MAJORS)
89+
hidden = not show_it
90+
91+
update = ver["active"] != active or ver["hidden"] != hidden
92+
if update:
93+
print(f"Updating {ver['verbose_name']} to {active=}, {hidden=}")
94+
url = ver["_links"]["_self"]
95+
resp = session.patch(url, data={"active": active, "hidden": hidden})
96+
resp.raise_for_status()
97+
98+
# Set the default version.
99+
latest = version_list[0][1]
100+
print(f"Setting default version to {latest['slug']}")
101+
url = latest["_links"]["project"]
102+
resp = session.patch(url, data={"default_version": latest["slug"]})
103+
resp.raise_for_status()
104+
105+
106+
if __name__ == "__main__":
107+
main(sys.argv[1])

howto.txt

+2-6
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,15 @@ $ deopvars
6969
- Bump version:
7070
$ make bump_version
7171
- Update readthedocs
72-
- @ https://readthedocs.org/projects/coverage/versions/
72+
- IF PRE-RELEASE
7373
- find the latest tag in the inactive list, edit it, make it active.
7474
- keep just the latest version of each x.y release, make the rest active but hidden.
7575
- pre-releases should be hidden
7676
- IF NOT PRE-RELEASE:
77-
- @ https://readthedocs.org/dashboard/coverage/advanced/
78-
- change the "default version" to the new version
77+
$ make update_rtd
7978
- Once CI passes, merge the bump-version branch to master and push it
8079
$ gshipit
8180

82-
- things to automate:
83-
- readthedocs api to do the readthedocs changes
84-
8581

8682
* Testing
8783

0 commit comments

Comments
 (0)