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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
23 changes: 17 additions & 6 deletions build_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: None | 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:
Expand Down Expand Up @@ -1132,7 +1135,12 @@ 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",
action="store_true",
help="Also build security-fixes branches (ignored when --branches is given).",
)
parser.add_argument(
"-r",
Expand Down Expand Up @@ -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
Expand Down
52 changes: 25 additions & 27 deletions tests/test_build_docs_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=""),
])


Expand All @@ -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"


Expand Down Expand Up @@ -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