From c7a1ca2154a17d8350186bb5ac384c613471e4ee Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 28 Jul 2024 05:02:26 +0100 Subject: [PATCH] Adopt Ruff and use stricter MyPy settings --- .flake8 | 4 -- .github/workflows/test.yml | 4 +- .ruff.toml | 53 +++++++++++++++++++++++ Makefile | 2 +- pyproject.toml | 36 +++++++++++++-- sphinxcontrib/serializinghtml/__init__.py | 24 +++++++--- sphinxcontrib/serializinghtml/jsonimpl.py | 6 +-- sphinxcontrib/serializinghtml/py.typed | 0 tests/conftest.py | 15 +++---- tests/test_serializinghtml.py | 11 ++++- tox.ini | 6 +-- 11 files changed, 128 insertions(+), 33 deletions(-) delete mode 100644 .flake8 create mode 100644 .ruff.toml create mode 100644 sphinxcontrib/serializinghtml/py.typed diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 5af0a95..0000000 --- a/.flake8 +++ /dev/null @@ -1,4 +0,0 @@ -[flake8] -max-line-length = 95 -ignore = E116,E241,E251 -exclude = .git,.tox,.venv diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8cd7db1..856df5b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,7 +71,9 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - env: [flake8, mypy] + env: + - ruff + - mypy steps: - uses: actions/checkout@v3 diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..4b7dd2a --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,53 @@ +target-version = "py39" # Pin Ruff to Python 3.9 +output-format = "full" +line-length = 95 + +[lint] +preview = true +select = [ +# "ANN", # flake8-annotations + "C4", # flake8-comprehensions + "COM", # flake8-commas + "B", # flake8-bugbear + "DTZ", # flake8-datetimez + "E", # pycodestyle + "EM", # flake8-errmsg + "EXE", # flake8-executable + "F", # pyflakes + "FA", # flake8-future-annotations + "FLY", # flynt + "FURB", # refurb + "G", # flake8-logging-format + "I", # isort + "ICN", # flake8-import-conventions + "INT", # flake8-gettext + "LOG", # flake8-logging + "PERF", # perflint + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PT", # flake8-pytest-style + "SIM", # flake8-simplify + "SLOT", # flake8-slots + "TCH", # flake8-type-checking + "UP", # pyupgrade + "W", # pycodestyle + "YTT", # flake8-2020 +] +ignore = [ + "E116", + "E241", + "E251", +] + +[lint.per-file-ignores] +"tests/*" = [ + "ANN", # tests don't need annotations +] + +[lint.isort] +forced-separate = [ + "tests", +] +required-imports = [ + "from __future__ import annotations", +] diff --git a/Makefile b/Makefile index 26f411a..438ee54 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ clean-mypyfiles: .PHONY: style-check style-check: - @flake8 + @ruff check .PHONY: type-check type-check: diff --git a/pyproject.toml b/pyproject.toml index 7f88862..27d3008 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,9 +48,9 @@ test = [ "pytest", ] lint = [ - "flake8", + "ruff==0.5.5", "mypy", - "docutils-stubs", + "types-docutils", ] standalone = [ "Sphinx>=5", @@ -76,4 +76,34 @@ exclude = [ ] [tool.mypy] -ignore_missing_imports = true +python_version = "3.9" +packages = [ + "sphinxcontrib", + "tests", +] +exclude = [ + "tests/roots", +] +check_untyped_defs = true +disallow_any_generics = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +explicit_package_bases = true +extra_checks = true +no_implicit_reexport = true +show_column_numbers = true +show_error_context = true +strict_optional = true +warn_redundant_casts = true +warn_unused_configs = true +warn_unused_ignores = true +enable_error_code = [ + "type-arg", + "redundant-self", + "truthy-iterable", + "ignore-without-code", + "unused-awaitable", +] diff --git a/sphinxcontrib/serializinghtml/__init__.py b/sphinxcontrib/serializinghtml/__init__.py index 11301c4..ee41019 100644 --- a/sphinxcontrib/serializinghtml/__init__.py +++ b/sphinxcontrib/serializinghtml/__init__.py @@ -4,7 +4,7 @@ import pickle import types from os import path -from typing import Any +from typing import TYPE_CHECKING from sphinx.application import ENV_PICKLE_FILENAME, Sphinx from sphinx.builders.html import BuildInfo, StandaloneHTMLBuilder @@ -13,6 +13,16 @@ from sphinxcontrib.serializinghtml import jsonimpl +if TYPE_CHECKING: + from collections.abc import Sequence + from typing import Any, Protocol + + class SerialisingImplementation(Protocol): + def dump(self, obj: Any, file: Any, *args: Any, **kwargs: Any) -> None: ... + def dumps(self, obj: Any, *args: Any, **kwargs: Any) -> str | bytes: ... + def load(self, file: Any, *args: Any, **kwargs: Any) -> Any: ... + def loads(self, data: Any, *args: Any, **kwargs: Any) -> Any: ... + __version__ = '1.1.10' __version_info__ = (1, 1, 10) @@ -31,11 +41,11 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder): """ #: the serializing implementation to use. Set this to a module that #: implements a `dump`, `load`, `dumps` and `loads` functions - #: (pickle, simplejson etc.) - implementation: Any = None + #: (pickle, json etc.) + implementation: SerialisingImplementation implementation_dumps_unicode = False #: additional arguments for dump() - additional_dump_args: tuple = () + additional_dump_args: Sequence[Any] = () #: the filename for the global context file globalcontext_filename: str = '' @@ -62,7 +72,7 @@ def get_target_uri(self, docname: str, typ: str | None = None) -> str: return docname[:-5] # up to sep return docname + SEP - def dump_context(self, context: dict, filename: str | os.PathLike[str]) -> None: + def dump_context(self, context: dict[str, Any], filename: str | os.PathLike[str]) -> None: context = context.copy() if 'css_files' in context: context['css_files'] = [css.filename for css in context['css_files']] @@ -75,7 +85,7 @@ def dump_context(self, context: dict, filename: str | os.PathLike[str]) -> None: with open(filename, 'wb') as fb: self.implementation.dump(context, fb, *self.additional_dump_args) - def handle_page(self, pagename: str, ctx: dict, templatename: str = 'page.html', + def handle_page(self, pagename: str, ctx: dict[str, Any], templatename: str = 'page.html', outfilename: str | None = None, event_arg: Any = None) -> None: ctx['current_page_name'] = pagename ctx.setdefault('pathto', lambda p: p) @@ -132,7 +142,7 @@ class PickleHTMLBuilder(SerializingHTMLBuilder): implementation = pickle implementation_dumps_unicode = False - additional_dump_args = (pickle.HIGHEST_PROTOCOL,) + additional_dump_args: tuple[Any] = (pickle.HIGHEST_PROTOCOL,) indexer_format = pickle indexer_dumps_unicode = False out_suffix = '.fpickle' diff --git a/sphinxcontrib/serializinghtml/jsonimpl.py b/sphinxcontrib/serializinghtml/jsonimpl.py index e80c047..9b89875 100644 --- a/sphinxcontrib/serializinghtml/jsonimpl.py +++ b/sphinxcontrib/serializinghtml/jsonimpl.py @@ -4,7 +4,7 @@ import json from collections import UserString -from typing import Any, IO +from typing import IO, Any class SphinxJSONEncoder(json.JSONEncoder): @@ -15,9 +15,9 @@ def default(self, obj: Any) -> str: return super().default(obj) -def dump(obj: Any, fp: IO, *args: Any, **kwds: Any) -> None: +def dump(obj: Any, file: IO[str] | IO[bytes], *args: Any, **kwds: Any) -> None: kwds['cls'] = SphinxJSONEncoder - json.dump(obj, fp, *args, **kwds) + json.dump(obj, file, *args, **kwds) def dumps(obj: Any, *args: Any, **kwds: Any) -> str: diff --git a/sphinxcontrib/serializinghtml/py.typed b/sphinxcontrib/serializinghtml/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py index d4b08e5..3934d3f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,17 +1,14 @@ +from __future__ import annotations + from pathlib import Path import pytest -import sphinx - -pytest_plugins = 'sphinx.testing.fixtures' +pytest_plugins = ( + 'sphinx.testing.fixtures', +) @pytest.fixture(scope='session') -def rootdir(): - if sphinx.version_info[:2] < (7, 2): - from sphinx.testing.path import path - - return path(__file__).parent.abspath() / 'roots' - +def rootdir() -> Path: return Path(__file__).resolve().parent / 'roots' diff --git a/tests/test_serializinghtml.py b/tests/test_serializinghtml.py index b6f6380..480930b 100644 --- a/tests/test_serializinghtml.py +++ b/tests/test_serializinghtml.py @@ -1,13 +1,20 @@ """Test for serializinghtml extension.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + import pytest +if TYPE_CHECKING: + from sphinx.application import Sphinx + @pytest.mark.sphinx('json', testroot='basic') -def test_json(app, status, warning): +def test_json(app: Sphinx) -> None: app.builder.build_all() @pytest.mark.sphinx('pickle', testroot='basic') -def test_pickle(app, status, warning): +def test_pickle(app: Sphinx) -> None: app.builder.build_all() diff --git a/tox.ini b/tox.ini index 9ad9f73..233022d 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ minversion = 2.4.0 envlist = py{39,310,311,312,313}, - flake8, + ruff, mypy isolated_build = True @@ -18,14 +18,14 @@ setenv = commands= pytest --durations 25 {posargs} -[testenv:flake8] +[testenv:ruff] description = Run style checks. extras = test lint commands= - flake8 + ruff check [testenv:mypy] description =