Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/docstub-stubs/_docstrings.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class DoctypeTransformer(lark.visitors.Transformer):
self, *, matcher: TypeMatcher | None = ..., **kwargs: dict[Any, Any]
) -> None: ...
def doctype_to_annotation(
self, doctype: str
self, doctype: str, *, reporter: ContextReporter | None = ...
) -> tuple[Annotation, list[tuple[str, int, int]]]: ...
def qualname(self, tree: lark.Tree) -> lark.Token: ...
def rst_role(self, tree: lark.Tree) -> lark.Token: ...
Expand Down
19 changes: 14 additions & 5 deletions src/docstub-stubs/_report.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,24 @@ class ContextReporter:
line_offset: int | None = ...
) -> Self: ...
def report(
self, short: str, *, log_level: int, details: str | None = ..., **log_kw: Any
self,
short: str,
*args: Any,
log_level: int,
details: str | None = ...,
**log_kw: Any
) -> None: ...
def debug(
self, short: str, *, details: str | None = ..., **log_kw: Any
self, short: str, *args: Any, details: str | None = ..., **log_kw: Any
) -> None: ...
def info(
self, short: str, *args: Any, details: str | None = ..., **log_kw: Any
) -> None: ...
def warn(
self, short: str, *args: Any, details: str | None = ..., **log_kw: Any
) -> None: ...
def info(self, short: str, *, details: str | None = ..., **log_kw: Any) -> None: ...
def warn(self, short: str, *, details: str | None = ..., **log_kw: Any) -> None: ...
def error(
self, short: str, *, details: str | None = ..., **log_kw: Any
self, short: str, *args: Any, details: str | None = ..., **log_kw: Any
) -> None: ...
def __post_init__(self) -> None: ...
@staticmethod
Expand Down
19 changes: 11 additions & 8 deletions src/docstub/_docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ def __init__(self, *, matcher=None, **kwargs):

self.matcher = matcher

self._reporter = None
self._collected_imports = None
self._unknown_qualnames = None

Expand All @@ -276,13 +277,14 @@ def __init__(self, *, matcher=None, **kwargs):
"transformed": 0,
}

def doctype_to_annotation(self, doctype):
def doctype_to_annotation(self, doctype, *, reporter=None):
"""Turn a type description in a docstring into a type annotation.

Parameters
----------
doctype : str
The doctype to parse.
reporter : ~.ContextReporter

Returns
-------
Expand All @@ -293,6 +295,7 @@ def doctype_to_annotation(self, doctype):
end index relative to the given `doctype`.
"""
try:
self._reporter = reporter or ContextReporter(logger=logger)
self._collected_imports = set()
self._unknown_qualnames = []
tree = _lark.parse(doctype)
Expand All @@ -310,6 +313,7 @@ def doctype_to_annotation(self, doctype):
self.stats["syntax_errors"] += 1
raise
finally:
self._reporter = None
self._collected_imports = None
self._unknown_qualnames = None

Expand Down Expand Up @@ -394,11 +398,10 @@ def natlang_literal(self, tree):
out = f"Literal[{out}]"

