Skip to content

Commit

Permalink
Add an option for private class attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
jsh9 committed Jul 5, 2024
1 parent 98f7a5c commit ad054d1
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 2 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

## [unpublished] - 2024-07-04

- Added
- An option `--should-document-private-class-attributes` (if False, private
class attributes should not appear in the docstring)

## [0.5.3] - 2024-06-26

- Changed
Expand Down
24 changes: 24 additions & 0 deletions pydoclint/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,18 @@ def validateStyleValue(
' "class MyClass:") are checked against the docstring.'
),
)
@click.option(
'-sdpca',
'--should-document-private-class-attributes',
type=bool,
show_default=True,
default=False,
help=(
'If True, private class attributes (the ones starting with _)'
' should be documented in the docstring. If False, private'
' class attributes should not appear in the docstring.'
),
)
@click.option(
'--baseline',
type=click.Path(
Expand Down Expand Up @@ -291,6 +303,7 @@ def main( # noqa: C901
check_yield_types: bool,
ignore_underscore_args: bool,
check_class_attributes: bool,
should_document_private_class_attributes: bool,
require_return_section_when_returning_none: bool,
require_return_section_when_returning_nothing: bool,
require_yield_section_when_yielding_nothing: bool,
Expand Down Expand Up @@ -376,6 +389,9 @@ def main( # noqa: C901
checkYieldTypes=check_yield_types,
ignoreUnderscoreArgs=ignore_underscore_args,
checkClassAttributes=check_class_attributes,
shouldDocumentPrivateClassAttributes=(
should_document_private_class_attributes
),
requireReturnSectionWhenReturningNothing=(
require_return_section_when_returning_nothing
),
Expand Down Expand Up @@ -491,6 +507,7 @@ def _checkPaths(
checkYieldTypes: bool = True,
ignoreUnderscoreArgs: bool = True,
checkClassAttributes: bool = True,
shouldDocumentPrivateClassAttributes: bool = False,
requireReturnSectionWhenReturningNothing: bool = False,
requireYieldSectionWhenYieldingNothing: bool = False,
quiet: bool = False,
Expand Down Expand Up @@ -537,6 +554,9 @@ def _checkPaths(
checkYieldTypes=checkYieldTypes,
ignoreUnderscoreArgs=ignoreUnderscoreArgs,
checkClassAttributes=checkClassAttributes,
shouldDocumentPrivateClassAttributes=(
shouldDocumentPrivateClassAttributes
),
requireReturnSectionWhenReturningNothing=(
requireReturnSectionWhenReturningNothing
),
Expand All @@ -562,6 +582,7 @@ def _checkFile(
checkYieldTypes: bool = True,
ignoreUnderscoreArgs: bool = True,
checkClassAttributes: bool = True,
shouldDocumentPrivateClassAttributes: bool = False,
requireReturnSectionWhenReturningNothing: bool = False,
requireYieldSectionWhenYieldingNothing: bool = False,
) -> List[Violation]:
Expand All @@ -581,6 +602,9 @@ def _checkFile(
checkYieldTypes=checkYieldTypes,
ignoreUnderscoreArgs=ignoreUnderscoreArgs,
checkClassAttributes=checkClassAttributes,
shouldDocumentPrivateClassAttributes=(
shouldDocumentPrivateClassAttributes
),
requireReturnSectionWhenReturningNothing=(
requireReturnSectionWhenReturningNothing
),
Expand Down
27 changes: 25 additions & 2 deletions pydoclint/utils/visitor_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,15 @@ def checkClassAttributesAgainstClassDocstring(
argTypeHintsInSignature: bool,
argTypeHintsInDocstring: bool,
skipCheckingShortDocstrings: bool,
shouldDocumentPrivateClassAttributes: bool,
) -> None:
"""Check class attribute list against the attribute list in docstring"""
classAttributes = _collectClassAttributes(node)
classAttributes = _collectClassAttributes(
node=node,
shouldDocumentPrivateClassAttributes=(
shouldDocumentPrivateClassAttributes
),
)
actualArgs: ArgList = _convertClassAttributesIntoArgList(classAttributes)

classDocstring: str = getDocstring(node)
Expand Down Expand Up @@ -113,19 +119,36 @@ def checkClassAttributesAgainstClassDocstring(


def _collectClassAttributes(
*,
node: ast.ClassDef,
shouldDocumentPrivateClassAttributes: bool,
) -> List[Union[ast.Assign, ast.AnnAssign]]:
if 'body' not in node.__dict__ or len(node.body) == 0:
return []

attributes: List[Union[ast.Assign, ast.AnnAssign]] = []
for item in node.body:
if isinstance(item, (ast.Assign, ast.AnnAssign)):
attributes.append(item)
classAttrName: str = _getClassAttrName(item)
if shouldDocumentPrivateClassAttributes:
attributes.append(item)
else:
if not classAttrName.startswith('_'):
attributes.append(item)

return attributes


def _getClassAttrName(attrItem: Union[ast.Assign, ast.AnnAssign]) -> str:
if isinstance(attrItem, ast.Assign):
return attrItem.targets[0].id

if isinstance(attrItem, ast.AnnAssign):
return attrItem.target.id

raise InternalError(f'Unrecognized attrItem type: {type(attrItem)}')


def _convertClassAttributesIntoArgList(
classAttributes: List[Union[ast.Assign, ast.AnnAssign]],
) -> ArgList:
Expand Down
7 changes: 7 additions & 0 deletions pydoclint/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def __init__(
checkYieldTypes: bool = True,
ignoreUnderscoreArgs: bool = True,
checkClassAttributes: bool = True,
shouldDocumentPrivateClassAttributes: bool = False,
requireReturnSectionWhenReturningNothing: bool = False,
requireYieldSectionWhenYieldingNothing: bool = False,
) -> None:
Expand All @@ -73,6 +74,9 @@ def __init__(
self.checkYieldTypes: bool = checkYieldTypes
self.ignoreUnderscoreArgs: bool = ignoreUnderscoreArgs
self.checkClassAttributes: bool = checkClassAttributes
self.shouldDocumentPrivateClassAttributes: bool = (
shouldDocumentPrivateClassAttributes
)
self.requireReturnSectionWhenReturningNothing: bool = (
requireReturnSectionWhenReturningNothing
)
Expand All @@ -98,6 +102,9 @@ def visit_ClassDef(self, node: ast.ClassDef): # noqa: D102
argTypeHintsInSignature=self.argTypeHintsInSignature,
argTypeHintsInDocstring=self.argTypeHintsInDocstring,
skipCheckingShortDocstrings=self.skipCheckingShortDocstrings,
shouldDocumentPrivateClassAttributes=(
self.shouldDocumentPrivateClassAttributes
),
)

self.generic_visit(node)
Expand Down
16 changes: 16 additions & 0 deletions tests/data/edge_cases/11_private_class_attr/google.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class MyClass:
"""
My Class.
This edge case comes from: https://github.com/jsh9/pydoclint/issues/148
Attributes:
attr_1 (str): The first attribute
attr_2: The 2nd attribute
attr_3 (float): The 3rd attribute
"""

attr_1: str = 'hello'
attr_2 = 4
attr_3: float
_hidden_attr: bool
25 changes: 25 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1304,6 +1304,28 @@ def testNonAscii() -> None:
'has 1 type(s).',
],
),
(
'11_private_class_attr/google.py',
{'style': 'google', 'shouldDocumentPrivateClassAttributes': False},
[],
),
(
'11_private_class_attr/google.py',
{'style': 'google', 'shouldDocumentPrivateClassAttributes': True},
[
'DOC601: Class `MyClass`: Class docstring contains fewer class attributes '
'than actual class attributes. (Please read '
'https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to '
'correctly document class attributes.)',
'DOC603: Class `MyClass`: Class docstring attributes are different from '
'actual class attributes. (Or could be other formatting issues: '
'https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). '
'Attributes in the class definition but not in the docstring: [_hidden_attr: '
'bool]. (Please read '
'https://jsh9.github.io/pydoclint/checking_class_attributes.html on how to '
'correctly document class attributes.)',
],
),
],
)
def testEdgeCases(
Expand All @@ -1328,6 +1350,9 @@ def testPlayground() -> None:
violations = _checkFile(
filename=DATA_DIR / 'playground.py',
style='google',
argTypeHintsInDocstring=False,
checkReturnTypes=False,
checkYieldTypes=False,
)
expected = []
assert list(map(str, violations)) == expected

0 comments on commit ad054d1

Please sign in to comment.