Skip to content

Commit 4394fda

Browse files
Do not check private functions for args, returns or raises (#31)
* Do not check private functions for args, returns or raises
1 parent 83fdea8 commit 4394fda

File tree

7 files changed

+171
-8
lines changed

7 files changed

+171
-8
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ assumes that the docstrings already pass `pydocstyle` checks. This
66
[blog post](https://jdkandersson.com/2023/01/07/writing-great-docstrings-in-python/)
77
discusses how to write great docstrings and the motivation for this linter!
88

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

1117
```shell
@@ -1672,3 +1678,6 @@ Section information is extracted using the following algorithm:
16721678
- Check that argument, exceptions and attributes have non-empty description.
16731679
- Check that arguments, exceptions and attributes have meaningful descriptions.
16741680
- Check other other PEP257 conventions
1681+
- The definition for private functions is a function starting with a single `_`. This could be extended to functions starting with `__`
1682+
and not ending in `__`, that is functions with [name mangling](https://docs.python.org/3/tutorial/classes.html#private-variables)
1683+
but not [magic methods](https://docs.python.org/3/reference/datamodel.html#special-lookup).

flake8_docstrings_complete/__init__.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
f"{MORE_INFO_BASE}{MULT_YIELDS_SECTIONS_IN_DOCSTR_CODE.lower()}"
5454
)
5555

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

7879

7980
def _check_returns(
80-
docstr_info: docstring.Docstring, docstr_node: ast.Constant, return_nodes: Iterable[ast.Return]
81+
docstr_info: docstring.Docstring,
82+
docstr_node: ast.Constant,
83+
return_nodes: Iterable[ast.Return],
84+
is_private: bool,
8185
) -> Iterator[types_.Problem]:
8286
"""Check function/ method returns section.
8387
8488
Args:
8589
docstr_info: Information about the docstring.
8690
docstr_node: The docstring node.
8791
return_nodes: The return nodes of the function.
92+
is_private: If the function for the docstring is private.
8893
8994
Yields:
9095
All the problems with the returns section.
9196
"""
9297
return_nodes_with_value = list(node for node in return_nodes if node.value is not None)
9398

9499
# Check for return statements with value and no returns section in docstring
95-
if return_nodes_with_value and not docstr_info.returns_sections:
100+
if return_nodes_with_value and not docstr_info.returns_sections and not is_private:
96101
yield from (
97102
types_.Problem(node.lineno, node.col_offset, RETURNS_SECTION_NOT_IN_DOCSTR_MSG)
98103
for node in return_nodes_with_value
@@ -117,21 +122,23 @@ def _check_yields(
117122
docstr_info: docstring.Docstring,
118123
docstr_node: ast.Constant,
119124
yield_nodes: Iterable[ast.Yield | ast.YieldFrom],
125+
is_private: bool,
120126
) -> Iterator[types_.Problem]:
121127
"""Check function/ method yields section.
122128
123129
Args:
124130
docstr_info: Information about the docstring.
125131
docstr_node: The docstring node.
126132
yield_nodes: The yield and yield from nodes of the function.
133+
is_private: If the function for the docstring is private.
127134
128135
Yields:
129136
All the problems with the yields section.
130137
"""
131138
yield_nodes_with_value = list(node for node in yield_nodes if node.value is not None)
132139

133140
# Check for yield statements with value and no yields section in docstring
134-
if yield_nodes_with_value and not docstr_info.yields_sections:
141+
if yield_nodes_with_value and not docstr_info.yields_sections and not is_private:
135142
yield from (
136143
types_.Problem(node.lineno, node.col_offset, YIELDS_SECTION_NOT_IN_DOCSTR_MSG)
137144
for node in yield_nodes_with_value
@@ -372,11 +379,17 @@ def visit_any_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> No
372379
and isinstance(node.body[0].value, ast.Constant)
373380
and isinstance(node.body[0].value.value, str)
374381
):
382+
is_private = bool(re.match(PRIVATE_FUNCTION_PATTERN, node.name))
375383
# Check args
376384
docstr_info = docstring.parse(value=node.body[0].value.value)
377385
docstr_node = node.body[0].value
378386
self.problems.extend(
379-
args.check(docstr_info=docstr_info, docstr_node=docstr_node, args=node.args)
387+
args.check(
388+
docstr_info=docstr_info,
389+
docstr_node=docstr_node,
390+
args=node.args,
391+
is_private=is_private,
392+
)
380393
)
381394

382395
# Check returns
@@ -387,6 +400,7 @@ def visit_any_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> No
387400
docstr_info=docstr_info,
388401
docstr_node=docstr_node,
389402
return_nodes=visitor_within_function.return_nodes,
403+
is_private=is_private,
390404
)
391405
)
392406

@@ -396,6 +410,7 @@ def visit_any_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> No
396410
docstr_info=docstr_info,
397411
docstr_node=docstr_node,
398412
yield_nodes=visitor_within_function.yield_nodes,
413+
is_private=is_private,
399414
)
400415
)
401416

@@ -405,6 +420,7 @@ def visit_any_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> No
405420
docstr_info=docstr_info,
406421
docstr_node=docstr_node,
407422
raise_nodes=visitor_within_function.raise_nodes,
423+
is_private=is_private,
408424
)
409425
)
410426

flake8_docstrings_complete/args.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ def _iter_args(args: ast.arguments) -> Iterator[ast.arg]:
6767

6868

6969
def check(
70-
docstr_info: docstring.Docstring, docstr_node: ast.Constant, args: ast.arguments
70+
docstr_info: docstring.Docstring,
71+
docstr_node: ast.Constant,
72+
args: ast.arguments,
73+
is_private: bool,
7174
) -> Iterator[types_.Problem]:
7275
"""Check that all function/ method arguments are described in the docstring.
7376
@@ -80,6 +83,7 @@ def check(
8083
docstr_info: Information about the docstring.
8184
docstr_node: The docstring node.
8285
args: The arguments of the function.
86+
is_private: If the function for the docstring is private.
8387
8488
Yields:
8589
All the problems with the arguments.
@@ -88,7 +92,7 @@ def check(
8892
all_used_args = list(arg for arg in all_args if not arg.arg.startswith(UNUSED_ARGS_PREFIX))
8993

9094
# Check that args section is in docstring if function/ method has used arguments
91-
if all_used_args and docstr_info.args is None:
95+
if all_used_args and docstr_info.args is None and not is_private:
9296
yield types_.Problem(
9397
docstr_node.lineno, docstr_node.col_offset, ARGS_SECTION_NOT_IN_DOCSTR_MSG
9498
)

flake8_docstrings_complete/raises.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ def _get_exc_node(node: ast.Raise) -> types_.Node | None:
8585

8686

8787
def check(
88-
docstr_info: docstring.Docstring, docstr_node: ast.Constant, raise_nodes: Iterable[ast.Raise]
88+
docstr_info: docstring.Docstring,
89+
docstr_node: ast.Constant,
90+
raise_nodes: Iterable[ast.Raise],
91+
is_private: bool,
8992
) -> Iterator[types_.Problem]:
9093
"""Check that all raised exceptions arguments are described in the docstring.
9194
@@ -97,6 +100,7 @@ def check(
97100
docstr_info: Information about the docstring.
98101
docstr_node: The docstring node.
99102
raise_nodes: The raise nodes.
103+
is_private: If the function for the docstring is private.
100104
101105
Yields:
102106
All the problems with exceptions.
@@ -106,7 +110,7 @@ def check(
106110
all_raise_no_value = all(exc is None for exc in all_excs)
107111

108112
# Check that raises section is in docstring if function/ method raises exceptions
109-
if all_excs and docstr_info.raises is None:
113+
if all_excs and docstr_info.raises is None and not is_private:
110114
yield types_.Problem(
111115
docstr_node.lineno, docstr_node.col_offset, RAISES_SECTION_NOT_IN_DOCSTR_MSG
112116
)

tests/unit/test___init__.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"""Unit tests for plugin except for args rules."""
22

3+
# The lines represent the number of test cases
4+
# pylint: disable=too-many-lines
5+
36
from __future__ import annotations
47

58
import pytest
@@ -31,6 +34,14 @@ def function_1():
3134
),
3235
pytest.param(
3336
"""
37+
def _function_1():
38+
return
39+
""",
40+
(f"2:0 {DOCSTR_MISSING_MSG}",),
41+
id="private function docstring missing return",
42+
),
43+
pytest.param(
44+
"""
3445
@overload
3546
def function_1():
3647
...
@@ -96,6 +107,17 @@ def function_1():
96107
),
97108
pytest.param(
98109
'''
110+
def _function_1():
111+
"""Docstring.
112+
113+
Returns:
114+
"""
115+
''',
116+
(f"3:4 {RETURNS_SECTION_IN_DOCSTR_MSG}",),
117+
id="private function no return returns in docstring",
118+
),
119+
pytest.param(
120+
'''
99121
class Class1:
100122
"""Docstring."""
101123
def function_1():
@@ -250,6 +272,15 @@ def function_1():
250272
),
251273
pytest.param(
252274
'''
275+
def _function_1():
276+
"""Docstring."""
277+
yield 1
278+
''',
279+
(),
280+
id="private function single yield value yields not in docstring",
281+
),
282+
pytest.param(
283+
'''
253284
def function_1():
254285
"""Docstring."""
255286
yield from tuple()
@@ -384,6 +415,17 @@ def function_1():
384415
),
385416
pytest.param(
386417
'''
418+
def _function_1():
419+
"""Docstring.
420+
421+
Yields:
422+
"""
423+
''',
424+
(f"3:4 {YIELDS_SECTION_IN_DOCSTR_MSG}",),
425+
id="private function no yield yields in docstring",
426+
),
427+
pytest.param(
428+
'''
387429
class Class1:
388430
"""Docstring."""
389431
def function_1():
@@ -511,6 +553,18 @@ def function_1():
511553
),
512554
pytest.param(
513555
'''
556+
def _function_1():
557+
"""Docstring 1.
558+
559+
Returns:
560+
"""
561+
return 1
562+
''',
563+
(),
564+
id="private function return value docstring returns section",
565+
),
566+
pytest.param(
567+
'''
514568
def function_1():
515569
"""Docstring 1."""
516570
def function_2():
@@ -678,6 +732,18 @@ def function_1():
678732
),
679733
pytest.param(
680734
'''
735+
def _function_1():
736+
"""Docstring 1.
737+
738+
Yields:
739+
"""
740+
yield 1
741+
''',
742+
(),
743+
id="private function yield value docstring yields section",
744+
),
745+
pytest.param(
746+
'''
681747
def function_1():
682748
"""Docstring 1.
683749

tests/unit/test___init__args.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ def function_1():
5454
),
5555
pytest.param(
5656
'''
57+
def _function_1():
58+
"""Docstring 1.
59+
60+
Args:
61+
"""
62+
''',
63+
(f"3:4 {ARGS_SECTION_IN_DOCSTR_MSG}",),
64+
id="private function has no args docstring args section",
65+
),
66+
pytest.param(
67+
'''
5768
def function_1(arg_1):
5869
"""Docstring 1.
5970
@@ -462,6 +473,18 @@ def function_1(arg_1):
462473
),
463474
pytest.param(
464475
'''
476+
def _function_1(arg_1):
477+
"""Docstring 1.
478+
479+
Args:
480+
arg_1:
481+
"""
482+
''',
483+
(),
484+
id="private function single arg docstring single arg",
485+
),
486+
pytest.param(
487+
'''
465488
def function_1(_arg_1):
466489
"""Docstring 1.
467490
@@ -474,6 +497,14 @@ def function_1(_arg_1):
474497
),
475498
pytest.param(
476499
'''
500+
def _function_1(arg_1):
501+
"""Docstring 1."""
502+
''',
503+
(),
504+
id="private function single arg docstring no arg",
505+
),
506+
pytest.param(
507+
'''
477508
def function_1(_arg_1):
478509
"""Docstring 1."""
479510
''',

0 commit comments

Comments
 (0)