Skip to content

Commit

Permalink
Add option to allow __init__() to have docstring (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsh9 authored May 24, 2023
1 parent b705f60 commit 30eedbd
Show file tree
Hide file tree
Showing 14 changed files with 525 additions and 45 deletions.
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

0 comments on commit 30eedbd

Please sign in to comment.