Skip to content

Commit

Permalink
BUG: validator now handles decorators (#496)
Browse files Browse the repository at this point in the history
* BUG: validator now handles decorators

* slight regex enhancement to match def/class

* TST: test `source_file_def_line` with decorators

* fix tests for python 3.8
  • Loading branch information
sdiebolt authored Aug 30, 2023
1 parent 307992e commit df55b8a
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 1 deletion.
59 changes: 59 additions & 0 deletions numpydoc/tests/test_validate.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pytest
import sys
import warnings
from inspect import getsourcelines

from numpydoc import validate
import numpydoc.tests
Expand Down Expand Up @@ -1528,6 +1530,35 @@ def test_bad_docstrings(self, capsys, klass, func, msgs):
assert msg in " ".join(err[1] for err in result["errors"])


def decorator(x):
"""Test decorator."""
return x


@decorator
@decorator
class DecoratorClass:
"""
Class and methods with decorators.
`DecoratorClass` has two decorators, `DecoratorClass.test_no_decorator` has no
decorator and `DecoratorClass.test_three_decorators` has three decorators.
`Validator.source_file_def_line` should return the `def` or `class` line number, not
the line of the first decorator.
"""

def test_no_decorator(self):
"""Test method without decorators."""
pass

@decorator
@decorator
@decorator
def test_three_decorators(self):
"""Test method with three decorators."""
pass


class TestValidatorClass:
@pytest.mark.parametrize("invalid_name", ["unknown_mod", "unknown_mod.MyClass"])
def test_raises_for_invalid_module_name(self, invalid_name):
Expand All @@ -1544,3 +1575,31 @@ def test_raises_for_invalid_attribute_name(self, invalid_name):
msg = f"'{obj_name}' has no attribute '{invalid_attr_name}'"
with pytest.raises(AttributeError, match=msg):
numpydoc.validate.Validator._load_obj(invalid_name)

# inspect.getsourcelines does not return class decorators for Python 3.8. This was
# fixed starting with 3.9: https://github.com/python/cpython/issues/60060
@pytest.mark.parametrize(
["decorated_obj", "def_line"],
[
[
"numpydoc.tests.test_validate.DecoratorClass",
getsourcelines(DecoratorClass)[-1]
+ (2 if sys.version_info.minor > 8 else 0),
],
[
"numpydoc.tests.test_validate.DecoratorClass.test_no_decorator",
getsourcelines(DecoratorClass.test_no_decorator)[-1],
],
[
"numpydoc.tests.test_validate.DecoratorClass.test_three_decorators",
getsourcelines(DecoratorClass.test_three_decorators)[-1] + 3,
],
],
)
def test_source_file_def_line_with_decorators(self, decorated_obj, def_line):
doc = numpydoc.validate.Validator(
numpydoc.docscrape.get_doc_object(
numpydoc.validate.Validator._load_obj(decorated_obj)
)
)
assert doc.source_file_def_line == def_line
12 changes: 11 additions & 1 deletion numpydoc/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,17 @@ def source_file_def_line(self):
Number of line where the object is defined in its file.
"""
try:
return inspect.getsourcelines(self.code_obj)[-1]
sourcelines = inspect.getsourcelines(self.code_obj)
# getsourcelines will return the line of the first decorator found for the
# current function. We have to find the def declaration after that.
def_line = next(
i
for i, x in enumerate(
re.match("^ *(def|class) ", s) for s in sourcelines[0]
)
if x is not None
)
return sourcelines[-1] + def_line
except (OSError, TypeError):
# In some cases the object is something complex like a cython
# object that can't be easily introspected. An it's better to
Expand Down

0 comments on commit df55b8a

Please sign in to comment.