Skip to content

Commit 044aba7

Browse files
authored
Create a dataclass for Versions (#254)
1 parent e096701 commit 044aba7

File tree

1 file changed

+69
-54
lines changed

1 file changed

+69
-54
lines changed

build_docs.py

+69-54
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,9 @@
5050

5151
TYPE_CHECKING = False
5252
if TYPE_CHECKING:
53-
from collections.abc import Sequence
53+
from collections.abc import Iterator, Sequence
5454
from typing import Literal, TypeAlias
5555

56-
Versions: TypeAlias = Sequence["Version"]
5756
Languages: TypeAlias = Sequence["Language"]
5857

5958
try:
@@ -71,6 +70,57 @@
7170
HERE = Path(__file__).resolve().parent
7271

7372

73+
@dataclass(frozen=True, slots=True)
74+
class Versions:
75+
_seq: Sequence[Version]
76+
77+
def __iter__(self) -> Iterator[Version]:
78+
return iter(self._seq)
79+
80+
def __reversed__(self) -> Iterator[Version]:
81+
return reversed(self._seq)
82+
83+
@classmethod
84+
def from_json(cls, data) -> Versions:
85+
versions = sorted(
86+
[Version.from_json(name, release) for name, release in data.items()],
87+
key=Version.as_tuple,
88+
)
89+
return cls(versions)
90+
91+
def filter(self, branch: str = "") -> Sequence[Version]:
92+
"""Filter the given versions.
93+
94+
If *branch* is given, only *versions* matching *branch* are returned.
95+
96+
Else all live versions are returned (this means no EOL and no
97+
security-fixes branches).
98+
"""
99+
if branch:
100+
return [v for v in self if branch in (v.name, v.branch_or_tag)]
101+
return [v for v in self if v.status not in {"EOL", "security-fixes"}]
102+
103+
@property
104+
def current_stable(self) -> Version:
105+
"""Find the current stable CPython version."""
106+
return max((v for v in self if v.status == "stable"), key=Version.as_tuple)
107+
108+
@property
109+
def current_dev(self) -> Version:
110+
"""Find the current CPython version in development."""
111+
return max(self, key=Version.as_tuple)
112+
113+
def setup_indexsidebar(self, current: Version, dest_path: Path) -> None:
114+
"""Build indexsidebar.html for Sphinx."""
115+
template_path = HERE / "templates" / "indexsidebar.html"
116+
template = jinja2.Template(template_path.read_text(encoding="UTF-8"))
117+
rendered_template = template.render(
118+
current_version=current,
119+
versions=list(reversed(self)),
120+
)
121+
dest_path.write_text(rendered_template, encoding="UTF-8")
122+
123+
74124
@total_ordering
75125
class Version:
76126
"""Represents a CPython version and its documentation build dependencies."""
@@ -101,6 +151,17 @@ def __init__(self, name, *, status, branch_or_tag=None):
101151
def __repr__(self):
102152
return f"Version({self.name})"
103153

154+
def __eq__(self, other):
155+
return self.name == other.name
156+
157+
def __gt__(self, other):
158+
return self.as_tuple() > other.as_tuple()
159+
160+
@classmethod
161+
def from_json(cls, name, values):
162+
"""Loads a version from devguide's json representation."""
163+
return cls(name, status=values["status"], branch_or_tag=values["branch"])
164+
104165
@property
105166
def requirements(self):
106167
"""Generate the right requirements for this version.
@@ -144,29 +205,6 @@ def title(self):
144205
"""The title of this version's doc, for the sidebar."""
145206
return f"Python {self.name} ({self.status})"
146207

147-
@staticmethod
148-
def filter(versions, branch=None):
149-
"""Filter the given versions.
150-
151-
If *branch* is given, only *versions* matching *branch* are returned.
152-
153-
Else all live versions are returned (this means no EOL and no
154-
security-fixes branches).
155-
"""
156-
if branch:
157-
return [v for v in versions if branch in (v.name, v.branch_or_tag)]
158-
return [v for v in versions if v.status not in ("EOL", "security-fixes")]
159-
160-
@staticmethod
161-
def current_stable(versions):
162-
"""Find the current stable CPython version."""
163-
return max((v for v in versions if v.status == "stable"), key=Version.as_tuple)
164-
165-
@staticmethod
166-
def current_dev(versions):
167-
"""Find the current CPython version in development."""
168-
return max(versions, key=Version.as_tuple)
169-
170208
@property
171209
def picker_label(self):
172210
"""Forge the label of a version picker."""
@@ -176,27 +214,6 @@ def picker_label(self):
176214
return f"pre ({self.name})"
177215
return self.name
178216

179-
def setup_indexsidebar(self, versions: Versions, dest_path: Path):
180-
"""Build indexsidebar.html for Sphinx."""
181-
template_path = HERE / "templates" / "indexsidebar.html"
182-
template = jinja2.Template(template_path.read_text(encoding="UTF-8"))
183-
rendered_template = template.render(
184-
current_version=self,
185-
versions=versions[::-1],
186-
)
187-
dest_path.write_text(rendered_template, encoding="UTF-8")
188-
189-
@classmethod
190-
def from_json(cls, name, values):
191-
"""Loads a version from devguide's json representation."""
192-
return cls(name, status=values["status"], branch_or_tag=values["branch"])
193-
194-
def __eq__(self, other):
195-
return self.name == other.name
196-
197-
def __gt__(self, other):
198-
return self.as_tuple() > other.as_tuple()
199-
200217

201218
@dataclass(order=True, frozen=True, kw_only=True)
202219
class Language:
@@ -619,8 +636,8 @@ def build(self):
619636
+ ([""] if sys.platform == "darwin" else [])
620637
+ ["s/ *-A switchers=1//", self.checkout / "Doc" / "Makefile"]
621638
)
622-
self.version.setup_indexsidebar(
623-
self.versions,
639+
self.versions.setup_indexsidebar(
640+
self.version,
624641
self.checkout / "Doc" / "tools" / "templates" / "indexsidebar.html",
625642
)
626643
run_with_logging(
@@ -1013,7 +1030,7 @@ def build_docs(args) -> bool:
10131030
# This runs languages in config.toml order and versions newest first.
10141031
todo = [
10151032
(version, language)
1016-
for version in Version.filter(versions, args.branch)
1033+
for version in versions.filter(args.branch)
10171034
for language in reversed(Language.filter(languages, args.languages))
10181035
]
10191036
del args.branch
@@ -1081,9 +1098,7 @@ def parse_versions_from_devguide(http: urllib3.PoolManager) -> Versions:
10811098
"python/devguide/main/include/release-cycle.json",
10821099
timeout=30,
10831100
).json()
1084-
versions = [Version.from_json(name, release) for name, release in releases.items()]
1085-
versions.sort(key=Version.as_tuple)
1086-
return versions
1101+
return Versions.from_json(releases)
10871102

10881103

10891104
def parse_languages_from_config() -> Languages:
@@ -1170,7 +1185,7 @@ def major_symlinks(
11701185
- /es/3/ → /es/3.9/
11711186
"""
11721187
logging.info("Creating major version symlinks...")
1173-
current_stable = Version.current_stable(versions).name
1188+
current_stable = versions.current_stable.name
11741189
for language in languages:
11751190
symlink(
11761191
www_root,
@@ -1200,7 +1215,7 @@ def dev_symlink(
12001215
- /es/dev/ → /es/3.11/
12011216
"""
12021217
logging.info("Creating development version symlinks...")
1203-
current_dev = Version.current_dev(versions).name
1218+
current_dev = versions.current_dev.name
12041219
for language in languages:
12051220
symlink(
12061221
www_root,

0 commit comments

Comments
 (0)