Skip to content

Commit 0237ee6

Browse files
committed
Add the BuildMetadata class
1 parent c1be80e commit 0237ee6

File tree

1 file changed

+108
-54
lines changed

1 file changed

+108
-54
lines changed

build_docs.py

+108-54
Original file line numberDiff line numberDiff line change
@@ -294,21 +294,82 @@ class Language:
294294
def tag(self) -> str:
295295
return self.iso639_tag.replace("_", "-").lower()
296296

297-
@property
298-
def is_translation(self) -> bool:
299-
return self.tag != "en"
300-
301-
@property
302-
def locale_repo_url(self) -> str:
303-
return f"https://github.com/python/python-docs-{self.tag}.git"
304-
305297
@property
306298
def switcher_label(self) -> str:
307299
if self.translated_name:
308300
return f"{self.name} | {self.translated_name}"
309301
return self.name
310302

311303

304+
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
305+
class BuildMetadata:
306+
_ver: Version
307+
_lang: Language
308+
309+
@property
310+
def sphinxopts(self) -> Sequence[str]:
311+
return self._lang.sphinxopts
312+
313+
@property
314+
def iso639_tag(self) -> str:
315+
return self._lang.iso639_tag
316+
317+
@property
318+
def html_only(self) -> bool:
319+
return self._lang.html_only
320+
321+
@property
322+
def url(self):
323+
"""The URL of this version in production."""
324+
if self.is_translation:
325+
return f"https://docs.python.org/{self.version}/{self.language}/"
326+
return f"https://docs.python.org/{self.version}/"
327+
328+
@property
329+
def branch_or_tag(self) -> str:
330+
return self._ver.branch_or_tag
331+
332+
@property
333+
def status(self) -> str:
334+
return self._ver.status
335+
336+
@property
337+
def is_eol(self) -> bool:
338+
return self._ver.status == "EOL"
339+
340+
@property
341+
def dependencies(self) -> list[str]:
342+
return self._ver.requirements
343+
344+
@property
345+
def version(self):
346+
return self._ver.name
347+
348+
@property
349+
def version_tuple(self):
350+
return self._ver.as_tuple()
351+
352+
@property
353+
def language(self):
354+
return self._lang.tag
355+
356+
@property
357+
def is_translation(self):
358+
return self.language != "en"
359+
360+
@property
361+
def slug(self) -> str:
362+
return f"{self.language}/{self.version}"
363+
364+
@property
365+
def venv_name(self) -> str:
366+
return f"venv-{self.version}"
367+
368+
@property
369+
def locale_repo_url(self) -> str:
370+
return f"https://github.com/python/python-docs-{self.language}.git"
371+
372+
312373
def run(
313374
cmd: Sequence[str | Path], cwd: Path | None = None
314375
) -> subprocess.CompletedProcess:
@@ -534,8 +595,7 @@ def version_info() -> None:
534595
class DocBuilder:
535596
"""Builder for a CPython version and a language."""
536597

537-
version: Version
538-
language: Language
598+
build_meta: BuildMetadata
539599
cpython_repo: Repository
540600
docs_by_version_content: bytes
541601
switchers_content: bytes
@@ -553,7 +613,7 @@ def html_only(self) -> bool:
553613
return (
554614
self.select_output in {"only-html", "only-html-en"}
555615
or self.quick
556-
or self.language.html_only
616+
or self.build_meta.html_only
557617
)
558618

559619
@property
@@ -567,11 +627,11 @@ def run(self, http: urllib3.PoolManager, force_build: bool) -> bool | None:
567627
start_timestamp = dt.datetime.now(tz=dt.UTC).replace(microsecond=0)
568628
logging.info("Running.")
569629
try:
570-
if self.language.html_only and not self.includes_html:
630+
if self.build_meta.html_only and not self.includes_html:
571631
logging.info("Skipping non-HTML build (language is HTML-only).")
572632
return None # skipped
573-
self.cpython_repo.switch(self.version.branch_or_tag)
574-
if self.language.is_translation:
633+
self.cpython_repo.switch(self.build_meta.branch_or_tag)
634+
if self.build_meta.is_translation:
575635
self.clone_translation()
576636
if trigger_reason := self.should_rebuild(force_build):
577637
self.build_venv()
@@ -593,7 +653,7 @@ def run(self, http: urllib3.PoolManager, force_build: bool) -> bool | None:
593653

594654
@property
595655
def locale_dir(self) -> Path:
596-
return self.build_root / self.version.name / "locale"
656+
return self.build_root / self.build_meta.version / "locale"
597657

598658
@property
599659
def checkout(self) -> Path:
@@ -608,8 +668,8 @@ def clone_translation(self) -> None:
608668
def translation_repo(self) -> Repository:
609669
"""See PEP 545 for translations repository naming convention."""
610670

