Skip to content
This repository has been archived by the owner on Nov 3, 2023. It is now read-only.

Add new codes for private definitions #562

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions docs/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ New Features

* Add support for `property_decorators` config to ignore D401.
* Add support for Python 3.10 (#554).
# Add reporting for private definitions (#562)

6.1.1 - May 17th, 2021
---------------------------
Expand Down
39 changes: 20 additions & 19 deletions src/pydocstyle/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
Class,
Definition,
Function,
InaccessibleClass,
InaccessibleFunction,
Method,
Module,
NestedClass,
NestedFunction,
Package,
ParseError,
Parser,
Expand Down Expand Up @@ -184,7 +185,7 @@ def checks(self):

@check_for(Definition, terminal=True)
def check_docstring_missing(self, definition, docstring):
"""D10{0,1,2,3}: Public definitions should have docstrings.
"""D1XX: Definitions should have docstrings.

All modules should normally have docstrings. [...] all functions and
classes exported by a module should also have docstrings. Public
Expand All @@ -196,36 +197,36 @@ def check_docstring_missing(self, definition, docstring):
with a single underscore.

"""

def violation(code):
code = code if definition.is_public else code + 50
return getattr(violations, f"D{code}")()

if (
not docstring
and definition.is_public
or docstring
and is_blank(ast.literal_eval(docstring))
):
codes = {
Module: violations.D100,
Class: violations.D101,
NestedClass: violations.D106,
Method: lambda: violations.D105()
Module: lambda: 100,
Class: lambda: 101,
NestedClass: lambda: 106,
InaccessibleClass: lambda: 121,
Method: lambda: 105
if definition.is_magic
else (
violations.D107()
107
if definition.is_init
else (
violations.D102()
if not definition.is_overload
else None
)
else (102 if not definition.is_overload else None)
),
NestedFunction: violations.D103,
InaccessibleFunction: lambda: 123,
Function: (
lambda: violations.D103()
if not definition.is_overload
else None
lambda: 103 if not definition.is_overload else None
),
Package: violations.D104,
Package: lambda: 104,
}
return codes[type(definition)]()
code = codes[type(definition)]()
return violation(code) if code is not None else None

@check_for(Definition)
def check_one_liners(self, definition, docstring):
Expand Down
12 changes: 7 additions & 5 deletions src/pydocstyle/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from re import compile as re

from .utils import __version__, log
from .violations import ErrorRegistry, conventions
from .violations import ErrorRegistry, conventions, default_ignored

try:
import toml
Expand Down Expand Up @@ -594,10 +594,12 @@ def _get_exclusive_error_codes(cls, options):
if options.ignore is not None:
ignored = cls._expand_error_codes(options.ignore)
checked_codes = codes - ignored
elif options.select is not None:
checked_codes = cls._expand_error_codes(options.select)
elif options.convention is not None:
checked_codes = getattr(conventions, options.convention)
else:
codes -= default_ignored
if options.select is not None:
checked_codes = cls._expand_error_codes(options.select)
elif options.convention is not None:
checked_codes = getattr(conventions, options.convention)

# To not override the conventions nor the options - copy them.
return copy.deepcopy(checked_codes)
Expand Down
35 changes: 30 additions & 5 deletions src/pydocstyle/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
'Module',
'Package',
'Function',
'NestedFunction',
'InaccessibleFunction',
'Method',
'Class',
'NestedClass',
'InaccessibleClass',
'AllError',
'StringIO',
'ParseError',
Expand Down Expand Up @@ -199,8 +200,9 @@ class Function(Definition):
"""A Python source code function."""

_nest = staticmethod(
lambda s: {'def': NestedFunction, 'class': NestedClass}[s]
lambda s: {'def': InaccessibleFunction, 'class': InaccessibleClass}[s]
)
is_accessible = True

@property
def is_public(self):
Expand Down Expand Up @@ -236,10 +238,18 @@ def is_test(self):
return self.name.startswith('test') or self.name == 'runTest'


class NestedFunction(Function):
"""A Python source code nested function."""
class InaccessibleFunction(Function):
"""A Python source code function which is inaccessible.

is_public = False
A function is inaccessible if it is defined inside another function.

Publicness is still evaluated based on the name, to allow devs to signal between public and
private if they so wish. (E.g. if a function returns another function, they may want the inner
function to be documented. Conversely a purely helper inner function might not need to be
documented)
"""

is_accessible = False


class Method(Function):
Expand Down Expand Up @@ -289,6 +299,7 @@ class Class(Definition):
_nest = staticmethod(lambda s: {'def': Method, 'class': NestedClass}[s])
is_public = Function.is_public
is_class = True
is_accessible = True


class NestedClass(Class):
Expand All @@ -304,6 +315,20 @@ def is_public(self):
)


class InaccessibleClass(Class):
"""A Python source code class, which is inaccessible.

An class is inaccessible if it is defined inside of a function.

Publicness is still evaluated based on the name, to allow devs to signal between public and
private if they so wish. (E.g. if a function returns a class, they may want the returned
class to be documented. Conversely a purely helper inner class might not need to be
documented)
"""

is_accessible = False


class Decorator(Value):
"""A decorator for function, method or class."""

Expand Down
67 changes: 66 additions & 1 deletion src/pydocstyle/violations.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,55 @@ def to_rst(cls) -> str:
'D107',
'Missing docstring in __init__',
)
D121 = D1xx.create_error(
'D121',
'Missing docstring in inaccessible public class',
)
D123 = D1xx.create_error(
'D123',
'Missing docstring in inaccessible public function',
)
# For private docstrings, we add 50 to the public codes
D150 = D1xx.create_error(
'D150',
'Missing docstring in private module',
)
D151 = D1xx.create_error(
'D151',
'Missing docstring in private class',
)
D152 = D1xx.create_error(
'D152',
'Missing docstring in private method',
)
D153 = D1xx.create_error(
'D153',
'Missing docstring in private function',
)
D154 = D1xx.create_error(
'D154',
'Missing docstring in private package',
)
D155 = D1xx.create_error(
'D155',
'Missing docstring in private magic method',
)
D156 = D1xx.create_error(
'D156',
'Missing docstring in private nested class',
)
D157 = D1xx.create_error(
'D157',
'Missing docstring in private __init__',
)
D171 = D1xx.create_error(
'D171',
'Missing docstring in inaccessible private class',
)
D173 = D1xx.create_error(
'D173',
'Missing docstring in inaccessible private function',
)

D2xx = ErrorRegistry.create_group('D2', 'Whitespace Issues')
D200 = D2xx.create_error(
Expand Down Expand Up @@ -423,11 +472,25 @@ def __getattr__(self, item: str) -> Any:


all_errors = set(ErrorRegistry.get_error_codes())

default_ignored = {
"D121",
"D123",
"D150",
"D151",
"D152",
"D153",
"D154",
"D155",
"D156",
"D157",
"D171",
"D173",
}

conventions = AttrDict(
{
'pep257': all_errors
- default_ignored
- {
'D203',
'D212',
Expand All @@ -449,6 +512,7 @@ def __getattr__(self, item: str) -> Any:
'D418',
},
'numpy': all_errors
- default_ignored
- {
'D107',
'D203',
Expand All @@ -461,6 +525,7 @@ def __getattr__(self, item: str) -> Any:
'D417',
},
'google': all_errors
- default_ignored
- {
'D203',
'D204',
Expand Down
Loading