Skip to content

Do not check private functions for args, returns or raises #31

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

Merged
merged 4 commits into from
Nov 7, 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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ assumes that the docstrings already pass `pydocstyle` checks. This
[blog post](https://jdkandersson.com/2023/01/07/writing-great-docstrings-in-python/)
discusses how to write great docstrings and the motivation for this linter!

Following [PEP-8](https://peps.python.org/pep-0008/#documentation-strings),
Docstrings are not necessary for non-public methods, but you should have a
comment that describes what the method does. The definition taken for private
functions/methods is that they start with a single underscore (`_`).


## Getting Started

```shell
Expand Down Expand Up @@ -1672,3 +1678,6 @@ Section information is extracted using the following algorithm:
- Check that argument, exceptions and attributes have non-empty description.
- Check that arguments, exceptions and attributes have meaningful descriptions.
- Check other other PEP257 conventions
- The definition for private functions is a function starting with a single `_`. This could be extended to functions starting with `__`
and not ending in `__`, that is functions with [name mangling](https://docs.python.org/3/tutorial/classes.html#private-variables)
but not [magic methods](https://docs.python.org/3/reference/datamodel.html#special-lookup).
24 changes: 20 additions & 4 deletions flake8_docstrings_complete/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
f"{MORE_INFO_BASE}{MULT_YIELDS_SECTIONS_IN_DOCSTR_CODE.lower()}"
)

PRIVATE_FUNCTION_PATTERN = r"_[^_].*"
TEST_FILENAME_PATTERN_ARG_NAME = "--docstrings-complete-test-filename-pattern"
TEST_FILENAME_PATTERN_DEFAULT = r"test_.*\.py"
TEST_FUNCTION_PATTERN_ARG_NAME = "--docstrings-complete-test-function-pattern"
Expand All @@ -77,22 +78,26 @@ def _cli_arg_name_to_attr(cli_arg_name: str) -> str:


def _check_returns(
docstr_info: docstring.Docstring, docstr_node: ast.Constant, return_nodes: Iterable[ast.Return]
docstr_info: docstring.Docstring,
docstr_node: ast.Constant,
return_nodes: Iterable[ast.Return],
is_private: bool,
) -> Iterator[types_.Problem]:
"""Check function/ method returns section.

Args:
docstr_info: Information about the docstring.
docstr_node: The docstring node.
return_nodes: The return nodes of the function.
is_private: If the function for the docstring is private.

Yields:
All the problems with the returns section.
"""
return_nodes_with_value = list(node for node in return_nodes if node.value is not None)

# Check for return statements with value and no returns section in docstring
if return_nodes_with_value and not docstr_info.returns_sections:
if return_nodes_with_value and not docstr_info.returns_sections and not is_private:
yield from (
types_.Problem(node.lineno, node.col_offset, RETURNS_SECTION_NOT_IN_DOCSTR_MSG)
for node in return_nodes_with_value
Expand All @@ -117,21 +122,23 @@ def _check_yields(
docstr_info: docstring.Docstring,
docstr_node: ast.Constant,
yield_nodes: Iterable[ast.Yield | ast.YieldFrom],
is_private: bool,
) -> Iterator[types_.Problem]:
"""Check function/ method yields section.

Args:
docstr_info: Information about the docstring.
docstr_node: The docstring node.
yield_nodes: The yield and yield from nodes of the function.
is_private: If the function for the docstring is private.

Yields:
All the problems with the yields section.
"""
yield_nodes_with_value = list(node for node in yield_nodes if node.value is not None)

# Check for yield statements with value and no yields section in docstring
if yield_nodes_with_value and not docstr_info.yields_sections:
if yield_nodes_with_value and not docstr_info.yields_sections and not is_private:
yield from (
types_.Problem(node.lineno, node.col_offset, YIELDS_SECTION_NOT_IN_DOCSTR_MSG)
for node in yield_nodes_with_value
Expand Down Expand Up @@ -372,11 +379,17 @@ def visit_any_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> No
and isinstance(node.body[0].value, ast.Constant)
and isinstance(node.body[0].value.value, str)
):
is_private = bool(re.match(PRIVATE_FUNCTION_PATTERN, node.name))
# Check args
docstr_info = docstring.parse(value=node.body[0].value.value)
docstr_node = node.body[0].value
self.problems.extend(
args.check(docstr_info=docstr_info, docstr_node=docstr_node, args=node.args)
args.check(
docstr_info=docstr_info,
docstr_node=docstr_node,
args=node.args,
is_private=is_private,
)
)

# Check returns
Expand All @@ -387,6 +400,7 @@ def visit_any_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> No
docstr_info=docstr_info,
docstr_node=docstr_node,
return_nodes=visitor_within_function.return_nodes,
is_private=is_private,
)
)

Expand All @@ -396,6 +410,7 @@ def visit_any_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> No
docstr_info=docstr_info,
docstr_node=docstr_node,
yield_nodes=visitor_within_function.yield_nodes,
is_private=is_private,
)
)

Expand All @@ -405,6 +420,7 @@ def visit_any_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> No
docstr_info=docstr_info,
docstr_node=docstr_node,
raise_nodes=visitor_within_function.raise_nodes,
is_private=is_private,
)
)

Expand Down
8 changes: 6 additions & 2 deletions flake8_docstrings_complete/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ def _iter_args(args: ast.arguments) -> Iterator[ast.arg]:


def check(
docstr_info: docstring.Docstring, docstr_node: ast.Constant, args: ast.arguments
docstr_info: docstring.Docstring,
docstr_node: ast.Constant,
args: ast.arguments,
is_private: bool,
) -> Iterator[types_.Problem]:
"""Check that all function/ method arguments are described in the docstring.

Expand All @@ -80,6 +83,7 @@ def check(
docstr_info: Information about the docstring.
docstr_node: The docstring node.
args: The arguments of the function.
is_private: If the function for the docstring is private.

Yields:
All the problems with the arguments.
Expand All @@ -88,7 +92,7 @@ def check(
all_used_args = list(arg for arg in all_args if not arg.arg.startswith(UNUSED_ARGS_PREFIX))

# Check that args section is in docstring if function/ method has used arguments
if all_used_args and docstr_info.args is None:
if all_used_args and docstr_info.args is None and not is_private:
yield types_.Problem(
docstr_node.lineno, docstr_node.col_offset, ARGS_SECTION_NOT_IN_DOCSTR_MSG
)
Expand Down
8 changes: 6 additions & 2 deletions flake8_docstrings_complete/raises.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ def _get_exc_node(node: ast.Raise) -> types_.Node | None:


def check(
docstr_info: docstring.Docstring, docstr_node: ast.Constant, raise_nodes: Iterable[ast.Raise]
docstr_info: docstring.Docstring,
docstr_node: ast.Constant,
raise_nodes: Iterable[ast.Raise],
is_private: bool,
) -> Iterator[types_.Problem]:
"""Check that all raised exceptions arguments are described in the docstring.

Expand All @@ -97,6 +100,7 @@ def check(
docstr_info: Information about the docstring.
docstr_node: The docstring node.
raise_nodes: The raise nodes.
is_private: If the function for the docstring is private.

Yields:
All the problems with exceptions.
Expand All @@ -106,7 +110,7 @@ def check(
all_raise_no_value = all(exc is None for exc in all_excs)

# Check that raises section is in docstring if function/ method raises exceptions
if all_excs and docstr_info.raises is None:
if all_excs and docstr_info.raises is None and not is_private:
yield types_.Problem(
docstr_node.lineno, docstr_node.col_offset, RAISES_SECTION_NOT_IN_DOCSTR_MSG
)
Expand Down
66 changes: 66 additions & 0 deletions tests/unit/test___init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"""Unit tests for plugin except for args rules."""

# The lines represent the number of test cases
# pylint: disable=too-many-lines

from __future__ import annotations

import pytest
Expand Down Expand Up @@ -31,6 +34,14 @@ def function_1():
),
pytest.param(
"""
def _function_1():
return
""",
(f"2:0 {DOCSTR_MISSING_MSG}",),
id="private function docstring missing return",
),
pytest.param(
"""
@overload
def function_1():
...
Expand Down Expand Up @@ -96,6 +107,17 @@ def function_1():
),
pytest.param(
'''
def _function_1():
"""Docstring.

Returns:
"""
''',
(f"3:4 {RETURNS_SECTION_IN_DOCSTR_MSG}",),
id="private function no return returns in docstring",
),
pytest.param(
'''
class Class1:
"""Docstring."""
def function_1():
Expand Down Expand Up @@ -250,6 +272,15 @@ def function_1():
),
pytest.param(
'''
def _function_1():
"""Docstring."""
yield 1
''',
(),
id="private function single yield value yields not in docstring",
),
pytest.param(
'''
def function_1():
"""Docstring."""
yield from tuple()
Expand Down Expand Up @@ -384,6 +415,17 @@ def function_1():
),
pytest.param(
'''
def _function_1():
"""Docstring.

Yields:
"""
''',
(f"3:4 {YIELDS_SECTION_IN_DOCSTR_MSG}",),
id="private function no yield yields in docstring",
),
pytest.param(
'''
class Class1:
"""Docstring."""
def function_1():
Expand Down Expand Up @@ -511,6 +553,18 @@ def function_1():
),
pytest.param(
'''
def _function_1():
"""Docstring 1.

Returns:
"""
return 1
''',
(),
id="private function return value docstring returns section",
),
pytest.param(
'''
def function_1():
"""Docstring 1."""
def function_2():
Expand Down Expand Up @@ -678,6 +732,18 @@ def function_1():
),
pytest.param(
'''
def _function_1():
"""Docstring 1.

Yields:
"""
yield 1
''',
(),
id="private function yield value docstring yields section",
),
pytest.param(
'''
def function_1():
"""Docstring 1.

Expand Down
31 changes: 31 additions & 0 deletions tests/unit/test___init__args.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ def function_1():
),
pytest.param(
'''
def _function_1():
"""Docstring 1.

Args:
"""
''',
(f"3:4 {ARGS_SECTION_IN_DOCSTR_MSG}",),
id="private function has no args docstring args section",
),
pytest.param(
'''
def function_1(arg_1):
"""Docstring 1.

Expand Down Expand Up @@ -462,6 +473,18 @@ def function_1(arg_1):
),
pytest.param(
'''
def _function_1(arg_1):
"""Docstring 1.

Args:
arg_1:
"""
''',
(),
id="private function single arg docstring single arg",
),
pytest.param(
'''
def function_1(_arg_1):
"""Docstring 1.

Expand All @@ -474,6 +497,14 @@ def function_1(_arg_1):
),
pytest.param(
'''
def _function_1(arg_1):
"""Docstring 1."""
''',
(),
id="private function single arg docstring no arg",
),
pytest.param(
'''
def function_1(_arg_1):
"""Docstring 1."""
''',
Expand Down
Loading
Loading