Pass variable to an exception? - python

I am trying to learn Python and I want to know if it is possible to pass a variable to an Exception? This is the code I have:
try:
staffId = int(row['staffId'])
openingSalary = int(row['initialSalary'])
monthsWorked = float(row['monthsWorked'])
except CutomException:
pass
class CustomException(ValueError): # raised if data conversion fails
def __init__(self):
print("There was a problem converting data")
I want to pass staffId to the exception so that I can print something like:
print("There was a problem converting data for staff Id: ", staffId)
I tried this with no success: How to pass a variable to an exception when raised and retrieve it when excepted?

The caller of the exception, e.g. the one that raise exception will have to pass an argument to the constructor.
class CustomException(ValueError): # raised if data conversion fails
def __init__(self, message):
self.message = message;
print("There was a problem converting data")
try:
try:
staffId = int(row['staffId'])
openingSalary = int(row['initialSalary'])
monthsWorked = float(row['monthsWorked'])
except ValueError as e:
raise CustomException(e);
except CustomException:
pass

The custom exception will need to be raise'd conditionally by the try block to include the staffId variable. As an example, when the staffId is a str and not an int.
try:
# conditionalize a scenario where you'd want to raise an error
# (e.g. the variable is a string)
if type(staffId) is str:
raise CustomException(staffId)
else:
staffId = int(row['staffId'])
openingSalary = int(row['initialSalary'])
monthsWorked = float(row['monthsWorked'])
except CutomException:
pass
class CustomException(ValueError): # raised if data conversion fails
def __init__(self, id):
print("There was a problem converting data %s" % id)

I think you should handle the exception in the except block and not inside the exception class.
try:
raise CustomException(foo)
except CutomException as e:
print(e.args)
handle_exception()
class CustomException(Exception):
def __init__(self, foo):
super().__init__(foo, bar)

Related

Python OOP only initialise object if function arguments are met

How would I only allow an object to be created if the arguments are met? I tried using error handling but the object still gets created regardless of the value inputted.
class Age(object):
def __init__(self, age):
try:
if age > 5:
self.testAge = age
else:
raise ValueError('Number is too low')
except ValueError as exp:
print(f"Error: {exp}")
def __str__(self):
return f"Testing"
test = Age(3)
print(test)
In the example, I only want an object to be created if the Age is greater than 5. I use 3 as a test, the error is handled but the object is still created.
When you raise the exception, you except it again
You can either remove the try/except or re-raise the Exception
try:
function_which_may_raise()
except Exception as ex:
print("something went wrong")
raise ex # re-raise Exception

Jump to a Top level function if error in sub level function and return flag python

I have a function "main_func" , from which i am calling another function and so on.
class Error(Exception):
"""Base class for other exceptions"""
pass
def main_func():
return sub_func()
def sub_func():
return sub_sub_func()
def sub_sub_func():
return sub_sub_sub_func()
def sub_sub_sub_func():
try:
x = len(10)
res = 'b'
except:
raise Error
return res
main_func()
If you see in sub_sub_sub_func() i have added a line x = len(10) which will cause exception.
What i want is, if this happens, i should directly jump to main_func() and return a flag(str) as 'fail'
I looked into defining custom exceptions , but it didn't helped me.
I want to return after i raise.
len(10) will raise a TypeError you can catch this specific exception in your main_func and do the thing that needs to happen then.
Please note that you need to create an instance of your error class when raising. Error()
class Error(Exception):
"""Base class for other exceptions"""
pass
def main_func():
try:
return sub_func()
except (Error as e):
# The raised error will be cought here.
# Do the stuff that needs to happen here.
return 'fail'
def sub_func():
return sub_sub_func()
def sub_sub_func():
return sub_sub_sub_func()
def sub_sub_sub_func():
try:
x = len(10) # Will raise a `TypeError`
res = 'b'
except:
# `TypeError` that is raised will get here
raise Error()
return res
main_func()
Note: Your custom Error hides a lot of information that can come in handy later. What happened what raised this error. Best is to put the original TypeError as an inner exception to Error.
try:
x = len(10)
except Throwable as e:
raise Error(e)
In theory in your code a potential out of memory exception will be converted to your Error without knowing what happened.

Clearing concepts on the exception

Let's say I have a function.
def foo(data):
if data:
return data[0]
else:
raise ValueError('data is empty')
def main_foo(..):
ele = foo(data)
Now, i want to catch that exception as one of my friends commented
Please re-raise the errors at main_foo
So does that mean I do something like:
def main_foo( .. ):
try:
ele = foo(data)
except ValueError:
logger.log("exception caught")
If you want to log the occurrence of an exception in main_foo, but let some other function actually handle the exception, do this:
def main_foo():
try:
ele = foo(data)
except ValueError:
logger.log("Exception caught")
raise
This will raise the same exception for the caller of main_foo to deal with.

exception handler as a function

