diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fa98ba..c450118 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,10 +15,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python 3.9 + - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: "3.11" - uses: pre-commit/action@v3.0.1 tests: @@ -27,22 +27,18 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.9", "3.10", "3.11", "3.12"] - sphinx-version: ["~=7.0"] + python-version: ["3.11", "3.12", "3.13", "3.14"] + sphinx-version: ["~=7.0", "~=8.0"] extras: ["testing"] include: - - os: ubuntu-latest - python-version: "3.9" - sphinx-version: "~=6.0" - extras: "testing" - - os: ubuntu-latest - python-version: "3.10" - sphinx-version: "~=8.0" - extras: "testing-no-myst" # TODO myst does not yet support Sphinx 8.0 - os: windows-latest - python-version: "3.9" + python-version: "3.11" sphinx-version: "~=7.0" extras: "testing" + - os: windows-latest + python-version: "3.14" + sphinx-version: "~=8.0" + extras: "testing" runs-on: ${{ matrix.os }} @@ -81,10 +77,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python 3.9 + - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.9" + python-version: "3.11" cache: pip - name: Install dependencies run: | @@ -123,7 +119,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: "3.11" - name: install flit run: | pip install flit~=3.4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d1d68f3..7b072d5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ exclude: > repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v6.0.0 hooks: - id: check-json - id: check-yaml @@ -19,14 +19,14 @@ repos: - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.5 + rev: v0.14.9 hooks: - id: ruff args: [--fix] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.1 + rev: v1.19.1 hooks: - id: mypy additional_dependencies: [] diff --git a/.readthedocs.yml b/.readthedocs.yml index 929b95b..405595e 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -3,7 +3,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.10" + python: "3.11" python: install: @@ -13,5 +13,6 @@ python: - rtd sphinx: + configuration: docs/conf.py builder: html fail_on_warning: true diff --git a/pyproject.toml b/pyproject.toml index 995c00c..6f3c60d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,11 +15,10 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Text Processing :: Markup", @@ -27,8 +26,8 @@ classifiers = [ "Topic :: Text Processing :: Markup :: reStructuredText", ] keywords = ["sphinx", "extension", "material design", "web components"] -requires-python = ">=3.9" -dependencies = ["sphinx>=6,<9"] +requires-python = ">=3.11" +dependencies = ["sphinx>=7,<9"] [project.urls] Homepage = "https://github.com/executablebooks/sphinx-design" @@ -36,9 +35,9 @@ Documentation = "https://sphinx-design.readthedocs.io" [project.optional-dependencies] code-style = ["pre-commit>=3,<4"] -rtd = ["myst-parser>=2,<4"] +rtd = ["myst-parser>=3,<5"] testing = [ - "myst-parser>=2,<4", + "myst-parser>=3,<5", "pytest~=8.3", "pytest-cov", "pytest-regressions", diff --git a/sphinx_design/__init__.py b/sphinx_design/__init__.py index 581dfc7..2a38a8b 100644 --- a/sphinx_design/__init__.py +++ b/sphinx_design/__init__.py @@ -9,7 +9,7 @@ def setup(app: "Sphinx") -> dict: - from .extension import setup_extension + from .extension import setup_extension # noqa: PLC0415 setup_extension(app) return { diff --git a/sphinx_design/_compat.py b/sphinx_design/_compat.py index 3797c73..e909d1e 100644 --- a/sphinx_design/_compat.py +++ b/sphinx_design/_compat.py @@ -1,8 +1,7 @@ """Helpers for cross compatibility across dependency versions.""" -from collections.abc import Iterable +from collections.abc import Callable, Iterable from importlib import resources -from typing import Callable from docutils.nodes import Element @@ -14,8 +13,5 @@ def findall(node: Element) -> Callable[..., Iterable[Element]]: return getattr(node, "findall", node.traverse) -# TODO: >= Python 3.9, only use `resources.files` and drop `resources.read_text` def read_text(module: resources.Package, filename: str) -> str: - if hasattr(resources, "files"): - return resources.files(module).joinpath(filename).read_text() - return resources.read_text(module, filename) + return resources.files(module).joinpath(filename).read_text() diff --git a/sphinx_design/article_info.py b/sphinx_design/article_info.py index 7961d12..d003bd3 100644 --- a/sphinx_design/article_info.py +++ b/sphinx_design/article_info.py @@ -1,5 +1,3 @@ -from typing import Optional - from docutils import nodes from docutils.parsers.rst import directives from sphinx.application import Sphinx @@ -32,7 +30,7 @@ class ArticleInfoDirective(SdDirective): } def _parse_text( - self, text: str, icon: Optional[nodes.Node] = None, parse: bool = False + self, text: str, icon: nodes.Node | None = None, parse: bool = False ) -> nodes.Node: """Parse the text.""" if not parse: diff --git a/sphinx_design/badges_buttons.py b/sphinx_design/badges_buttons.py index c610d12..61b83a0 100644 --- a/sphinx_design/badges_buttons.py +++ b/sphinx_design/badges_buttons.py @@ -1,5 +1,3 @@ -from typing import Optional - from docutils import nodes from docutils.parsers.rst import directives from sphinx import addnodes @@ -45,7 +43,7 @@ def setup_badges_and_buttons(app: Sphinx) -> None: app.add_directive(DIRECTIVE_NAME_BUTTON_REF, ButtonRefDirective) -def create_bdg_classes(color: Optional[str], outline: bool) -> list[str]: +def create_bdg_classes(color: str | None, outline: bool) -> list[str]: """Create the badge classes.""" classes = [ "sd-sphinx-override", @@ -63,7 +61,7 @@ def create_bdg_classes(color: Optional[str], outline: bool) -> list[str]: class BadgeRole(SphinxRole): """Role to display a badge.""" - def __init__(self, color: Optional[str] = None, *, outline: bool = False) -> None: + def __init__(self, color: str | None = None, *, outline: bool = False) -> None: super().__init__() self.color = color self.outline = outline @@ -82,7 +80,7 @@ def run(self) -> tuple[list[nodes.Node], list[nodes.system_message]]: class LinkBadgeRole(ReferenceRole): """Role to display a badge with an external link.""" - def __init__(self, color: Optional[str] = None, *, outline: bool = False) -> None: + def __init__(self, color: str | None = None, *, outline: bool = False) -> None: super().__init__() self.color = color self.outline = outline @@ -105,7 +103,7 @@ def run(self) -> tuple[list[nodes.Node], list[nodes.system_message]]: class XRefBadgeRole(ReferenceRole): """Role to display a badge with an internal link.""" - def __init__(self, color: Optional[str] = None, *, outline: bool = False) -> None: + def __init__(self, color: str | None = None, *, outline: bool = False) -> None: super().__init__() self.color = color self.outline = outline diff --git a/sphinx_design/cards.py b/sphinx_design/cards.py index da522f5..c243d90 100644 --- a/sphinx_design/cards.py +++ b/sphinx_design/cards.py @@ -1,5 +1,5 @@ import re -from typing import NamedTuple, Optional +from typing import NamedTuple from docutils import nodes from docutils.parsers.rst import directives @@ -42,8 +42,8 @@ class CardContent(NamedTuple): """ body: tuple[int, StringList] - header: Optional[tuple[int, StringList]] = None - footer: Optional[tuple[int, StringList]] = None + header: tuple[int, StringList] | None = None + footer: tuple[int, StringList] | None = None class CardDirective(SdDirective): @@ -79,13 +79,13 @@ def run_with_defaults(self) -> list[nodes.Node]: @classmethod def create_card( # noqa: PLR0915 - cls, inst: SphinxDirective, arguments: Optional[list], options: dict + cls, inst: SphinxDirective, arguments: list | None, options: dict ) -> nodes.Node: """Run the directive.""" # TODO better degradation for latex card_classes = ["sd-card", "sd-sphinx-override"] if "width" in options: - card_classes += [f'sd-w-{options["width"].rstrip("%")}'] + card_classes += [f"sd-w-{options['width'].rstrip('%')}"] card_classes += options.get("margin", ["sd-mb-3"]) card_classes += [f"sd-shadow-{options.get('shadow', 'sm')}"] if "link" in options: diff --git a/sphinx_design/grids.py b/sphinx_design/grids.py index 3614822..1610c0c 100644 --- a/sphinx_design/grids.py +++ b/sphinx_design/grids.py @@ -1,5 +1,3 @@ -from typing import Optional - from docutils import nodes from docutils.parsers.rst import directives from sphinx.application import Sphinx @@ -33,7 +31,7 @@ def setup_grids(app: Sphinx): def _media_option( - argument: Optional[str], + argument: str | None, prefix: str, *, allow_auto: bool = False, @@ -66,11 +64,11 @@ def _media_option( raise ValueError(validate_error_msg) return [f"{prefix}{values[0]}"] + [ f"{prefix}{size}-{value}" - for size, value in zip(["xs", "sm", "md", "lg"], values) + for size, value in zip(["xs", "sm", "md", "lg"], values, strict=False) ] -def row_columns_option(argument: Optional[str]) -> list[str]: +def row_columns_option(argument: str | None) -> list[str]: """Validate the number of columns (out of 12) a grid row will have. One or four integers (for "xs sm md lg") between 1 and 12 (or 'auto'). @@ -78,7 +76,7 @@ def row_columns_option(argument: Optional[str]) -> list[str]: return _media_option(argument, "sd-row-cols-", allow_auto=True) -def item_columns_option(argument: Optional[str]) -> list[str]: +def item_columns_option(argument: str | None) -> list[str]: """Validate the number of columns (out of 12) a grid-item will take up. One or four integers (for "xs sm md lg") between 1 and 12 (or 'auto'). @@ -86,7 +84,7 @@ def item_columns_option(argument: Optional[str]) -> list[str]: return _media_option(argument, "sd-col-", allow_auto=True) -def gutter_option(argument: Optional[str]) -> list[str]: +def gutter_option(argument: str | None) -> list[str]: """Validate the gutter size between grid items. One or four integers (for "xs sm md lg") between 0 and 5. @@ -191,7 +189,7 @@ def run_with_defaults(self) -> list[nodes.Node]: + self.options.get("margin", []) + self.options.get("padding", []) + ( - [f'sd-align-major-{self.options["child-align"]}'] + [f"sd-align-major-{self.options['child-align']}"] if "child-align" in self.options else [] ) diff --git a/sphinx_design/icons.py b/sphinx_design/icons.py index 9aceb12..2a3f4f9 100644 --- a/sphinx_design/icons.py +++ b/sphinx_design/icons.py @@ -2,7 +2,7 @@ from functools import lru_cache import json import re -from typing import Any, Optional +from typing import Any from docutils import nodes from docutils.parsers.rst import directives @@ -65,7 +65,7 @@ def get_octicon( name: str, height: str = "1em", classes: Sequence[str] = (), - aria_label: Optional[str] = None, + aria_label: str | None = None, ) -> str: """Return the HTML for an GitHub octicon SVG icon. @@ -258,7 +258,7 @@ def get_material_icon( name: str, height: str = "1em", classes: Sequence[str] = (), - aria_label: Optional[str] = None, + aria_label: str | None = None, ) -> str: """Return the HTML for an Google material icon SVG icon. diff --git a/sphinx_design/shared.py b/sphinx_design/shared.py index a092048..d10f895 100644 --- a/sphinx_design/shared.py +++ b/sphinx_design/shared.py @@ -167,7 +167,7 @@ def _margin_or_padding_option( if len(values) == 4: return [ f"{class_prefix}{side}-{value}" - for side, value in zip(["t", "b", "l", "r"], values) + for side, value in zip(["t", "b", "l", "r"], values, strict=False) ] raise ValueError( "argument must be one (all) or four (top bottom left right) integers" diff --git a/tests/conftest.py b/tests/conftest.py index 409f586..5b53a86 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ import os from pathlib import Path -from typing import Any, Optional +from typing import Any from docutils import nodes import pytest @@ -69,7 +69,7 @@ def get_doctree( @pytest.fixture() def sphinx_builder(tmp_path: Path, make_app, monkeypatch): def _create_project( - buildername: str = "html", conf_kwargs: Optional[dict[str, Any]] = None + buildername: str = "html", conf_kwargs: dict[str, Any] | None = None ): src_path = tmp_path / "srcdir" src_path.mkdir() diff --git a/tests/test_snippets.py b/tests/test_snippets.py index e47bea7..5af018f 100644 --- a/tests/test_snippets.py +++ b/tests/test_snippets.py @@ -1,7 +1,7 @@ """Test the documented snippets run correctly, and are the same for both RST and MyST.""" +from collections.abc import Callable from pathlib import Path -from typing import Callable import pytest @@ -45,7 +45,7 @@ def test_snippets_rst( doctree.attributes.pop("translation_progress", None) # added in sphinx 7.1 file_regression.check( doctree.pformat(), - basename=f"snippet_pre_{path.name[:-len(path.suffix)]}", + basename=f"snippet_pre_{path.name[: -len(path.suffix)]}", extension=".xml", encoding="utf8", ) @@ -70,7 +70,7 @@ def test_snippets_myst( doctree.attributes.pop("translation_progress", None) # added in sphinx 7.1 file_regression.check( doctree.pformat(), - basename=f"snippet_pre_{path.name[:-len(path.suffix)]}", + basename=f"snippet_pre_{path.name[: -len(path.suffix)]}", extension=".xml", encoding="utf8", ) @@ -94,7 +94,7 @@ def test_snippets_rst_post( doctree.attributes.pop("translation_progress", None) # added in sphinx 7.1 file_regression.check( doctree.pformat(), - basename=f"snippet_post_{path.name[:-len(path.suffix)]}", + basename=f"snippet_post_{path.name[: -len(path.suffix)]}", extension=".xml", encoding="utf8", ) @@ -119,7 +119,7 @@ def test_snippets_myst_post( doctree.attributes.pop("translation_progress", None) # added in sphinx 7.1 file_regression.check( doctree.pformat(), - basename=f"snippet_post_{path.name[:-len(path.suffix)]}", + basename=f"snippet_post_{path.name[: -len(path.suffix)]}", extension=".xml", encoding="utf8", ) diff --git a/tox.ini b/tox.ini index 57aa07f..805c762 100644 --- a/tox.ini +++ b/tox.ini @@ -3,18 +3,18 @@ # then run `tox` or `tox -- {pytest args}` # run in parallel using `tox -p` [tox] -envlist = py39 +envlist = py311 [testenv] usedevelop = true -[testenv:py{39,310,311,312,313}] +[testenv:py{311,312,313,314}] description = Run unit tests with this Python version extras = testing commands = pytest {posargs} -[testenv:py{39,310,311,312,313}-no-myst] +[testenv:py{311,312,313,314}-no-myst] description = Run unit tests with this Python version extras = testing-no-myst