Acho que uma solução só pode ser imprecisa por causa da falta de regras de digitação estática.
Não estou ciente de alguma ferramenta que verifica exceções, mas você pode criar sua própria ferramenta que atenda às suas necessidades (uma boa chance de brincar um pouco com a análise estática).
Como uma primeira tentativa, você pode escrever uma função que constrói um AST, encontra todos os Raisenós e, em seguida, tenta descobrir padrões comuns de levantamento de exceções (por exemplo, chamar um construtor diretamente)
Seja xo seguinte programa:
x = '''\
if f(x):
raise IOError(errno.ENOENT, 'not found')
else:
e = g(x)
raise e
'''
Crie o AST usando o compilerpacote:
tree = compiler.parse(x)
Em seguida, defina uma Raiseclasse de visitante:
class RaiseVisitor(object):
def __init__(self):
self.nodes = []
def visitRaise(self, n):
self.nodes.append(n)
E percorra os Raisenós de coleta de AST :
v = RaiseVisitor()
compiler.walk(tree, v)
>>> print v.nodes
[
Raise(
CallFunc(
Name('IOError'),
[Getattr(Name('errno'), 'ENOENT'), Const('not found')],
None, None),
None, None),
Raise(Name('e'), None, None),
]
Você pode continuar resolvendo símbolos usando tabelas de símbolos do compilador, analisando dependências de dados, etc. Ou você pode apenas deduzir que CallFunc(Name('IOError'), ...)"deve definitivamente significar aumento IOError", o que é bastante bom para resultados práticos rápidos :)
raiseusar strings, não apenasBaseExceptionsubclasses. Portanto, se você estiver chamando um código de biblioteca que está fora de seu controle, nem mesmoexcept Exceptioné suficiente, pois não detectará exceções de string. Como outros apontaram, você está latindo para a árvore errada aqui.