72
72
73
73
TYPE_CHECKING = False
74
74
if TYPE_CHECKING :
75
- from collections .abc import Iterator , Sequence , Set
75
+ from collections .abc import Collection , Iterator , Sequence , Set
76
76
from typing import Literal
77
77
78
78
try :
@@ -101,7 +101,7 @@ def __reversed__(self) -> Iterator[Version]:
101
101
return reversed (self ._seq )
102
102
103
103
@classmethod
104
- def from_json (cls , data ) -> Versions :
104
+ def from_json (cls , data : dict ) -> Versions :
105
105
versions = sorted (
106
106
[Version .from_json (name , release ) for name , release in data .items ()],
107
107
key = Version .as_tuple ,
@@ -158,7 +158,9 @@ class Version:
158
158
"prerelease" : "pre-release" ,
159
159
}
160
160
161
- def __init__ (self , name , * , status , branch_or_tag = None ):
161
+ def __init__ (
162
+ self , name : str , * , status : str , branch_or_tag : str | None = None
163
+ ) -> None :
162
164
status = self .SYNONYMS .get (status , status )
163
165
if status not in self .STATUSES :
164
166
raise ValueError (
@@ -169,22 +171,22 @@ def __init__(self, name, *, status, branch_or_tag=None):
169
171
self .branch_or_tag = branch_or_tag
170
172
self .status = status
171
173
172
- def __repr__ (self ):
174
+ def __repr__ (self ) -> str :
173
175
return f"Version({ self .name } )"
174
176
175
- def __eq__ (self , other ) :
177
+ def __eq__ (self , other : Version ) -> bool :
176
178
return self .name == other .name
177
179
178
- def __gt__ (self , other ) :
180
+ def __gt__ (self , other : Version ) -> bool :
179
181
return self .as_tuple () > other .as_tuple ()
180
182
181
183
@classmethod
182
- def from_json (cls , name , values ) :
184
+ def from_json (cls , name : str , values : dict ) -> Version :
183
185
"""Loads a version from devguide's json representation."""
184
186
return cls (name , status = values ["status" ], branch_or_tag = values ["branch" ])
185
187
186
188
@property
187
- def requirements (self ):
189
+ def requirements (self ) -> list [ str ] :
188
190
"""Generate the right requirements for this version.
189
191
190
192
Since CPython 3.8 a Doc/requirements.txt file can be used.
@@ -213,9 +215,10 @@ def requirements(self):
213
215
return reqs + ["sphinx==2.3.1" ]
214
216
if self .name == "3.5" :
215
217
return reqs + ["sphinx==1.8.4" ]
218
+ raise ValueError ("unreachable" )
216
219
217
220
@property
218
- def changefreq (self ):
221
+ def changefreq (self ) -> str :
219
222
"""Estimate this version change frequency, for the sitemap."""
220
223
return {"EOL" : "never" , "security-fixes" : "yearly" }.get (self .status , "daily" )
221
224
@@ -224,17 +227,17 @@ def as_tuple(self) -> tuple[int, ...]:
224
227
return version_to_tuple (self .name )
225
228
226
229
@property
227
- def url (self ):
230
+ def url (self ) -> str :
228
231
"""The doc URL of this version in production."""
229
232
return f"https://docs.python.org/{ self .name } /"
230
233
231
234
@property
232
- def title (self ):
235
+ def title (self ) -> str :
233
236
"""The title of this version's doc, for the sidebar."""
234
237
return f"Python { self .name } ({ self .status } )"
235
238
236
239
@property
237
- def picker_label (self ):
240
+ def picker_label (self ) -> str :
238
241
"""Forge the label of a version picker."""
239
242
if self .status == "in development" :
240
243
return f"dev ({ self .name } )"
@@ -254,7 +257,7 @@ def __reversed__(self) -> Iterator[Language]:
254
257
return reversed (self ._seq )
255
258
256
259
@classmethod
257
- def from_json (cls , defaults , languages ) -> Languages :
260
+ def from_json (cls , defaults : dict , languages : dict ) -> Languages :
258
261
default_translated_name = defaults .get ("translated_name" , "" )
259
262
default_in_prod = defaults .get ("in_prod" , True )
260
263
default_sphinxopts = defaults .get ("sphinxopts" , [])
@@ -290,17 +293,19 @@ class Language:
290
293
html_only : bool = False
291
294
292
295
@property
293
- def tag (self ):
296
+ def tag (self ) -> str :
294
297
return self .iso639_tag .replace ("_" , "-" ).lower ()
295
298
296
299
@property
297
- def switcher_label (self ):
300
+ def switcher_label (self ) -> str :
298
301
if self .translated_name :
299
302
return f"{ self .name } | { self .translated_name } "
300
303
return self .name
301
304
302
305
303
- def run (cmd , cwd = None ) -> subprocess .CompletedProcess :
306
+ def run (
307
+ cmd : Sequence [str | Path ], cwd : Path | None = None
308
+ ) -> subprocess .CompletedProcess :
304
309
"""Like subprocess.run, with logging before and after the command execution."""
305
310
cmd = list (map (str , cmd ))
306
311
cmdstring = shlex .join (cmd )
@@ -326,7 +331,7 @@ def run(cmd, cwd=None) -> subprocess.CompletedProcess:
326
331
return result
327
332
328
333
329
- def run_with_logging (cmd , cwd = None ):
334
+ def run_with_logging (cmd : Sequence [ str | Path ] , cwd : Path | None = None ) -> None :
330
335
"""Like subprocess.check_call, with logging before the command execution."""
331
336
cmd = list (map (str , cmd ))
332
337
logging .debug ("Run: '%s'" , shlex .join (cmd ))
@@ -348,13 +353,13 @@ def run_with_logging(cmd, cwd=None):
348
353
raise subprocess .CalledProcessError (return_code , cmd [0 ])
349
354
350
355
351
- def changed_files (left , right ) :
356
+ def changed_files (left : Path , right : Path ) -> list [ str ] :
352
357
"""Compute a list of different files between left and right, recursively.
353
358
Resulting paths are relative to left.
354
359
"""
355
360
changed = []
356
361
357
- def traverse (dircmp_result ) :
362
+ def traverse (dircmp_result : filecmp . dircmp ) -> None :
358
363
base = Path (dircmp_result .left ).relative_to (left )
359
364
for file in dircmp_result .diff_files :
360
365
changed .append (str (base / file ))
@@ -374,11 +379,11 @@ class Repository:
374
379
remote : str
375
380
directory : Path
376
381
377
- def run (self , * args ) :
382
+ def run (self , * args : str ) -> subprocess . CompletedProcess :
378
383
"""Run git command in the clone repository."""
379
384
return run (("git" , "-C" , self .directory ) + args )
380
385
381
- def get_ref (self , pattern ) :
386
+ def get_ref (self , pattern : str ) -> str :
382
387
"""Return the reference of a given tag or branch."""
383
388
try :
384
389
# Maybe it's a branch
@@ -387,7 +392,7 @@ def get_ref(self, pattern):
387
392
# Maybe it's a tag
388
393
return self .run ("show-ref" , "-s" , "tags/" + pattern ).stdout .strip ()
389
394
390
- def fetch (self ):
395
+ def fetch (self ) -> subprocess . CompletedProcess :
391
396
"""Try (and retry) to run git fetch."""
392
397
try :
393
398
return self .run ("fetch" )
@@ -396,12 +401,12 @@ def fetch(self):
396
401
sleep (5 )
397
402
return self .run ("fetch" )
398
403
399
- def switch (self , branch_or_tag ) :
404
+ def switch (self , branch_or_tag : str ) -> None :
400
405
"""Reset and cleans the repository to the given branch or tag."""
401
406
self .run ("reset" , "--hard" , self .get_ref (branch_or_tag ), "--" )
402
407
self .run ("clean" , "-dfqx" )
403
408
404
- def clone (self ):
409
+ def clone (self ) -> bool :
405
410
"""Maybe clone the repository, if not already cloned."""
406
411
if (self .directory / ".git" ).is_dir ():
407
412
return False # Already cloned
@@ -410,21 +415,23 @@ def clone(self):
410
415
run (["git" , "clone" , self .remote , self .directory ])
411
416
return True
412
417
413
- def update (self ):
418
+ def update (self ) -> None :
414
419
self .clone () or self .fetch ()
415
420
416
421
417
- def version_to_tuple (version ) -> tuple [int , ...]:
422
+ def version_to_tuple (version : str ) -> tuple [int , ...]:
418
423
"""Transform a version string to a tuple, for easy comparisons."""
419
424
return tuple (int (part ) for part in version .split ("." ))
420
425
421
426
422
- def tuple_to_version (version_tuple ) :
427
+ def tuple_to_version (version_tuple : tuple [ int , ...]) -> str :
423
428
"""Reverse version_to_tuple."""
424
429
return "." .join (str (part ) for part in version_tuple )
425
430
426
431
427
- def locate_nearest_version (available_versions , target_version ):
432
+ def locate_nearest_version (
433
+ available_versions : Collection [str ], target_version : str
434
+ ) -> str :
428
435
"""Look for the nearest version of target_version in available_versions.
429
436
Versions are to be given as tuples, like (3, 7) for 3.7.
430
437
@@ -468,7 +475,7 @@ def edit(file: Path):
468
475
temporary .rename (file )
469
476
470
477
471
- def setup_switchers (versions : Versions , languages : Languages , html_root : Path ):
478
+ def setup_switchers (versions : Versions , languages : Languages , html_root : Path ) -> None :
472
479
"""Setup cross-links between CPython versions:
473
480
- Cross-link various languages in a language switcher
474
481
- Cross-link various versions in a version switcher
@@ -499,12 +506,12 @@ def setup_switchers(versions: Versions, languages: Languages, html_root: Path):
499
506
ofile .write (line )
500
507
501
508
502
- def head (text , lines = 10 ):
509
+ def head (text : str , lines : int = 10 ) -> str :
503
510
"""Return the first *lines* lines from the given text."""
504
511
return "\n " .join (text .split ("\n " )[:lines ])
505
512
506
513
507
- def version_info ():
514
+ def version_info () -> None :
508
515
"""Handler for --version."""
509
516
try :
510
517
platex_version = head (
@@ -554,15 +561,15 @@ class DocBuilder:
554
561
theme : Path
555
562
556
563
@property
557
- def html_only (self ):
564
+ def html_only (self ) -> bool :
558
565
return (
559
566
self .select_output in {"only-html" , "only-html-en" }
560
567
or self .quick
561
568
or self .language .html_only
562
569
)
563
570
564
571
@property
565
- def includes_html (self ):
572
+ def includes_html (self ) -> bool :
566
573
"""Does the build we are running include HTML output?"""
567
574
return self .select_output != "no-html"
568
575
@@ -601,12 +608,12 @@ def checkout(self) -> Path:
601
608
"""Path to CPython git clone."""
602
609
return self .build_root / _checkout_name (self .select_output )
603
610
604
- def clone_translation (self ):
611
+ def clone_translation (self ) -> None :
605
612
self .translation_repo .update ()
606
613
self .translation_repo .switch (self .translation_branch )
607
614
608
615
@property
609
- def translation_repo (self ):
616
+ def translation_repo (self ) -> Repository :
610
617
"""See PEP 545 for translations repository naming convention."""
611
618
612
619
locale_repo = f"https://github.com/python/python-docs-{ self .language .tag } .git"
@@ -620,7 +627,7 @@ def translation_repo(self):
620
627
return Repository (locale_repo , locale_clone_dir )
621
628
622
629
@property
623
- def translation_branch (self ):
630
+ def translation_branch (self ) -> str :
624
631
"""Some CPython versions may be untranslated, being either too old or
625
632
too new.
626
633
@@ -633,7 +640,7 @@ def translation_branch(self):
633
640
branches = re .findall (r"/([0-9]+\.[0-9]+)$" , remote_branches , re .M )
634
641
return locate_nearest_version (branches , self .version .name )
635
642
636
- def build (self ):
643
+ def build (self ) -> None :
637
644
"""Build this version/language doc."""
638
645
logging .info ("Build start." )
639
646
start_time = perf_counter ()
@@ -702,7 +709,7 @@ def build(self):
702
709
)
703
710
logging .info ("Build done (%s)." , format_seconds (perf_counter () - start_time ))
704
711
705
- def build_venv (self ):
712
+ def build_venv (self ) -> None :
706
713
"""Build a venv for the specific Python version.
707
714
708
715
So we can reuse them from builds to builds, while they contain
@@ -819,7 +826,7 @@ def copy_build_to_webroot(self, http: urllib3.PoolManager) -> None:
819
826
"Publishing done (%s)." , format_seconds (perf_counter () - start_time )
820
827
)
821
828
822
- def should_rebuild (self , force : bool ):
829
+ def should_rebuild (self , force : bool ) -> str | Literal [ False ] :
823
830
state = self .load_state ()
824
831
if not state :
825
832
logging .info ("Should rebuild: no previous state found." )
@@ -865,7 +872,9 @@ def load_state(self) -> dict:
865
872
except (KeyError , FileNotFoundError ):
866
873
return {}
867
874
868
- def save_state (self , build_start : dt .datetime , build_duration : float , trigger : str ):
875
+ def save_state (
876
+ self , build_start : dt .datetime , build_duration : float , trigger : str
877
+ ) -> None :
869
878
"""Save current CPython sha1 and current translation sha1.
870
879
871
880
Using this we can deduce if a rebuild is needed or not.
@@ -911,14 +920,16 @@ def format_seconds(seconds: float) -> str:
911
920
case h , m , s :
912
921
return f"{ h } h { m } m { s } s"
913
922
923
+ raise ValueError ("unreachable" )
924
+
914
925
915
926
def _checkout_name (select_output : str | None ) -> str :
916
927
if select_output is not None :
917
928
return f"cpython-{ select_output } "
918
929
return "cpython"
919
930
920
931
921
- def main ():
932
+ def main () -> None :
922
933
"""Script entry point."""
923
934
args = parse_args ()
924
935
setup_logging (args .log_directory , args .select_output )
@@ -934,7 +945,7 @@ def main():
934
945
build_docs_with_lock (args , "build_docs_html_en.lock" )
935
946
936
947
937
- def parse_args ():
948
+ def parse_args () -> argparse . Namespace :
938
949
"""Parse command-line arguments."""
939
950
940
951
parser = argparse .ArgumentParser (
@@ -1028,7 +1039,7 @@ def parse_args():
1028
1039
return args
1029
1040
1030
1041
1031
- def setup_logging (log_directory : Path , select_output : str | None ):
1042
+ def setup_logging (log_directory : Path , select_output : str | None ) -> None :
1032
1043
"""Setup logging to stderr if run by a human, or to a file if run from a cron."""
1033
1044
log_format = "%(asctime)s %(levelname)s: %(message)s"
1034
1045
if sys .stderr .isatty ():
@@ -1174,7 +1185,9 @@ def parse_languages_from_config() -> Languages:
1174
1185
return Languages .from_json (config ["defaults" ], config ["languages" ])
1175
1186
1176
1187
1177
- def build_sitemap (versions : Versions , languages : Languages , www_root : Path , group ):
1188
+ def build_sitemap (
1189
+ versions : Versions , languages : Languages , www_root : Path , group : str
1190
+ ) -> None :
1178
1191
"""Build a sitemap with all live versions and translations."""
1179
1192
if not www_root .exists ():
1180
1193
logging .info ("Skipping sitemap generation (www root does not even exist)." )
@@ -1189,7 +1202,7 @@ def build_sitemap(versions: Versions, languages: Languages, www_root: Path, grou
1189
1202
run (["chgrp" , group , sitemap_path ])
1190
1203
1191
1204
1192
- def build_404 (www_root : Path , group ) :
1205
+ def build_404 (www_root : Path , group : str ) -> None :
1193
1206
"""Build a nice 404 error page to display in case PDFs are not built yet."""
1194
1207
if not www_root .exists ():
1195
1208
logging .info ("Skipping 404 page generation (www root does not even exist)." )
@@ -1203,8 +1216,8 @@ def build_404(www_root: Path, group):
1203
1216
1204
1217
def copy_robots_txt (
1205
1218
www_root : Path ,
1206
- group ,
1207
- skip_cache_invalidation ,
1219
+ group : str ,
1220
+ skip_cache_invalidation : bool ,
1208
1221
http : urllib3 .PoolManager ,
1209
1222
) -> None :
1210
1223
"""Copy robots.txt to www_root."""
@@ -1322,7 +1335,7 @@ def proofread_canonicals(
1322
1335
)
1323
1336
1324
1337
1325
- def _check_canonical_rel (file : Path , www_root : Path ):
1338
+ def _check_canonical_rel (file : Path , www_root : Path ) -> Path | None :
1326
1339
# Check for a canonical relation link in the HTML.
1327
1340
# If one exists, ensure that the target exists
1328
1341
# or otherwise remove the canonical link element.
0 commit comments