From 881c0abf0a75c1b517c23a819f9365fbfc9cded7 Mon Sep 17 00:00:00 2001 From: Samuel Diebolt Date: Thu, 7 Sep 2023 17:28:58 +0200 Subject: [PATCH] BUG: validator now handles properties (#500) * TST: add regression tests for properties * BUG: validator now handles properties --------- Co-authored-by: Jarrod Millman --- numpydoc/tests/test_validate.py | 52 ++++++++++++++++++++++++++++++--- numpydoc/validate.py | 15 ++++++++-- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/numpydoc/tests/test_validate.py b/numpydoc/tests/test_validate.py index 5f76cd9c..68040add 100644 --- a/numpydoc/tests/test_validate.py +++ b/numpydoc/tests/test_validate.py @@ -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 @@ -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. """ @@ -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 @@ -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"], [ @@ -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, @@ -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 diff --git a/numpydoc/validate.py b/numpydoc/validate.py index 2cd11251..e98d3851 100644 --- a/numpydoc/validate.py +++ b/numpydoc/validate.py @@ -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 @@ -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(