From 0e83b79b2d2488ec9226f28c93e215b5217e6730 Mon Sep 17 00:00:00 2001 From: Julien Palard Date: Thu, 22 May 2025 14:35:36 +0200 Subject: [PATCH 01/26] =?UTF-8?q?Hello=20=E0=A6=AC=E0=A6=BE=E0=A6=82?= =?UTF-8?q?=E0=A6=B2=E0=A6=BE=20(#301)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config.toml b/config.toml index 489c774..f25052a 100644 --- a/config.toml +++ b/config.toml @@ -36,6 +36,11 @@ sphinxopts = [ '-D latex_elements.fontenc=\\usepackage{fontspec}', ] +[languages.bn_IN] +name = "Bengali" +translated_name = "বাংলা" +in_prod = false + [languages.id] name = "Indonesian" translated_name = "Indonesia" From 15f94c0b7caec8b12f4bab3227967fd859ff6bad Mon Sep 17 00:00:00 2001 From: Octavian Mustafa Date: Mon, 16 Jun 2025 21:44:24 +0300 Subject: [PATCH 02/26] Start building Romanian translations (#302) --- config.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config.toml b/config.toml index f25052a..d628ca0 100644 --- a/config.toml +++ b/config.toml @@ -92,6 +92,11 @@ translated_name = "polski" name = "Brazilian Portuguese" translated_name = "Português brasileiro" +[languages.ro] +name = "Romanian" +translated_name = "Românește" +in_prod = false + [languages.tr] name = "Turkish" translated_name = "Türkçe" From a601ce67c6c2f3be7fde3376d3e5d3851f19950b Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Fri, 20 Jun 2025 16:56:41 +0200 Subject: [PATCH 03/26] Add Greek translation (#248) --- config.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config.toml b/config.toml index d628ca0..4a5958f 100644 --- a/config.toml +++ b/config.toml @@ -15,6 +15,10 @@ sphinxopts = [ "-D latex_elements.fontenc=", ] +[languages.el] +name = "Greek" +translated_name = "Ελληνικά" + [languages.en] name = "English" From 599b4ffd7f1cdde02bd8d0bf7781599f631e0752 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 15 Aug 2025 18:36:46 +0300 Subject: [PATCH 04/26] Enable ``suggest_on_error`` for argparse (#303) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- build_docs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/build_docs.py b/build_docs.py index c75f096..e2798a5 100755 --- a/build_docs.py +++ b/build_docs.py @@ -967,6 +967,7 @@ def parse_args() -> argparse.Namespace: description="Runs a build of the Python docs for various branches.", allow_abbrev=False, ) + parser.suggest_on_error = True parser.add_argument( "--select-output", choices=("no-html", "only-html", "only-html-en"), From 0f2598a66e0193d64b2bc49176d71162928aff5f Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:50:00 +0100 Subject: [PATCH 05/26] Add the ``BuildMetadata`` class (#299) --- build_docs.py | 156 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 106 insertions(+), 50 deletions(-) diff --git a/build_docs.py b/build_docs.py index e2798a5..20c3518 100755 --- a/build_docs.py +++ b/build_docs.py @@ -294,14 +294,6 @@ class Language: def tag(self) -> str: return self.iso639_tag.replace("_", "-").lower() - @property - def is_translation(self) -> bool: - return self.tag != "en" - - @property - def locale_repo_url(self) -> str: - return f"https://github.com/python/python-docs-{self.tag}.git" - @property def switcher_label(self) -> str: if self.translated_name: @@ -309,6 +301,75 @@ def switcher_label(self) -> str: return self.name +@dataclasses.dataclass(frozen=True, kw_only=True, slots=True) +class BuildMetadata: + _version: Version + _language: Language + + @property + def sphinxopts(self) -> Sequence[str]: + return self._language.sphinxopts + + @property + def iso639_tag(self) -> str: + return self._language.iso639_tag + + @property + def html_only(self) -> bool: + return self._language.html_only + + @property + def url(self): + """The URL of this version in production.""" + if self.is_translation: + return f"https://docs.python.org/{self.version}/{self.language}/" + return f"https://docs.python.org/{self.version}/" + + @property + def branch_or_tag(self) -> str: + return self._version.branch_or_tag + + @property + def status(self) -> str: + return self._version.status + + @property + def is_eol(self) -> bool: + return self._version.status == "EOL" + + @property + def dependencies(self) -> list[str]: + return self._version.requirements + + @property + def version(self): + return self._version.name + + @property + def version_tuple(self): + return self._version.as_tuple() + + @property + def language(self): + return self._language.tag + + @property + def is_translation(self): + return self.language != "en" + + @property + def slug(self) -> str: + return f"{self.language}/{self.version}" + + @property + def venv_name(self) -> str: + return f"venv-{self.version}" + + @property + def locale_repo_url(self) -> str: + return f"https://github.com/python/python-docs-{self.language}.git" + + def run( cmd: Sequence[str | Path], cwd: Path | None = None ) -> subprocess.CompletedProcess: @@ -534,8 +595,7 @@ def version_info() -> None: class DocBuilder: """Builder for a CPython version and a language.""" - version: Version - language: Language + build_meta: BuildMetadata cpython_repo: Repository docs_by_version_content: bytes switchers_content: bytes @@ -553,7 +613,7 @@ def html_only(self) -> bool: return ( self.select_output in {"only-html", "only-html-en"} or self.quick - or self.language.html_only + or self.build_meta.html_only ) @property @@ -567,11 +627,11 @@ def run(self, http: urllib3.PoolManager, force_build: bool) -> bool | None: start_timestamp = dt.datetime.now(tz=dt.UTC).replace(microsecond=0) logging.info("Running.") try: - if self.language.html_only and not self.includes_html: + if self.build_meta.html_only and not self.includes_html: logging.info("Skipping non-HTML build (language is HTML-only).") return None # skipped - self.cpython_repo.switch(self.version.branch_or_tag) - if self.language.is_translation: + self.cpython_repo.switch(self.build_meta.branch_or_tag) + if self.build_meta.is_translation: self.clone_translation() if trigger_reason := self.should_rebuild(force_build): self.build_venv() @@ -593,7 +653,7 @@ def run(self, http: urllib3.PoolManager, force_build: bool) -> bool | None: @property def locale_dir(self) -> Path: - return self.build_root / self.version.name / "locale" + return self.build_root / self.build_meta.version / "locale" @property def checkout(self) -> Path: @@ -608,8 +668,8 @@ def clone_translation(self) -> None: def translation_repo(self) -> Repository: """See PEP 545 for translations repository naming convention.""" - locale_clone_dir = self.locale_dir / self.language.iso639_tag / "LC_MESSAGES" - return Repository(self.language.locale_repo_url, locale_clone_dir) + locale_clone_dir = self.locale_dir / self.build_meta.iso639_tag / "LC_MESSAGES" + return Repository(self.build_meta.locale_repo_url, locale_clone_dir) @property def translation_branch(self) -> str: @@ -623,25 +683,25 @@ def translation_branch(self) -> str: """ remote_branches = self.translation_repo.run("branch", "-r").stdout branches = re.findall(r"/([0-9]+\.[0-9]+)$", remote_branches, re.M) - return locate_nearest_version(branches, self.version.name) + return locate_nearest_version(branches, self.build_meta.version) def build(self) -> None: """Build this version/language doc.""" logging.info("Build start.") start_time = perf_counter() - sphinxopts = list(self.language.sphinxopts) - if self.language.is_translation: + sphinxopts = list(self.build_meta.sphinxopts) + if self.build_meta.is_translation: sphinxopts.extend(( f"-D locale_dirs={self.locale_dir}", - f"-D language={self.language.iso639_tag}", + f"-D language={self.build_meta.iso639_tag}", "-D gettext_compact=0", "-D translation_progress_classes=1", )) - if self.version.status == "EOL": + if self.build_meta.is_eol: sphinxopts.append("-D html_context.outdated=1") - if self.version.status in ("in development", "pre-release"): + if self.build_meta.status in ("in development", "pre-release"): maketarget = "autobuild-dev" else: maketarget = "autobuild-stable" @@ -653,9 +713,7 @@ def build(self) -> None: blurb = self.venv / "bin" / "blurb" if self.includes_html: - site_url = self.version.url - if self.language.is_translation: - site_url += f"{self.language.tag}/" + site_url = self.build_meta.url # Define a tag to enable opengraph socialcards previews # (used in Doc/conf.py and requires matplotlib) sphinxopts += ( @@ -663,7 +721,7 @@ def build(self) -> None: f"-D ogp_site_url={site_url}", ) - if self.version.as_tuple() < (3, 8): + if self.build_meta.version_tuple < (3, 8): # Disable CPython switchers, we handle them now: text = (self.checkout / "Doc" / "Makefile").read_text(encoding="utf-8") text = text.replace(" -A switchers=1", "") @@ -696,12 +754,12 @@ def build_venv(self) -> None: So we can reuse them from builds to builds, while they contain different Sphinx versions. """ - requirements = list(self.version.requirements) + requirements = list(self.build_meta.dependencies) if self.includes_html: # opengraph previews requirements.append("matplotlib>=3") - venv_path = self.build_root / f"venv-{self.version.name}" + venv_path = self.build_root / self.build_meta.venv_name venv.create(venv_path, symlinks=os.name != "nt", with_pip=True) run( ( @@ -726,7 +784,7 @@ def setup_indexsidebar(self) -> None: dbv_path = tmpl_dst / "_docs_by_version.html" shutil.copy(tmpl_src / "indexsidebar.html", tmpl_dst / "indexsidebar.html") - if self.version.status != "EOL": + if not self.build_meta.is_eol: dbv_path.write_bytes(self.docs_by_version_content) else: shutil.copy(tmpl_src / "_docs_by_version.html", dbv_path) @@ -736,14 +794,14 @@ def copy_build_to_webroot(self, http: urllib3.PoolManager) -> None: logging.info("Publishing start.") start_time = perf_counter() self.www_root.mkdir(parents=True, exist_ok=True) - if not self.language.is_translation: - target = self.www_root / self.version.name + if not self.build_meta.is_translation: + target = self.www_root / self.build_meta.version else: - language_dir = self.www_root / self.language.tag + language_dir = self.www_root / self.build_meta.language language_dir.mkdir(parents=True, exist_ok=True) chgrp(language_dir, group=self.group, recursive=True) language_dir.chmod(0o775) - target = language_dir / self.version.name + target = language_dir / self.build_meta.version target.mkdir(parents=True, exist_ok=True) try: @@ -792,8 +850,7 @@ def copy_build_to_webroot(self, http: urllib3.PoolManager) -> None: logging.info("%s files changed", changed) if changed and not self.skip_cache_invalidation: - surrogate_key = f"{self.language.tag}/{self.version.name}" - purge_surrogate_key(http, surrogate_key) + purge_surrogate_key(http, self.build_meta.slug) logging.info( "Publishing done (%s).", format_seconds(perf_counter() - start_time) ) @@ -804,7 +861,7 @@ def should_rebuild(self, force: bool) -> str | Literal[False]: logging.info("Should rebuild: no previous state found.") return "no previous state" cpython_sha = self.cpython_repo.run("rev-parse", "HEAD").stdout.strip() - if self.language.is_translation: + if self.build_meta.is_translation: translation_sha = self.translation_repo.run( "rev-parse", "HEAD" ).stdout.strip() @@ -839,7 +896,7 @@ def load_state(self) -> dict: state_file = self.build_root / "state.toml" try: return tomlkit.loads(state_file.read_text(encoding="UTF-8"))[ - f"/{self.language.tag}/{self.version.name}/" + f"/{self.build_meta.slug}/" ] except (KeyError, FileNotFoundError): return {} @@ -860,14 +917,14 @@ def save_state( except FileNotFoundError: states = tomlkit.document() - key = f"/{self.language.tag}/{self.version.name}/" + key = f"/{self.build_meta.slug}/" state = { "last_build_start": build_start, "last_build_duration": round(build_duration, 0), "triggered_by": trigger, "cpython_sha": self.cpython_repo.run("rev-parse", "HEAD").stdout.strip(), } - if self.language.is_translation: + if self.build_meta.is_translation: state["translation_sha"] = self.translation_repo.run( "rev-parse", "HEAD" ).stdout.strip() @@ -1123,7 +1180,7 @@ def build_docs(args: argparse.Namespace) -> int: # pairs from the end of the list, effectively reversing it. # This runs languages in config.toml order and versions newest first. todo = [ - (version, language) + BuildMetadata(_version=version, _language=language) for version in versions.filter(args.branches) for language in reversed(languages.filter(args.languages)) ] @@ -1142,20 +1199,19 @@ def build_docs(args: argparse.Namespace) -> int: args.build_root / _checkout_name(args.select_output), ) while todo: - version, language = todo.pop() + build_props = todo.pop() logging.root.handlers[0].setFormatter( logging.Formatter( - f"%(asctime)s %(levelname)s {language.tag}/{version.name}: %(message)s" + f"%(asctime)s %(levelname)s {build_props.slug}: %(message)s" ) ) if sentry_sdk: scope = sentry_sdk.get_isolation_scope() - scope.set_tag("version", version.name) - scope.set_tag("language", language.tag) + scope.set_tag("version", build_props.version) + scope.set_tag("language", build_props.language) cpython_repo.update() builder = DocBuilder( - version, - language, + build_props, cpython_repo, docs_by_version_content, switchers_content, @@ -1163,7 +1219,7 @@ def build_docs(args: argparse.Namespace) -> int: ) built_successfully = builder.run(http, force_build=force_build) if built_successfully: - build_succeeded.add((version.name, language.tag)) + build_succeeded.add(build_props.slug) elif built_successfully is not None: any_build_failed = True @@ -1286,7 +1342,7 @@ def make_symlinks( group: str, versions: Versions, languages: Languages, - successful_builds: Set[tuple[str, str]], + successful_builds: Set[str], skip_cache_invalidation: bool, http: urllib3.PoolManager, ) -> None: @@ -1306,7 +1362,7 @@ def make_symlinks( ("dev", versions.current_dev.name), ): for language in languages: - if (symlink_target, language.tag) in successful_builds: + if f"{language.tag}/{symlink_target}" in successful_builds: symlink( www_root, language.tag, From 5453926d03a56f7b74b1a5ee4e1ef482c61f3e26 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 15 Aug 2025 19:51:55 +0300 Subject: [PATCH 06/26] Add options to only show certain build times (#307) --- check_times.py | 48 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/check_times.py b/check_times.py index 2b3d2f9..e3bbaea 100644 --- a/check_times.py +++ b/check_times.py @@ -10,6 +10,7 @@ $ python check_times.py """ +import argparse import gzip import tomllib from pathlib import Path @@ -78,17 +79,36 @@ def calc_time(lines: list[str]) -> None: if __name__ == "__main__": - print("Build times (HTML only; English)") - print("=======================") - print() - calc_time(get_lines("docsbuild-only-html-en.log")) - - print("Build times (HTML only)") - print("=======================") - print() - calc_time(get_lines("docsbuild-only-html.log")) - - print("Build times (no HTML)") - print("=====================") - print() - calc_time(get_lines("docsbuild-no-html.log")) + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + ALL_BUILDS = ("no-html", "only-html", "only-html-en") + parser.add_argument( + "--select-output", + choices=ALL_BUILDS, + nargs="*", + help="Choose what builds to show (default: all).", + ) + args = parser.parse_args() + parser.suggest_on_error = True + + if not args.select_output: + args.select_output = ALL_BUILDS + + if "only-html-en" in args.select_output: + print("Build times (HTML only; English)") + print("=======================") + print() + calc_time(get_lines("docsbuild-only-html-en.log")) + + if "only-html" in args.select_output: + print("Build times (HTML only)") + print("=======================") + print() + calc_time(get_lines("docsbuild-only-html.log")) + + if "no-html" in args.select_output: + print("Build times (no HTML)") + print("=====================") + print() + calc_time(get_lines("docsbuild-no-html.log")) From 1860eda1aba9329d93591e4d10c0408d2c48bb6d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Fri, 15 Aug 2025 18:29:47 +0100 Subject: [PATCH 07/26] Only build HTML for non-switcher languages (#308) --- build_docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_docs.py b/build_docs.py index 20c3518..93121df 100755 --- a/build_docs.py +++ b/build_docs.py @@ -316,7 +316,7 @@ def iso639_tag(self) -> str: @property def html_only(self) -> bool: - return self._language.html_only + return self._language.html_only or not self._language.in_prod @property def url(self): From 6f7b363186286d49c142b003153a57f728fd83bd Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 8 Sep 2025 22:12:53 +0300 Subject: [PATCH 08/26] Use ``--branches`` in the README's example (#312) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f914c6..fb16b51 100644 --- a/README.md +++ b/README.md @@ -71,5 +71,5 @@ To manually rebuild a branch, for example 3.11: ssh docs.nyc1.psf.io sudo su --shell=/bin/bash docsbuild screen -DUR # Rejoin screen session if it exists, otherwise create a new one -/srv/docsbuild/venv/bin/python /srv/docsbuild/scripts/build_docs.py --force --branch 3.11 +/srv/docsbuild/venv/bin/python /srv/docsbuild/scripts/build_docs.py --force --branches 3.11 ``` From 7787668fb2deaa8ea6febfb0f45667fcc489f04d Mon Sep 17 00:00:00 2001 From: Octavian Mustafa Date: Tue, 30 Sep 2025 11:25:59 +0300 Subject: [PATCH 09/26] Add Romanian to the language switcher (#310) --- config.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/config.toml b/config.toml index 4a5958f..1896b19 100644 --- a/config.toml +++ b/config.toml @@ -99,7 +99,6 @@ translated_name = "Português brasileiro" [languages.ro] name = "Romanian" translated_name = "Românește" -in_prod = false [languages.tr] name = "Turkish" From c08754f67818bba2795025b8e9d5f9ca9f0d7dfc Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 17 Oct 2025 09:47:16 +0300 Subject: [PATCH 10/26] Add `tox -e cog` to update README tables (#313) --- README.md | 90 +++++++++++++++++++++++++++++------------------ check_versions.py | 15 ++++---- tox.ini | 11 ++++++ 3 files changed, 76 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index fb16b51..8ba7070 100644 --- a/README.md +++ b/README.md @@ -20,42 +20,64 @@ If you don't need to build all translations of all branches, add `--languages en --branches main`. -## Check current version +## Sphinx versions + + +Sphinx configuration in various branches: + +| version | requirements.txt | conf.py | +|-----------|--------------------|----------------------| +| 2.6 | ø | ø | +| 2.7 | ø | ø | +| 3.0 | ø | ø | +| 3.1 | ø | ø | +| 3.2 | ø | ø | +| 3.3 | ø | ø | +| 3.4 | ø | needs_sphinx='1.2' | +| 3.5 | ø | ø | +| 3.6 | ø | ø | +| 3.7 | ø | ø | +| 3.8 | ø | ø | +| 3.9 | sphinx==2.4.4 | needs_sphinx='1.8' | +| 3.10 | sphinx==3.4.3 | needs_sphinx='3.2' | +| 3.11 | sphinx~=7.2.0 | needs_sphinx='4.2' | +| 3.12 | sphinx~=8.2.0 | needs_sphinx='8.2.0' | +| 3.13 | sphinx~=8.2.0 | needs_sphinx='8.2.0' | +| 3.14 | sphinx~=8.2.0 | needs_sphinx='8.2.0' | +| 3.15 | sphinx~=8.2.0 | needs_sphinx='8.2.0' | + +Sphinx build as seen on docs.python.org: + +| version | el | en | es | fr | bn-in | id | it | ja | ko | pl | pt-br | ro | tr | uk | zh-cn | zh-tw | +|-----------|-------|-------|-------|-------|---------|-------|-------|-------|-------|-------|---------|-------|-------|-------|---------|---------| +| 2.6 | ø | 0.6.5 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | +| 2.7 | ø | 2.3.1 | ø | 2.3.1 | ø | 2.3.1 | ø | 2.3.1 | 2.3.1 | ø | 2.3.1 | ø | ø | ø | 2.3.1 | 2.3.1 | +| 3.0 | ø | 0.6 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | +| 3.1 | ø | 0.6.5 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | +| 3.2 | ø | 1.0.7 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | +| 3.3 | ø | 1.2 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | +| 3.4 | ø | 1.2.3 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | +| 3.5 | ø | 1.8.4 | 1.8.4 | 1.8.4 | ø | 1.8.4 | ø | 1.8.4 | 1.8.4 | 1.8.4 | 1.8.4 | ø | ø | ø | 1.8.4 | 1.8.4 | +| 3.6 | ø | 2.3.1 | 2.3.1 | 2.3.1 | ø | 2.3.1 | ø | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | ø | ø | ø | 2.3.1 | 2.3.1 | +| 3.7 | ø | 2.3.1 | 2.3.1 | 2.3.1 | ø | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | ø | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | +| 3.8 | ø | 2.4.4 | 2.4.4 | 2.4.4 | ø | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | ø | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | +| 3.9 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | +| 3.10 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | +| 3.11 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | +| 3.12 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | +| 3.13 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | +| 3.14 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | +| 3.15 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | + Install `tools_requirements.txt` then run `python check_versions.py -../cpython/` (pointing to a real CPython clone) to see which version -of Sphinx we're using where: - - Sphinx configuration in various branches: - - ========= ============= ================== ==================== - version travis requirements.txt conf.py - ========= ============= ================== ==================== - 2.7 sphinx~=2.0.1 ø needs_sphinx='1.2' - 3.5 sphinx==1.8.2 ø needs_sphinx='1.8' - 3.6 sphinx==1.8.2 ø needs_sphinx='1.2' - 3.7 sphinx==1.8.2 sphinx==2.3.1 needs_sphinx="1.6.6" - 3.8 ø sphinx==2.4.4 needs_sphinx='1.8' - 3.9 ø sphinx==2.4.4 needs_sphinx='1.8' - 3.10 ø sphinx==3.4.3 needs_sphinx='3.2' - 3.11 ø sphinx~=7.2.0 needs_sphinx='4.2' - 3.12 ø sphinx~=8.2.0 needs_sphinx='8.2.0' - 3.13 ø sphinx~=8.2.0 needs_sphinx='8.2.0' - 3.14 ø sphinx~=8.2.0 needs_sphinx='8.2.0' - ========= ============= ================== ==================== - - Sphinx build as seen on docs.python.org: - - ========= ===== ===== ===== ===== ===== ===== ===== ===== ======= ===== ===== ======= ======= - version en es fr id it ja ko pl pt-br tr uk zh-cn zh-tw - ========= ===== ===== ===== ===== ===== ===== ===== ===== ======= ===== ===== ======= ======= - 3.9 2.4.4 2.4.4 2.4.4 2.4.4 2.4.4 2.4.4 2.4.4 2.4.4 2.4.4 2.4.4 2.4.4 2.4.4 2.4.4 - 3.10 3.4.3 3.4.3 3.4.3 3.4.3 3.4.3 3.4.3 3.4.3 3.4.3 3.4.3 3.4.3 3.4.3 3.4.3 3.4.3 - 3.11 7.2.6 7.2.6 7.2.6 7.2.6 7.2.6 7.2.6 7.2.6 7.2.6 7.2.6 7.2.6 7.2.6 7.2.6 7.2.6 - 3.12 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 - 3.13 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 - 3.14 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 8.2.3 - ========= ===== ===== ===== ===== ===== ===== ===== ===== ======= ===== ===== ======= ======= +../cpython/` (pointing to a real CPython clone) to see which versions +of Sphinx we're using. + +Or run `tox -e cog` (with a clone at `../cpython`) to directly update these tables. ## Manually rebuild a branch diff --git a/check_versions.py b/check_versions.py index 1a1016f..e381424 100644 --- a/check_versions.py +++ b/check_versions.py @@ -62,7 +62,6 @@ def find_sphinx_in_files(repo: git.Repo, branch_or_tag, filenames): CONF_FILES = { - "travis": ".travis.yml", "requirements.txt": "Doc/requirements.txt", "conf.py": "Doc/conf.py", } @@ -85,7 +84,7 @@ def search_sphinx_versions_in_cpython(repo: git.Repo): for version in VERSIONS ] headers = ["version", *CONF_FILES.keys()] - print(tabulate(table, headers=headers, tablefmt="rst", disable_numparse=True)) + print(tabulate(table, headers=headers, tablefmt="github", disable_numparse=True)) async def get_version_in_prod(language: str, version: str) -> str: @@ -119,16 +118,15 @@ async def which_sphinx_is_used_in_production(): for version in VERSIONS ] headers = ["version", *[language.tag for language in LANGUAGES]] - print(tabulate(table, headers=headers, tablefmt="rst", disable_numparse=True)) + print(tabulate(table, headers=headers, tablefmt="github", disable_numparse=True)) -def main(): +def check_versions(cpython_clone: str) -> None: logging.basicConfig(level=logging.INFO) logging.getLogger("charset_normalizer").setLevel(logging.WARNING) logging.getLogger("asyncio").setLevel(logging.WARNING) logging.getLogger("httpx").setLevel(logging.WARNING) - args = parse_args() - repo = git.Repo(args.cpython_clone) + repo = git.Repo(cpython_clone) print("Sphinx configuration in various branches:", end="\n\n") search_sphinx_versions_in_cpython(repo) print() @@ -136,5 +134,10 @@ def main(): asyncio.run(which_sphinx_is_used_in_production()) +def main(): + args = parse_args() + check_versions(args.cpython_clone) + + if __name__ == "__main__": main() diff --git a/tox.ini b/tox.ini index 12efcdf..a4d051c 100644 --- a/tox.ini +++ b/tox.ini @@ -2,6 +2,7 @@ requires = tox>=4.2 env_list = + cog lint py{314, 313} @@ -26,6 +27,16 @@ commands = --cov-report xml \ {posargs} +[testenv:cog] +base_python = python3.13 +skip_install = true +deps = + -r requirements.txt + -r tools_requirements.txt + cogapp +commands = + cog -Pr README.md + [testenv:lint] skip_install = true deps = From 89b13f9dee65c03f11455340745cb3db1127db3e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 10 Nov 2025 16:32:14 +0000 Subject: [PATCH 11/26] Use ``release-cycle.json`` from peps.python.org (#315) --- build_docs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build_docs.py b/build_docs.py index 93121df..3111d52 100755 --- a/build_docs.py +++ b/build_docs.py @@ -1256,8 +1256,7 @@ def build_docs(args: argparse.Namespace) -> int: def parse_versions_from_devguide(http: urllib3.PoolManager) -> Versions: releases = http.request( "GET", - "https://raw.githubusercontent.com/" - "python/devguide/main/include/release-cycle.json", + "https://peps.python.org/api/release-cycle.json", timeout=30, ).json() return Versions.from_json(releases) From 02a00426ffd16b647fc3a1f1cd820623530e851c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 10 Nov 2025 18:55:52 +0200 Subject: [PATCH 12/26] Note that ``release-cycle.json`` is now sourced from peps.python.org (#314) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- build_docs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build_docs.py b/build_docs.py index 3111d52..973c9a0 100755 --- a/build_docs.py +++ b/build_docs.py @@ -26,7 +26,7 @@ ``` Languages are stored in `config.toml` while versions are discovered -from the devguide. +from peps.python.org (generated by `python-releases.toml`). -q selects "quick build", which means to build only HTML. @@ -1174,7 +1174,7 @@ def build_docs(args: argparse.Namespace) -> int: logging.info("Full build start.") start_time = perf_counter() http = urllib3.PoolManager() - versions = parse_versions_from_devguide(http) + versions = parse_versions_from_peps_site(http) languages = parse_languages_from_config() # Reverse languages but not versions, because we take version-language # pairs from the end of the list, effectively reversing it. @@ -1253,7 +1253,7 @@ def build_docs(args: argparse.Namespace) -> int: return EX_FAILURE if any_build_failed else EX_OK -def parse_versions_from_devguide(http: urllib3.PoolManager) -> Versions: +def parse_versions_from_peps_site(http: urllib3.PoolManager) -> Versions: releases = http.request( "GET", "https://peps.python.org/api/release-cycle.json", From 7554a0378dfe6280d40fa9ae61636cef963843f5 Mon Sep 17 00:00:00 2001 From: Daniel Nylander Date: Wed, 19 Nov 2025 11:02:44 +0100 Subject: [PATCH 13/26] Adding Swedish to config.toml (#316) --- config.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config.toml b/config.toml index 1896b19..1be0745 100644 --- a/config.toml +++ b/config.toml @@ -100,6 +100,11 @@ translated_name = "Português brasileiro" name = "Romanian" translated_name = "Românește" +[languages.sv] +name = "Swedish" +translated_name = "Svenska" +in_prod = false + [languages.tr] name = "Turkish" translated_name = "Türkçe" From 83f43d3130b7d4dbb9b57ed1a8479cb28ca8e2fd Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Fri, 21 Nov 2025 18:25:07 +0000 Subject: [PATCH 14/26] Commit --- config.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config.toml b/config.toml index 1be0745..7ef45b0 100644 --- a/config.toml +++ b/config.toml @@ -4,6 +4,9 @@ # html_only: If true, only create HTML files. # sphinxopts: Extra options to pass to SPHINXOPTS in the Makefile. +# Remember to update the Salt config with redirects for new translations! +# For example: https://github.com/python/psf-salt/commit/14bdc3ae054468092e5a8c3cdacfd02f43c32e19 + [defaults] # name has no default, it is mandatory. translated_name = "" From 4becd180ce7ea197b0a4d00b848cae6cc7a883e3 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Date: Mon, 8 Dec 2025 07:50:21 +0000 Subject: [PATCH 15/26] Add labels to switchers (#319) --- templates/switchers.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/switchers.js b/templates/switchers.js index e54a278..0a042fd 100644 --- a/templates/switchers.js +++ b/templates/switchers.js @@ -63,6 +63,7 @@ const _create_placeholders_if_missing = () => { const _create_version_select = (versions) => { const select = document.createElement("select"); select.className = "version-select"; + select.setAttribute("aria-label", "Python version"); if (_IS_LOCAL) { select.disabled = true; select.title = "Version switching is disabled in local builds"; @@ -96,6 +97,7 @@ const _create_language_select = (languages) => { const select = document.createElement("select"); select.className = "language-select"; + select.setAttribute("aria-label", "Language"); if (_IS_LOCAL) { select.disabled = true; select.title = "Language switching is disabled in local builds"; From 0464101ea67237e898fc0d0e6c626dd85bde83ef Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 3 Jan 2026 17:50:52 +0200 Subject: [PATCH 16/26] Replace pre-commit with prek in CI and add Dependabot (#324) Co-authored-by: Ezio Melotti --- .github/dependabot.yml | 27 +++++++++++++++++++++++++++ .github/workflows/lint.yml | 9 +++------ .github/zizmor.yml | 6 ++++++ .pre-commit-config.yaml | 21 +++++++++++---------- 4 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/zizmor.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1003a92 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,27 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: monthly + assignees: + - "ezio-melotti" + groups: + actions: + patterns: + - "*" + cooldown: + default-days: 7 + + - package-ecosystem: pip + directory: "/" + schedule: + interval: monthly + assignees: + - "ezio-melotti" + groups: + pip: + patterns: + - "*" + cooldown: + default-days: 7 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cd9d07e..2068239 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,17 +7,14 @@ permissions: {} env: FORCE_COLOR: 1 PIP_DISABLE_PIP_VERSION_CHECK: 1 + RUFF_OUTPUT_FORMAT: github jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: actions/setup-python@v5 - with: - python-version: "3.x" - cache: pip - - uses: pre-commit/action@v3.0.1 + - uses: j178/prek-action@v1 diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 0000000..1000265 --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,6 @@ +# https://docs.zizmor.sh/configuration/ +rules: + unpinned-uses: + config: + policies: + "*": ref-pin diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 869a979..d4d9fdf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -14,44 +14,45 @@ repos: - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.5 + rev: v0.14.10 hooks: - - id: ruff + - id: ruff-check args: [--fix] - id: ruff-format - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.31.1 + rev: 0.36.0 hooks: + - id: check-dependabot - id: check-github-workflows - repo: https://github.com/rhysd/actionlint - rev: v1.7.7 + rev: v1.7.10 hooks: - id: actionlint - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: v1.3.1 + rev: v1.19.0 hooks: - id: zizmor - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.5.0 + rev: v2.11.1 hooks: - id: pyproject-fmt - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.23 + rev: v0.24.1 hooks: - id: validate-pyproject - repo: https://github.com/tox-dev/tox-ini-fmt - rev: 1.5.0 + rev: 1.7.1 hooks: - id: tox-ini-fmt - repo: https://github.com/rbubley/mirrors-prettier - rev: v3.5.1 + rev: v3.7.4 hooks: - id: prettier files: templates/switchers.js From 06f50bd9ceb6bafce95c7d0917b72e93e668cc7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 3 Jan 2026 16:56:13 +0100 Subject: [PATCH 17/26] Bump the actions group with 3 updates (#325) Bumps the actions group with 3 updates: [actions/checkout](https://github.com/actions/checkout), [actions/setup-python](https://github.com/actions/setup-python) and [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `actions/checkout` from 4 to 6 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v6) Updates `actions/setup-python` from 5 to 6 - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) Updates `actions/upload-artifact` from 4 to 6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b64e8a3..e54a235 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,10 +15,10 @@ jobs: name: Integration test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.13" @@ -40,7 +40,7 @@ jobs: --branches 3.14 - name: Upload documentation - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: www-root path: ./www @@ -56,12 +56,12 @@ jobs: os: [windows-latest, macos-latest, ubuntu-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} allow-prereleases: true From 51f17bd2d1523a588fdab49addedf594d54166c2 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 3 Jan 2026 19:07:43 +0200 Subject: [PATCH 18/26] Add support for Python 3.15 (#322) Co-authored-by: Ezio Melotti --- .github/workflows/test.yml | 7 ++----- tox.ini | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e54a235..d00a215 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,9 +1,6 @@ name: Test -on: - push: - pull_request: - workflow_dispatch: +on: [push, pull_request, workflow_dispatch] permissions: {} @@ -52,7 +49,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.13", "3.14"] + python-version: ["3.13", "3.14", "3.15"] os: [windows-latest, macos-latest, ubuntu-latest] steps: diff --git a/tox.ini b/tox.ini index a4d051c..9047969 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ requires = env_list = cog lint - py{314, 313} + py{315, 314, 313} [testenv] package = wheel From 9db09dbb8cdafd0fb90dea1962f7e1c346b35822 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 2 Jan 2026 19:08:55 +0200 Subject: [PATCH 19/26] Refresh version tables in README --- README.md | 50 +++++++++++++++++++++++------------------------ check_versions.py | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 8ba7070..cd70025 100644 --- a/README.md +++ b/README.md @@ -31,16 +31,16 @@ Sphinx configuration in various branches: | version | requirements.txt | conf.py | |-----------|--------------------|----------------------| | 2.6 | ø | ø | -| 2.7 | ø | ø | +| 2.7 | ø | needs_sphinx='1.2' | | 3.0 | ø | ø | | 3.1 | ø | ø | | 3.2 | ø | ø | | 3.3 | ø | ø | | 3.4 | ø | needs_sphinx='1.2' | -| 3.5 | ø | ø | -| 3.6 | ø | ø | -| 3.7 | ø | ø | -| 3.8 | ø | ø | +| 3.5 | ø | needs_sphinx='1.8' | +| 3.6 | ø | needs_sphinx='1.2' | +| 3.7 | sphinx==2.3.1 | needs_sphinx="1.6.6" | +| 3.8 | sphinx==2.4.4 | needs_sphinx='1.8' | | 3.9 | sphinx==2.4.4 | needs_sphinx='1.8' | | 3.10 | sphinx==3.4.3 | needs_sphinx='3.2' | | 3.11 | sphinx~=7.2.0 | needs_sphinx='4.2' | @@ -51,26 +51,26 @@ Sphinx configuration in various branches: Sphinx build as seen on docs.python.org: -| version | el | en | es | fr | bn-in | id | it | ja | ko | pl | pt-br | ro | tr | uk | zh-cn | zh-tw | -|-----------|-------|-------|-------|-------|---------|-------|-------|-------|-------|-------|---------|-------|-------|-------|---------|---------| -| 2.6 | ø | 0.6.5 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | -| 2.7 | ø | 2.3.1 | ø | 2.3.1 | ø | 2.3.1 | ø | 2.3.1 | 2.3.1 | ø | 2.3.1 | ø | ø | ø | 2.3.1 | 2.3.1 | -| 3.0 | ø | 0.6 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | -| 3.1 | ø | 0.6.5 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | -| 3.2 | ø | 1.0.7 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | -| 3.3 | ø | 1.2 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | -| 3.4 | ø | 1.2.3 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | -| 3.5 | ø | 1.8.4 | 1.8.4 | 1.8.4 | ø | 1.8.4 | ø | 1.8.4 | 1.8.4 | 1.8.4 | 1.8.4 | ø | ø | ø | 1.8.4 | 1.8.4 | -| 3.6 | ø | 2.3.1 | 2.3.1 | 2.3.1 | ø | 2.3.1 | ø | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | ø | ø | ø | 2.3.1 | 2.3.1 | -| 3.7 | ø | 2.3.1 | 2.3.1 | 2.3.1 | ø | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | ø | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | -| 3.8 | ø | 2.4.4 | 2.4.4 | 2.4.4 | ø | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | ø | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | -| 3.9 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | -| 3.10 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | -| 3.11 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | -| 3.12 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | -| 3.13 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | -| 3.14 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | -| 3.15 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | +| version | el | en | es | fr | bn-in | id | it | ja | ko | pl | pt-br | ro | sv | tr | uk | zh-cn | zh-tw | +|-----------|-------|-------|-------|-------|---------|-------|-------|-------|-------|-------|---------|-------|-------|-------|-------|---------|---------| +| 2.6 | ø | 0.6.5 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | +| 2.7 | ø | 2.3.1 | ø | 2.3.1 | ø | 2.3.1 | ø | 2.3.1 | 2.3.1 | ø | 2.3.1 | ø | ø | ø | ø | 2.3.1 | 2.3.1 | +| 3.0 | ø | 0.6 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | +| 3.1 | ø | 0.6.5 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | +| 3.2 | ø | 1.0.7 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | +| 3.3 | ø | 1.2 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | +| 3.4 | ø | 1.2.3 | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | ø | +| 3.5 | ø | 1.8.4 | 1.8.4 | 1.8.4 | ø | 1.8.4 | ø | 1.8.4 | 1.8.4 | 1.8.4 | 1.8.4 | ø | ø | ø | ø | 1.8.4 | 1.8.4 | +| 3.6 | ø | 2.3.1 | 2.3.1 | 2.3.1 | ø | 2.3.1 | ø | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | ø | ø | ø | ø | 2.3.1 | 2.3.1 | +| 3.7 | ø | 2.3.1 | 2.3.1 | 2.3.1 | ø | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | ø | ø | 2.3.1 | 2.3.1 | 2.3.1 | 2.3.1 | +| 3.8 | ø | 2.4.4 | 2.4.4 | 2.4.4 | ø | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | ø | ø | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | +| 3.9 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | ø | 2.4.4 | 2.4.4 | 2.4.4 | 2.4.4 | +| 3.10 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | ø | 3.4.3 | 3.4.3 | 3.4.3 | 3.4.3 | +| 3.11 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | ø | 7.2.6 | 7.2.6 | 7.2.6 | 7.2.6 | +| 3.12 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | ø | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | +| 3.13 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | +| 3.14 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | +| 3.15 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | 8.2.3 | Install `tools_requirements.txt` then run `python check_versions.py diff --git a/check_versions.py b/check_versions.py index e381424..f62b53e 100644 --- a/check_versions.py +++ b/check_versions.py @@ -15,7 +15,7 @@ logger = logging.getLogger(__name__) http = urllib3.PoolManager() -VERSIONS = build_docs.parse_versions_from_devguide(http) +VERSIONS = build_docs.parse_versions_from_peps_site(http) LANGUAGES = build_docs.parse_languages_from_config() From 10b7e27ba5a6f2aa9d67bf758821093101e3be11 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 4 Jan 2026 14:14:59 +0200 Subject: [PATCH 20/26] Normalise to single quotes --- README.md | 2 +- check_versions.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cd70025..407d0f2 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Sphinx configuration in various branches: | 3.4 | ø | needs_sphinx='1.2' | | 3.5 | ø | needs_sphinx='1.8' | | 3.6 | ø | needs_sphinx='1.2' | -| 3.7 | sphinx==2.3.1 | needs_sphinx="1.6.6" | +| 3.7 | sphinx==2.3.1 | needs_sphinx='1.6.6' | | 3.8 | sphinx==2.4.4 | needs_sphinx='1.8' | | 3.9 | sphinx==2.4.4 | needs_sphinx='1.8' | | 3.10 | sphinx==3.4.3 | needs_sphinx='3.2' | diff --git a/check_versions.py b/check_versions.py index f62b53e..b50d69d 100644 --- a/check_versions.py +++ b/check_versions.py @@ -36,13 +36,13 @@ def find_upstream_remote_name(repo: git.Repo) -> str: return f"{remote.name}/" -def find_sphinx_spec(text: str): +def find_sphinx_spec(text: str) -> str: if found := re.search( """sphinx[=<>~]{1,2}[0-9.]{3,}|needs_sphinx = [0-9.'"]*""", text, flags=re.IGNORECASE, ): - return found.group(0).replace(" ", "") + return found.group(0).replace(" ", "").replace('"', "'") return "ø" From 33bccd1e1742639115268961e68d4af48c8148f6 Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Tue, 6 Jan 2026 15:35:22 -0600 Subject: [PATCH 21/26] init after loading dsn (#326) --- build_docs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build_docs.py b/build_docs.py index 973c9a0..fdeee67 100755 --- a/build_docs.py +++ b/build_docs.py @@ -86,8 +86,6 @@ import sentry_sdk except ImportError: sentry_sdk = None -else: - sentry_sdk.init() HERE = Path(__file__).resolve().parent @@ -1005,6 +1003,8 @@ def main() -> int: args = parse_args() setup_logging(args.log_directory, args.select_output) load_environment_variables() + if sentry_sdk: + sentry_sdk.init() if args.select_output is None: return build_docs_with_lock(args, "build_docs.lock") From 257c1051952ea7e5e83f59aae1a8cc49a3ea2594 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Date: Sun, 25 Jan 2026 16:17:59 +0000 Subject: [PATCH 22/26] Don't include Plausible in download copies (#321) --- build_docs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build_docs.py b/build_docs.py index fdeee67..cabfaec 100755 --- a/build_docs.py +++ b/build_docs.py @@ -1009,6 +1009,8 @@ def main() -> int: if args.select_output is None: return build_docs_with_lock(args, "build_docs.lock") if args.select_output == "no-html": + # Disable Plausible analytics for download copies + os.environ.pop("PYTHON_DOCS_ENABLE_ANALYTICS", None) return build_docs_with_lock(args, "build_docs_archives.lock") if args.select_output == "only-html": return build_docs_with_lock(args, "build_docs_html.lock") From 311645129e17631849fc0441607de18c3b17ca6e Mon Sep 17 00:00:00 2001 From: Maciej Olko Date: Thu, 26 Feb 2026 14:30:56 +0100 Subject: [PATCH 23/26] Don't panic on invalid version status (#328) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- build_docs.py | 10 ++++++---- tests/test_build_docs_versions.py | 32 ++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/build_docs.py b/build_docs.py index cabfaec..2473fd7 100755 --- a/build_docs.py +++ b/build_docs.py @@ -111,11 +111,13 @@ def from_json(cls, data: dict) -> Versions: status = release["status"] status = Version.SYNONYMS.get(status, status) if status not in Version.STATUSES: - msg = ( - f"Saw invalid version status {status!r}, " - f"expected to be one of {permitted}." + logging.warning( + "Saw invalid version status %r, expected to be one of %s. Context: %s", + status, + permitted, + release, ) - raise ValueError(msg) + continue versions.append(Version(name=name, status=status, branch_or_tag=branch)) return cls(sorted(versions, key=Version.as_tuple)) diff --git a/tests/test_build_docs_versions.py b/tests/test_build_docs_versions.py index 1d8f6dc..8e7e230 100644 --- a/tests/test_build_docs_versions.py +++ b/tests/test_build_docs_versions.py @@ -1,5 +1,7 @@ from __future__ import annotations +import logging + import pytest from build_docs import Version, Versions @@ -57,16 +59,28 @@ def test_from_json() -> None: ] -def test_from_json_error() -> None: +def test_from_json_warning(caplog) -> None: # Arrange - json_data = {"2.8": {"branch": "2.8", "pep": 404, "status": "ex-release"}} - - # Act / Assert - with pytest.raises( - ValueError, - match="Saw invalid version status 'ex-release', expected to be one of", - ): - Versions.from_json(json_data) + json_data = { + "2.8": {"branch": "2.8", "pep": 404, "status": "ex-release"}, + "3.16": { + "branch": "", + "pep": 826, + "status": "", + "first_release": "2027-10-06", + "end_of_life": "2032-10", + "release_manager": "Savannah Ostrowski", + }, + } + + # Act + with caplog.at_level(logging.WARNING): + versions = list(Versions.from_json(json_data)) + + # Assert: both should be skipped + assert versions == [] + assert "Saw invalid version status 'ex-release'" in caplog.text + assert "Saw invalid version status ''" in caplog.text def test_current_stable(versions) -> None: From 97d2d71e3bfacd88530af53b7d12958ef8a39c7d Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 7 Mar 2026 19:17:33 +0200 Subject: [PATCH 24/26] Add "planned" status for pre-branch version (#329) --- build_docs.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/build_docs.py b/build_docs.py index 2473fd7..4305a68 100755 --- a/build_docs.py +++ b/build_docs.py @@ -151,10 +151,24 @@ class Version: """Represents a CPython version and its documentation build dependencies.""" name: str - status: Literal["EOL", "security-fixes", "stable", "pre-release", "in development"] + status: Literal[ + "planned", + "in development", + "pre-release", + "stable", + "security-fixes", + "EOL", + ] branch_or_tag: str - STATUSES = {"EOL", "security-fixes", "stable", "pre-release", "in development"} + STATUSES = { + "planned", + "in development", + "pre-release", + "stable", + "security-fixes", + "EOL", + } # Those synonyms map branch status vocabulary found in the devguide # with our vocabulary. From 0d0ad87aaad1d66fb90268fd293259afe1396622 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 11 Mar 2026 09:53:43 +0200 Subject: [PATCH 25/26] Skip "planned" branches such as 3.16 (#330) --- build_docs.py | 8 ++++++-- tests/test_build_docs_versions.py | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/build_docs.py b/build_docs.py index 4305a68..e44ffa0 100755 --- a/build_docs.py +++ b/build_docs.py @@ -109,6 +109,9 @@ def from_json(cls, data: dict) -> Versions: for name, release in data.items(): branch = release["branch"] status = release["status"] + if status in Version.SKIP_STATUSES: + logging.info("Skipping %s with status %r", name, status) + continue status = Version.SYNONYMS.get(status, status) if status not in Version.STATUSES: logging.warning( @@ -152,7 +155,6 @@ class Version: name: str status: Literal[ - "planned", "in development", "pre-release", "stable", @@ -162,7 +164,6 @@ class Version: branch_or_tag: str STATUSES = { - "planned", "in development", "pre-release", "stable", @@ -170,6 +171,9 @@ class Version: "EOL", } + # Statuses for versions we don't build docs for at all. + SKIP_STATUSES = {"planned"} + # Those synonyms map branch status vocabulary found in the devguide # with our vocabulary. SYNONYMS = { diff --git a/tests/test_build_docs_versions.py b/tests/test_build_docs_versions.py index 8e7e230..4af2a08 100644 --- a/tests/test_build_docs_versions.py +++ b/tests/test_build_docs_versions.py @@ -66,7 +66,7 @@ def test_from_json_warning(caplog) -> None: "3.16": { "branch": "", "pep": 826, - "status": "", + "status": "planned", "first_release": "2027-10-06", "end_of_life": "2032-10", "release_manager": "Savannah Ostrowski", @@ -74,13 +74,13 @@ def test_from_json_warning(caplog) -> None: } # Act - with caplog.at_level(logging.WARNING): + with caplog.at_level(logging.INFO): versions = list(Versions.from_json(json_data)) # Assert: both should be skipped assert versions == [] assert "Saw invalid version status 'ex-release'" in caplog.text - assert "Saw invalid version status ''" in caplog.text + assert "Skipping 3.16 with status 'planned'" in caplog.text def test_current_stable(versions) -> None: From c672bf5527554a69004e661562e60a3342ae8c1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 09:50:57 +0800 Subject: [PATCH 26/26] Bump the actions group with 2 updates (#331) Bumps the actions group with 2 updates: [j178/prek-action](https://github.com/j178/prek-action) and [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `j178/prek-action` from 1 to 2 - [Release notes](https://github.com/j178/prek-action/releases) - [Commits](https://github.com/j178/prek-action/compare/v1...v2) Updates `actions/upload-artifact` from 6 to 7 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: j178/prek-action dependency-version: '2' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2068239..7e18045 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,4 +17,4 @@ jobs: - uses: actions/checkout@v6 with: persist-credentials: false - - uses: j178/prek-action@v1 + - uses: j178/prek-action@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d00a215..61e095f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,7 +37,7 @@ jobs: --branches 3.14 - name: Upload documentation - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: www-root path: ./www