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
53 changes: 22 additions & 31 deletions build_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
48 changes: 24 additions & 24 deletions tests/test_build_docs_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,59 @@
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
filtered = versions.filter()

# 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
filtered = versions.filter(["3.13", "3.14"])

# 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=""),
]