diff --git a/README.md b/README.md index 4da9476..384e226 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,13 @@ The following configuration options are accepted: code or `None` to fall back to the default formatter. - `typehints_use_signature` (default: `False`): If `True`, typehints for parameters in the signature are shown. - `typehints_use_signature_return` (default: `False`): If `True`, return annotations in the signature are shown. +- `suppress_warnings`: sphinx-autodoc-typehints supports to suppress warning messages via Sphinx's `suppress_warnings`. It allows following additional warning types: + - `sphinx_autodoc_typehints` + - `sphinx_autodoc_typehints.comment` + - `sphinx_autodoc_typehints.forward_reference` + - `sphinx_autodoc_typehints.guarded_import` + - `sphinx_autodoc_typehints.local_function` + - `sphinx_autodoc_typehints.multiple_ast_nodes` ## How it works diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index 7a45ec1..c547569 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -407,7 +407,12 @@ def _get_formatted_annotation(annotation: TypeVar) -> TypeVar: elif what == "method": # bail if it is a local method as we cannot determine if first argument needs to be deleted or not if "" in obj.__qualname__ and not _is_dataclass(name, what, obj.__qualname__): - _LOGGER.warning('Cannot handle as a local function: "%s" (use @functools.wraps)', name) + _LOGGER.warning( + 'Cannot handle as a local function: "%s" (use @functools.wraps)', + name, + type="sphinx_autodoc_typehints", + subtype="local_function", + ) return None outer = inspect.getmodule(obj) for class_name in obj.__qualname__.split(".")[:-1]: @@ -500,7 +505,9 @@ def _execute_guarded_code(autodoc_mock_imports: list[str], obj: Any, module_code with mock(autodoc_mock_imports): exec(guarded_code, getattr(obj, "__globals__", obj.__dict__)) # noqa: S102 except Exception as exc: # noqa: BLE001 - _LOGGER.warning("Failed guarded type import with %r", exc) + _LOGGER.warning( + "Failed guarded type import with %r", exc, type="sphinx_autodoc_typehints", subtype="guarded_import" + ) def _resolve_type_guarded_imports(autodoc_mock_imports: list[str], obj: Any) -> None: @@ -536,7 +543,13 @@ def _get_type_hint( else: result = {} except NameError as exc: - _LOGGER.warning('Cannot resolve forward reference in type annotations of "%s": %s', name, exc) + _LOGGER.warning( + 'Cannot resolve forward reference in type annotations of "%s": %s', + name, + exc, + type="sphinx_autodoc_typehints", + subtype="forward_reference", + ) result = obj.__annotations__ return result @@ -554,7 +567,13 @@ def backfill_type_hints(obj: Any, name: str) -> dict[str, Any]: # noqa: C901, P def _one_child(module: Module) -> stmt | None: children = module.body # use the body to ignore type comments if len(children) != 1: - _LOGGER.warning('Did not get exactly one node from AST for "%s", got %s', name, len(children)) + _LOGGER.warning( + 'Did not get exactly one node from AST for "%s", got %s', + name, + len(children), + type="sphinx_autodoc_typehints", + subtype="multiple_ast_nodes", + ) return None return children[0] @@ -579,7 +598,12 @@ def _one_child(module: Module) -> stmt | None: try: comment_args_str, comment_returns = type_comment.split(" -> ") except ValueError: - _LOGGER.warning('Unparseable type hint comment for "%s": Expected to contain ` -> `', name) + _LOGGER.warning( + 'Unparseable type hint comment for "%s": Expected to contain ` -> `', + name, + type="sphinx_autodoc_typehints", + subtype="comment", + ) return {} rv = {} @@ -594,7 +618,9 @@ def _one_child(module: Module) -> stmt | None: comment_args.insert(0, None) # self/cls may be omitted in type comments, insert blank if len(args) != len(comment_args): - _LOGGER.warning('Not enough type comments found on "%s"', name) + _LOGGER.warning( + 'Not enough type comments found on "%s"', name, type="sphinx_autodoc_typehints", subtype="comment" + ) return rv for at, arg in enumerate(args): diff --git a/tests/test_integration.py b/tests/test_integration.py index 9c1e355..02369f8 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -31,6 +31,28 @@ W = NewType("W", str) +@dataclass +class WarningInfo: + """Properties and assertion methods for warnings.""" + + regexp: str + type: str + + def assert_regexp(self, message: str) -> None: + regexp = self.regexp + msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {message!r}" + assert re.search(regexp, message), msg + + def assert_type(self, message: str) -> None: + expected = f"[{self.type}]" + msg = f"Warning did not contain type and subtype.\n Expected: {expected}\n Input: {message}" + assert expected in message, msg + + def assert_warning(self, message: str) -> None: + self.assert_regexp(message) + self.assert_type(message) + + def expected(expected: str, **options: dict[str, Any]) -> Callable[[T], T]: def dec(val: T) -> T: val.EXPECTED = expected @@ -40,9 +62,9 @@ def dec(val: T) -> T: return dec -def warns(pattern: str) -> Callable[[T], T]: +def warns(info: WarningInfo) -> Callable[[T], T]: def dec(val: T) -> T: - val.WARNING = pattern + val.WARNING = info return val return dec @@ -58,7 +80,7 @@ def wrapper(self) -> str: # noqa: ANN001 return wrapper -@warns("Cannot handle as a local function") +@warns(WarningInfo(regexp="Cannot handle as a local function", type="sphinx_autodoc_typehints.local_function")) @expected( """\ class mod.Class(x, y, z=None) @@ -330,7 +352,11 @@ def function_with_escaped_default(x: str = "\b"): # noqa: ANN201 """ -@warns("Cannot resolve forward reference in type annotations") +@warns( + WarningInfo( + regexp="Cannot resolve forward reference in type annotations", type="sphinx_autodoc_typehints.forward_reference" + ) +) @expected( """\ mod.function_with_unresolvable_annotation(x) @@ -1196,7 +1222,7 @@ def docstring_with_enum_list_after_params(param: int) -> None: """ -@warns("Definition list ends without a blank line") +@warns(WarningInfo(regexp="Definition list ends without a blank line", type="docutils")) @expected( """ mod.docstring_with_definition_list_after_params_no_blank_line(param) @@ -1457,7 +1483,7 @@ def has_doctest1() -> None: Unformatted = TypeVar("Unformatted") -@warns("cannot cache unpickleable configuration value: 'typehints_formatter'") +@warns(WarningInfo(regexp="cannot cache unpickleable configuration value: 'typehints_formatter'", type="config.cache")) @expected( """ mod.typehints_formatter_applied_to_signature(param: Formatted) -> Formatted @@ -1525,11 +1551,10 @@ def test_integration( app.build() assert "build succeeded" in status.getvalue() # Build succeeded - regexp = getattr(val, "WARNING", None) + warning_info: Union[WarningInfo, None] = getattr(val, "WARNING", None) value = warning.getvalue().strip() - if regexp: - msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}" - assert re.search(regexp, value), msg + if warning_info: + warning_info.assert_warning(value) else: assert not value