Raise a string variable as an exception

Viewed 152

I'm using a library that will return something like the following when calling one of their functions

result = getResult(foo)
print(result)

The result is a dict

{'errorMessage': "name 'foo' is not defined", 'errorType': 'NameError'}

I want to raise a NameError exception, but the following doesn't work

raise result['errorType'](result['errorMessage'])

It gives a new error

TypeError: 'str' object is not callable

How can I raise an exception who's type is defined from a string variable?

3 Answers

The value of result["errorType"] is a string that names an error class, not the error class itself.

If the error type will always be a built-in type, you can look it up in __builtins__.

raise getattr(__builtins__, result['errorType'])(result['errorMessage'])

This should cover most of the bases and allows for non-builtin exceptions. Basically, get the exception class name and look up the corresponding class object up from a pre-computed dictionary of classnames to Exception subclasses.

from django.core.exceptions import ImproperlyConfigured

class MyCustomExc(Exception):
    pass

def raiser(di : dict):

    di = di.copy()
    cls = None

    def get_excs(di : dict):
        di_exc = {k:v for k,v in di.items() if type(v) == type and issubclass(v, Exception)}    
        return di_exc

    di_exc = get_excs(vars(__builtins__))

    di_exc.update(**get_excs(globals()))

    dflt_exc = RuntimeError

    errorType = di.pop("errorType", dflt_exc.__name__)


    cls = di_exc.get(errorType)

    if not cls:
        di = di.copy()
        di["unknown_exception"] = errorType
        cls = dflt_exc

    raise cls(di)

    #the alternative version which does not raise
    return cls, di





inputs = [
    {'errorMessage': "name 'foo' is not defined", 'errorType': 'NameError'},
    {'errorMessage': "custom stuff", 'errorType': 'MyCustomExc'},
    {'errorMessage': "Django stuff", 'errorType': 'ImproperlyConfigured'},
    {'errorMessage': "Django stuff", 'errorType': 'UnknownExc'},
    ]

for inp in inputs:
    try:
        dummy = raiser(inp)
        print(dummy)
        
    except (Exception,) as got:
        # breakpoint()
        msg = f"{str(inp):60.60} => {repr(got)}"
        print(msg)

output:

{'errorMessage': "name 'foo' is not defined", 'errorType': ' => NameError({'errorMessage': "name 'foo' is not defined"})
{'errorMessage': 'custom stuff', 'errorType': 'MyCustomExc'} => MyCustomExc({'errorMessage': 'custom stuff'})
{'errorMessage': 'Django stuff', 'errorType': 'ImproperlyCon => ImproperlyConfigured({'errorMessage': 'Django stuff'})
{'errorMessage': 'Django stuff', 'errorType': 'UnknownExc'}  => RuntimeError({'errorMessage': 'Django stuff', 'unknown_exception': 'UnknownExc'})

One thing I would probably do however is to just get the bits you need from the function rather than raise-ing in it (that way the stacktrace shows the actual location of your call, not raiser). I would also pre-compute the exceptions dictionary from builtins and globals and pass it into raiser rather than re-computing each time.

result = getResult(foo)
cls_exc, exc_data = raiser(result)
if cls_exc:
  raise cls_exc(exc_data)

The conditional allows handling when you don't get an error.

You could use eval but carefully:

>>> raise eval(f"{result['errorType']}(\"{result['errorMessage']}\")")

NameError: name 'foo' is not defined
Related