diff --git a/docs/conf.py b/docs/conf.py index bbb23ac..1413c85 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,13 +34,6 @@ nitpicky = True nitpick_ignore: list[tuple[str, str]] = [] -nitpick_ignore = [ - ('py:class', '_pytest.nodes.Item'), - ('py:class', 'Config'), - ('py:class', 'Path'), - ('py:class', 'Session'), -] - # Include Python intersphinx mapping to prevent failures # jaraco/skeleton#51 extensions += ['sphinx.ext.intersphinx'] @@ -65,3 +58,13 @@ intersphinx_mapping.update( pytest=('https://docs.pytest.org/en/latest/', None), ) + +# local + +nitpick_ignore += [ + ('py:class', 'Config'), + ('py:class', 'Session'), + # jaraco/pytest-checkdocs#25 + ('py:class', 'PackageMetadata'), + ('py:class', 'Node'), +] diff --git a/mypy.ini b/mypy.ini index efcb8cb..8cbce3d 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,6 +1,6 @@ [mypy] # Is the project well-typed? -strict = False +strict = True # Early opt-in even when strict = False warn_unused_ignores = True @@ -13,3 +13,7 @@ explicit_package_bases = True disable_error_code = # Disable due to many false positives overload-overlap, + +# jaraco/jaraco.packaging#20 +[mypy-jaraco.packaging.*] +ignore_missing_imports = True diff --git a/newsfragments/25.feature.rst b/newsfragments/25.feature.rst new file mode 100644 index 0000000..88e9b7c --- /dev/null +++ b/newsfragments/25.feature.rst @@ -0,0 +1 @@ +Complete annotations and add ``py.typed`` marker -- by :user:`Avasam` diff --git a/pyproject.toml b/pyproject.toml index eb59798..13abe37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,6 +65,8 @@ type = [ # local "types-docutils", # pytest-checkdocs#13 + "importlib_metadata; python_version < '3.12'", # Only used for annotations, not at runtime + "pytest>=8.3.3" # Typing fixes not necessary at runtime ] @@ -73,7 +75,3 @@ pytest11 = {checkdocs = "pytest_checkdocs"} [tool.setuptools_scm] - - -[tool.pytest-enabler.mypy] -# Disabled due to jaraco/skeleton#143 diff --git a/pytest_checkdocs/__init__.py b/pytest_checkdocs/__init__.py index 6f5d28c..ccf8dce 100644 --- a/pytest_checkdocs/__init__.py +++ b/pytest_checkdocs/__init__.py @@ -1,49 +1,72 @@ +from __future__ import annotations + import contextlib import pathlib import re +import sys +from collections.abc import Iterator +from typing import TYPE_CHECKING, Any -import pytest import docutils.core +import docutils.nodes +import docutils.utils +import pytest from jaraco.packaging import metadata +if TYPE_CHECKING: + if sys.version_info >= (3, 12): + from importlib.metadata import PackageMetadata + else: + from importlib_metadata import PackageMetadata + + from _pytest.nodes import Node + from typing_extensions import Self project_files = 'setup.py', 'setup.cfg', 'pyproject.toml' -def pytest_collect_file(file_path: pathlib.Path, parent): +def pytest_collect_file(file_path: pathlib.Path, parent: Node) -> CheckdocsItem | None: if file_path.name not in project_files: - return + return None return CheckdocsItem.from_parent(parent, name='project') class Description(str): + content_type: str = "" + @classmethod - def from_md(cls, md): + def from_md(cls, md: PackageMetadata) -> Self: desc = cls(md.get('Description')) desc.content_type = md.get('Description-Content-Type', 'text/x-rst') return desc class CheckdocsItem(pytest.Item): - def runtest(self): + def runtest(self) -> None: desc = self.get_long_description() method_name = f"run_{re.sub('[-/]', '_', desc.content_type)}" getattr(self, method_name)(desc) - def run_text_markdown(self, desc): + def run_text_markdown(self, desc: str) -> None: "stubbed" - def run_text_x_rst(self, desc): + def run_text_x_rst(self, desc: str) -> None: with self.monkey_patch_system_message() as reports: self.rst2html(desc) assert not reports @contextlib.contextmanager - def monkey_patch_system_message(self): - reports = [] + def monkey_patch_system_message(self) -> Iterator[list[str | Exception]]: + reports: list[str | Exception] = [] orig = docutils.utils.Reporter.system_message - def system_message(reporter, level, message, *children, **kwargs): + def system_message( + reporter: docutils.utils.Reporter, + level: int, + message: str | Exception, + *children: docutils.nodes.Node, + **kwargs: Any, + ) -> docutils.nodes.system_message: result = orig(reporter, level, message, *children, **kwargs) if level >= reporter.WARNING_LEVEL: # All reST failures preventing doc publishing go to reports @@ -52,17 +75,14 @@ def system_message(reporter, level, message, *children, **kwargs): return result - docutils.utils.Reporter.system_message = system_message + docutils.utils.Reporter.system_message = system_message # type: ignore[assignment] # type-stubs expands the kwargs yield reports - docutils.utils.Reporter.system_message = orig + docutils.utils.Reporter.system_message = orig # type: ignore[method-assign] - def get_long_description(self): + def get_long_description(self) -> Description: return Description.from_md(metadata.load('.')) @staticmethod - def rst2html(value): - docutils_settings = {} - parts = docutils.core.publish_parts( - source=value, writer_name="html4css1", settings_overrides=docutils_settings - ) - return parts['whole'] + def rst2html(value: str) -> str: + parts = docutils.core.publish_parts(source=value, writer_name="html4css1") + return parts['whole'] # type: ignore[no-any-return] # python/typeshed#12595 diff --git a/pytest_checkdocs/py.typed b/pytest_checkdocs/py.typed new file mode 100644 index 0000000..e69de29