Is it a bad idea to implement my own exceptions with special error handling/logging system embedded? I wish to concentrate some of my error handling stuff in just one place.
For example:
errors.py
class ReallyBadException(Exception):
def __init__(self, msg, error_handling):
super(Exception, self).__init__(msg)
...
error_handling.logger.error(msg)
error_handling.db.do_stuff(msg)
call_the_police()
...
Obviously I know I still will need to handle the exception locally in the main function etc
main.py
from errors import ReallyBadException, NotSoBadException, ...
...
try:
do_something()
except RellyBadException:
do_another_thing()
Simply put, no this is not a bad idea at all.
In fact, the way I see it, what you're trying to do is ultimately reason why programming languages have exception handling in the first place.
Related
I have code that raises exceptions from other exceptions so that we can see details about eveything that went wrong. In the example below we include information about what we are processing and what specific thing in the processing went wrong.
def process(thing):
try:
process_widgets(thing)
except Exception as e:
raise CouldNotProcess(thing) from e
def process_widgets(thing):
for widget in get_widgets(thing):
raise CouldNotProcessWidget(widget)
def do_processing():
for thing in things_to_process():
process(thing)
I am trying to change this so that process_widgets can raise a specific type of exception and do_processing can change its behaviour based on this exception. However the raise from in process is masking this exception which makes this impossible. Is there a good to let do_processing know about what went wrong in process_widgets while also doing raise from.
Ideas:
Python 3.11 has exception groups. So perhaps there is a way of adding exceptions to group and catching them with the likely confusing except* syntax.
There is a dirty trick where I do raise e from CouldNoPorcess(thing) to get both the helpful logging.
Apparently internally exception chaining works by adding __cause__ property (forming a linked list) so I could manually look through causes in the top most exception to manually implement behaviour like except* with exception groups.
def raised_from(e, type):
while e is not None:
if isinstance(e, Specific):
return True
e = e.__cause__
return False
...
try:
do_processing()
except CouldNotProcess as e:
if raised_from(e, CouldNotProcessWidget):
do_stuff()
You see this pattern quite a lot with status_codes from http.
I could use logging rather than adding information to exceptions. This hides information from the exception handling code, but works for logging. I think this is the work around that I'll use at the moment.
It's noticeable that the PEP says that exception chaining isn't quite designed for adding information to exceptions.
Update
python 3.11 has an add_note method and notes property which can be used to add information - which works for some use cases.
For this use case exception groups might be the way to go, though I am concerned that this might be a little confusing.
I have code that looks like this:
# foo.py
from internallib.a import A
A.some(new_schema)
# a.py
from internallib.b import B
class A:
#staticmethod
def some(schema_name):
B.thing(schema_name, other_parameter)
# b.py
import marshmallow
class B:
#staticmethod
def thing(schema_name, other_parameter):
marshmallow.class_registry.get_class(schema_name)
Marshmallow throws an error that I'd like to catch and print nicely for the user and then exit (instead of just printing a stack trace). The specifics here are that Marshmallow throws a RegistryError when get_class is called and the schema hasn't been registered.
However, this is a completely expected condition - it's ok to TRY to add the schema, and I want to present that error to the user - think of it kind like a 'file not found' error. A stack trace feels way too low level to present.
However, A.some() is called in many places in the code. What's the pythonic way of doing this? My thoughts:
Wrap the marshmallow() in a try-except, print something out and then exit()
Go everywhere I call A.some(new_schema) and wrap that (it's not my preference, but if necessary)
Something else?
Solution
Option 1 - uniform behavior for all A.some() callers: Wrap B.thing() call in A.some() in a try except catching very specific exceptions while using logger to log error in the except block prior to exiting.
Option 2 - varying behavior needed for A.some() callers: If you wanted varying behavior depending on the context of where A.some() is being called, then you would need to refactor to catch the same error from Option 1 and raise your own internal error named FileNotFound, which each caller to A.some() would be able to handle behavior individually.
Let's say I want to be able to log to file every time any exception is raised, anywhere in my program. I don't want to modify any existing code.
Of course, this could be generalized to being able to insert a hook every time an exception is raised.
Would the following code be considered safe for doing such a thing?
class MyException(Exception):
def my_hook(self):
print('---> my_hook() was called');
def __init__(self, *args, **kwargs):
global BackupException;
self.my_hook();
return BackupException.__init__(self, *args, **kwargs);
def main():
global BackupException;
global Exception;
BackupException = Exception;
Exception = MyException;
raise Exception('Contrived Exception');
if __name__ == '__main__':
main();
If you want to log uncaught exceptions, just use sys.excepthook.
I'm not sure I see the value of logging all raised exceptions, since lots of libraries will raise/catch exceptions internally for things you probably won't care about.
Your code as far as I can tell would not work.
__init__ has to return None and you are trying to return an instance of backup exception. In general if you would like to change what instance is returned when instantiating a class you should override __new__.
Unfortunately you can't change any of the attributes on the Exception class. If that was an option you could have changed Exception.__new__ and placed your hook there.
the "global Exception" trick will only work for code in the current module. Exception is a builtin and if you really want to change it globally you need to import __builtin__; __builtin__.Exception = MyException
Even if you changed __builtin__.Exception it will only affect future uses of Exception, subclasses that have already been defined will use the original Exception class and will be unaffected by your changes. You could loop over Exception.__subclasses__ and change the __bases__ for each one of them to insert your Exception subclass there.
There are subclasses of Exception that are also built-in types that you also cannot modify, although I'm not sure you would want to hook any of them (think StopIterration).
I think that the only decent way to do what you want is to patch the Python sources.
This code will not affect any exception classes that were created before the start of main, and most of the exceptions that happen will be of such kinds (KeyError, AttributeError, and so forth). And you can't really affect those "built-in exceptions" in the most important sense -- if anywhere in your code is e.g. a 1/0, the real ZeroDivisionError will be raised (by Python's own internals), not whatever else you may have bound to that exceptions' name.
So, I don't think your code can do what you want (despite all the semicolons, it's still supposed to be Python, right?) -- it could be done by patching the C sources for the Python runtime, essentially (e.g. by providing a hook potentially caught on any exception even if it's later caught) -- such a hook currently does not exist because the use cases for it would be pretty rare (for example, a StopIteration is always raised at the normal end of every for loop -- and caught, too; why on Earth would one want to trace that, and the many other routine uses of caught exceptions in the Python internals and standard library?!).
Download pypy and instrument it.
I have:
a function call_vendor_code() that calls external vendor code;
an exception class MyDomainException(Exceprion).
I know that function call_vendor_code() could throw MyDomainException (because that logic in function is my). But how do I know which vendor code exceptions could be thrown in call_vendor_code() in subcalls?
Example:
def call_vendor_code():
if not vendor.package.module.make_more_love():
raise MyDomainException('Not enough love.')
Vendor code could throw any other exceptions that I don't handle. It is possible to intercept all excepions from vendor code, but i thing that it is bad solution.
def call_vendor_code():
try:
if not vendor.package.module.make_more_love():
raise MyDomainException('Not enough love.')
except Exception, e:
raise MyDomainException(e)
How to interact/not interact with other-level exceptions?
Your logic is sound.
How you handle these vendor specific exceptions, more elegantly, depends
on the type of exceptions raised by the vendor code.
Are they plain python exceptions(e.g. ValueError, TypeError, etc)? Then
you don't have much of a choice. You should wrap them under a generic
except Exception statement. In these unfortunate cases, many people
try to differentiate the type of error by parsing the exception message.
A major anti-pattern, but what can you do?
Do they raise their own vendor specific exceptions(best case scenario)?
Then you can easily differentiate and handle them more elegantly in your
code. They should have some kind of documentation about it :)
Or, if the code is available, read through the code and see for yourself.
If we're talking about vendor code that talks to some external system(some
kind of DB for example), they might have a generic VendorException with
some internal error code(1133, 623, whatever) that is stored in the exception
instance as an attribute. Again, they most probably will have a listing of
those error codes, along with their description in some kind of documentation,
that you can use to map them into your own custom exceptions.
I have a python module containing functions and a few classes. This module is basically used as a tool-set by several of my co-workers.
I want to set-up a sort of bug reporting system where anytime someone generates an exception that I don't handle, an email will be sent with information on the exception. This way I can continually improve the robustness of my code and the help-fullness of my own error messages. Is the best way to do this to just put a try/except block around the entire module?
There are several reasons I think your approach might not be the best.
Sometimes exceptions should be thrown. For example, if I pass some stupid argument to a function, it should complain by throwing an exception. You don't want to get an email every time someone passes a string instead of an integer, etc. do you?
Besides, wrapping the entire thing in a try...except won't work, as that will only be catching exceptions that would occur during the definition of the classes/functions (when your module is loaded/imported). For example,
# Your python library
try:
def foo():
raise Exception('foo exception')
return 42
except Exception as e:
print 'Handled: ', e
# A consumer of your library
foo()
The exception is still uncaught.
I guess you can make your own SelfMailingException and subclass it. Not that I would recommend this approach.
another option:
def raises(*exception_list):
def wrap(f):
def wrapped_f(*x, **y):
try:
f(*x, **y)
except Exception as e:
if not isinstance(e, tuple(exception_list)):
print('send mail')
# send mail
raise
return wrapped_f
return wrap
usage:
#raises(MyException)
def foo():
...