From 33a7dd8fcf5c77f0cbb8b490997ff88a6eb895ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Thu, 15 Jun 2023 10:06:55 -0700 Subject: [PATCH] Run formatter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bernát Gábor --- CHANGELOG.md | 2 +- pyproject.toml | 6 +- src/sphinx_autodoc_typehints/__init__.py | 89 +++++----- .../attributes_patch.py | 32 ++-- src/sphinx_autodoc_typehints/patches.py | 28 ++- tests/conftest.py | 8 +- tests/roots/test-dummy/conf.py | 2 + tests/roots/test-dummy/dummy_module.py | 2 + .../dummy_module_future_annotations.py | 5 +- tests/roots/test-dummy/dummy_module_simple.py | 5 +- .../dummy_module_simple_no_use_rtype.py | 11 +- ...dummy_module_without_complete_typehints.py | 11 +- tests/roots/test-integration/conf.py | 2 + .../test-resolve-typing-guard-tmp/conf.py | 2 + .../demo_typing_guard.py | 5 +- tests/roots/test-resolve-typing-guard/conf.py | 2 + .../demo_typing_guard.py | 2 +- .../demo_typing_guard_dummy.py | 4 +- tests/test_integration.py | 162 +++++++++--------- tests/test_sphinx_autodoc_typehints.py | 52 ++++-- 20 files changed, 249 insertions(+), 183 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ffac0b2..c17fb4cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -273,7 +273,7 @@ ## 1.2.1 -- Fixed `` ValueError` when ``getargspec()\`\` encounters a built-in function +- Fixed ``ValueError` when``getargspec()\`\` encounters a built-in function - Fixed `AttributeError` when `Any` is combined with another type in a `Union` (thanks Davis Kirkendall) ## 1.2.0 diff --git a/pyproject.toml b/pyproject.toml index 78da6ca2..bde45b1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,8 +26,12 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python", - "Programming Language :: Python :: 3", "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", + "Programming Language :: Python :: 3.12", "Topic :: Documentation :: Sphinx", ] dynamic = [ diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index c1bd24a7..83f4883c 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -1,22 +1,17 @@ from __future__ import annotations +import ast import inspect import re import sys import textwrap import types -from ast import FunctionDef, Module, stmt from dataclasses import dataclass -from typing import Any, AnyStr, Callable, ForwardRef, NewType, TypeVar, get_type_hints +from typing import TYPE_CHECKING, Any, AnyStr, Callable, ForwardRef, NewType, TypeVar, get_type_hints from docutils.frontend import OptionParser -from docutils.nodes import Node from docutils.parsers.rst import Parser as RstParser from docutils.utils import new_document -from sphinx.application import Sphinx -from sphinx.config import Config -from sphinx.environment import BuildEnvironment -from sphinx.ext.autodoc import Options from sphinx.ext.autodoc.mock import mock from sphinx.util import logging from sphinx.util.inspect import signature as sphinx_signature @@ -25,6 +20,15 @@ from .patches import install_patches from .version import __version__ +if TYPE_CHECKING: + from ast import FunctionDef, Module, stmt + + from docutils.nodes import Node + from sphinx.application import Sphinx + from sphinx.config import Config + from sphinx.environment import BuildEnvironment + from sphinx.ext.autodoc import Options + _LOGGER = logging.getLogger(__name__) _PYDATA_ANNOTATIONS = {"Any", "AnyStr", "Callable", "ClassVar", "Literal", "NoReturn", "Optional", "Tuple", "Union"} @@ -59,7 +63,8 @@ def get_annotation_module(annotation: Any) -> str: return annotation.__module__ # type: ignore # deduced Any if hasattr(annotation, "__origin__"): return annotation.__origin__.__module__ # type: ignore # deduced Any - raise ValueError(f"Cannot determine the module of {annotation}") + msg = f"Cannot determine the module of {annotation}" + raise ValueError(msg) def _is_newtype(annotation: Any) -> bool: @@ -130,9 +135,7 @@ def get_annotation_args(annotation: Any, module: str, class_name: str) -> tuple[ def format_internal_tuple(t: tuple[Any, ...], config: Config) -> str: # An annotation can be a tuple, e.g., for nptyping: - # NDArray[(typing.Any, ...), Float] # In this case, format_annotation receives: - # (typing.Any, Ellipsis) # This solution should hopefully be general for *any* type that allows tuples in annotations fmt = [format_annotation(a, config) for a in t] if len(fmt) == 0: @@ -153,7 +156,7 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901 # t # Special cases if isinstance(annotation, ForwardRef): return annotation.__forward_arg__ - if annotation is None or annotation is type(None): # noqa: E721 + if annotation is None or annotation is type(None): return ":py:obj:`None`" if annotation is Ellipsis: return ":py:data:`...`" @@ -178,10 +181,7 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901 # t full_name = f"{module}.{class_name}" if module != "builtins" else class_name fully_qualified: bool = getattr(config, "typehints_fully_qualified", False) prefix = "" if fully_qualified or full_name == class_name else "~" - if module == "typing" and class_name in _PYDATA_ANNOTATIONS: - role = "data" - else: - role = "class" + role = "data" if module == "typing" and class_name in _PYDATA_ANNOTATIONS else "class" args_format = "\\[{}]" formatted_args: str | None = "" @@ -200,19 +200,19 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901 # t args_format += ")" formatted_args = None if args else args_format elif full_name == "typing.Optional": - args = tuple(x for x in args if x is not type(None)) # noqa: E721 + args = tuple(x for x in args if x is not type(None)) elif full_name in ("typing.Union", "types.UnionType") and type(None) in args: if len(args) == 2: full_name = "typing.Optional" role = "data" - args = tuple(x for x in args if x is not type(None)) # noqa: E721 + args = tuple(x for x in args if x is not type(None)) else: simplify_optional_unions: bool = getattr(config, "simplify_optional_unions", True) if not simplify_optional_unions: full_name = "typing.Optional" role = "data" args_format = f"\\[:py:data:`{prefix}typing.Union`\\[{{}}]]" - args = tuple(x for x in args if x is not type(None)) # noqa: E721 + args = tuple(x for x in args if x is not type(None)) elif full_name in ("typing.Callable", "collections.abc.Callable") and args and args[0] is not ...: fmt = [format_annotation(arg, config) for arg in args] formatted_args = f"\\[\\[{', '.join(fmt[:-1])}], {fmt[-1]}]" @@ -237,15 +237,12 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901 # t # reference: https://github.com/pytorch/pytorch/pull/46548/files def normalize_source_lines(source_lines: str) -> str: """ - This helper function accepts a list of source lines. It finds the - indentation level of the function definition (`def`), then it indents - all lines in the function body to a point at or greater than that - level. This allows for comments and continued string literals that - are at a lower indentation than the rest of the code. - Arguments: - source_lines: source code - Returns: - source lines that have been correctly aligned + This helper function accepts a list of source lines. It finds the indentation level of the function definition + (`def`), then it indents all lines in the function body to a point at or greater than that level. This allows for + comments and continued string literals that are at a lower indentation than the rest of the code. + + :param source_lines: source code + :return: source lines that have been correctly aligned """ lines = source_lines.split("\n") @@ -280,7 +277,13 @@ def remove_prefix(text: str, prefix: str) -> str: def process_signature( - app: Sphinx, what: str, name: str, obj: Any, options: Options, signature: str, return_annotation: str # noqa: U100 + app: Sphinx, + what: str, + name: str, + obj: Any, + options: Options, + signature: str, + return_annotation: str, ) -> tuple[str, None] | None: if not callable(obj): return None @@ -405,9 +408,6 @@ def _get_type_hint(autodoc_mock_imports: list[str], name: str, obj: Any) -> dict def backfill_type_hints(obj: Any, name: str) -> dict[str, Any]: - parse_kwargs = {} - import ast - parse_kwargs = {"type_comments": True} def _one_child(module: Module) -> stmt | None: @@ -531,7 +531,12 @@ def format_default(app: Sphinx, default: Any, is_annotated: bool) -> str | None: def process_docstring( - app: Sphinx, what: str, name: str, obj: Any, options: Options | None, lines: list[str] # noqa: U100 + app: Sphinx, + what: str, + name: str, + obj: Any, + options: Options | None, + lines: list[str], ) -> None: original_obj = obj obj = obj.fget if isinstance(obj, property) else obj @@ -564,16 +569,15 @@ def _get_sphinx_line_keyword_and_argument(line: str) -> tuple[str, str | None] | >>> _get_sphinx_line_keyword_and_argument("some invalid line") None """ - - param_line_without_description = line.split(":", maxsplit=2) # noqa: SC200 + param_line_without_description = line.split(":", maxsplit=2) if len(param_line_without_description) != 3: return None - split_directive_and_name = param_line_without_description[1].split(maxsplit=1) # noqa: SC200 + split_directive_and_name = param_line_without_description[1].split(maxsplit=1) if len(split_directive_and_name) != 2: if not len(split_directive_and_name): return None - return (split_directive_and_name[0], None) + return split_directive_and_name[0], None return tuple(split_directive_and_name) # type: ignore @@ -591,10 +595,7 @@ def _line_is_param_line_for_arg(line: str, arg_name: str) -> bool: if keyword not in {"param", "parameter", "arg", "argument"}: return False - for prefix in ("", r"\*", r"\**", r"\*\*"): - if doc_name == prefix + arg_name: - return True - return False + return any(doc_name == prefix + arg_name for prefix in ("", "\\*", "\\**", "\\*\\*")) def _inject_types_to_docstring( @@ -786,14 +787,16 @@ def _inject_rtype( lines[insert_index] = f":return: {formatted_annotation} --{line[line.find(' '):]}" -def validate_config(app: Sphinx, env: BuildEnvironment, docnames: list[str]) -> None: # noqa: U100 +def validate_config(app: Sphinx, env: BuildEnvironment, docnames: list[str]) -> None: valid = {None, "comma", "braces", "braces-after"} if app.config.typehints_defaults not in valid | {False}: - raise ValueError(f"typehints_defaults needs to be one of {valid!r}, not {app.config.typehints_defaults!r}") + msg = f"typehints_defaults needs to be one of {valid!r}, not {app.config.typehints_defaults!r}" + raise ValueError(msg) formatter = app.config.typehints_formatter if formatter is not None and not callable(formatter): - raise ValueError(f"typehints_formatter needs to be callable or `None`, not {formatter}") + msg = f"typehints_formatter needs to be callable or `None`, not {formatter}" + raise ValueError(msg) def setup(app: Sphinx) -> dict[str, bool]: diff --git a/src/sphinx_autodoc_typehints/attributes_patch.py b/src/sphinx_autodoc_typehints/attributes_patch.py index b5c571fb..af5922ab 100644 --- a/src/sphinx_autodoc_typehints/attributes_patch.py +++ b/src/sphinx_autodoc_typehints/attributes_patch.py @@ -1,17 +1,22 @@ +from __future__ import annotations + from functools import partial -from optparse import Values -from typing import Any, Tuple +from typing import TYPE_CHECKING, Any from unittest.mock import patch import sphinx.domains.python import sphinx.ext.autodoc from docutils.parsers.rst import Parser as RstParser from docutils.utils import new_document -from sphinx.addnodes import desc_signature -from sphinx.application import Sphinx from sphinx.domains.python import PyAttribute from sphinx.ext.autodoc import AttributeDocumenter +if TYPE_CHECKING: + from optparse import Values + + from sphinx.addnodes import desc_signature + from sphinx.application import Sphinx + # Defensively check for the things we want to patch _parse_annotation = getattr(sphinx.domains.python, "_parse_annotation", None) @@ -36,18 +41,21 @@ orig_handle_signature = PyAttribute.handle_signature -def stringify_annotation(app: Sphinx, annotation: Any, mode: str = "") -> str: # noqa: U100 - """Format the annotation with sphinx-autodoc-typehints and inject our +def stringify_annotation(app: Sphinx, annotation: Any, mode: str = "") -> str: + """ + Format the annotation with sphinx-autodoc-typehints and inject our magic prefix to tell our patched PyAttribute.handle_signature to treat - it as rst.""" + it as rst. + """ from . import format_annotation return TYPE_IS_RST_LABEL + format_annotation(annotation, app.config) def patch_attribute_documenter(app: Sphinx) -> None: - """Instead of using stringify_typehint in - `AttributeDocumenter.add_directive_header`, use `format_annotation` + """ + Instead of using stringify_typehint in + `AttributeDocumenter.add_directive_header`, use `format_annotation`. """ def add_directive_header(*args: Any, **kwargs: Any) -> Any: @@ -58,7 +66,7 @@ def add_directive_header(*args: Any, **kwargs: Any) -> Any: def rst_to_docutils(settings: Values, rst: str) -> Any: - """Convert rst to a sequence of docutils nodes""" + """Convert rst to a sequence of docutils nodes.""" doc = new_document("", settings) RstParser().parse(rst, doc) # Remove top level paragraph node so that there is no line break. @@ -74,7 +82,7 @@ def patched_parse_annotation(settings: Values, typ: str, env: Any) -> Any: return rst_to_docutils(settings, typ) -def patched_handle_signature(self: PyAttribute, sig: str, signode: desc_signature) -> Tuple[str, str]: +def patched_handle_signature(self: PyAttribute, sig: str, signode: desc_signature) -> tuple[str, str]: target = "sphinx.domains.python._parse_annotation" new_func = partial(patched_parse_annotation, self.state.document.settings) with patch(target, new_func): @@ -82,7 +90,7 @@ def patched_handle_signature(self: PyAttribute, sig: str, signode: desc_signatur def patch_attribute_handling(app: Sphinx) -> None: - """Use format_signature to format class attribute type annotations""" + """Use format_signature to format class attribute type annotations.""" if not OKAY_TO_PATCH: return PyAttribute.handle_signature = patched_handle_signature # type:ignore[method-assign] diff --git a/src/sphinx_autodoc_typehints/patches.py b/src/sphinx_autodoc_typehints/patches.py index 193528c1..3f2d6703 100644 --- a/src/sphinx_autodoc_typehints/patches.py +++ b/src/sphinx_autodoc_typehints/patches.py @@ -1,16 +1,18 @@ from __future__ import annotations from functools import lru_cache -from typing import Any +from typing import TYPE_CHECKING, Any from docutils.parsers.rst.directives.admonitions import BaseAdmonition from docutils.parsers.rst.states import Text -from sphinx.application import Sphinx -from sphinx.ext.autodoc import Options from sphinx.ext.napoleon.docstring import GoogleDocstring from .attributes_patch import patch_attribute_handling +if TYPE_CHECKING: + from sphinx.application import Sphinx + from sphinx.ext.autodoc import Options + @lru_cache # A cute way to make sure the function only runs once. def fix_autodoc_typehints_for_overloaded_methods() -> None: @@ -36,7 +38,12 @@ def fix_autodoc_typehints_for_overloaded_methods() -> None: def napoleon_numpy_docstring_return_type_processor( - app: Sphinx, what: str, name: str, obj: Any, options: Options | None, lines: list[str] # noqa: U100 + app: Sphinx, + what: str, + name: str, + obj: Any, + options: Options | None, + lines: list[str], ) -> None: """Insert a : under Returns: to tell napoleon not to look for a return type.""" if what not in ["function", "method"]: @@ -73,8 +80,9 @@ def fix_napoleon_numpy_docstring_return_type(app: Sphinx) -> None: app.connect("autodoc-process-docstring", napoleon_numpy_docstring_return_type_processor, priority=499) -def patched_lookup_annotation(*_args: Any) -> str: # noqa: U101 - """GoogleDocstring._lookup_annotation sometimes adds incorrect type +def patched_lookup_annotation(*_args: Any) -> str: + """ + GoogleDocstring._lookup_annotation sometimes adds incorrect type annotations to constructor parameters (and otherwise does nothing). Disable it so we can handle this on our own. """ @@ -82,8 +90,9 @@ def patched_lookup_annotation(*_args: Any) -> str: # noqa: U101 def patch_google_docstring_lookup_annotation() -> None: - """Fix issue 308: - https://github.com/tox-dev/sphinx-autodoc-typehints/issues/308 + """ + Fix issue 308: + https://github.com/tox-dev/sphinx-autodoc-typehints/issues/308. """ GoogleDocstring._lookup_annotation = patched_lookup_annotation # type: ignore[assignment] @@ -111,7 +120,8 @@ def patched_text_indent(self: Text, *args: Any) -> Any: def patch_line_numbers() -> None: - """Make the rst parser put line numbers on more nodes. + """ + Make the rst parser put line numbers on more nodes. When the line numbers are missing, we have a hard time placing the :rtype:. """ diff --git a/tests/conftest.py b/tests/conftest.py index 373a444c..04dbb8ce 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,13 +5,15 @@ import shutil import sys from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any import pytest -from _pytest.config import Config from sphinx.testing.path import path from sphobjinv import Inventory +if TYPE_CHECKING: + from _pytest.config import Config + pytest_plugins = "sphinx.testing.fixtures" collect_ignore = ["roots"] @@ -50,7 +52,7 @@ def rootdir() -> path: return path(os.path.dirname(__file__) or ".").abspath() / "roots" -def pytest_ignore_collect(path: Any, config: Config) -> bool | None: # noqa: U100 +def pytest_ignore_collect(path: Any, config: Config) -> bool | None: version_re = re.compile(r"_py(\d)(\d)\.py$") match = version_re.search(path.basename) if match: diff --git a/tests/roots/test-dummy/conf.py b/tests/roots/test-dummy/conf.py index 14a5de11..079621cd 100644 --- a/tests/roots/test-dummy/conf.py +++ b/tests/roots/test-dummy/conf.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pathlib import sys diff --git a/tests/roots/test-dummy/dummy_module.py b/tests/roots/test-dummy/dummy_module.py index 65d35a89..95197cd2 100644 --- a/tests/roots/test-dummy/dummy_module.py +++ b/tests/roots/test-dummy/dummy_module.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass diff --git a/tests/roots/test-dummy/dummy_module_future_annotations.py b/tests/roots/test-dummy/dummy_module_future_annotations.py index 5cc16cb1..1b1302b5 100644 --- a/tests/roots/test-dummy/dummy_module_future_annotations.py +++ b/tests/roots/test-dummy/dummy_module_future_annotations.py @@ -2,7 +2,10 @@ def function_with_py310_annotations( - self, x: bool | None, y: int | str | float, z: str | None = None # noqa: U100 + self, + x: bool | None, + y: int | str | float, + z: str | None = None, ) -> str: """ Method docstring. diff --git a/tests/roots/test-dummy/dummy_module_simple.py b/tests/roots/test-dummy/dummy_module_simple.py index 400c748a..b1b03ed0 100644 --- a/tests/roots/test-dummy/dummy_module_simple.py +++ b/tests/roots/test-dummy/dummy_module_simple.py @@ -1,4 +1,7 @@ -def function(x: bool, y: int = 1) -> str: # noqa: U100 +from __future__ import annotations + + +def function(x: bool, y: int = 1) -> str: """ Function docstring. diff --git a/tests/roots/test-dummy/dummy_module_simple_no_use_rtype.py b/tests/roots/test-dummy/dummy_module_simple_no_use_rtype.py index 3f10b4dc..b7c8277d 100644 --- a/tests/roots/test-dummy/dummy_module_simple_no_use_rtype.py +++ b/tests/roots/test-dummy/dummy_module_simple_no_use_rtype.py @@ -1,4 +1,7 @@ -def function_no_returns(x: bool, y: int = 1) -> str: # noqa: U100 +from __future__ import annotations + + +def function_no_returns(x: bool, y: int = 1) -> str: """ Function docstring. @@ -7,7 +10,7 @@ def function_no_returns(x: bool, y: int = 1) -> str: # noqa: U100 """ -def function_returns_with_type(x: bool, y: int = 1) -> str: # noqa: U100 +def function_returns_with_type(x: bool, y: int = 1) -> str: """ Function docstring. @@ -17,7 +20,7 @@ def function_returns_with_type(x: bool, y: int = 1) -> str: # noqa: U100 """ -def function_returns_with_compound_type(x: bool, y: int = 1) -> str: # noqa: U100 +def function_returns_with_compound_type(x: bool, y: int = 1) -> str: """ Function docstring. @@ -27,7 +30,7 @@ def function_returns_with_compound_type(x: bool, y: int = 1) -> str: # noqa: U1 """ -def function_returns_without_type(x: bool, y: int = 1) -> str: # noqa: U100 +def function_returns_without_type(x: bool, y: int = 1) -> str: """ Function docstring. diff --git a/tests/roots/test-dummy/dummy_module_without_complete_typehints.py b/tests/roots/test-dummy/dummy_module_without_complete_typehints.py index 6d7e91ef..1fbbcd42 100644 --- a/tests/roots/test-dummy/dummy_module_without_complete_typehints.py +++ b/tests/roots/test-dummy/dummy_module_without_complete_typehints.py @@ -1,4 +1,7 @@ -def function_with_some_defaults_and_without_typehints(x, y=None): # noqa: U100 +from __future__ import annotations + + +def function_with_some_defaults_and_without_typehints(x, y=None): """ Function docstring. @@ -7,7 +10,7 @@ def function_with_some_defaults_and_without_typehints(x, y=None): # noqa: U100 """ -def function_with_some_defaults_and_some_typehints(x: int, y=None): # noqa: U100 +def function_with_some_defaults_and_some_typehints(x: int, y=None): """ Function docstring. @@ -16,7 +19,7 @@ def function_with_some_defaults_and_some_typehints(x: int, y=None): # noqa: U10 """ -def function_with_some_defaults_and_more_typehints(x: int, y=None) -> str: # noqa: U100 +def function_with_some_defaults_and_more_typehints(x: int, y=None) -> str: """ Function docstring. @@ -25,7 +28,7 @@ def function_with_some_defaults_and_more_typehints(x: int, y=None) -> str: # no """ -def function_with_defaults_and_some_typehints(x: int = 0, y=None) -> str: # noqa: U100 +def function_with_defaults_and_some_typehints(x: int = 0, y=None) -> str: """ Function docstring. diff --git a/tests/roots/test-integration/conf.py b/tests/roots/test-integration/conf.py index 14a5de11..079621cd 100644 --- a/tests/roots/test-integration/conf.py +++ b/tests/roots/test-integration/conf.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pathlib import sys diff --git a/tests/roots/test-resolve-typing-guard-tmp/conf.py b/tests/roots/test-resolve-typing-guard-tmp/conf.py index bb939ea4..a327e5bd 100644 --- a/tests/roots/test-resolve-typing-guard-tmp/conf.py +++ b/tests/roots/test-resolve-typing-guard-tmp/conf.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pathlib import sys diff --git a/tests/roots/test-resolve-typing-guard-tmp/demo_typing_guard.py b/tests/roots/test-resolve-typing-guard-tmp/demo_typing_guard.py index 392488aa..e048b012 100644 --- a/tests/roots/test-resolve-typing-guard-tmp/demo_typing_guard.py +++ b/tests/roots/test-resolve-typing-guard-tmp/demo_typing_guard.py @@ -1,13 +1,10 @@ """Module demonstrating imports that are type guarded""" from __future__ import annotations -from typing import TYPE_CHECKING +import datetime from attrs import define -if TYPE_CHECKING: - import datetime - @define() class SomeClass: diff --git a/tests/roots/test-resolve-typing-guard/conf.py b/tests/roots/test-resolve-typing-guard/conf.py index bb939ea4..a327e5bd 100644 --- a/tests/roots/test-resolve-typing-guard/conf.py +++ b/tests/roots/test-resolve-typing-guard/conf.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pathlib import sys diff --git a/tests/roots/test-resolve-typing-guard/demo_typing_guard.py b/tests/roots/test-resolve-typing-guard/demo_typing_guard.py index fe120ad7..2b5c6cdc 100644 --- a/tests/roots/test-resolve-typing-guard/demo_typing_guard.py +++ b/tests/roots/test-resolve-typing-guard/demo_typing_guard.py @@ -36,7 +36,7 @@ class SomeClass: if TYPE_CHECKING: # Classes doesn't have `__globals__` attribute - def __getattr__(self, item: str): # noqa: U100 + def __getattr__(self, item: str): """This method do something.""" diff --git a/tests/roots/test-resolve-typing-guard/demo_typing_guard_dummy.py b/tests/roots/test-resolve-typing-guard/demo_typing_guard_dummy.py index 43964e24..f06dfbc7 100644 --- a/tests/roots/test-resolve-typing-guard/demo_typing_guard_dummy.py +++ b/tests/roots/test-resolve-typing-guard/demo_typing_guard_dummy.py @@ -1,4 +1,6 @@ -from viktor import AI # module part of autodoc_mock_imports # noqa: F401,SC100,SC200 +from __future__ import annotations + +from viktor import AI # module part of autodoc_mock_imports # noqa: F401 class AnotherClass: diff --git a/tests/test_integration.py b/tests/test_integration.py index ace46091..c388fe39 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,16 +1,21 @@ +from __future__ import annotations + import re import sys from dataclasses import dataclass from inspect import isclass -from io import StringIO -from mailbox import Mailbox from pathlib import Path from textwrap import dedent, indent -from types import CodeType, ModuleType -from typing import Any, Callable, Optional, TypeVar, Union, overload +from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union, overload # no type comments import pytest -from sphinx.testing.util import SphinxTestApp + +if TYPE_CHECKING: + from io import StringIO + from mailbox import Mailbox + from types import CodeType, ModuleType + + from sphinx.testing.util import SphinxTestApp T = TypeVar("T") @@ -121,7 +126,7 @@ class InnerClass Return type: "str" - """ + """, ) class Class: """ @@ -132,10 +137,10 @@ class Class: :param z: baz """ - def __init__(self, x: bool, y: int, z: Optional[str] = None) -> None: # noqa: U100 + def __init__(self, x: bool, y: int, z: Optional[str] = None) -> None: # noqa: UP007 pass - def a_method(self, x: bool, y: int, z: Optional[str] = None) -> str: # noqa: U100 + def a_method(self, x: bool, y: int, z: Optional[str] = None) -> str: # noqa: UP007 """ Method docstring. @@ -144,21 +149,21 @@ def a_method(self, x: bool, y: int, z: Optional[str] = None) -> str: # noqa: U1 :param z: baz """ - def _private_method(self, x: str) -> str: # noqa: U100 + def _private_method(self, x: str) -> str: """ Private method docstring. :param x: foo """ - def __dunder_method(self, x: str) -> str: # noqa: U100 + def __dunder_method(self, x: str) -> str: """ Dunder method docstring. :param x: foo """ - def __magic_custom_method__(self, x: str) -> str: # noqa: U100 + def __magic_custom_method__(self, x: str) -> str: """ Magic dunder method docstring. @@ -166,7 +171,7 @@ def __magic_custom_method__(self, x: str) -> str: # noqa: U100 """ @classmethod - def a_classmethod(cls, x: bool, y: int, z: Optional[str] = None) -> str: # noqa: U100 + def a_classmethod(cls, x: bool, y: int, z: Optional[str] = None) -> str: # noqa: UP007 """ Classmethod docstring. @@ -176,7 +181,7 @@ def a_classmethod(cls, x: bool, y: int, z: Optional[str] = None) -> str: # noqa """ @staticmethod - def a_staticmethod(x: bool, y: int, z: Optional[str] = None) -> str: # noqa: U100 + def a_staticmethod(x: bool, y: int, z: Optional[str] = None) -> str: # noqa: UP007 """ Staticmethod docstring. @@ -196,14 +201,14 @@ class InnerClass: Inner class. """ - def inner_method(self, x: bool) -> str: # noqa: U100 + def inner_method(self, x: bool) -> str: """ Inner method. :param x: foo """ - def __dunder_inner_method(self, x: bool) -> str: # noqa: U100 + def __dunder_inner_method(self, x: bool) -> str: """ Dunder inner method. @@ -221,7 +226,7 @@ def __dunder_inner_method(self, x: bool) -> str: # noqa: U100 Parameters: **message** ("str") -- blah -""" +""", ) class DummyException(Exception): # noqa: N818 """ @@ -252,9 +257,9 @@ def __init__(self, message: str) -> None: Return type: bytes -""" +""", ) -def function(x: bool, y: int, z_: Optional[str] = None) -> str: # noqa: U100 +def function(x: bool, y: int, z_: Optional[str] = None) -> str: # noqa: UP007 """ Function docstring. @@ -280,9 +285,9 @@ def function(x: bool, y: int, z_: Optional[str] = None) -> str: # noqa: U100 * ***args** ("int") -- foo * ****kwargs** ("str") -- bar -""" +""", ) -def function_with_starred_documentation_param_names(*args: int, **kwargs: str): # noqa: U100 +def function_with_starred_documentation_param_names(*args: int, **kwargs: str): r""" Function docstring. @@ -303,9 +308,9 @@ def function_with_starred_documentation_param_names(*args: int, **kwargs: str): Parameters: **x** ("str") -- foo -""" +""", ) -def function_with_escaped_default(x: str = "\b"): # noqa: U100 +def function_with_escaped_default(x: str = "\b"): """ Function docstring. @@ -322,9 +327,9 @@ def function_with_escaped_default(x: str = "\b"): # noqa: U100 Parameters: **x** (*a.b.c*) -- foo -""" +""", ) -def function_with_unresolvable_annotation(x: "a.b.c"): # noqa: U100,F821 +def function_with_unresolvable_annotation(x: a.b.c): # noqa: F821 """ Function docstring. @@ -345,11 +350,11 @@ def function_with_unresolvable_annotation(x: "a.b.c"): # noqa: U100,F821 Return type: "None" -""" +""", ) def function_with_typehint_comment( - x, # type: int # noqa: U100 - y, # type: str # noqa: U100 + x, # type: int + y, # type: str ): # type: (...) -> None """ @@ -382,7 +387,7 @@ class mod.ClassWithTypehints(x) method_without_typehint(x) Method docstring. -""" +""", ) class ClassWithTypehints: """ @@ -392,13 +397,15 @@ class ClassWithTypehints: """ def __init__( - self, x # type: int # noqa: U100 - ): + self, + x, # type: int + ) -> None: # type: (...) -> None pass def foo( - self, x # type: str # noqa: U100 + self, + x, # type: str ): # type: (...) -> int """ @@ -408,7 +415,7 @@ def foo( """ return 42 - def method_without_typehint(self, x): # noqa: U100 + def method_without_typehint(self, x): """ Method docstring. """ @@ -436,9 +443,9 @@ def method_without_typehint(self, x): # noqa: U100 Return type: "None" -""" +""", ) -def function_with_typehint_comment_not_inline(x=None, *y, z, **kwargs): # noqa: U100 +def function_with_typehint_comment_not_inline(x=None, *y, z, **kwargs): # type: (Union[str, bytes, None], *str, bytes, **int) -> None """ Function docstring. @@ -479,7 +486,7 @@ class mod.ClassWithTypehintsNotInline(x=None) Return type: "ClassWithTypehintsNotInline" -""" +""", ) class ClassWithTypehintsNotInline: """ @@ -488,12 +495,10 @@ class ClassWithTypehintsNotInline: :param x: foo """ - def __init__(self, x=None): # noqa: U100 - # type: (Optional[Callable[[int, bytes], int]]) -> None + def __init__(self, x=None) -> None: # type: (Optional[Callable[[int, bytes], int]]) -> None pass - def foo(self, x=1): - # type: (Callable[[int, bytes], int]) -> int + def foo(self, x=1): # type: (Callable[[int, bytes], int]) -> int """ Method docstring. @@ -502,8 +507,7 @@ def foo(self, x=1): return x(1, b"") @classmethod - def mk(cls, x=None): - # type: (Optional[Callable[[int, bytes], int]]) -> ClassWithTypehintsNotInline + def mk(cls, x=None): # type: (Optional[Callable[[int, bytes], int]]) -> ClassWithTypehintsNotInline """ Method docstring. @@ -520,7 +524,7 @@ def mk(cls, x=None): Return type: "str" -""" +""", ) def undocumented_function(x: int) -> str: """Hi""" @@ -533,7 +537,7 @@ def undocumented_function(x: int) -> str: class mod.DataClass(x) Class docstring. -""" +""", ) @dataclass class DataClass: @@ -550,7 +554,7 @@ class mod.Decorator(func) Parameters: **func** ("Callable"[["int", "str"], "str"]) -- function -""" +""", ) class Decorator: """ @@ -559,7 +563,7 @@ class Decorator: :param func: function """ - def __init__(self, func: Callable[[int, str], str]): # noqa: U100 + def __init__(self, func: Callable[[int, str], str]) -> None: pass @@ -571,9 +575,9 @@ def __init__(self, func: Callable[[int, str], str]): # noqa: U100 Parameters: **x** ("Mailbox") -- function -""" +""", ) -def mocked_import(x: Mailbox): # noqa: U100 +def mocked_import(x: Mailbox): """ A docstring. @@ -593,7 +597,7 @@ def mocked_import(x: Mailbox): # noqa: U100 -[ Examples ]- Here are a couple of examples of how to use this function. -""" +""", ) def func_with_examples() -> int: """ @@ -606,12 +610,12 @@ def func_with_examples() -> int: @overload -def func_with_overload(a: int, b: int) -> None: # noqa: U100 +def func_with_overload(a: int, b: int) -> None: ... @overload -def func_with_overload(a: str, b: str) -> None: # noqa: U100 +def func_with_overload(a: str, b: str) -> None: ... @@ -629,9 +633,9 @@ def func_with_overload(a: str, b: str) -> None: # noqa: U100 Return type: "None" -""" +""", ) -def func_with_overload(a: Union[int, str], b: Union[int, str]) -> None: # noqa: U100 +def func_with_overload(a: Union[int, str], b: Union[int, str]) -> None: # noqa: UP007 """ f does the thing. The arguments can either be ints or strings but they must both have the same type. @@ -654,12 +658,12 @@ class mod.TestClassAttributeDocs code: "Optional"["CodeType"] An attribute -""" +""", ) class TestClassAttributeDocs: """A class""" - code: Union[CodeType, None] + code: Optional[CodeType] # noqa: UP007 """An attribute""" @@ -678,7 +682,7 @@ class TestClassAttributeDocs: Returns: The index of the widget -""" +""", ) def func_with_examples_and_returns_after() -> int: """ @@ -708,9 +712,9 @@ def func_with_examples_and_returns_after() -> int: "int" More info about the function here. -""" +""", ) -def func_with_parameters_and_stuff_after(a: int, b: int) -> int: # noqa: U100 +def func_with_parameters_and_stuff_after(a: int, b: int) -> int: """A func :param a: a tells us something @@ -742,9 +746,9 @@ def func_with_parameters_and_stuff_after(a: int, b: int) -> int: # noqa: U100 Return type: int -""" +""", ) -def func_with_rtype_in_weird_spot(a: int, b: int) -> int: # noqa: U100 +def func_with_rtype_in_weird_spot(a: int, b: int) -> int: """A func :param a: a tells us something @@ -790,9 +794,9 @@ def func_with_rtype_in_weird_spot(a: int, b: int) -> int: # noqa: U100 "int" More stuff here. -""" +""", ) -def empty_line_between_parameters(a: int, b: int) -> int: # noqa: U100 +def empty_line_between_parameters(a: int, b: int) -> int: """A func :param a: One of the following possibilities: @@ -827,7 +831,7 @@ def empty_line_between_parameters(a: int, b: int) -> int: # noqa: U100 -[ Examples ]- Here are a couple of examples of how to use this function. -""" +""", ) def func_with_code_block() -> int: """ @@ -860,7 +864,7 @@ def func_with_code_block() -> int: xyz something - """ + """, ) def func_with_definition_list() -> int: """Some text and then a definition list. @@ -889,7 +893,7 @@ def func_with_definition_list() -> int: -[ Examples ]- A -""" +""", ) def decorator_2(f: Any) -> Any: """Run the decorated function with `asyncio.run`. @@ -922,7 +926,7 @@ class mod.ParamAndAttributeHaveSameName(blah) Description of attribute blah - """ + """, ) class ParamAndAttributeHaveSameName: """ @@ -934,7 +938,7 @@ class ParamAndAttributeHaveSameName: Description of parameter blah """ - def __init__(self, blah: CodeType): # noqa: U100 + def __init__(self, blah: CodeType) -> None: pass blah: ModuleType @@ -952,7 +956,7 @@ def __init__(self, blah: CodeType): # noqa: U100 Returns: The info about the whatever. - """ + """, ) def napoleon_returns() -> CodeType: """ @@ -983,9 +987,9 @@ def napoleon_returns() -> CodeType: Returns: Description of return value - """ + """, ) -def google_docstrings(arg1: CodeType, arg2: ModuleType) -> CodeType: # noqa: U100 +def google_docstrings(arg1: CodeType, arg2: ModuleType) -> CodeType: """Summary line. Extended description of function. @@ -1015,9 +1019,9 @@ def google_docstrings(arg1: CodeType, arg2: ModuleType) -> CodeType: # noqa: U1 Some notes. More notes - """ + """, ) -def docstring_with_multiline_note_after_params(param: int) -> None: # noqa: U100 +def docstring_with_multiline_note_after_params(param: int) -> None: """Do something. Args: @@ -1046,9 +1050,9 @@ def docstring_with_multiline_note_after_params(param: int) -> None: # noqa: U10 * C: D - """ + """, ) -def docstring_with_bullet_list_after_params(param: int) -> None: # noqa: U100 +def docstring_with_bullet_list_after_params(param: int) -> None: """Do something. Args: @@ -1079,9 +1083,9 @@ def docstring_with_bullet_list_after_params(param: int) -> None: # noqa: U100 Next Term Somethign about it - """ + """, ) -def docstring_with_definition_list_after_params(param: int) -> None: # noqa: U100 +def docstring_with_definition_list_after_params(param: int) -> None: """Do something. Args: @@ -1113,9 +1117,9 @@ def docstring_with_definition_list_after_params(param: int) -> None: # noqa: U1 2. C: D - """ + """, ) -def docstring_with_enum_list_after_params(param: int) -> None: # noqa: U100 +def docstring_with_enum_list_after_params(param: int) -> None: """Do something. Args: @@ -1148,9 +1152,9 @@ def docstring_with_enum_list_after_params(param: int) -> None: # noqa: U100 Somethign about it -[ Example ]- - """ + """, ) -def docstring_with_definition_list_after_params_no_blank_line(param: int) -> None: # noqa: U100 +def docstring_with_definition_list_after_params_no_blank_line(param: int) -> None: """Do something. Args: diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py index 7bc9892b..b805bf2f 100644 --- a/tests/test_sphinx_autodoc_typehints.py +++ b/tests/test_sphinx_autodoc_typehints.py @@ -35,9 +35,6 @@ import typing_extensions from sphinx.application import Sphinx from sphinx.config import Config -from sphinx.testing.util import SphinxTestApp -from sphobjinv import Inventory - from sphinx_autodoc_typehints import ( _resolve_type_guarded_imports, backfill_type_hints, @@ -49,6 +46,10 @@ process_docstring, ) +if typing.TYPE_CHECKING: + from sphinx.testing.util import SphinxTestApp + from sphobjinv import Inventory + T = TypeVar("T") U = TypeVar("U", covariant=True) V = TypeVar("V", contravariant=True) @@ -120,7 +121,7 @@ def method(self: T) -> T: # type: ignore # Hacks to make it work the same in old versions. # We could also set AbcCallable = typing.Callable and x fail the tests that # use AbcCallable when in versions less than 3.9. - class MyGenericAlias(typing._VariadicGenericAlias, _root=True): # noqa: SC200 + class MyGenericAlias(typing._VariadicGenericAlias, _root=True): def __getitem__(self, params): result = super().__getitem__(params) # Make a copy so we don't change the name of a cached annotation @@ -243,7 +244,8 @@ def test_parse_annotation(annotation: Any, module: str, class_name: str, args: t Union[str, Any], ":py:data:`~typing.Union`\\[:py:class:`str`, " ":py:data:`~typing.Any`]", marks=pytest.mark.skipif( - (3, 5, 0) <= sys.version_info[:3] <= (3, 5, 2), reason="Union erases the str on 3.5.0 -> 3.5.2" + (3, 5, 0) <= sys.version_info[:3] <= (3, 5, 2), + reason="Union erases the str on 3.5.0 -> 3.5.2", ), ), (Optional[str], ":py:data:`~typing.Optional`\\[:py:class:`str`]"), @@ -376,7 +378,10 @@ def test_format_annotation(inv: Inventory, annotation: Any, expected_result: str if "typing" in expected_result_not_simplified: expected_result_not_simplified = expected_result_not_simplified.replace("~typing", "typing") conf = create_autospec( - Config, typehints_fully_qualified=True, simplify_optional_unions=False, _annotation_globals=globals() + Config, + typehints_fully_qualified=True, + simplify_optional_unions=False, + _annotation_globals=globals(), ) assert format_annotation(annotation, conf) == expected_result_not_simplified @@ -450,7 +455,10 @@ def set_python_path() -> None: @pytest.mark.sphinx("text", testroot="dummy") @patch("sphinx.writers.text.MAXWIDTH", 2000) def test_always_document_param_types( - app: SphinxTestApp, status: StringIO, warning: StringIO, always_document_param_types: bool + app: SphinxTestApp, + status: StringIO, + warning: StringIO, + always_document_param_types: bool, ) -> None: set_python_path() @@ -468,8 +476,8 @@ def test_always_document_param_types( .. autoclass:: dummy_module.DataClass :undoc-members: :special-members: __init__ - """ - ) + """, + ), ) app.build() @@ -567,7 +575,10 @@ def test_sphinx_output_future_annotations(app: SphinxTestApp, status: StringIO) @pytest.mark.sphinx("text", testroot="dummy") @patch("sphinx.writers.text.MAXWIDTH", 2000) def test_sphinx_output_defaults( - app: SphinxTestApp, status: StringIO, defaults_config_val: str, expected: str | Exception + app: SphinxTestApp, + status: StringIO, + defaults_config_val: str, + expected: str | Exception, ) -> None: set_python_path() @@ -613,7 +624,10 @@ def test_sphinx_output_defaults( @pytest.mark.sphinx("text", testroot="dummy") @patch("sphinx.writers.text.MAXWIDTH", 2000) def test_sphinx_output_formatter( - app: SphinxTestApp, status: StringIO, formatter_config_val: str, expected: tuple[str, ...] | Exception + app: SphinxTestApp, + status: StringIO, + formatter_config_val: str, + expected: tuple[str, ...] | Exception, ) -> None: set_python_path() @@ -626,7 +640,7 @@ def test_sphinx_output_formatter( raise assert str(expected) in str(e) return - assert not isinstance(expected, Exception), "Expected app.build() to raise exception, but it didn’t" + assert not isinstance(expected, Exception), "Expected app.build() to raise exception, but it didn`t" assert "build succeeded" in status.getvalue() contents = (Path(app.srcdir) / "_build/text/simple.txt").read_text() @@ -735,8 +749,8 @@ def test_bound_class_method(method: FunctionType) -> None: def test_syntax_error_backfill() -> None: # Regression test for #188 # fmt: off - func = ( # Note: line break here is what previously led to SyntaxError in process_docstring - lambda x: x) + def func(x): + return x # fmt: on backfill_type_hints(func, "func") @@ -777,7 +791,7 @@ def test_sphinx_output_formatter_no_use_rtype(app: SphinxTestApp, status: String app.build() assert "build succeeded" in status.getvalue() text_path = Path(app.srcdir) / "_build" / "text" / "simple_no_use_rtype.txt" - text_contents = text_path.read_text().replace("–", "--") + text_contents = text_path.read_text().replace("–", "--") # noqa: RUF001 # keep ambiguous EN DASH expected_contents = """\ Simple Module ************* @@ -842,7 +856,7 @@ def test_sphinx_output_with_use_signature(app: SphinxTestApp, status: StringIO) app.build() assert "build succeeded" in status.getvalue() text_path = Path(app.srcdir) / "_build" / "text" / "simple.txt" - text_contents = text_path.read_text().replace("–", "--") + text_contents = text_path.read_text().replace("–", "--") # noqa: RUF001 # keep ambiguous EN DASH expected_contents = """\ Simple Module ************* @@ -871,7 +885,7 @@ def test_sphinx_output_with_use_signature_return(app: SphinxTestApp, status: Str app.build() assert "build succeeded" in status.getvalue() text_path = Path(app.srcdir) / "_build" / "text" / "simple.txt" - text_contents = text_path.read_text().replace("–", "--") + text_contents = text_path.read_text().replace("–", "--") # noqa: RUF001 # keep ambiguous EN DASH expected_contents = """\ Simple Module ************* @@ -901,7 +915,7 @@ def test_sphinx_output_with_use_signature_and_return(app: SphinxTestApp, status: app.build() assert "build succeeded" in status.getvalue() text_path = Path(app.srcdir) / "_build" / "text" / "simple.txt" - text_contents = text_path.read_text().replace("–", "--") + text_contents = text_path.read_text().replace("–", "--") # noqa: RUF001 # keep ambiguous EN DASH expected_contents = """\ Simple Module ************* @@ -930,7 +944,7 @@ def test_default_annotation_without_typehints(app: SphinxTestApp, status: String app.build() assert "build succeeded" in status.getvalue() text_path = Path(app.srcdir) / "_build" / "text" / "without_complete_typehints.txt" - text_contents = text_path.read_text().replace("–", "--") + text_contents = text_path.read_text().replace("–", "--") # noqa: RUF001 # keep ambiguous EN DASH expected_contents = """\ Simple Module *************