5050
5151TYPE_CHECKING = False
5252if 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
5958try :
7170HERE = 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
75125class 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 )
202219class 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
10891104def 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