From 55af14d4a3d25c2df8b69502d4944a7e67d347c7 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 14 Sep 2026 00:42:16 +0300 Subject: [PATCH 1/3] Add --include-security-branches flag --- README.md | 7 +++-- build_docs.py | 21 ++++++++++--- tests/test_build_docs_versions.py | 52 +++++++++++++++---------------- 3 files changed, 45 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 407d0f2..2a78cd1 100644 --- a/README.md +++ b/README.md @@ -82,10 +82,11 @@ Or run `tox -e cog` (with a clone at `../cpython`) to directly update these tabl ## Manually rebuild a branch Docs for [feature and bugfix branches](https://devguide.python.org/versions/) are -automatically built from a cron. +automatically built from a cron. Security branches are also built from a cron +(the crons passing `--include-security-branches`), but less frequently. -Manual rebuilds are needed for new security releases, -and to add the end-of-life banner for newly end-of-life branches. +Manual rebuilds are needed to add the end-of-life banner for +newly end-of-life branches. To manually rebuild a branch, for example 3.11: diff --git a/build_docs.py b/build_docs.py index 384f222..68c7035 100755 --- a/build_docs.py +++ b/build_docs.py @@ -125,18 +125,21 @@ def from_json(cls, data: dict) -> Versions: return cls(sorted(versions, key=Version.as_tuple)) - def filter(self, branches: Sequence[str] = ()) -> Sequence[Version]: + def filter( + self, branches: Sequence[str] = (), *, include_security: bool = False + ) -> Sequence[Version]: """Filter the given versions. If *branches* is given, only *versions* matching *branches* are returned. - Else all live versions are returned (this means no EOL and no - security-fixes branches). + Else all live versions are returned (this means no EOL branches), + plus security-fixes branches if *include_security* is true. """ if branches: branches = frozenset(branches) return [v for v in self if {v.name, v.branch_or_tag} & branches] - return [v for v in self if v.status not in {"EOL", "security-fixes"}] + excluded = {"EOL"} if include_security else {"EOL", "security-fixes"} + return [v for v in self if v.status not in excluded] @property def current_stable(self) -> Version: @@ -1134,6 +1137,11 @@ def parse_args() -> argparse.Namespace: metavar="3.12", help="Versions to build (defaults to all maintained branches).", ) + parser.add_argument( + "--include-security-branches", + action="store_true", + help="Also build security-fixes branches (ignored when --branches is given).", + ) parser.add_argument( "-r", "--build-root", @@ -1272,10 +1280,13 @@ def build_docs(args: argparse.Namespace) -> int: # This runs languages in config.toml order and versions newest first. todo = [ BuildMetadata(_version=version, _language=language) - for version in versions.filter(args.branches) + for version in versions.filter( + args.branches, include_security=args.include_security_branches + ) for language in reversed(languages.filter(args.languages)) ] del args.branches + del args.include_security_branches del args.languages force_build = args.force del args.force diff --git a/tests/test_build_docs_versions.py b/tests/test_build_docs_versions.py index 4af2a08..915b375 100644 --- a/tests/test_build_docs_versions.py +++ b/tests/test_build_docs_versions.py @@ -16,6 +16,7 @@ def versions() -> Versions: Version(name="3.11", status="security-fixes", branch_or_tag=""), Version(name="3.10", status="security-fixes", branch_or_tag=""), Version(name="3.9", status="security-fixes", branch_or_tag=""), + Version(name="3.8", status="EOL", branch_or_tag=""), ]) @@ -24,7 +25,7 @@ def test_reversed(versions: Versions) -> None: output = list(reversed(versions)) # Assert - assert output[0].name == "3.9" + assert output[0].name == "3.8" assert output[-1].name == "3.14" @@ -101,32 +102,29 @@ def test_current_dev(versions) -> None: assert current_dev.status == "in development" -def test_filter_default(versions) -> None: +@pytest.mark.parametrize( + ("branches", "include_security", "expected"), + [ + # Default: no EOL and no security-fixes branches + ((), False, ["3.14", "3.13", "3.12"]), + # Security-fixes branches included, EOL still excluded + ((), True, ["3.14", "3.13", "3.12", "3.11", "3.10", "3.9"]), + # Explicit branches are returned regardless of status + (["3.13"], False, ["3.13"]), + (["3.13", "3.14"], False, ["3.14", "3.13"]), + (["3.9", "3.8"], False, ["3.9", "3.8"]), + # Explicit branches take precedence over include_security + (["3.13"], True, ["3.13"]), + ], +) +def test_filter( + versions, + branches: list[str], + include_security: bool, + expected: list[str], +) -> None: # Act - filtered = versions.filter() + filtered = versions.filter(branches, include_security=include_security) # Assert - assert filtered == [ - Version(name="3.14", status="in development", branch_or_tag=""), - Version(name="3.13", status="stable", branch_or_tag=""), - Version(name="3.12", status="stable", branch_or_tag=""), - ] - - -def test_filter_one(versions) -> None: - # Act - filtered = versions.filter(["3.13"]) - - # Assert - assert filtered == [Version(name="3.13", status="security-fixes", branch_or_tag="")] - - -def test_filter_multiple(versions) -> None: - # Act - filtered = versions.filter(["3.13", "3.14"]) - - # Assert - assert filtered == [ - Version(name="3.14", status="in development", branch_or_tag=""), - Version(name="3.13", status="security-fixes", branch_or_tag=""), - ] + assert [v.name for v in filtered] == expected From 776bffbc15e629f5ad899034894cea72b5e6f418 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 14 Sep 2026 19:51:31 +0300 Subject: [PATCH 2/3] Update type hint Co-authored-by: Stan Ulbrych --- build_docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_docs.py b/build_docs.py index 68c7035..28f6600 100755 --- a/build_docs.py +++ b/build_docs.py @@ -126,7 +126,7 @@ def from_json(cls, data: dict) -> Versions: return cls(sorted(versions, key=Version.as_tuple)) def filter( - self, branches: Sequence[str] = (), *, include_security: bool = False + self, branches: None | Sequence[str] = (), *, include_security: bool = False ) -> Sequence[Version]: """Filter the given versions. From 8859c877d84100687e3839c9ec8ded7387f4d730 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 14 Sep 2026 19:56:10 +0300 Subject: [PATCH 3/3] Improve help text --- build_docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_docs.py b/build_docs.py index 28f6600..efc6591 100755 --- a/build_docs.py +++ b/build_docs.py @@ -1135,7 +1135,7 @@ def parse_args() -> argparse.Namespace: "--branches", nargs="*", metavar="3.12", - help="Versions to build (defaults to all maintained branches).", + help="Versions to build (defaults to feature and bugfix branches).", ) parser.add_argument( "--include-security-branches",