Skip to content

Commit

Permalink
fix: Log debug instead of errors when failing to parse Numpy annotations
Browse files Browse the repository at this point in the history
Issue #93: #93
  • Loading branch information
pawamoy committed Sep 23, 2022
1 parent 6a57194 commit 75eeeda
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 9 deletions.
11 changes: 8 additions & 3 deletions src/griffe/agents/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
from griffe.collections import LinesCollection
from griffe.exceptions import LastNodeError, RootNodeError
from griffe.expressions import Expression, Name
from griffe.logger import get_logger
from griffe.logger import LogLevel, get_logger

logger = get_logger(__name__)

Expand Down Expand Up @@ -778,12 +778,17 @@ def get_annotation(node: AST | None, parent: Module | Class) -> str | Name | Exp
return _get_annotation(node, parent)


def safe_get_annotation(node: AST | None, parent: Module | Class) -> str | Name | Expression | None:
def safe_get_annotation(
node: AST | None,
parent: Module | Class,
log_level: LogLevel = LogLevel.error,
) -> str | Name | Expression | None:
"""Safely (no exception) extract a resolvable annotation.
Parameters:
node: The annotation node.
parent: The parent used to resolve the name.
log_level: Log level to use to log a message.
Returns:
A string or resovable name or expression.
Expand All @@ -796,7 +801,7 @@ def safe_get_annotation(node: AST | None, parent: Module | Class) -> str | Name
message += f" at {parent.relative_filepath}:{node.lineno}" # type: ignore[union-attr]
if not isinstance(error, KeyError):
message += f": {error}"
logger.error(message)
getattr(logger, log_level.value)(message)
return None


Expand Down
3 changes: 2 additions & 1 deletion src/griffe/docstrings/numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
)
from griffe.docstrings.utils import parse_annotation, warning
from griffe.expressions import Expression, Name
from griffe.logger import LogLevel

if TYPE_CHECKING:
from typing import Any, Literal, Pattern # type: ignore[attr-defined]
Expand Down Expand Up @@ -253,7 +254,7 @@ def _read_parameters( # noqa: WPS231
else:
_warn(docstring, new_offset, f"No types or annotations for parameters {names}")
else:
annotation = parse_annotation(annotation, docstring) # type: ignore[arg-type]
annotation = parse_annotation(annotation, docstring, log_level=LogLevel.debug) # type: ignore[arg-type]

if default is None:
for name in names: # noqa: WPS440
Expand Down
16 changes: 13 additions & 3 deletions src/griffe/docstrings/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from griffe.agents.nodes import safe_get_annotation
from griffe.expressions import Expression, Name
from griffe.logger import get_logger
from griffe.logger import LogLevel, get_logger

if TYPE_CHECKING:
from griffe.dataclasses import Docstring
Expand Down Expand Up @@ -42,13 +42,18 @@ def warn(docstring: Docstring, offset: int, message: str) -> None: # noqa: WPS4
return warn


def parse_annotation(annotation: str, docstring: Docstring) -> str | Name | Expression:
def parse_annotation(
annotation: str,
docstring: Docstring,
log_level: LogLevel = LogLevel.error,
) -> str | Name | Expression:
"""Parse a string into a true name or expression that can be resolved later.
Parameters:
annotation: The annotation to parse.
docstring: The docstring in which the annotation appears.
The docstring's parent is accessed to bind a resolver to the resulting name/expression.
log_level: Log level to use to log a message.
Returns:
The string unchanged, or a new name or expression.
Expand All @@ -59,5 +64,10 @@ def parse_annotation(annotation: str, docstring: Docstring) -> str | Name | Expr
):
code = compile(annotation, mode="eval", filename="", flags=PyCF_ONLY_AST, optimize=2)
if code.body:
return safe_get_annotation(code.body, parent=docstring.parent) or annotation # type: ignore[arg-type]
name_or_expr = safe_get_annotation(
code.body,
parent=docstring.parent, # type: ignore[arg-type]
log_level=log_level,
)
return name_or_expr or annotation
return annotation
13 changes: 13 additions & 0 deletions src/griffe/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,22 @@ def get_logger(name):
from __future__ import annotations

import logging
from enum import Enum
from typing import Any, Callable


class LogLevel(Enum): # noqa: WPS600
"""Enumeration of available log levels."""

trace: str = "trace"
debug: str = "debug"
info: str = "info"
success: str = "success"
warning: str = "warning"
error: str = "error"
critical: str = "critical"


class _Logger:
_default_logger: Any = logging.getLogger
_instances: dict[str, _Logger] = {}
Expand Down
8 changes: 6 additions & 2 deletions tests/test_docstrings/test_numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

import logging

import pytest

from griffe.dataclasses import Attribute, Class, Docstring, Function, Module, Parameter, Parameters
Expand Down Expand Up @@ -172,20 +174,22 @@ def test_parse_annotations_in_all_sections(parse_numpy, docstring, name):
assert sections[0].value[0].annotation == Name(name, name)


# https://github.com/mkdocstrings/mkdocstrings/issues/416
def test_dont_crash_on_text_annotations(parse_numpy):
def test_dont_crash_on_text_annotations(parse_numpy, caplog):
"""Don't crash while parsing annotations containing unhandled nodes.
Parameters:
parse_numpy: Fixture parser.
caplog: Pytest fixture used to capture logs.
"""
docstring = """
Parameters
----------
region : str, list-like, geopandas.GeoSeries, geopandas.GeoDataFrame, geometric
Description.
"""
caplog.set_level(logging.DEBUG)
assert parse_numpy(docstring, parent=Function("f"))
assert all(record.levelname == "DEBUG" for record in caplog.records if "Failed to parse" in record.message)


# =============================================================================================
Expand Down

0 comments on commit 75eeeda

Please sign in to comment.