Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
17 changes: 17 additions & 0 deletions tests/unit/test___init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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 @@ -250,6 +258,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
8 changes: 8 additions & 0 deletions tests/unit/test___init__args.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,14 @@ def function_1(_arg_1):
),
pytest.param(
'''
def _function_1(arg_1):
"""Docstring 1."""
''',
(),
id="prive function single arg docstring no arg",
),
pytest.param(
'''
def function_1(_arg_1):
"""Docstring 1."""
''',
Expand Down
9 changes: 9 additions & 0 deletions tests/unit/test___init__raises.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ def function_1():
),
pytest.param(
'''
def _function_1():
"""Docstring 1."""
raise Exc1
''',
(),
id="private function raises single exc docstring no raises section",
),
pytest.param(
'''
def function_1():
"""Docstring 1."""
raise Exc1
Expand Down
Loading