Installation ------------ Merry is installed with pip:: pip install merry Getting Started --------------- The purpose of merry is to help you move error handling code away from your application logic. Take, for example, this function, which embeds error handling code:: def write_to_file(filename, data): try: with open(filename, 'w') as f: f.write(data) except IOError: print('Error: can't write to file') except Exception as e: print('Unexpected error: ' + str(e)) write_to_file('some_file', 'some_data') Even with this simple example, you can see how the indentation forced by the try/except block makes the code much harder to read and visually follow. Merry allows you to move exception handlers to external functions, so that they don't interfere with the application logic:: from merry import Merry merry = Merry() @merry._try def write_to_file(filename, data): with open(filename, 'w') as f: f.write(data) @merry._except(IOError) def ioerror(): print('Error: can't write to file') @merry._except(Exception) def catch_all(e): print('Unexpected error: ' + str(e) write_to_file('some_file', 'some_data') While in this example there are more lines of code after merry is used, the key benefit is that the application logic, which is in the ``write_to_file`` function, is now completely clean of try/except statements. The exception handlers become auxiliary functions that can even be moved to a separate module so that they stay completely out of the way. Access to the Exception Object ------------------------------ The decorated exception handlers can optionally take one argument. If you include this argument, then merry sends the exception object:: @merry._except(Exception) def catch_all(e): print('Unexpected error: ' + str(e) The `else` and `finally` clauses -------------------------------- For cases that require a more complex try/except block, there are also decorators available for ``else`` and ``finally``:: @merry._else def else_clause(): print('No exceptions where raised!') @merry._finally def finally_clause(): print('Clean up time!') Returning values ---------------- Returning values from functions protected with the ``try`` decorator or the corresponding ``except``, ``else`` and ``finally`` handlers follow certain rules that try to implement a behavior similar to a Python try/except: - The value returned by a function decorated with the ``try`` decorator is normally returned to the caller. - If there is an exception, then the value returned by the ``except`` handler that matches the exception is returned to the caller. - The ``else`` handler only runs if the ``try`` function does not raise an exception and returns ``None`` - If the ``try`` function returns ``None`` and there is an ``else`` handler, then its return value is given to the caller. - If there is a ``finally`` handler and it returns a value that is not ``None``, then this value takes the place of any other returned value. Passing context to error handlers --------------------------------- In many cases, exception handlers need to have access to application state to do their work. When using merry, the ``merry.g`` object can be used as storage of application state that needs to be accessible to error handlers:: @merry._try def app_logic(): db = open_database() merry.g.database = db # save it in the error context just in case # do database stuff here @merry._except(Exception) def catch_all(): db = getattr(merry.g, 'database', None) if db is not None and is_database_open(db): close_database(db) print('Unexpected error, quitting') sys.exit(1) Debug mode ---------- When working with debuggers, it is a good idea to let all exceptions reach the top of the stack, so that the debugger handles them. With merry, if you enable debug mode all exceptions bubble all the way up:: merry = Merry(debug=True) But when working in debug mode, there might be certain exceptions that are expected to trigger and do not need to bubble up. For this reason, the debug mode can be overriden by individual error handlers:: @merry._except(IOError, debug=False) def ioerror(): # this function will run even in debug mode print('Error: can't write to file') The reverse is also possible. If you are running with debug mode turned off, but want to suspend an exception handler and have that exception bubble up, just set ``debug=True`` for that handler. Logging ------- When an application is running in production mode, it is desired that all errors are suppressed and instead they are sent to a log. Merry creates a logger on which it writes all the exceptions it handles, include their backtraces. This logger is a standard instance of the Python standard library logging class. The default logger instance is called ``'merry'``, and can be referenced as ``merry.logger``. If desired, merry can hook up to a logger object owned by the application:: custom_logger = logging.getLogger('my_logger') custom_logger.setLevel(logging.INFO) merry = Merry(logger_name='my_logger') By default, the logger created by merry does not have any handlers attached, so caught exceptions will not be logged anywhere. If you want exceptions to be written to the console, you can add a handler that writes to stderr:: merry = Merry() merry.logger.addHandler(logging.StreamHandler(sys.stderr)) The log level and format can be adjusted as well. See the documentation on the logging module for more information on how to do this.