diff --git a/build_docs.py b/build_docs.py index 2301918..1efdabf 100755 --- a/build_docs.py +++ b/build_docs.py @@ -58,7 +58,6 @@ import sys from bisect import bisect_left as bisect from contextlib import contextmanager, suppress -from functools import total_ordering from pathlib import Path from string import Template from time import perf_counter, sleep @@ -103,11 +102,23 @@ def __reversed__(self) -> Iterator[Version]: @classmethod def from_json(cls, data: dict) -> Versions: - versions = sorted( - [Version.from_json(name, release) for name, release in data.items()], - key=Version.as_tuple, - ) - return cls(versions) + """Load versions from the devguide's JSON representation.""" + permitted = ", ".join(sorted(Version.STATUSES | Version.SYNONYMS.keys())) + + versions = [] + for name, release in data.items(): + branch = release["branch"] + status = release["status"] + status = Version.SYNONYMS.get(status, status) + if status not in Version.STATUSES: + msg = ( + f"Saw invalid version status {status!r}, " + f"expected to be one of {permitted}." + ) + raise ValueError(msg) + versions.append(Version(name=name, status=status, branch_or_tag=branch)) + + return cls(sorted(versions, key=Version.as_tuple)) def filter(self, branches: Sequence[str] = ()) -> Sequence[Version]: """Filter the given versions. @@ -143,10 +154,14 @@ def setup_indexsidebar(self, current: Version, dest_path: Path) -> None: dest_path.write_text(rendered_template, encoding="UTF-8") -@total_ordering +@dataclasses.dataclass(frozen=True, kw_only=True, slots=True) class Version: """Represents a CPython version and its documentation build dependencies.""" + name: str + status: Literal["EOL", "security-fixes", "stable", "pre-release", "in development"] + branch_or_tag: str + STATUSES = {"EOL", "security-fixes", "stable", "pre-release", "in development"} # Those synonyms map branch status vocabulary found in the devguide @@ -159,33 +174,9 @@ class Version: "prerelease": "pre-release", } - def __init__( - self, name: str, *, status: str, branch_or_tag: str | None = None - ) -> None: - status = self.SYNONYMS.get(status, status) - if status not in self.STATUSES: - raise ValueError( - "Version status expected to be one of: " - f"{', '.join(self.STATUSES | set(self.SYNONYMS.keys()))}, got {status!r}." - ) - self.name = name - self.branch_or_tag = branch_or_tag - self.status = status - - def __repr__(self) -> str: - return f"Version({self.name})" - def __eq__(self, other: Version) -> bool: return self.name == other.name - def __gt__(self, other: Version) -> bool: - return self.as_tuple() > other.as_tuple() - - @classmethod - def from_json(cls, name: str, values: dict) -> Version: - """Loads a version from devguide's json representation.""" - return cls(name, status=values["status"], branch_or_tag=values["branch"]) - @property def requirements(self) -> list[str]: """Generate the right requirements for this version. diff --git a/tests/test_build_docs_versions.py b/tests/test_build_docs_versions.py index 662838e..42b5392 100644 --- a/tests/test_build_docs_versions.py +++ b/tests/test_build_docs_versions.py @@ -4,12 +4,12 @@ def test_filter_default() -> None: # Arrange versions = Versions([ - Version("3.14", status="feature"), - Version("3.13", status="bugfix"), - Version("3.12", status="bugfix"), - Version("3.11", status="security"), - Version("3.10", status="security"), - Version("3.9", status="security"), + 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=""), + 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=""), ]) # Act @@ -17,39 +17,39 @@ def test_filter_default() -> None: # Assert assert filtered == [ - Version("3.14", status="feature"), - Version("3.13", status="bugfix"), - Version("3.12", status="bugfix"), + 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() -> None: # Arrange versions = Versions([ - Version("3.14", status="feature"), - Version("3.13", status="bugfix"), - Version("3.12", status="bugfix"), - Version("3.11", status="security"), - Version("3.10", status="security"), - Version("3.9", status="security"), + 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=""), + 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=""), ]) # Act filtered = versions.filter(["3.13"]) # Assert - assert filtered == [Version("3.13", status="security")] + assert filtered == [Version(name="3.13", status="security-fixes", branch_or_tag="")] def test_filter_multiple() -> None: # Arrange versions = Versions([ - Version("3.14", status="feature"), - Version("3.13", status="bugfix"), - Version("3.12", status="bugfix"), - Version("3.11", status="security"), - Version("3.10", status="security"), - Version("3.9", status="security"), + 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=""), + 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=""), ]) # Act @@ -57,6 +57,6 @@ def test_filter_multiple() -> None: # Assert assert filtered == [ - Version("3.14", status="feature"), - Version("3.13", status="security"), + Version(name="3.14", status="in development", branch_or_tag=""), + Version(name="3.13", status="security-fixes", branch_or_tag=""), ]