diff --git a/.babel.cfg b/.babel.cfg new file mode 100644 index 00000000..692580d8 --- /dev/null +++ b/.babel.cfg @@ -0,0 +1 @@ +[jinja2: **.html] diff --git a/.github/workflows/pypi-package.yml b/.github/workflows/pypi-package.yml index c7acc055..744d49bd 100644 --- a/.github/workflows/pypi-package.yml +++ b/.github/workflows/pypi-package.yml @@ -11,6 +11,9 @@ on: permissions: contents: read +env: + FORCE_COLOR: 1 + jobs: # Always build & lint package. build-package: @@ -45,3 +48,5 @@ jobs: - name: Upload package to PyPI uses: pypa/gh-action-pypi-publish@release/v1 + with: + attestations: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 70182f02..ace177ed 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - branch: ["origin/main", "3.12", "3.11", "3.10"] + branch: ["origin/main", "3.13", "3.12", "3.11", "3.10"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -48,3 +48,41 @@ jobs: with: name: doc-html-${{ matrix.branch }} path: www/ + + translations: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest", "windows-latest"] + # Test minimum supported and latest stable from 3.x series + python-version: ["3.9", "3"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + cache: pip + - name: Install dependencies + run: | + pip install --upgrade pip + pip install -r requirements.txt + - name: Remove locale file for testing + shell: bash + run: rm -rf locales/pt_BR/ + - run: python babel_runner.py extract + - run: python babel_runner.py init -l pt_BR + - run: python babel_runner.py update + - run: python babel_runner.py update -l pt_BR + - run: python babel_runner.py compile + - run: python babel_runner.py compile -l pt_BR + - name: Print .pot file + shell: bash + run: cat locales/messages.pot + - name: Print .po file + shell: bash + run: cat locales/pt_BR/LC_MESSAGES/messages.po + - name: list files in locales dir + shell: bash + run: ls -R locales/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 42cd35e9..071ad406 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,12 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.0 + rev: v0.6.8 hooks: - id: ruff args: [--exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.4.0 + rev: 24.8.0 hooks: - id: black @@ -22,24 +22,24 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.2 + rev: 0.29.2 hooks: - id: check-dependabot - id: check-github-workflows - repo: https://github.com/rhysd/actionlint - rev: v1.6.27 + rev: v1.7.2 hooks: - id: actionlint - repo: https://github.com/tox-dev/pyproject-fmt - rev: 1.8.0 + rev: 2.2.4 hooks: - id: pyproject-fmt args: [--max-supported-python=3.13] - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.16 + rev: v0.20.2 hooks: - id: validate-pyproject diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0ed7628e..2f4ac84b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,18 @@ Changelog ========= +`2024.10 `_ +------------------------------------------------------------------------------ + +- Add support for Python 3.13 (#196) + Contributed by Hugo van Kemenade +- Drop support for Python 3.8 (#197) + Contributed by Hugo van Kemenade +- Add script for handling translations (#195) + Contributed by Rafael Fontenelle +- Generate digital attestations for PyPI (PEP 740) (#198) + Contributed by Hugo van Kemenade + `2024.6 `_ ---------------------------------------------------------------------------- diff --git a/babel_runner.py b/babel_runner.py new file mode 100755 index 00000000..da4001c7 --- /dev/null +++ b/babel_runner.py @@ -0,0 +1,120 @@ +#!/usr/bin/venv python3 +"""Script for handling translations with Babel""" +from __future__ import annotations + +import argparse +import subprocess +from pathlib import Path + +try: + import tomllib +except ImportError: + try: + import tomli as tomllib + except ImportError as ie: + raise ImportError( + "tomli or tomllib is required to parse pyproject.toml" + ) from ie + +PROJECT_DIR = Path(__file__).resolve().parent + +# Global variables used by pybabel below (paths relative to PROJECT_DIR) +DOMAIN = "messages" +COPYRIGHT_HOLDER = "Python Software Foundation" +LOCALES_DIR = "locales" +POT_FILE = Path(LOCALES_DIR, f"{DOMAIN}.pot") +SOURCE_DIR = "python_docs_theme" +MAPPING_FILE = ".babel.cfg" + + +def get_project_info() -> dict: + """Retrieve project's info to populate the message catalog template""" + with open(Path(PROJECT_DIR / "pyproject.toml"), "rb") as f: + data = tomllib.load(f) + return data["project"] + + +def extract_messages() -> None: + """Extract messages from all source files into message catalog template""" + Path(PROJECT_DIR, LOCALES_DIR).mkdir(parents=True, exist_ok=True) + project_data = get_project_info() + subprocess.run( + [ + "pybabel", + "extract", + "-F", + MAPPING_FILE, + "--copyright-holder", + COPYRIGHT_HOLDER, + "--project", + project_data["name"], + "--version", + project_data["version"], + "--msgid-bugs-address", + project_data["urls"]["Issue tracker"], + "-o", + POT_FILE, + SOURCE_DIR, + ], + cwd=PROJECT_DIR, + check=True, + ) + + +def init_locale(locale: str) -> None: + """Initialize a new locale based on existing message catalog template""" + pofile = PROJECT_DIR / LOCALES_DIR / locale / "LC_MESSAGES" / f"{DOMAIN}.po" + if pofile.exists(): + print(f"There is already a message catalog for locale {locale}, skipping.") + return + cmd = ["pybabel", "init", "-i", POT_FILE, "-d", LOCALES_DIR, "-l", locale] + subprocess.run(cmd, cwd=PROJECT_DIR, check=True) + + +def update_catalogs(locale: str) -> None: + """Update translations from existing message catalogs""" + cmd = ["pybabel", "update", "-i", POT_FILE, "-d", LOCALES_DIR] + if locale: + cmd.extend(["-l", locale]) + subprocess.run(cmd, cwd=PROJECT_DIR, check=True) + + +def compile_catalogs(locale: str) -> None: + """Compile existing message catalogs""" + cmd = ["pybabel", "compile", "-d", LOCALES_DIR] + if locale: + cmd.extend(["-l", locale]) + subprocess.run(cmd, cwd=PROJECT_DIR, check=True) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "command", + choices=["extract", "init", "update", "compile"], + help="command to be executed", + ) + parser.add_argument( + "-l", + "--locale", + default="", + help="language code (needed for init, optional for update and compile)", + ) + + args = parser.parse_args() + locale = args.locale + + if args.command == "extract": + extract_messages() + elif args.command == "init": + if not locale: + parser.error("init requires passing the --locale option") + init_locale(locale) + elif args.command == "update": + update_catalogs(locale) + elif args.command == "compile": + compile_catalogs(locale) + + +if __name__ == "__main__": + main() diff --git a/code_of_conduct.rst b/code_of_conduct.rst deleted file mode 100644 index 4bc6630f..00000000 --- a/code_of_conduct.rst +++ /dev/null @@ -1,13 +0,0 @@ -Code of Conduct -=============== - -Please note that all interactions on -`Python Software Foundation `__-supported -infrastructure is `covered -`__ -by the `PSF Code of Conduct `__, -which includes all infrastructure used in the development of Python itself -(e.g. mailing lists, issue trackers, GitHub, etc.). - -In general this means everyone is expected to be open, considerate, and -respectful of others no matter what their position is within the project. diff --git a/pyproject.toml b/pyproject.toml index 4571a414..a39131e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,17 +1,17 @@ [build-system] build-backend = "flit_core.buildapi" requires = [ - "flit_core>=3.7", + "flit-core>=3.7", ] [project] name = "python-docs-theme" -version = "2024.6" +version = "2024.10" description = "The Sphinx theme for the CPython docs and related projects" readme = "README.md" license.file = "LICENSE" -authors = [{name = "PyPA", email = "distutils-sig@python.org"}] -requires-python = ">=3.8" +authors = [ { name = "PyPA", email = "distutils-sig@python.org" } ] +requires-python = ">=3.9" classifiers = [ "Development Status :: 5 - Production/Stable", "Framework :: Sphinx :: Theme", @@ -20,7 +20,6 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -33,22 +32,18 @@ urls.Code = "https://github.com/python/python-docs-theme" urls.Download = "https://pypi.org/project/python-docs-theme/" urls.Homepage = "https://github.com/python/python-docs-theme/" urls."Issue tracker" = "https://github.com/python/python-docs-theme/issues" -[project.entry-points."sphinx.html_themes"] -python_docs_theme = 'python_docs_theme' +entry-points."sphinx.html_themes".python_docs_theme = "python_docs_theme" [tool.flit.module] name = "python_docs_theme" [tool.flit.sdist] -include = [ - "python_docs_theme/", -] +include = [ "python_docs_theme/" ] [tool.ruff] fix = true -[tool.ruff.lint] -select = [ +lint.select = [ "C4", # flake8-comprehensions "E", # pycodestyle errors "F", # pyflakes errors @@ -57,19 +52,16 @@ select = [ "LOG", # flake8-logging "PGH", # pygrep-hooks "PYI", # flake8-pyi - "RUF100", # unused noqa (yesqa) "RUF022", # unsorted-dunder-all + "RUF100", # unused noqa (yesqa) "UP", # pyupgrade "W", # pycodestyle warnings "YTT", # flake8-2020 ] -ignore = [ - "E203", # Whitespace before ':' - "E221", # Multiple spaces before operator - "E226", # Missing whitespace around arithmetic operator - "E241", # Multiple spaces after ',' +lint.ignore = [ + "E203", # Whitespace before ':' + "E221", # Multiple spaces before operator + "E226", # Missing whitespace around arithmetic operator + "E241", # Multiple spaces after ',' ] - - -[tool.ruff.lint.isort] -required-imports = ["from __future__ import annotations"] +lint.isort.required-imports = [ "from __future__ import annotations" ] diff --git a/python_docs_theme/__init__.py b/python_docs_theme/__init__.py index 7b9df306..0ee7d25d 100644 --- a/python_docs_theme/__init__.py +++ b/python_docs_theme/__init__.py @@ -2,7 +2,7 @@ import hashlib import os -from functools import lru_cache +from functools import cache from pathlib import Path from typing import Any @@ -12,7 +12,7 @@ THEME_PATH = Path(__file__).parent.resolve() -@lru_cache(maxsize=None) +@cache def _asset_hash(path: str) -> str: """Append a `?digest=` to an url based on the file content.""" full_path = THEME_PATH / path.replace("_static/", "static/") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..18e6c17c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +# for babel_runner.py +setuptools +Babel +Jinja2 +tomli; python_version < "3.10"