if len(tree.children) == 1:
logger.warning(
"Natural language literal with one item `%s`, "
"consider using `%s` to improve readability",
self._reporter.warn(
"Natural language literal with one item: `{%s}`",
tree.children[0],
out,
details=f"Consider using `{out}` to improve readability",
)

if self.matcher is not None:
Expand Down Expand Up @@ -463,7 +466,7 @@ def shape(self, tree):
-------
out : lark.visitors._DiscardType
"""
logger.debug("Dropping shape information %r", tree)
# self._reporter.debug("Dropping shape information %r", tree)
return lark.Discard

def optional_info(self, tree):
Expand All @@ -476,7 +479,7 @@ def optional_info(self, tree):
-------
out : lark.visitors._DiscardType
"""
# logger.debug("Dropping optional info %r", tree)
# self._reporter.debug("Dropping optional info %r", tree)
return lark.Discard

def __default__(self, data, children, meta):
Expand Down Expand Up @@ -629,7 +632,7 @@ def _doctype_to_annotation(self, doctype, ds_line=0):

try:
annotation, unknown_qualnames = self.transformer.doctype_to_annotation(
doctype
doctype, reporter=reporter
)
reporter.debug(
"Transformed doctype", details=(" %s\n-> %s", doctype, annotation)
Expand Down
38 changes: 28 additions & 10 deletions src/docstub/_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,15 @@ def copy_with(self, *, logger=None, path=None, line=None, line_offset=None):
new = type(self)(**kwargs)
return new

def report(self, short, *, log_level, details=None, **log_kw):
def report(self, short, *args, log_level, details=None, **log_kw):
"""Log a report in context of the saved location.

Parameters
----------
short : str
A short summarizing report that shouldn't wrap over multiple lines.
*args : Any
Optional formatting arguments for `short`.
log_level : int
The logging level.
details : str, optional
Expand All @@ -100,59 +102,75 @@ def report(self, short, *, log_level, details=None, **log_kw):
location = f"{location}:{self.line}"
extra["src_location"] = location

self.logger.log(log_level, msg=short, extra=extra, **log_kw)
self.logger.log(log_level, short, *args, extra=extra, **log_kw)

def debug(self, short, *, details=None, **log_kw):
def debug(self, short, *args, details=None, **log_kw):
"""Log information with context of the relevant source.

Parameters
----------
short : str
A short summarizing report that shouldn't wrap over multiple lines.
*args : Any
Optional formatting arguments for `short`.
details : str, optional
An optional multiline report with more details.
**log_kw : Any
"""
return self.report(short, log_level=logging.DEBUG, details=details, **log_kw)
return self.report(
short, *args, log_level=logging.DEBUG, details=details, **log_kw
)

def info(self, short, *, details=None, **log_kw):
def info(self, short, *args, details=None, **log_kw):
"""Log information with context of the relevant source.

Parameters
----------
short : str
A short summarizing report that shouldn't wrap over multiple lines.
*args : Any
Optional formatting arguments for `short`.
details : str, optional
An optional multiline report with more details.
**log_kw : Any
"""
return self.report(short, log_level=logging.INFO, details=details, **log_kw)
return self.report(
short, *args, log_level=logging.INFO, details=details, **log_kw
)

def warn(self, short, *, details=None, **log_kw):
def warn(self, short, *args, details=None, **log_kw):
"""Log a warning with context of the relevant source.

Parameters
----------
short : str
A short summarizing report that shouldn't wrap over multiple lines.
*args : Any
Optional formatting arguments for `short`.
details : str, optional
An optional multiline report with more details.
**log_kw : Any
"""
return self.report(short, log_level=logging.WARNING, details=details, **log_kw)
return self.report(
short, *args, log_level=logging.WARNING, details=details, **log_kw
)

def error(self, short, *, details=None, **log_kw):
def error(self, short, *args, details=None, **log_kw):
"""Log an error with context of the relevant source.

Parameters
----------
short : str
A short summarizing report that shouldn't wrap over multiple lines.
*args : Any
Optional formatting arguments for `short`.
details : str, optional
An optional multiline report with more details.
**log_kw : Any
"""
return self.report(short, log_level=logging.ERROR, details=details, **log_kw)
return self.report(
short, *args, log_level=logging.ERROR, details=details, **log_kw
)

def __post_init__(self):
if self.path is not None and not isinstance(self.path, Path):
Expand Down
18 changes: 17 additions & 1 deletion tests/test_docstrings.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import logging
from textwrap import dedent

import lark
import pytest

from docstub._analysis import PyImport
from docstub._docstrings import Annotation, DocstringAnnotations, DoctypeTransformer
from docstub._docstrings import (
Annotation,
DocstringAnnotations,
DoctypeTransformer,
)


class Test_Annotation:
Expand Down Expand Up @@ -180,6 +185,17 @@ def test_literals(self, doctype, expected):
annotation, _ = transformer.doctype_to_annotation(doctype)
assert annotation.value == expected

def test_single_natlang_literal_warning(self, caplog):
transformer = DoctypeTransformer()
annotation, _ = transformer.doctype_to_annotation("{True}")
assert annotation.value == "Literal[True]"
assert caplog.messages == ["Natural language literal with one item: `{True}`"]
assert caplog.records[0].levelno == logging.WARNING
assert (
caplog.records[0].details
== "Consider using `Literal[True]` to improve readability"
)

@pytest.mark.parametrize(
("doctype", "expected"),
[
Expand Down
Loading