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

Add option to allow __init__() to have docstring #7

Merged
merged 4 commits into from
May 24, 2023
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
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ repos:
- id: isort

- repo: https://github.com/jsh9/cercis
rev: 0.1.5
rev: 0.1.6
hooks:
- id: cercis
args: [--extend-exclude=^.*tests/data.*\.py$]
Expand All @@ -17,7 +17,7 @@ repos:
exclude: .*\.ya?ml

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v4.4.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Change Log

## Unreleased

- Added
- A new option to allow `__init__()` methods to have docstring (and when
users activate this option, check arguments and "Raises" in the docstring
of `__init__()` instead of in the class docstring)

## [0.0.3] - 2023-05-18

- Added
Expand Down
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,15 @@ Here's comparison:

### 3.3. `DOC3xx`: Violations about class docstring and class constructor

| Code | Explanation |
| -------- | ------------------------------------------------------------------------------------------- |
| `DOC301` | `__init__()` should not have a docstring; please combine it with the docstring of the class |
| `DOC302` | The docstring for the class does not need a "Returns" sections |
| Code | Explanation |
| -------- | ------------------------------------------------------------------------------------------------------- |
| `DOC301` | `__init__()` should not have a docstring; please combine it with the docstring of the class |
| `DOC302` | The class docstring does not need a "Returns" section, because `__init__()` cannot return anything |
| `DOC303` | The `__init__()` docstring does not need a "Returns" section, because it cannot return anything |
| `DOC304` | Class docstring has an argument/parameter section; please put it in the `__init__()` docstring |
| `DOC305` | Class docstring has a "Raises" section; please put it in the `__init__()` docstring |
| `DOC306` | The class docstring does not need a "Yields" section, because `__init__()` cannot yield anything |
| `DOC307` | The `__init__()` docstring does not need a "Yields" section, because `__init__()` cannot yield anything |

### 3.4. `DOC4xx`: Violations about "yield" statements

Expand Down Expand Up @@ -220,3 +225,9 @@ flake8 --skip-checking-short-docstrings=False <FILE_OR_FOLDER>
If `True`, _pydoclint_ won't report `DOC501` or `DOC502` if there are `raise`
statements in the function/method but there aren't any "raises" sections in the
docstring (or vice versa).

### 4.8. `--allow-init-docstring` (shortform: `-aid`, default: `False`)

If it is set to `True`, having a docstring for class constructors
(`__init__()`) is allowed, and the arguments are expected to be documented
under `__init__()` rather than in the class docstring.
13 changes: 13 additions & 0 deletions pydoclint/flake8_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ def add_options(cls, parser): # noqa: D102
default='False',
help='If True, skip checking docstring "Raises" section against "raise" statements',
)
parser.add_option(
'-aid',
'--allow-init-docstring',
action='store',
default='False',
help='If True, allow both __init__() and the class def to have docstrings',
)

@classmethod
def parse_options(cls, options): # noqa: D102
Expand All @@ -62,6 +69,7 @@ def parse_options(cls, options): # noqa: D102
options.skip_checking_short_docstrings
)
cls.skip_checking_raises = options.skip_checking_raises
cls.allow_init_docstring = options.allow_init_docstring
cls.style = options.style

def run(self) -> Generator[Tuple[int, int, str, Any], None, None]:
Expand All @@ -76,6 +84,10 @@ def run(self) -> Generator[Tuple[int, int, str, Any], None, None]:
'--skip-checking-raises',
self.skip_checking_raises,
)
allowInitDocstring = self._bool(
'--allow-init-docstring',
self.allow_init_docstring,
)

