Skip to content

Commit 3b3fe28

Browse files
committed
Add more type hints
1 parent feef5f1 commit 3b3fe28

File tree

1 file changed

+61
-48
lines changed

1 file changed

+61
-48
lines changed

build_docs.py

+61-48
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272

7373
TYPE_CHECKING = False
7474
if TYPE_CHECKING:
75-
from collections.abc import Iterator, Sequence, Set
75+
from collections.abc import Collection, Iterator, Sequence, Set
7676
from typing import Literal
7777

7878
try:
@@ -101,7 +101,7 @@ def __reversed__(self) -> Iterator[Version]:
101101
return reversed(self._seq)
102102

103103
@classmethod
104-
def from_json(cls, data) -> Versions:
104+
def from_json(cls, data: dict) -> Versions:
105105
versions = sorted(
106106
[Version.from_json(name, release) for name, release in data.items()],
107107
key=Version.as_tuple,
@@ -158,7 +158,9 @@ class Version:
158158
"prerelease": "pre-release",
159159
}
160160

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:
162164
status = self.SYNONYMS.get(status, status)
163165
if status not in self.STATUSES:
164166
raise ValueError(
@@ -169,22 +171,22 @@ def __init__(self, name, *, status, branch_or_tag=None):
169171
self.branch_or_tag = branch_or_tag
170172
self.status = status
171173

172-
def __repr__(self):
174+
def __repr__(self) -> str:
173175
return f"Version({self.name})"
174176

175-
def __eq__(self, other):
177+
def __eq__(self, other: Version) -> bool:
176178
return self.name == other.name
177179

178-
def __gt__(self, other):
180+
def __gt__(self, other: Version) -> bool:
179181
return self.as_tuple() > other.as_tuple()
180182

181183
@classmethod
182-
def from_json(cls, name, values):
184+
def from_json(cls, name: str, values: dict) -> Version:
183185
"""Loads a version from devguide's json representation."""
184186
return cls(name, status=values["status"], branch_or_tag=values["branch"])
185187

186188
@property
187-
def requirements(self):
189+
def requirements(self) -> list[str]:
188190
"""Generate the right requirements for this version.
189191
190192
Since CPython 3.8 a Doc/requirements.txt file can be used.
@@ -213,9 +215,10 @@ def requirements(self):
213215
return reqs + ["sphinx==2.3.1"]
214216
if self.name == "3.5":
215217
return reqs + ["sphinx==1.8.4"]
218+
raise ValueError("unreachable")
216219

217220
@property
218-
def changefreq(self):
221+
def changefreq(self) -> str:
219222
"""Estimate this version change frequency, for the sitemap."""
220223
return {"EOL": "never", "security-fixes": "yearly"}.get(self.status, "daily")
221224

@@ -224,17 +227,17 @@ def as_tuple(self) -> tuple[int, ...]:
224227
return version_to_tuple(self.name)
225228

226229
@property
227-
def url(self):
230+
def url(self) -> str:
228231
"""The doc URL of this version in production."""
229232
return f"https://docs.python.org/{self.name}/"
230233

231234
@property
232-
def title(self):
235+
def title(self) -> str:
233236
"""The title of this version's doc, for the sidebar."""
234237
return f"Python {self.name} ({self.status})"
235238

236239
@property
237-
def picker_label(self):
240+
def picker_label(self) -> str:
238241
"""Forge the label of a version picker."""
239242
if self.status == "in development":
240243
return f"dev ({self.name})"
@@ -254,7 +257,7 @@ def __reversed__(self) -> Iterator[Language]:
254257
return reversed(self._seq)
255258

256259
@classmethod
257-
def from_json(cls, defaults, languages) -> Languages:
260+
def from_json(cls, defaults: dict, languages: dict) -> Languages:
258261
default_translated_name = defaults.get("translated_name", "")
259262
default_in_prod = defaults.get("in_prod", True)
260263
default_sphinxopts = defaults.get("sphinxopts", [])
@@ -290,17 +293,19 @@ class Language:
290293
html_only: bool = False
291294

292295
@property
293-
def tag(self):
296+
def tag(self) -> str:
294297
return self.iso639_tag.replace("_", "-").lower()
295298

296299
@property
297-
def switcher_label(self):
300+
def switcher_label(self) -> str:
298301
if self.translated_name:
299302
return f"{self.name} | {self.translated_name}"
300303
return self.name
301304

302305

303-
def run(cmd, cwd=None) -> subprocess.CompletedProcess:
306+
def run(
307+
cmd: Sequence[str | Path], cwd: Path | None = None
308+
) -> subprocess.CompletedProcess:
304309
"""Like subprocess.run, with logging before and after the command execution."""
305310
cmd = list(map(str, cmd))
306311
cmdstring = shlex.join(cmd)
@@ -326,7 +331,7 @@ def run(cmd, cwd=None) -> subprocess.CompletedProcess:
326331
return result
327332

328333

329-
def run_with_logging(cmd, cwd=None):
334+
def run_with_logging(cmd: Sequence[str | Path], cwd: Path | None = None) -> None:
330335
"""Like subprocess.check_call, with logging before the command execution."""
331336
cmd = list(map(str, cmd))
332337
logging.debug("Run: '%s'", shlex.join(cmd))
@@ -348,13 +353,13 @@ def run_with_logging(cmd, cwd=None):
348353
raise subprocess.CalledProcessError(return_code, cmd[0])
349354

350355

351-
def changed_files(left, right):
356+
def changed_files(left: Path, right: Path) -> list[str]:
352357
"""Compute a list of different files between left and right, recursively.
353358
Resulting paths are relative to left.
354359
"""
355360
changed = []
356361

357-
def traverse(dircmp_result):
362+
def traverse(dircmp_result: filecmp.dircmp) -> None:
358363
base = Path(dircmp_result.left).relative_to(left)
359364
for file in dircmp_result.diff_files:
360365
changed.append(str(base / file))
@@ -374,11 +379,11 @@ class Repository:
374379
remote: str
375380
directory: Path
376381

377-
def run(self, *args):
382+
def run(self, *args: str) -> subprocess.CompletedProcess:
378383
"""Run git command in the clone repository."""
379384
return run(("git", "-C", self.directory) + args)
380385

381-
def get_ref(self, pattern):
386+
def get_ref(self, pattern: str) -> str:
382387
"""Return the reference of a given tag or branch."""
383388
try:
384389
# Maybe it's a branch
@@ -387,7 +392,7 @@ def get_ref(self, pattern):
387392
# Maybe it's a tag
388393
return self.run("show-ref", "-s", "tags/" + pattern).stdout.strip()
389394

390-
def fetch(self):
395+
def fetch(self) -> subprocess.CompletedProcess:
391396
"""Try (and retry) to run git fetch."""
392397
try:
393398
return self.run("fetch")
@@ -396,12 +401,12 @@ def fetch(self):
396401
sleep(5)
397402
return self.run("fetch")
398403

399-
def switch(self, branch_or_tag):
404+
def switch(self, branch_or_tag: str) -> None:
400405
"""Reset and cleans the repository to the given branch or tag."""
401406
self.run("reset", "--hard", self.get_ref(branch_or_tag), "--")
402407
self.run("clean", "-dfqx")
403408

404-
def clone(self):
409+
def clone(self) -> bool:
405410
"""Maybe clone the repository, if not already cloned."""
406411
if (self.directory / ".git").is_dir():
407412
return False # Already cloned
@@ -410,21 +415,23 @@ def clone(self):
410415
run(["git", "clone", self.remote, self.directory])
411416
return True
412417

413-
def update(self):
418+
def update(self) -> None:
414419
self.clone() or self.fetch()
415420

416421

417-
def version_to_tuple(version) -> tuple[int, ...]:
422+
def version_to_tuple(version: str) -> tuple[int, ...]:
418423
"""Transform a version string to a tuple, for easy comparisons."""
419424
return tuple(int(part) for part in version.split("."))
420425

421426

422-
def tuple_to_version(version_tuple):
427+
def tuple_to_version(version_tuple: tuple[int, ...]) -> str:
423428
"""Reverse version_to_tuple."""
424429
return ".".join(str(part) for part in version_tuple)
425430

426431

427-
def locate_nearest_version(available_versions, target_version):
432+
def locate_nearest_version(
433+
available_versions: Collection[str], target_version: str
434+
) -> str:
428435
"""Look for the nearest version of target_version in available_versions.
429436
Versions are to be given as tuples, like (3, 7) for 3.7.
430437
@@ -468,7 +475,7 @@ def edit(file: Path):
468475
temporary.rename(file)
469476

470477

471-
def setup_switchers(versions: Versions, languages: Languages, html_root: Path):
478+
def setup_switchers(versions: Versions, languages: Languages, html_root: Path) -> None:
472479
"""Setup cross-links between CPython versions:
473480
- Cross-link various languages in a language switcher
474481
- Cross-link various versions in a version switcher
@@ -499,12 +506,12 @@ def setup_switchers(versions: Versions, languages: Languages, html_root: Path):
499506
ofile.write(line)
500507

501508

502-
def head(text, lines=10):
509+
def head(text: str, lines: int = 10) -> str:
503510
"""Return the first *lines* lines from the given text."""
504511
return "\n".join(text.split("\n")[:lines])
505512

506513

507-
def version_info():
514+
def version_info() -> None:
508515
"""Handler for --version."""
509516
try:
510517
platex_version = head(
@@ -554,15 +561,15 @@ class DocBuilder:
554561
theme: Path
555562

556563
@property
557-
def html_only(self):
564+
def html_only(self) -> bool:
558565
return (
559566
self.select_output in {"only-html", "only-html-en"}
560567
or self.quick
561568
or self.language.html_only
562569
)
563570

564571
@property
565-
def includes_html(self):
572+
def includes_html(self) -> bool:
566573
"""Does the build we are running include HTML output?"""
567574
return self.select_output != "no-html"
568575

@@ -601,12 +608,12 @@ def checkout(self) -> Path:
601608
"""Path to CPython git clone."""
602609
return self.build_root / _checkout_name(self.select_output)
603610

604-
def clone_translation(self):
611+
def clone_translation(self) -> None:
605612
self.translation_repo.update()
606613
self.translation_repo.switch(self.translation_branch)
607614

608615
@property
609-
def translation_repo(self):
616+
def translation_repo(self) -> Repository:
610617
"""See PEP 545 for translations repository naming convention."""
611618

612619
locale_repo = f"https://github.com/python/python-docs-{self.language.tag}.git"
@@ -620,7 +627,7 @@ def translation_repo(self):
620627
return Repository(locale_repo, locale_clone_dir)
621628

622629
@property
623-
def translation_branch(self):
630+
def translation_branch(self) -> str:
624631
"""Some CPython versions may be untranslated, being either too old or
625632
too new.
626633
@@ -633,7 +640,7 @@ def translation_branch(self):
633640
branches = re.findall(r"/([0-9]+\.[0-9]+)$", remote_branches, re.M)
634641
return locate_nearest_version(branches, self.version.name)
635642

636-
def build(self):
643+
def build(self) -> None:
637644
"""Build this version/language doc."""
638645
logging.info("Build start.")
639646
start_time = perf_counter()
@@ -702,7 +709,7 @@ def build(self):
702709
)
703710
logging.info("Build done (%s).", format_seconds(perf_counter() - start_time))
704711

705-
def build_venv(self):
712+
def build_venv(self) -> None:
706713
"""Build a venv for the specific Python version.
707714
708715
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:
819826
"Publishing done (%s).", format_seconds(perf_counter() - start_time)
820827
)
821828

822-
def should_rebuild(self, force: bool):
829+
def should_rebuild(self, force: bool) -> str | Literal[False]:
823830
state = self.load_state()
824831
if not state:
825832
logging.info("Should rebuild: no previous state found.")
@@ -865,7 +872,9 @@ def load_state(self) -> dict:
865872
except (KeyError, FileNotFoundError):
866873
return {}
867874

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:
869878
"""Save current CPython sha1 and current translation sha1.
870879
871880
Using this we can deduce if a rebuild is needed or not.
@@ -911,14 +920,16 @@ def format_seconds(seconds: float) -> str:
911920
case h, m, s:
912921
return f"{h}h {m}m {s}s"
913922

923+
raise ValueError("unreachable")
924+
914925

915926
def _checkout_name(select_output: str | None) -> str:
916927
if select_output is not None:
917928
return f"cpython-{select_output}"
918929
return "cpython"
919930

920931

921-
def main():
932+
def main() -> None:
922933
"""Script entry point."""
923934
args = parse_args()
924935
setup_logging(args.log_directory, args.select_output)
@@ -934,7 +945,7 @@ def main():
934945
build_docs_with_lock(args, "build_docs_html_en.lock")
935946

936947

937-
def parse_args():
948+
def parse_args() -> argparse.Namespace:
938949
"""Parse command-line arguments."""
939950

940951
parser = argparse.ArgumentParser(
@@ -1028,7 +1039,7 @@ def parse_args():
10281039
return args
10291040

10301041

1031-
def setup_logging(log_directory: Path, select_output: str | None):
1042+
def setup_logging(log_directory: Path, select_output: str | None) -> None:
10321043
"""Setup logging to stderr if run by a human, or to a file if run from a cron."""
10331044
log_format = "%(asctime)s %(levelname)s: %(message)s"
10341045
if sys.stderr.isatty():
@@ -1174,7 +1185,9 @@ def parse_languages_from_config() -> Languages:
11741185
return Languages.from_json(config["defaults"], config["languages"])
11751186

11761187

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:
11781191
"""Build a sitemap with all live versions and translations."""
11791192
if not www_root.exists():
11801193
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
11891202
run(["chgrp", group, sitemap_path])
11901203

11911204

1192-
def build_404(www_root: Path, group):
1205+
def build_404(www_root: Path, group: str) -> None:
11931206
"""Build a nice 404 error page to display in case PDFs are not built yet."""
11941207
if not www_root.exists():
11951208
logging.info("Skipping 404 page generation (www root does not even exist).")
@@ -1203,8 +1216,8 @@ def build_404(www_root: Path, group):
12031216

12041217
def copy_robots_txt(
12051218
www_root: Path,
1206-
group,
1207-
skip_cache_invalidation,
1219+
group: str,
1220+
skip_cache_invalidation: bool,
12081221
http: urllib3.PoolManager,
12091222
) -> None:
12101223
"""Copy robots.txt to www_root."""
@@ -1322,7 +1335,7 @@ def proofread_canonicals(
13221335
)
13231336

13241337

1325-
def _check_canonical_rel(file: Path, www_root: Path):
1338+
def _check_canonical_rel(file: Path, www_root: Path) -> Path | None:
13261339
# Check for a canonical relation link in the HTML.
13271340
# If one exists, ensure that the target exists
13281341
# or otherwise remove the canonical link element.

0 commit comments

Comments
 (0)