Skip to content

Commit 881c0ab

Browse files
BUG: validator now handles properties (#500)
* TST: add regression tests for properties * BUG: validator now handles properties --------- Co-authored-by: Jarrod Millman <jarrod.millman@gmail.com>
1 parent ecd24ab commit 881c0ab

File tree

2 files changed

+61
-6
lines changed

2 files changed

+61
-6
lines changed

numpydoc/tests/test_validate.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import pytest
22
import sys
33
import warnings
4-
from inspect import getsourcelines
4+
from functools import cached_property
5+
from inspect import getsourcelines, getsourcefile
56

67
from numpydoc import validate
78
import numpydoc.tests
@@ -1541,8 +1542,12 @@ class DecoratorClass:
15411542
"""
15421543
Class and methods with decorators.
15431544
1544-
`DecoratorClass` has two decorators, `DecoratorClass.test_no_decorator` has no
1545-
decorator and `DecoratorClass.test_three_decorators` has three decorators.
1545+
* `DecoratorClass` has two decorators.
1546+
* `DecoratorClass.test_no_decorator` has no decorator.
1547+
* `DecoratorClass.test_property` has a `@property` decorator.
1548+
* `DecoratorClass.test_cached_property` has a `@cached_property` decorator.
1549+
* `DecoratorClass.test_three_decorators` has three decorators.
1550+
15461551
`Validator.source_file_def_line` should return the `def` or `class` line number, not
15471552
the line of the first decorator.
15481553
"""
@@ -1551,6 +1556,16 @@ def test_no_decorator(self):
15511556
"""Test method without decorators."""
15521557
pass
15531558

1559+
@property
1560+
def test_property(self):
1561+
"""Test property method."""
1562+
pass
1563+
1564+
@cached_property
1565+
def test_cached_property(self):
1566+
"""Test property method."""
1567+
pass
1568+
15541569
@decorator
15551570
@decorator
15561571
@decorator
@@ -1577,7 +1592,7 @@ def test_raises_for_invalid_attribute_name(self, invalid_name):
15771592
numpydoc.validate.Validator._load_obj(invalid_name)
15781593

15791594
# inspect.getsourcelines does not return class decorators for Python 3.8. This was
1580-
# fixed starting with 3.9: https://github.com/python/cpython/issues/60060
1595+
# fixed starting with 3.9: https://github.com/python/cpython/issues/60060.
15811596
@pytest.mark.parametrize(
15821597
["decorated_obj", "def_line"],
15831598
[
@@ -1590,6 +1605,14 @@ def test_raises_for_invalid_attribute_name(self, invalid_name):
15901605
"numpydoc.tests.test_validate.DecoratorClass.test_no_decorator",
15911606
getsourcelines(DecoratorClass.test_no_decorator)[-1],
15921607
],
1608+
[
1609+
"numpydoc.tests.test_validate.DecoratorClass.test_property",
1610+
getsourcelines(DecoratorClass.test_property.fget)[-1] + 1,
1611+
],
1612+
[
1613+
"numpydoc.tests.test_validate.DecoratorClass.test_cached_property",
1614+
getsourcelines(DecoratorClass.test_cached_property.func)[-1] + 1,
1615+
],
15931616
[
15941617
"numpydoc.tests.test_validate.DecoratorClass.test_three_decorators",
15951618
getsourcelines(DecoratorClass.test_three_decorators)[-1] + 3,
@@ -1603,3 +1626,24 @@ def test_source_file_def_line_with_decorators(self, decorated_obj, def_line):
16031626
)
16041627
)
16051628
assert doc.source_file_def_line == def_line
1629+
1630+
@pytest.mark.parametrize(
1631+
["property", "file_name"],
1632+
[
1633+
[
1634+
"numpydoc.tests.test_validate.DecoratorClass.test_property",
1635+
getsourcefile(DecoratorClass.test_property.fget),
1636+
],
1637+
[
1638+
"numpydoc.tests.test_validate.DecoratorClass.test_cached_property",
1639+
getsourcefile(DecoratorClass.test_cached_property.func),
1640+
],
1641+
],
1642+
)
1643+
def test_source_file_name_with_properties(self, property, file_name):
1644+
doc = numpydoc.validate.Validator(
1645+
numpydoc.docscrape.get_doc_object(
1646+
numpydoc.validate.Validator._load_obj(property)
1647+
)
1648+
)
1649+
assert doc.source_file_name == file_name

numpydoc/validate.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,13 @@ def source_file_name(self):
280280
File name where the object is implemented (e.g. pandas/core/frame.py).
281281
"""
282282
try:
283-
fname = inspect.getsourcefile(self.code_obj)
283+
if isinstance(self.code_obj, property):
284+
fname = inspect.getsourcefile(self.code_obj.fget)
285+
elif isinstance(self.code_obj, functools.cached_property):
286+
fname = inspect.getsourcefile(self.code_obj.func)
287+
else:
288+
fname = inspect.getsourcefile(self.code_obj)
289+
284290
except TypeError:
285291
# In some cases the object is something complex like a cython
286292
# object that can't be easily introspected. An it's better to
@@ -295,7 +301,12 @@ def source_file_def_line(self):
295301
Number of line where the object is defined in its file.
296302
"""
297303
try:
298-
sourcelines = inspect.getsourcelines(self.code_obj)
304+
if isinstance(self.code_obj, property):
305+
sourcelines = inspect.getsourcelines(self.code_obj.fget)
306+
elif isinstance(self.code_obj, functools.cached_property):
307+
sourcelines = inspect.getsourcelines(self.code_obj.func)
308+
else:
309+
sourcelines = inspect.getsourcelines(self.code_obj)
299310
# getsourcelines will return the line of the first decorator found for the
300311
# current function. We have to find the def declaration after that.
301312
def_line = next(

0 commit comments

Comments
 (0)