Skip to content

Commit

Permalink
BUG: validator now handles properties (#500)
Browse files Browse the repository at this point in the history
* TST: add regression tests for properties

* BUG: validator now handles properties

---------

Co-authored-by: Jarrod Millman <[email protected]>
  • Loading branch information
sdiebolt and jarrodmillman authored Sep 7, 2023
1 parent ecd24ab commit 881c0ab
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 6 deletions.
52 changes: 48 additions & 4 deletions numpydoc/tests/test_validate.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import pytest
import sys
import warnings
from inspect import getsourcelines
from functools import cached_property
from inspect import getsourcelines, getsourcefile

from numpydoc import validate
import numpydoc.tests
Expand Down Expand Up @@ -1541,8 +1542,12 @@ 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.
* `DecoratorClass` has two decorators.
* `DecoratorClass.test_no_decorator` has no decorator.
* `DecoratorClass.test_property` has a `@property` decorator.
* `DecoratorClass.test_cached_property` has a `@cached_property` decorator.
* `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.
"""
Expand All @@ -1551,6 +1556,16 @@ def test_no_decorator(self):
"""Test method without decorators."""
pass

@property
def test_property(self):
"""Test property method."""
pass

@cached_property
def test_cached_property(self):
"""Test property method."""
pass

@decorator
@decorator
@decorator
Expand All @@ -1577,7 +1592,7 @@ def test_raises_for_invalid_attribute_name(self, invalid_name):
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
# fixed starting with 3.9: https://github.com/python/cpython/issues/60060.
@pytest.mark.parametrize(
["decorated_obj", "def_line"],
[
Expand All @@ -1590,6 +1605,14 @@ def test_raises_for_invalid_attribute_name(self, invalid_name):
"numpydoc.tests.test_validate.DecoratorClass.test_no_decorator",
getsourcelines(DecoratorClass.test_no_decorator)[-1],
],
[
"numpydoc.tests.test_validate.DecoratorClass.test_property",
getsourcelines(DecoratorClass.test_property.fget)[-1] + 1,
],
[
"numpydoc.tests.test_validate.DecoratorClass.test_cached_property",
getsourcelines(DecoratorClass.test_cached_property.func)[-1] + 1,
],
[
"numpydoc.tests.test_validate.DecoratorClass.test_three_decorators",
getsourcelines(DecoratorClass.test_three_decorators)[-1] + 3,
Expand All @@ -1603,3 +1626,24 @@ def test_source_file_def_line_with_decorators(self, decorated_obj, def_line):
)
)
assert doc.source_file_def_line == def_line

@pytest.mark.parametrize(
["property", "file_name"],
[
[
"numpydoc.tests.test_validate.DecoratorClass.test_property",
getsourcefile(DecoratorClass.test_property.fget),
],
[
"numpydoc.tests.test_validate.DecoratorClass.test_cached_property",
getsourcefile(DecoratorClass.test_cached_property.func),
],
],
)
def test_source_file_name_with_properties(self, property, file_name):
doc = numpydoc.validate.Validator(
numpydoc.docscrape.get_doc_object(
numpydoc.validate.Validator._load_obj(property)
)
)
assert doc.source_file_name == file_name
15 changes: 13 additions & 2 deletions numpydoc/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,13 @@ def source_file_name(self):
File name where the object is implemented (e.g. pandas/core/frame.py).
"""
try:
fname = inspect.getsourcefile(self.code_obj)
if isinstance(self.code_obj, property):
fname = inspect.getsourcefile(self.code_obj.fget)
elif isinstance(self.code_obj, functools.cached_property):
fname = inspect.getsourcefile(self.code_obj.func)
else:
fname = inspect.getsourcefile(self.code_obj)

except 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 All @@ -295,7 +301,12 @@ def source_file_def_line(self):
Number of line where the object is defined in its file.
"""
try:
sourcelines = inspect.getsourcelines(self.code_obj)
if isinstance(self.code_obj, property):
sourcelines = inspect.getsourcelines(self.code_obj.fget)
elif isinstance(self.code_obj, functools.cached_property):
sourcelines = inspect.getsourcelines(self.code_obj.func)
else:
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(
Expand Down

0 comments on commit 881c0ab

Please sign in to comment.