611-
locale_clone_dir = self.locale_dir / self.language.iso639_tag / "LC_MESSAGES"
612-
return Repository(self.language.locale_repo_url, locale_clone_dir)
671+
locale_clone_dir = self.locale_dir / self.build_meta.iso639_tag / "LC_MESSAGES"
672+
return Repository(self.build_meta.locale_repo_url, locale_clone_dir)
613673

614674
@property
615675
def translation_branch(self) -> str:
@@ -623,25 +683,25 @@ def translation_branch(self) -> str:
623683
"""
624684
remote_branches = self.translation_repo.run("branch", "-r").stdout
625685
branches = re.findall(r"/([0-9]+\.[0-9]+)$", remote_branches, re.M)
626-
return locate_nearest_version(branches, self.version.name)
686+
return locate_nearest_version(branches, self.build_meta.version)
627687

628688
def build(self) -> None:
629689
"""Build this version/language doc."""
630690
logging.info("Build start.")
631691
start_time = perf_counter()
632-
sphinxopts = list(self.language.sphinxopts)
633-
if self.language.is_translation:
692+
sphinxopts = list(self.build_meta.sphinxopts)
693+
if self.build_meta.is_translation:
634694
sphinxopts.extend((
635695
f"-D locale_dirs={self.locale_dir}",
636-
f"-D language={self.language.iso639_tag}",
696+
f"-D language={self.build_meta.iso639_tag}",
637697
"-D gettext_compact=0",
638698
"-D translation_progress_classes=1",
639699
))
640700

641-
if self.version.status == "EOL":
701+
if self.build_meta.is_eol:
642702
sphinxopts.append("-D html_context.outdated=1")
643703

644-
if self.version.status in ("in development", "pre-release"):
704+
if self.build_meta.status in ("in development", "pre-release"):
645705
maketarget = "autobuild-dev"
646706
else:
647707
maketarget = "autobuild-stable"
@@ -653,17 +713,15 @@ def build(self) -> None:
653713
blurb = self.venv / "bin" / "blurb"
654714

655715
if self.includes_html:
656-
site_url = self.version.url
657-
if self.language.is_translation:
658-
site_url += f"{self.language.tag}/"
716+
site_url = self.build_meta.url
659717
# Define a tag to enable opengraph socialcards previews
660718
# (used in Doc/conf.py and requires matplotlib)
661719
sphinxopts += (
662720
"-t create-social-cards",
663721
f"-D ogp_site_url={site_url}",
664722
)
665723

666-
if self.version.as_tuple() < (3, 8):
724+
if self.build_meta.version_tuple < (3, 8):
667725
# Disable CPython switchers, we handle them now:
668726
text = (self.checkout / "Doc" / "Makefile").read_text(encoding="utf-8")
669727
text = text.replace(" -A switchers=1", "")
@@ -696,12 +754,12 @@ def build_venv(self) -> None:
696754
So we can reuse them from builds to builds, while they contain
697755
different Sphinx versions.
698756
"""
699-
requirements = list(self.version.requirements)
757+
requirements = list(self.build_meta.dependencies)
700758
if self.includes_html:
701759
# opengraph previews
702760
requirements.append("matplotlib>=3")
703761

