Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unwrap decorated objects for YD01 validation check #541

Merged
merged 2 commits into from
Apr 24, 2024
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
44 changes: 43 additions & 1 deletion numpydoc/tests/test_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
import sys
import warnings
from contextlib import nullcontext
from functools import cached_property, partial
from functools import cached_property, partial, wraps
from inspect import getsourcelines, getsourcefile

from numpydoc import validate
from numpydoc.validate import Validator
from numpydoc.docscrape import get_doc_object
import numpydoc.tests


Expand Down Expand Up @@ -1692,3 +1694,43 @@ def test_source_file_name_with_properties(self, property, file_name):
)
)
assert doc.source_file_name == file_name


def test_is_generator_validation_with_decorator():
"""Ensure that the check for a Yields section when an object is a generator
(YD01) works with decorated generators."""

def tinsel(f):
@wraps(f)
def wrapper(*args, **kwargs):
return f(*args, **kwargs)

return wrapper

def foo():
"""A simple generator"""
yield from range(10)

@tinsel
def bar():
"""Generator wrapped once"""
yield from range(10)

@tinsel
@tinsel
@tinsel
def baz():
"""Generator wrapped multiple times"""
yield from range(10)

# foo without wrapper is a generator
v = Validator(get_doc_object(foo))
assert v.is_generator_function

# Wrapped once
v = Validator(get_doc_object(bar))
assert v.is_generator_function

# Wrapped multiple times
v = Validator(get_doc_object(baz))
assert v.is_generator_function
9 changes: 8 additions & 1 deletion numpydoc/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@
IGNORE_COMMENT_PATTERN = re.compile("(?:.* numpydoc ignore[=|:] ?)(.+)")


def _unwrap(obj):
"""Iteratively traverse obj.__wrapped__ until first non-wrapped obj found."""
while hasattr(obj, "__wrapped__"):
obj = obj.__wrapped__
return obj


# This function gets called once per function/method to be validated.
# We have to balance memory usage with performance here. It shouldn't be too
# bad to store these `dict`s (they should be rare), but to be safe let's keep
Expand Down Expand Up @@ -273,7 +280,7 @@ def is_function_or_method(self):

@property
def is_generator_function(self):
return inspect.isgeneratorfunction(self.obj)
return inspect.isgeneratorfunction(_unwrap(self.obj))

@property
def source_file_name(self):
Expand Down