From abf6c139158e18e4329f9dcf034adbb32d7b8cbf Mon Sep 17 00:00:00 2001 From: Sambhav Kothari Date: Sun, 8 Jan 2023 19:50:37 +0000 Subject: [PATCH 1/4] Revert "fix: do not pass file names with pre-commit" (#624) --- .pre-commit-hooks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 3009bf5..e8ae25c 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -4,4 +4,4 @@ entry: pydocstyle language: python types: [python] - pass_file_names: false + pass_filenames: false From d395b01a82798584206b7695fecf6e8ca44dedac Mon Sep 17 00:00:00 2001 From: Sambhav Kothari Date: Sun, 8 Jan 2023 19:51:34 +0000 Subject: [PATCH 2/4] Revert changes to pre-commit hooks --- .pre-commit-hooks.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index e8ae25c..cb55ee4 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -4,4 +4,3 @@ entry: pydocstyle language: python types: [python] - pass_filenames: false From a9a73f90965f41f961ab6e8b11096cc3f7377255 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Tue, 17 Jan 2023 13:52:35 -0600 Subject: [PATCH 3/4] Add `ignore-self-only-init` option (#560) * add `ignore-self-only-init` option * docs * callable_args * fix * Update release_notes.rst * Update release_notes.rst Co-authored-by: Sambhav Kothari --- docs/release_notes.rst | 7 +++++++ docs/snippets/config.rst | 1 + src/pydocstyle/checker.py | 33 ++++++++++++++++++++++----------- src/pydocstyle/cli.py | 2 ++ src/pydocstyle/config.py | 20 ++++++++++++++++++-- src/pydocstyle/parser.py | 20 +++++++++++++++++++- src/tests/test_decorators.py | 4 ++-- src/tests/test_integration.py | 16 +++++++++++++++- 8 files changed, 86 insertions(+), 17 deletions(-) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index bb4595e..b64346b 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -4,6 +4,13 @@ Release Notes **pydocstyle** version numbers follow the `Semantic Versioning `_ specification. +6.3.0 - January 17th, 2023 +-------------------------- + +New Features + +* Add `ignore-self-only-init` config (#560). + 6.2.3 - January 8th, 2023 --------------------------- diff --git a/docs/snippets/config.rst b/docs/snippets/config.rst index 3c7b5ee..f54d678 100644 --- a/docs/snippets/config.rst +++ b/docs/snippets/config.rst @@ -45,6 +45,7 @@ Available options are: * ``match_dir`` * ``ignore_decorators`` * ``property_decorators`` +* ``ignore_self_only_init`` See the :ref:`cli_usage` section for more information. diff --git a/src/pydocstyle/checker.py b/src/pydocstyle/checker.py index 5cefc76..9b6376b 100644 --- a/src/pydocstyle/checker.py +++ b/src/pydocstyle/checker.py @@ -136,10 +136,12 @@ def check_source( ignore_decorators=None, property_decorators=None, ignore_inline_noqa=False, + ignore_self_only_init=False, ): self.property_decorators = ( {} if property_decorators is None else property_decorators ) + self.ignore_self_only_init = ignore_self_only_init module = parse(StringIO(source), filename) for definition in module: for this_check in self.checks: @@ -199,22 +201,27 @@ def check_docstring_missing(self, definition, docstring): with a single underscore. """ + + def method_violation(): + if definition.is_magic: + return violations.D105() + if definition.is_init: + if ( + self.ignore_self_only_init + and len(definition.param_names) == 1 + ): + return None + return violations.D107() + if not definition.is_overload: + return violations.D102() + return None + if not docstring and definition.is_public: codes = { Module: violations.D100, Class: violations.D101, NestedClass: violations.D106, - Method: lambda: violations.D105() - if definition.is_magic - else ( - violations.D107() - if definition.is_init - else ( - violations.D102() - if not definition.is_overload - else None - ) - ), + Method: method_violation, NestedFunction: violations.D103, Function: ( lambda: violations.D103() @@ -1102,6 +1109,7 @@ def check( ignore_decorators=None, property_decorators=None, ignore_inline_noqa=False, + ignore_self_only_init=False, ): """Generate docstring errors that exist in `filenames` iterable. @@ -1121,6 +1129,8 @@ def check( `ignore_inline_noqa` controls if `# noqa` comments are respected or not. + `ignore_self_only_init` controls if D107 is reported on __init__ only containing `self`. + Examples --------- >>> check(['pydocstyle.py']) @@ -1158,6 +1168,7 @@ def check( ignore_decorators, property_decorators, ignore_inline_noqa, + ignore_self_only_init, ): code = getattr(error, 'code', None) if code in checked_codes: diff --git a/src/pydocstyle/cli.py b/src/pydocstyle/cli.py index 241894f..6f9d6d8 100644 --- a/src/pydocstyle/cli.py +++ b/src/pydocstyle/cli.py @@ -43,6 +43,7 @@ def run_pydocstyle(): checked_codes, ignore_decorators, property_decorators, + ignore_self_only_init, ) in conf.get_files_to_check(): errors.extend( check( @@ -50,6 +51,7 @@ def run_pydocstyle(): select=checked_codes, ignore_decorators=ignore_decorators, property_decorators=property_decorators, + ignore_self_only_init=ignore_self_only_init, ) ) except IllegalConfiguration as error: diff --git a/src/pydocstyle/config.py b/src/pydocstyle/config.py index c05f7dc..34fb150 100644 --- a/src/pydocstyle/config.py +++ b/src/pydocstyle/config.py @@ -185,6 +185,7 @@ class ConfigurationParser: 'match', 'match-dir', 'ignore-decorators', + 'ignore-self-only-init', ) BASE_ERROR_SELECTION_OPTIONS = ('ignore', 'select', 'convention') @@ -195,6 +196,7 @@ class ConfigurationParser: "property,cached_property,functools.cached_property" ) DEFAULT_CONVENTION = conventions.pep257 + DEFAULT_IGNORE_SELF_ONLY_INIT = False PROJECT_CONFIG_FILES = ( 'setup.cfg', @@ -301,6 +303,7 @@ def _get_property_decorators(conf): list(config.checked_codes), ignore_decorators, property_decorators, + config.ignore_self_only_init, ) else: config = self._get_config(os.path.abspath(name)) @@ -313,6 +316,7 @@ def _get_property_decorators(conf): list(config.checked_codes), ignore_decorators, property_decorators, + config.ignore_self_only_init, ) # --------------------------- Private Methods ----------------------------- @@ -514,9 +518,13 @@ def _merge_configuration(self, parent_config, child_options): 'match_dir', 'ignore_decorators', 'property_decorators', + 'ignore_self_only_init', ): - kwargs[key] = getattr(child_options, key) or getattr( - parent_config, key + child_value = getattr(child_options, key) + kwargs[key] = ( + child_value + if child_value is not None + else getattr(parent_config, key) ) return CheckConfiguration(**kwargs) @@ -553,6 +561,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_self_only_init': "IGNORE_SELF_ONLY_INIT", } for key, default in defaults.items(): kwargs[key] = ( @@ -849,6 +858,12 @@ def _create_option_parser(cls): 'basic list previously set by --select, --ignore ' 'or --convention.', ) + add_check( + '--ignore-self-only-init', + default=None, + action='store_true', + help='ignore __init__ methods which only have a self param.', + ) parser.add_option_group(check_group) @@ -916,6 +931,7 @@ def _create_option_parser(cls): 'match_dir', 'ignore_decorators', 'property_decorators', + 'ignore_self_only_init', ), ) diff --git a/src/pydocstyle/parser.py b/src/pydocstyle/parser.py index cc768bb..95bd0a1 100644 --- a/src/pydocstyle/parser.py +++ b/src/pydocstyle/parser.py @@ -89,6 +89,7 @@ class Definition(Value): 'decorators', 'docstring', 'children', + 'callable_args', 'parent', 'skipped_error_codes', ) # type: Tuple[str, ...] @@ -235,6 +236,11 @@ def is_test(self): """ return self.name.startswith('test') or self.name == 'runTest' + @property + def param_names(self): + """Return the parameter names.""" + return self.callable_args + class NestedFunction(Function): """A Python source code nested function.""" @@ -666,8 +672,10 @@ def parse_definition(self, class_): name = self.current.value self.log.debug("parsing %s '%s'", class_.__name__, name) self.stream.move() + callable_args = [] if self.current.kind == tk.OP and self.current.value == '(': parenthesis_level = 0 + in_default_arg = False while True: if self.current.kind == tk.OP: if self.current.value == '(': @@ -676,6 +684,15 @@ def parse_definition(self, class_): parenthesis_level -= 1 if parenthesis_level == 0: break + elif self.current.value == ',': + in_default_arg = False + elif ( + parenthesis_level == 1 + and self.current.kind == tk.NAME + and not in_default_arg + ): + callable_args.append(self.current.value) + in_default_arg = True self.stream.move() if self.current.kind != tk.OP or self.current.value != ':': self.leapfrog(tk.OP, value=":") @@ -712,7 +729,8 @@ def parse_definition(self, class_): decorators, docstring, children, - None, + callable_args, + None, # parent skipped_error_codes, ) for child in definition.children: diff --git a/src/tests/test_decorators.py b/src/tests/test_decorators.py index 6fd050b..97ac7a5 100644 --- a/src/tests/test_decorators.py +++ b/src/tests/test_decorators.py @@ -182,10 +182,10 @@ def %s(self): dunder_all, None, None, '') cls = parser.Class('ClassName', source, 0, 1, [], - 'Docstring for class', children, module, '') + 'Docstring for class', children, [], module, '') return parser.Method(name, source, 0, 1, [], - 'Docstring for method', children, cls, '') + 'Docstring for method', children, [], cls, '') def test_is_public_normal(self): """Test that methods are normally public, even if decorated.""" diff --git a/src/tests/test_integration.py b/src/tests/test_integration.py index 2f2f57c..b812fd6 100644 --- a/src/tests/test_integration.py +++ b/src/tests/test_integration.py @@ -1551,6 +1551,20 @@ def test_comment_with_noqa_plus_docstring_file(env): assert code == 0 +def test_ignore_self_only_init(env): + """Test that ignore_self_only_init works ignores __init__ with only self.""" + with env.open('example.py', 'wt') as example: + example.write(textwrap.dedent("""\ + class Foo: + def __init__(self): + pass + """)) + + env.write_config(ignore_self_only_init=True, select="D107") + out, err, code = env.invoke() + assert '' == out + assert code == 0 + def test_match_considers_basenames_for_path_args(env): """Test that `match` option only considers basenames for path arguments. @@ -1570,4 +1584,4 @@ def test_match_considers_basenames_for_path_args(env): # env.invoke calls pydocstyle with full path to test_a.py out, _, code = env.invoke(target='test_a.py') assert '' == out - assert code == 0 + assert code == 0 \ No newline at end of file From 07f6707e2c5612960347f7c00125620457f490a7 Mon Sep 17 00:00:00 2001 From: Sambhav Kothari Date: Tue, 17 Jan 2023 20:27:44 +0000 Subject: [PATCH 4/4] Update release_notes.rst --- docs/release_notes.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index b64346b..5ab5e11 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -11,6 +11,11 @@ New Features * Add `ignore-self-only-init` config (#560). +Bug Fixes + +* Revert - Obey match rules in pre-commit usage (#610). + + 6.2.3 - January 8th, 2023 ---------------------------