if self.style not in {'numpy', 'google'}:
raise ValueError(
Expand All @@ -87,6 +99,7 @@ def run(self) -> Generator[Tuple[int, int, str, Any], None, None]:
checkArgOrder=checkArgOrder,
skipCheckingShortDocstrings=skipCheckingShortDocstrings,
skipCheckingRaises=skipCheckingRaises,
allowInitDocstring=allowInitDocstring,
style=self.style,
)
v.visit(self._tree)
Expand Down
14 changes: 14 additions & 0 deletions pydoclint/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ def validateStyleValue(
default=False,
help='If True, skip checking docstring "Raises" section against "raise" statements',
)
@click.option(
'-aid',
'--allow-init-docstring',
type=bool,
show_default=True,
default=False,
help='If True, allow both __init__() and the class def to have docstrings',
)
@click.argument(
'paths',
nargs=-1,
Expand All @@ -115,6 +123,7 @@ def main(
check_arg_order: bool,
skip_checking_short_docstrings: bool,
skip_checking_raises: bool,
allow_init_docstring: bool,
) -> None:
"""Command-line entry point of pydoclint"""
ctx.ensure_object(dict)
Expand All @@ -141,6 +150,7 @@ def main(
checkArgOrder=check_arg_order,
skipCheckingShortDocstrings=skip_checking_short_docstrings,
skipCheckingRaises=skip_checking_raises,
allowInitDocstring=allow_init_docstring,
)

violationCounter: int = 0
Expand Down Expand Up @@ -184,6 +194,7 @@ def _checkPaths(
checkArgOrder: bool = True,
skipCheckingShortDocstrings: bool = True,
skipCheckingRaises: bool = False,
allowInitDocstring: bool = False,
quiet: bool = False,
exclude: str = '',
) -> Dict[str, List[Violation]]:
Expand Down Expand Up @@ -218,6 +229,7 @@ def _checkPaths(
checkArgOrder=checkArgOrder,
skipCheckingShortDocstrings=skipCheckingShortDocstrings,
skipCheckingRaises=skipCheckingRaises,
allowInitDocstring=allowInitDocstring,
)
allViolations[filename.as_posix()] = violationsInThisFile

Expand All @@ -231,6 +243,7 @@ def _checkFile(
checkArgOrder: bool = True,
skipCheckingShortDocstrings: bool = True,
skipCheckingRaises: bool = False,
allowInitDocstring: bool = False,
) -> List[Violation]:
with open(filename) as fp:
src: str = ''.join(fp.readlines())
Expand All @@ -242,6 +255,7 @@ def _checkFile(
checkArgOrder=checkArgOrder,
skipCheckingShortDocstrings=skipCheckingShortDocstrings,
skipCheckingRaises=skipCheckingRaises,
allowInitDocstring=allowInitDocstring,
)
visitor.visit(tree)
return visitor.violations
Expand Down
28 changes: 27 additions & 1 deletion pydoclint/utils/arg.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,36 @@ def __eq__(self, other: 'ArgList') -> bool:

return self.infoList == other.infoList

@property
def isEmpty(self) -> bool:
"""Whether the arg list is empty"""
return self.length == 0

@property
def nonEmpty(self) -> bool:
"""Whether the arg list is non-empty"""
return not self.isEmpty

@property
def length(self) -> int:
"""Calculate the length of the list"""
return len(self.infoList)

@classmethod
def fromNumpydocParam(cls, params: List[Parameter]) -> 'ArgList':
"""Construct an Arglist from a list of Parameter objects"""
return ArgList([Arg.fromNumpydocParam(_) for _ in params])

@classmethod
def fromGoogleParsedParam(cls, params: List[DocstringParam]) -> 'ArgList':
"""Construct an ArgList from a list of DocstringParam objects"""
infoList = [
Arg.fromGoogleParsedParam(_)
for _ in params
if _.args[0] != 'attribute' # we only need 'param' not 'attribute'
]
return ArgList(infoList=infoList)

def contains(self, arg: Arg) -> bool:
"""Whether a given `Arg` object exists in the list"""
return arg.name in self.lookup
Expand Down Expand Up @@ -148,7 +174,7 @@ def equals(
if not isinstance(other, ArgList):
return False

if self.length() != other.length():
if self.length != other.length:
return False

verdict: bool
Expand Down
13 changes: 3 additions & 10 deletions pydoclint/utils/doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from docstring_parser.google import GoogleParser
from numpydoc.docscrape import NumpyDocString

from pydoclint.utils.arg import Arg, ArgList
from pydoclint.utils.arg import ArgList
from pydoclint.utils.internal_error import InternalError


Expand Down Expand Up @@ -75,17 +75,10 @@ def isShortDocstring(self) -> bool:
def argList(self) -> ArgList:
"""The argument info in the docstring, presented as an ArgList"""
if self.style == 'numpy':
return ArgList(
[
Arg.fromNumpydocParam(_)
for _ in self.parsed.get('Parameters', [])
]
)
return ArgList.fromNumpydocParam(self.parsed.get('Parameters', []))

if self.style == 'google':
return ArgList(
[Arg.fromGoogleParsedParam(_) for _ in self.parsed.params]
)
return ArgList.fromGoogleParsedParam(self.parsed.params)

self._raiseException() # noqa: R503

Expand Down
11 changes: 10 additions & 1 deletion pydoclint/utils/violation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,22 @@
),
104: 'Arguments are the same in the docstring and the function signature, but are in a different order.',
105: 'Argument names match, but type hints do not match',

201: 'does not have a return section in docstring',
202: 'has a return section in docstring, but there are no return statements or annotations',

301: '__init__() should not have a docstring; please combine it with the docstring of the class',
302: 'The docstring for the class does not need a "Returns" sections',
302: 'The class docstring does not need a "Returns" section, because __init__() cannot return anything',
303: 'The __init__() docstring does not need a "Returns" section, because it cannot return anything',
304: 'Class docstring has an argument/parameter section; please put it in the __init__() docstring',
305: 'Class docstring has a "Raises" section; please put it in the __init__() docstring',
306: 'The class docstring does not need a "Yields" section, because __init__() cannot yield anything',
307: 'The __init__() docstring does not need a "Yields" section, because __init__() cannot yield anything',

401: 'returns a Generator, but the docstring does not have a "Yields" section',
402: 'has "yield" statements, but the docstring does not have a "Yields" section',
403: 'has a "Yields" section in the docstring, but there are no "yield" statements or a Generator return annotation',

501: 'has "raise" statements, but the docstring does not have a "Raises" section',
502: 'has a "Raises" section in the docstring, but there are not "raise" statements in the body',
})
Expand Down
Loading