Skip to content

Commit

Permalink
fix: DOC503 catch namespaced exceptions (#168)
Browse files Browse the repository at this point in the history
Co-authored-by: jsh9 <25124332+jsh9@users.noreply.github.com>
  • Loading branch information
Amar1729 and jsh9 committed Sep 23, 2024
1 parent f758604 commit 5e941f5
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 9 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Change Log

## [0.5.8] - 2024-09-23

- Fixed

- Fixed the logic of handling exceptions namespaces (`a.b.c.MyException`)

- Full diff
- https://github.com/jsh9/pydoclint/compare/0.5.7...0.5.8

## [0.5.7] - 2024-09-02

- Added
Expand All @@ -8,8 +17,12 @@
function body match those in the "Raises" section of the docstring

- Changed

- Switched from tab to 4 spaces in baseline

- Full diff
- https://github.com/jsh9/pydoclint/compare/0.5.6...0.5.7

## [0.5.6] - 2024-07-17

- Fixed
Expand Down
10 changes: 9 additions & 1 deletion pydoclint/utils/generic.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ast
import copy
import re
from typing import List, Match, Optional, Tuple
from typing import List, Match, Optional, Tuple, Union

from pydoclint.utils.astTypes import ClassOrFunctionDef, FuncOrAsyncFuncDef
from pydoclint.utils.method_type import MethodType
Expand Down Expand Up @@ -233,3 +233,11 @@ def specialEqual(str1: str, str2: str) -> bool:
return False

return True


def getFullAttributeName(node: Union[ast.Attribute, ast.Name]) -> str:
"""Get the full name of a symbol like a.b.c.foo"""
if isinstance(node, ast.Name):
return node.id

return getFullAttributeName(node.value) + '.' + node.attr
27 changes: 22 additions & 5 deletions pydoclint/utils/return_yield_raise.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pydoclint.utils import walk
from pydoclint.utils.annotation import unparseAnnotation
from pydoclint.utils.astTypes import BlockType, FuncOrAsyncFuncDef
from pydoclint.utils.generic import stringStartsWith
from pydoclint.utils.generic import getFullAttributeName, stringStartsWith

ReturnType = Type[ast.Return]
ExprType = Type[ast.Expr]
Expand Down Expand Up @@ -132,7 +132,17 @@ def _getRaisedExceptions(
):
for subnode, _ in walk.walk_dfs(child):
if isinstance(subnode, ast.Name):
yield subnode.id
if isinstance(child.exc, ast.Attribute):
# case: looks like m.n.exception
yield getFullAttributeName(child.exc)
elif isinstance(child.exc, ast.Call) and isinstance(
child.exc.func, ast.Attribute
):
# case: looks like m.n.exception()
yield getFullAttributeName(child.exc.func)
else:
yield subnode.id

break
else:
# if "raise" statement was alone, it must be inside an "except"
Expand All @@ -148,10 +158,17 @@ def _extractExceptionsFromExcept(
if isinstance(node.type, ast.Name):
yield node.type.id

if isinstance(node.type, ast.Attribute):
# case: looks like m.n.exception
yield getFullAttributeName(node.type)

if isinstance(node.type, ast.Tuple):
for child, _ in walk.walk(node.type):
if isinstance(child, ast.Name):
yield child.id
for elt in node.type.elts:
if isinstance(elt, ast.Attribute):
# case: looks like m.n.exception
yield getFullAttributeName(elt)
elif isinstance(elt, ast.Name):
yield elt.id


def _hasExpectedStatements(
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = pydoclint
version = 0.5.7
version = 0.5.8
description = A Python docstring linter that checks arguments, returns, yields, and raises sections
long_description = file: README.md
long_description_content_type = text/markdown
Expand Down
23 changes: 23 additions & 0 deletions tests/data/google/raises/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,26 @@ def func13(self) -> None:
ValueError: typo!
"""
raise ValueError

def func14(self) -> None:
"""
Should fail, expects `exceptions.CustomError`.
Raises:
CustomError: every time.
"""
exceptions = object()
exceptions.CustomError = CustomError
raise exceptions.CustomError()

def func15(self) -> None:
"""
Should fail, expects `exceptions.m.CustomError`.
Raises:
CustomError: every time.
"""
exceptions = object()
exceptions.m = object()
exceptions.m.CustomError = CustomError
raise exceptions.m.CustomError
27 changes: 27 additions & 0 deletions tests/data/numpy/raises/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,30 @@ def func13(self) -> None:
typo!
"""
raise ValueError

def func14(self) -> None:
"""
Should fail, expects `exceptions.CustomError`.
Raises
------
CustomError
every time.
"""
exceptions = object()
exceptions.CustomError = CustomError
raise exceptions.CustomError()

def func15(self) -> None:
"""
Should fail, expects `exceptions.m.CustomError`.
Raises
------
CustomError
every time.
"""
exceptions = object()
exceptions.m = object()
exceptions.m.CustomError = CustomError
raise exceptions.m.CustomError
21 changes: 21 additions & 0 deletions tests/data/sphinx/raises/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,24 @@ def func13(self) -> None:
:raises ValueError: typo!
"""
raise ValueError

def func14(self) -> None:
"""
Should fail, expects `exceptions.CustomError`.
:raises CustomError: every time.
"""
exceptions = object()
exceptions.CustomError = CustomError
raise exceptions.CustomError()

def func15(self) -> None:
"""
Should fail, expects `exceptions.m.CustomError`.
:raises CustomError: every time.
"""
exceptions = object()
exceptions.m = object()
exceptions.m.CustomError = CustomError
raise exceptions.m.CustomError
8 changes: 8 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,14 @@ def testRaises(style: str, skipRaisesCheck: bool) -> None:
'docstring do not match those in the function body Raises values in the '
"docstring: ['ValueError', 'ValueError']. Raised exceptions in the body: "
"['ValueError'].",
'DOC503: Method `B.func14` exceptions in the "Raises" section in the '
'docstring do not match those in the function body Raises values in the '
"docstring: ['CustomError']. Raised exceptions in the body: "
"['exceptions.CustomError'].",
'DOC503: Method `B.func15` exceptions in the "Raises" section in the '
'docstring do not match those in the function body Raises values in the '
"docstring: ['CustomError']. Raised exceptions in the body: "
"['exceptions.m.CustomError'].",
]
expected1 = []
expected = expected1 if skipRaisesCheck else expected0
Expand Down
38 changes: 36 additions & 2 deletions tests/utils/test_returns_yields_raise.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ def func7(arg0):
def func8(d):
try:
d[0][0]
except (KeyError, TypeError):
except (KeyError, TypeError, m.ValueError):
raise
finally:
pass
Expand Down Expand Up @@ -416,6 +416,30 @@ def func12(a):
if a < 3:
raise Error3
def func13(a):
# ensure we get `Exception`, `Exception()`, and `Exception('something')`
if a < 1:
raise ValueError
elif a < 2:
raise TypeError()
else:
raise IOError('IO Error!')
def func14(a):
# check that we properly identify submodule exceptions.
if a < 1:
raise m.ValueError
elif a < 2:
raise m.n.ValueError()
else:
raise a.b.c.ValueError(msg="some msg")
def func15():
try:
x = 1
except other.Exception:
raise
"""


Expand All @@ -439,6 +463,9 @@ def testHasRaiseStatements() -> None:
(75, 0, 'func10'): True,
(83, 0, 'func11'): True,
(100, 0, 'func12'): True,
(117, 0, 'func13'): True,
(126, 0, 'func14'): True,
(135, 0, 'func15'): True,
}

assert result == expected
Expand All @@ -464,11 +491,18 @@ def testWhichRaiseStatements() -> None:
'RuntimeError',
'TypeError',
],
(54, 0, 'func8'): ['KeyError', 'TypeError'],
(54, 0, 'func8'): ['KeyError', 'TypeError', 'm.ValueError'],
(62, 0, 'func9'): ['AssertionError', 'IndexError'],
(75, 0, 'func10'): ['GError'],
(83, 0, 'func11'): ['ValueError'],
(100, 0, 'func12'): ['Error1', 'Error2', 'Error3'],
(117, 0, 'func13'): ['IOError', 'TypeError', 'ValueError'],
(126, 0, 'func14'): [
'a.b.c.ValueError',
'm.ValueError',
'm.n.ValueError',
],
(135, 0, 'func15'): ['other.Exception'],
}

assert result == expected

0 comments on commit 5e941f5

Please sign in to comment.