Is there a programmatic way to get a list of all exceptions a function could raise?
I know for example that os.makedirs(path[, mode])
can raise PermissionError
(and maybe others), but the documentation only mentions OSError
. (This is just an example - maybe even a bad one; I am not especially interested in this function - more in the problem in general).
Is there a programmatic way to find all the possible exceptions when they are not/poorly documented? This may be especially useful in 3rd-party libraries and libraries that do not ship with Python source code.
The solution presented in "Python: How can I know which exceptions might be thrown from a method call" does not work in Python 3; there is no compiler
package.
Another way to catch all Python exceptions when it occurs during runtime is to use the raise keyword. It is a manual process wherein you can optionally pass values to the exception to clarify the reason why it was raised. if x <= 0: raise ValueError(“It is not a positive number!”)
As a Python developer you can choose to throw an exception if a condition occurs. To throw (or raise) an exception, use the raise keyword.
Try and Except Statement – Catching all Exceptions Try and except statements are used to catch and handle exceptions in Python. Statements that can raise exceptions are kept inside the try clause and the statements that handle the exception are written inside except clause.
Python raise Keyword is used to raise exceptions or errors. The raise keyword raises an error and stops the control flow of the program. It is used to bring up the current exception in an exception handler so that it can be handled further up the call stack.
You can't get reliable results for some (if not most) functions. Some examples:
functions that execute arbitrary code (e.g. exec(')(rorrEeulaV esiar'[::-1])
raises ValueError
)
functions that aren't written in Python
functions that call other functions that can propagate errors to the caller
functions re-raising active exceptions in the except:
block
Unfortunately, this list is incomplete.
E.g. os.makedirs
is written in Python and you can see its source:
...
try:
mkdir(name, mode)
except OSError as e:
if not exist_ok or e.errno != errno.EEXIST or not path.isdir(name):
raise
Bare raise
re-raises the last active exception (OSError
or one of its subclasses). Here's the class hierarchy for OSError
:
+-- OSError
| +-- BlockingIOError
| +-- ChildProcessError
| +-- ConnectionError
| | +-- BrokenPipeError
| | +-- ConnectionAbortedError
| | +-- ConnectionRefusedError
| | +-- ConnectionResetError
| +-- FileExistsError
| +-- FileNotFoundError
| +-- InterruptedError
| +-- IsADirectoryError
| +-- NotADirectoryError
| +-- PermissionError
| +-- ProcessLookupError
| +-- TimeoutError
To get the exact exception types you'll need to look into mkdir
, functions it calls, functions those functions call etc.
So, getting possible exceptions without running the function is very hard and you really should not do it.
However for simple cases like
raise Exception # without arguments
raise Exception('abc') # with arguments
a combination of ast
module functionality and inspect.getclosurevars
(to get exception classes, was introduced in Python 3.3) can produce quite accurate results:
from inspect import getclosurevars, getsource
from collections import ChainMap
from textwrap import dedent
import ast, os
class MyException(Exception):
pass
def g():
raise Exception
class A():
def method():
raise OSError
def f(x):
int()
A.method()
os.makedirs()
g()
raise MyException
raise ValueError('argument')
def get_exceptions(func, ids=set()):
try:
vars = ChainMap(*getclosurevars(func)[:3])
source = dedent(getsource(func))
except TypeError:
return
class _visitor(ast.NodeTransformer):
def __init__(self):
self.nodes = []
self.other = []
def visit_Raise(self, n):
self.nodes.append(n.exc)
def visit_Expr(self, n):
if not isinstance(n.value, ast.Call):
return
c, ob = n.value.func, None
if isinstance(c, ast.Attribute):
parts = []
while getattr(c, 'value', None):
parts.append(c.attr)
c = c.value
if c.id in vars:
ob = vars[c.id]
for name in reversed(parts):
ob = getattr(ob, name)
elif isinstance(c, ast.Name):
if c.id in vars:
ob = vars[c.id]
if ob is not None and id(ob) not in ids:
self.other.append(ob)
ids.add(id(ob))
v = _visitor()
v.visit(ast.parse(source))
for n in v.nodes:
if isinstance(n, (ast.Call, ast.Name)):
name = n.id if isinstance(n, ast.Name) else n.func.id
if name in vars:
yield vars[name]
for o in v.other:
yield from get_exceptions(o)
for e in get_exceptions(f):
print(e)
prints
<class '__main__.MyException'>
<class 'ValueError'>
<class 'OSError'>
<class 'Exception'>
Keep in mind that this code only works for functions written in Python.
As said in the topic Python: How can I know which exceptions might be thrown from a method call, you can get the Abstract Syntax Tree and search for raised exceptions.
import ast
def find_raise(body):
raises = []
for ast_ in body:
if isinstance(ast_, ast.Raise):
raises.append(ast_)
if hasattr(ast_, 'body'):
raises += find_raise(ast_.body)
return list(set(raises))
test = '''
def f(arg):
raise OSError(arg)
'''
raises = find_raise(ast.parse(test).body)
print [i.type.func.id for i in raises] # print ['OSError']
This method works for every piece of code that you have written.
You cannot parse built-in function like os.makedirs
.
Two alternatives:
For all native C methods, you are stuck with the documentation and should trust it. When os.makedirs
says it only returns OSError
, it is true, since PermissionError
and FileExistError
exceptions are subclasses of OSError
.
To find Errors programmatically for built-in you can use this example:
>>> import re
>>> re.findall(r'\w+Error', open.__doc__)
['IOError', 'FileExistsError', 'ValueError']
>>> re.findall(r'\w+Error', os.makedirs.__doc__)
['OSError']
It catches all exceptions with a name ending with 'Error', it surely can be extended to find all standard exceptions.
I needed to do something similar and found this post. I decided I would write a little library to help.
Say hello to Deep-AST. It's very early alpha but it is pip installable. It has all of the limitations mentioned in this post and some additional ones but its already off to a really good start.
For example when parsing HTTPConnection.getresponse()
from http.client
it parses 24489
AST Nodes. It finds 181
total raised Exceptions (this includes duplicates) and 8
unique Exceptions were raised. A working code example.
The biggest flaw is this it currently does work with a bare raise
:
def foo():
try:
bar()
except TypeError:
raise
But I think this will be easy to solve and I plan on fixing it.
The library can handle more than just figuring out exceptions, what about listing all Parent classes? It can handle that too!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With