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

Commit

Permalink
Add the ability to skip checking some functions.
Browse files Browse the repository at this point in the history
There are times when you might not want to check a function's
docstring.  Specifically, we frequently want to ignore
test files.  There is already an exemption that causes test
functions to be considered non-public.  This extends on
that idea and makes it configurable so a user can cause
a test function to be allowed to have no docstring.
  • Loading branch information
mgilson-argo committed Apr 4, 2022
1 parent 50894da commit 140300c
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 8 deletions.
1 change: 1 addition & 0 deletions docs/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ New Features
* Add support for `property_decorators` config to ignore D401.
* Add support for Python 3.10 (#554).
* Replace D10X errors with D419 if docstring exists but is empty (#559).
* Add `ignore-functions` option to allow some functions to skip docstring checking (#587).

Bug Fixes

Expand Down
23 changes: 18 additions & 5 deletions src/pydocstyle/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def check_source(
ignore_decorators=None,
property_decorators=None,
ignore_inline_noqa=False,
ignore_functions=None,
):
self.property_decorators = (
{} if property_decorators is None else property_decorators
Expand All @@ -150,9 +151,19 @@ def check_source(
len(ignore_decorators.findall(dec.name)) > 0
for dec in definition.decorators
)
# Only skip checking the docstring if absent.
# If the docstring is present, then check it as normal
name_skip = (
ignore_functions is not None
and not definition.docstring
and bool(ignore_functions.findall(definition.name))
)

if (
ignore_inline_noqa or not skipping_all
) and not decorator_skip:
(ignore_inline_noqa or not skipping_all)
and not decorator_skip
and not name_skip
):
error = this_check(
self, definition, definition.docstring
)
Expand Down Expand Up @@ -1088,6 +1099,7 @@ def check(
ignore_decorators=None,
property_decorators=None,
ignore_inline_noqa=False,
ignore_functions=None,
):
"""Generate docstring errors that exist in `filenames` iterable.
Expand Down Expand Up @@ -1141,9 +1153,10 @@ def check(
for error in ConventionChecker().check_source(
source,
filename,
ignore_decorators,
property_decorators,
ignore_inline_noqa,
ignore_decorators=ignore_decorators,
property_decorators=property_decorators,
ignore_inline_noqa=ignore_inline_noqa,
ignore_functions=ignore_functions,
):
code = getattr(error, 'code', None)
if code in checked_codes:
Expand Down
2 changes: 2 additions & 0 deletions src/pydocstyle/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ def run_pydocstyle():
checked_codes,
ignore_decorators,
property_decorators,
ignore_functions,
) in conf.get_files_to_check():
errors.extend(
check(
(filename,),
select=checked_codes,
ignore_decorators=ignore_decorators,
property_decorators=property_decorators,
ignore_functions=ignore_functions,
)
)
except IllegalConfiguration as error:
Expand Down
34 changes: 31 additions & 3 deletions src/pydocstyle/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ class ConfigurationParser:
'match',
'match-dir',
'ignore-decorators',
'ignore-functions',
)
BASE_ERROR_SELECTION_OPTIONS = ('ignore', 'select', 'convention')

Expand All @@ -189,6 +190,7 @@ class ConfigurationParser:
DEFAULT_PROPERTY_DECORATORS = (
"property,cached_property,functools.cached_property"
)
DEFAULT_IGNORE_FUNCTIONS_RE = ''
DEFAULT_CONVENTION = conventions.pep257

PROJECT_CONFIG_FILES = (
Expand Down Expand Up @@ -263,6 +265,10 @@ def _get_matches(conf):
match_dir_func = re(conf.match_dir + '$').match
return match_func, match_dir_func

def _get_ignore_functions(conf):
"""Return the `ignore_functions` as None or regex."""
return re(conf.ignore_functions) if conf.ignore_functions else None

def _get_ignore_decorators(conf):
"""Return the `ignore_decorators` as None or regex."""
return (
Expand All @@ -284,6 +290,7 @@ def _get_property_decorators(conf):
match, match_dir = _get_matches(config)
ignore_decorators = _get_ignore_decorators(config)
property_decorators = _get_property_decorators(config)
ignore_functions = _get_ignore_functions(config)

# Skip any dirs that do not match match_dir
dirs[:] = [d for d in dirs if match_dir(d)]
Expand All @@ -296,18 +303,22 @@ def _get_property_decorators(conf):
list(config.checked_codes),
ignore_decorators,
property_decorators,
ignore_functions,
)
else:
config = self._get_config(os.path.abspath(name))
match, _ = _get_matches(config)
ignore_decorators = _get_ignore_decorators(config)
property_decorators = _get_property_decorators(config)
ignore_functions = _get_ignore_functions(config)

if match(os.path.basename(name)):
yield (
name,
list(config.checked_codes),
ignore_decorators,
property_decorators,
ignore_functions,
)

# --------------------------- Private Methods -----------------------------
Expand Down Expand Up @@ -509,6 +520,7 @@ def _merge_configuration(self, parent_config, child_options):
'match_dir',
'ignore_decorators',
'property_decorators',
'ignore_functions',
):
kwargs[key] = getattr(child_options, key) or getattr(
parent_config, key
Expand Down Expand Up @@ -548,6 +560,7 @@ def _create_check_config(cls, options, use_defaults=True):
'match_dir': "MATCH_DIR_RE",
'ignore_decorators': "IGNORE_DECORATORS_RE",
'property_decorators': "PROPERTY_DECORATORS",
'ignore_functions': "IGNORE_FUNCTIONS_RE",
}
for key, default in defaults.items():
kwargs[key] = (
Expand Down Expand Up @@ -780,9 +793,10 @@ def _create_option_parser(cls):
OptionGroup(
parser,
'Note',
'When using --match, --match-dir or --ignore-decorators consider '
'whether you should use a single quote (\') or a double quote ("), '
'depending on your OS, Shell, etc.',
'When using --match, --match-dir, --ignore-decorators or '
'--ignore-functions consider whether you should use a single '
'quote (\') or a double quote ("), depending on your OS, '
'Shell, etc.',
)
)

Expand Down Expand Up @@ -899,6 +913,19 @@ def _create_option_parser(cls):
),
)

# Function selection
option(
'--ignore-functions',
metavar='<functions>',
default=None,
help=(
"ignore any functions or methods whose names fit "
"the <functions> regular expression; default is "
"--ignore-functions='{}' which does not ignore any "
"functions.".format(cls.DEFAULT_IGNORE_DECORATORS_RE)
),
)

return parser


Expand All @@ -911,6 +938,7 @@ def _create_option_parser(cls):
'match_dir',
'ignore_decorators',
'property_decorators',
'ignore_functions',
),
)

Expand Down
18 changes: 18 additions & 0 deletions src/tests/test_cases/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,21 @@ class inner():
def func_with_weird_backslash():
"""Test a function with a weird backslash.\
"""


def ignored_function():
# This function does not need a docstring
# because the name matches "ignored_function.*"
pass


def ignored_function_with_suffix():
# This function does not need a docstring
# because the name matches "ignored_function.*"
pass


@expect('D103: Missing docstring in public function')
def missing_docstring_function():
# This function should have a docstring.
pass
1 change: 1 addition & 0 deletions src/tests/test_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def test_complex_file(test_case):
select=set(ErrorRegistry.get_error_codes()),
ignore_decorators=re.compile('wraps|ignored_decorator'),
property_decorators=DEFAULT_PROPERTY_DECORATORS,
ignore_functions=re.compile("ignored_function.*"),
)
)
for error in results:
Expand Down

0 comments on commit 140300c

Please sign in to comment.