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

TST: Test no-file for source #493

Merged
merged 3 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
27 changes: 27 additions & 0 deletions numpydoc/tests/test_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,33 @@ def test_get_validation_checks_validity(checks):
_ = validate.get_validation_checks(checks)


class _DummyList(list):
"""Dummy list class to test validation."""


def test_no_file():
"""Test that validation can be done on functions made on the fly."""
# Just a smoke test for now, <list> will have a None filename
validate.validate("numpydoc.tests.test_validate._DummyList.clear")
# This does something like decorator.FunctionMaker.make
src = """\
def func():
'''Do something.'''
return 1
"""
evaldict = {}
exec(compile(src, "<string>", "single"), evaldict)
func = evaldict["func"]
func.__source__ = src
func.__module__ = "numpydoc.tests.test_validate"
assert func() == 1
# This should get past the comment reading at least. A properly
# wrapped function *would* have source code, too. We could add such
# a test later
with pytest.raises(OSError, match="could not get source code"):
jarrodmillman marked this conversation as resolved.
Show resolved Hide resolved
validate.validate(validate.get_doc_object(func))


@pytest.mark.parametrize(
["file_contents", "expected"],
[
Expand Down
14 changes: 10 additions & 4 deletions numpydoc/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"""

from copy import deepcopy
from typing import Dict, List, Set
from typing import Dict, List, Set, Optional
import ast
import collections
import importlib
Expand Down Expand Up @@ -111,7 +111,9 @@
IGNORE_COMMENT_PATTERN = re.compile("(?:.* numpydoc ignore[=|:] ?)(.+)")


def extract_ignore_validation_comments(filepath: os.PathLike) -> Dict[int, List[str]]:
def extract_ignore_validation_comments(
filepath: Optional[os.PathLike],
) -> Dict[int, List[str]]:
"""
Extract inline comments indicating certain validation checks should be ignored.

Expand All @@ -125,8 +127,12 @@ def extract_ignore_validation_comments(filepath: os.PathLike) -> Dict[int, List[
dict[int, list[str]]
Mapping of line number to a list of checks to ignore.
"""
with open(filepath) as file:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would tweak this slightly.

numpydoc_ignore_comments = {}

try:
    with open(filepath) as file:
        last_declaration = 1
        declarations = ["def", "class"]
        for token in tokenize.generate_tokens(file.readline):
            if token.type == tokenize.NAME and token.string in declarations:
                last_declaration = token.start[0]
            if token.type == tokenize.COMMENT:
                match = re.match(IGNORE_COMMENT_PATTERN, token.string)
                if match:
                    rules = match.group(1).split(",")
                    numpydoc_ignore_comments[last_declaration] = rules
except (OSError, TypeError):  # can be None, nonexistent, or unreadable
    pass

return numpydoc_ignore_comments

Copy link
Collaborator Author

@larsoner larsoner Aug 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually prefer the current approach as I always try to limit the scope of try/except as much as possible. With your code there is a single return path, which is nice. However the scoping for the try/except is broader, which means you risk accidentally catching a TypeError somewhere in the remaining 10 lines that do more than just "try to open the file". And I don't think the interpretation of the current code is too bad: "try to open the file and if you can't, return an empty dict. if you can, proceed to parse it"

numpydoc_ignore_comments = {}
numpydoc_ignore_comments = {}
try:
file = open(filepath)
except (OSError, TypeError): # can be None, nonexistent, or unreadable
return numpydoc_ignore_comments
with file:
last_declaration = 1
declarations = ["def", "class"]
for token in tokenize.generate_tokens(file.readline):
Expand Down
Loading