704-
venv_path = self.build_root / f"venv-{self.version.name}"
762+
venv_path = self.build_root / self.build_meta.venv_name
705763
venv.create(venv_path, symlinks=os.name != "nt", with_pip=True)
706764
run(
707765
(
@@ -726,7 +784,7 @@ def setup_indexsidebar(self) -> None:
726784
dbv_path = tmpl_dst / "_docs_by_version.html"
727785

728786
shutil.copy(tmpl_src / "indexsidebar.html", tmpl_dst / "indexsidebar.html")
729-
if self.version.status != "EOL":
787+
if not self.build_meta.is_eol:
730788
dbv_path.write_bytes(self.docs_by_version_content)
731789
else:
732790
shutil.copy(tmpl_src / "_docs_by_version.html", dbv_path)
@@ -736,14 +794,14 @@ def copy_build_to_webroot(self, http: urllib3.PoolManager) -> None:
736794
logging.info("Publishing start.")
737795
start_time = perf_counter()
738796
self.www_root.mkdir(parents=True, exist_ok=True)
739-
if not self.language.is_translation:
740-
target = self.www_root / self.version.name
797+
if not self.build_meta.is_translation:
798+
target = self.www_root / self.build_meta.version
741799
else:
742-
language_dir = self.www_root / self.language.tag
800+
language_dir = self.www_root / self.build_meta.language
743801
language_dir.mkdir(parents=True, exist_ok=True)
744802
chgrp(language_dir, group=self.group, recursive=True)
745803
language_dir.chmod(0o775)
746-
target = language_dir / self.version.name
804+
target = language_dir / self.build_meta.version
747805

748806
target.mkdir(parents=True, exist_ok=True)
749807
try:
@@ -792,8 +850,7 @@ def copy_build_to_webroot(self, http: urllib3.PoolManager) -> None:
792850

793851
logging.info("%s files changed", changed)
794852
if changed and not self.skip_cache_invalidation:
795-
surrogate_key = f"{self.language.tag}/{self.version.name}"
796-
purge_surrogate_key(http, surrogate_key)
853+
purge_surrogate_key(http, self.build_meta.slug)
797854
logging.info(
798855
"Publishing done (%s).", format_seconds(perf_counter() - start_time)
799856
)
@@ -804,7 +861,7 @@ def should_rebuild(self, force: bool) -> str | Literal[False]:
804861
logging.info("Should rebuild: no previous state found.")
805862
return "no previous state"
806863
cpython_sha = self.cpython_repo.run("rev-parse", "HEAD").stdout.strip()
807-
if self.language.is_translation:
864+
if self.build_meta.is_translation:
808865
translation_sha = self.translation_repo.run(
809866
"rev-parse", "HEAD"
810867
).stdout.strip()
@@ -839,7 +896,7 @@ def load_state(self) -> dict:
839896
state_file = self.build_root / "state.toml"
840897
try:
841898
return tomlkit.loads(state_file.read_text(encoding="UTF-8"))[
842-
f"/{self.language.tag}/{self.version.name}/"
899+
f"/{self.build_meta.slug}/"
843900
]
844901
except (KeyError, FileNotFoundError):
845902
return {}
@@ -860,14 +917,14 @@ def save_state(
860917
except FileNotFoundError:
861918
states = tomlkit.document()
862919

863-
key = f"/{self.language.tag}/{self.version.name}/"
920+
key = f"/{self.build_meta.slug}/"
864921
state = {
865922
"last_build_start": build_start,
866923
"last_build_duration": round(build_duration, 0),
867924
"triggered_by": trigger,
868925
"cpython_sha": self.cpython_repo.run("rev-parse", "HEAD").stdout.strip(),
869926
}
870-
if self.language.is_translation:
927+
if self.build_meta.is_translation:
871928
state["translation_sha"] = self.translation_repo.run(
872929
"rev-parse", "HEAD"
873930
).stdout.strip()
@@ -1122,9 +1179,9 @@ def build_docs(args: argparse.Namespace) -> int:
11221179
# pairs from the end of the list, effectively reversing it.
11231180
# This runs languages in config.toml order and versions newest first.
11241181
todo = [
1125-
(version, language)
1126-
for version in versions.filter(args.branches)
1127-
for language in reversed(languages.filter(args.languages))
1182+
BuildMetadata(_ver=ver, _lang=lang)
1183+
for ver in versions.filter(args.branches)
1184+
for lang in reversed(languages.filter(args.languages))
11281185
]
11291186
del args.branches
11301187
del args.languages
@@ -1141,28 +1198,25 @@ def build_docs(args: argparse.Namespace) -> int:
11411198
args.build_root / _checkout_name(args.select_output),
11421199
)
11431200
while todo:
1144-
version, language = todo.pop()
1201+
b = todo.pop()
11451202
logging.root.handlers[0].setFormatter(
1146-
logging.Formatter(
1147-
f"%(asctime)s %(levelname)s {language.tag}/{version.name}: %(message)s"
1148-
)
1203+
logging.Formatter(f"%(asctime)s %(levelname)s {b.slug}: %(message)s")
11491204
)
11501205
if sentry_sdk:
11511206
scope = sentry_sdk.get_isolation_scope()
1152-
scope.set_tag("version", version.name)
1153-
scope.set_tag("language", language.tag)
1207+
scope.set_tag("version", b.version)
1208+
scope.set_tag("language", b.language)
11541209
cpython_repo.update()
11551210
builder = DocBuilder(
1156-
version,
1157-
language,
1211+
b,
11581212
cpython_repo,
11591213
docs_by_version_content,
11601214
switchers_content,
11611215
**vars(args),
11621216
)
11631217
built_successfully = builder.run(http, force_build=force_build)
11641218
if built_successfully:
1165-
build_succeeded.add((version.name, language.tag))
1219+
build_succeeded.add(b.slug)
11661220
elif built_successfully is not None:
11671221
any_build_failed = True
11681222

@@ -1285,7 +1339,7 @@ def make_symlinks(
12851339
group: str,
12861340
versions: Versions,
12871341
languages: Languages,
1288-
successful_builds: Set[tuple[str, str]],
1342+
successful_builds: Set[str],
12891343
skip_cache_invalidation: bool,
12901344
http: urllib3.PoolManager,
12911345
) -> None:
@@ -1305,7 +1359,7 @@ def make_symlinks(
13051359
("dev", versions.current_dev.name),
13061360
):
13071361
for language in languages:
1308-
if (symlink_target, language.tag) in successful_builds:
1362+
if f"{language.tag}/{symlink_target}" in successful_builds:
13091363
symlink(
13101364
www_root,
13111365
language.tag,

0 commit comments

Comments
 (0)