I am using tornado to create a rest handler.. Here i have some common exceptions like InvalidRequest, InvalidToken etc. So i wanted to know how i can create a sort of super exception handler for these and handle the rest in the same function itself.. Part of the example code
class RestRegisterHandler(RestHandler):
#gen.coroutine
def post(self):
self.raw_data = None
try:
yield self.validate_user()
self.raw_data = json_decode(self.request.body)
logger.debug(self.raw_data)
model_object = self.model(self.raw_data)
model_object.validate()
logger.debug("Inseting to database")
yield model_object.insert(self.db)
except InvalidRequest:
self.write_error(404, 'Invalid request')
except InvalidToken:
self.write_error(404, 'Token Validation Failed')
except ModelValidationError as error:
logger.error("Unknown Validation error: '{0}'".format(error))
raise utils.errors.ValidationError(400, error_messages=error.messages)
except DuplicateKeyError:
logger.debug("User already exists")
self.write_error(404, 'User already exists')
except Exception as e:
logger.error(e)
self.write_error(404, 'Invalid request')
else:
logger.debug("db saved")
self.write("Registered succesfully")
return
Something like
class RestHandler():
def super_exception():
except InvalidToken:
print()
except InvalidRequest:
print()
# the rest of exceptions should be handled by post function
Something like this?
class RestHandler(object):
# "default" exception handling in the super class
def handle_exception(self, e):
logger.error('Default error handling caught a "%s"' % e)
class RestRegisterHandler(RestHandler):
# "specific" exception handling in the child class
def handle_exception(self, e):
# Just an idea, you can implement this in different ways
WRITE_ERROR = (InvalidRequest, InvalidToken, DuplicateKeyError)
WRITE_ERROR_WITH_LOGGER = (Exception,)
RAISE_VALIDATION_ERROR = (ModelValidationError,)
if e in WRITE_ERROR:
self.write_error(404, str(e))
elif e in WRITE_ERROR_WITH_LOGGER:
logger.error(e)
self.write_error(404, str(e))
elif e in RAISE_VALIDATION_ERROR:
logger.error("Unknown Validation error: '{0}'".format(e))
raise utils.errors.ValidationError(400, error_messages=e.messages)
else:
# if couldn't be handled here, fallback to the default implementation
super(RestHandler, self).handle_exception(e)
#gen.coroutine
def post(self):
self.raw_data = None
try:
yield self.validate_user()
self.raw_data = json_decode(self.request.body)
logger.debug(self.raw_data)
model_object = self.model(self.raw_data)
model_object.validate()
logger.debug("Inseting to database")
yield model_object.insert(self.db)
except Exception as e:
self.handle_exception(e)
else:
logger.debug("db saved")
self.write("Registered succesfully")
return
The general solution for this is to override RequestHandler.write_error (and don't call write_error yourself, just let the errors escape the handler function). write_error will get an exc_info keyword argument that lets you see the exception that caused the request to fail.

How can I add context to an exception in Python

I would like to add context to an exception like this:
def process(vals):
for key in vals:
try:
do_something(vals[key])
except Exception as ex: # base class. Not sure what to expect.
raise # with context regarding the key that was being processed.
I found a way that is uncharacteristically long winded for Python. Is there a better way than this?
try:
do_something(vals[key])
except Exception as ex:
args = list(ex.args)
if len(args) > 1:
args[0] = "{}: {}".format(key, args[0])
ex.args = tuple(args)
raise # Will re-trhow ValueError with new args[0]
The first item in ex.args is always the message -- if there is any. (Note for some exceptions, such as the one raised by assert False, ex.args is an empty tuple.)
I don't know of a cleaner way to modify the message than reassigning a new tuple to ex.args. (We can't modify the tuple since tuples are immutable).
The code below is similar to yours, except it constructs the tuple without using an intermediate list, it handles the case when ex.args is empty, and to make the code more readable, it hides the boilerplate inside a context manager:
import contextlib
def process(val):
with context(val):
do_something(val)
def do_something(val):
# assert False
return 1/val
#contextlib.contextmanager
def context(msg):
try:
yield
except Exception as ex:
msg = '{}: {}'.format(msg, ex.args[0]) if ex.args else str(msg)
ex.args = (msg,) + ex.args[1:]
raise
process(0)
yields a stack trace with this as the final message:
ZeroDivisionError: 0: division by zero
You could just raise a new exception:
def process(vals):
for key in vals:
try:
do_something(vals[key])
except Exception as ex:
raise Error(key, context=ex)
On Python 3 you don't need to provide the old exception explicitly, it will be available as __context__ attribute on the new exception object and the default exception handler will report it automatically:
def process(vals):
for key in vals:
try:
do_something(vals[key])
except Exception:
raise Error(key)
In you case, you should probably use the explicit raise Error(key) from ex syntax that sets __cause__ attribute on the new exception, see Exception Chaining and Embedded Tracebacks.
If the only issue is the verbosity of the message-amending code in your question; you could encapsulate it in a function:
try:
do_something(vals[key])
except Exception:
reraise_with_context(key=key) # reraise with extra info
where:
import inspect
import sys
def reraise_with_context(**context):
ex = sys.exc_info()[1]
if not context: # use locals from the caller scope
context = inspect.currentframe().f_back.f_locals
extra_info = ", ".join("%s=%s" % item for item in context.items())
amend_message(ex, extra_info)
raise
def amend_message(ex, extra):
msg = '{} with context: {}'.format(ex.args[0], extra) if ex.args else extra
ex.args = (msg,) + ex.args[1:]

Categories

Resources