Skip to main content

Try not to stand out

Project description

logo

Downloads Downloads codecov Lines of code Hits-of-Code Test-Package Python versions PyPI version Checked with mypy Ruff

If you've just confessed and you can't wait to sin again, try this package. It will help you hide your mistakes and make your life more carefree :) Seriously, the library allows you to solve the problem of exception handling in a more adult way by providing:

  • 🛡️ A universal interface for the decorator and context manager.
  • 🛡️ Built-in logging.
  • 🛡️ Calling callbacks.

Table of contents

Quick start

Install it:

pip install escaping

And use:

import escape

@escape
def function():
    raise ValueError

function()  # The exception is suppressed.

Read about other library features below.

About

This project is dedicated to the most important problem in programming - how do we need to handle errors? Here are some answers to this question that it gives:

  • This should be done in a standardized way. You can decide for yourself how errors will be handled, but with this project, any method you choose can easily become the standard.

  • Mistakes should not be hidden. Even if the exception is suppressed, you should be aware of it.

An interesting solution that is proposed here is that you are provided with a single interface for error suppression, which can be used as a context manager for any block of code, as well as as a decorator for ordinary, coroutine and generator functions. Wherever you need to suppress an error, you do it the same way, according to the same rules:

import escape

@escape
def function():
    ...

@escape
async def function():
    ...

@escape
def function():
    yield something
    ...

with escape:
    ...

The rules by which you want to suppress errors can be "baked" into a special object so that you don't duplicate it in different parts of the code later. This means that you can come up with error suppression rules once, and then use them everywhere, without duplicating code, which is assumed when using ordinary try-except blocks.

Decorator mode

The @escape decorator suppresses exceptions in a wrapped function (including generator and coroutine ones), which are passed in parentheses. In this way, you can pass any number of exceptions, for example:

import asyncio
import escape

@escape(ValueError, ZeroDivisionError)
def function():
    raise ValueError('oh!')

@escape(ValueError, ZeroDivisionError)
async def async_function():
    raise ZeroDivisionError('oh!')

function()  # Silence.
asyncio.run(async_function())  # Silence.

If you use @escape with parentheses but do not pass any exception types, no exceptions will be suppressed:

@escape()
def function():
    raise ValueError('oh!')

function()
# > ValueError: oh!

If an exception occurred inside the function wrapped by the decorator, it will return the default value - None. You can specify your own default value:

@escape(ValueError, default='some value')
def function():
    raise ValueError

assert function() == 'some value'  # It's going to work.

Finally, you can use @escape as a decorator without parentheses.

@escape
def function():
    raise ValueError

function()  # Silence still.

In this mode, not all exceptions from the hierarchy are suppressed, but only those that can be expected in the user code. Exception and all its descendants are suppressed, as well as, starting with Python 3.11, groups of exceptions. However, exceptions GeneratorExit, KeyboardInterrupt and SystemExit are not escaped in this mode. This is due to the fact that in most programs none of them is part of the semantics of the program, but is used exclusively for system needs. For example, if KeyboardInterrupt was blocked, you would not be able to stop your program using the Control-C keyboard shortcut.

You can also use the same set of exceptions in parenthesis mode as without parentheses. To do this, use the Ellipsis (three dots):

@escape(...)
def function_1():
    raise ValueError

@escape
def function_2():
    raise ValueError

function_1()  # These two functions are completely equivalent.
function_2()  # These two functions are completely equivalent.

Ellipsis can also be used in enumeration, along with other exceptions:

@escape(GeneratorExit, ...)

Context manager mode

You can use escape as a context manager, which escapes exceptions in the code block wrapped by it. You can call it according to the same rules as the decorator - pass exceptions or ellipsis there. It also works almost the same way as contextlib.suppress from the standard library, but with a bit more opportunities. Some examples:

with escape(ValueError):
    raise ValueError

with escape:
    raise ValueError

with escape(...):
    raise ValueError

However, as you should understand, the default value cannot be specified in this case. If you try to specify a default value for the context manager, get ready to face an exception:

with escape(default='some value'):
    ...

# > escape.errors.SetDefaultReturnValueForContextManagerError: You cannot set a default value for the context manager. This is only possible for the decorator.

Logging

You can pass a logger object to the escape. In such case, if an exception is raised inside the context or the function wrapped by the decorator, it will be logged:

import logging
import escape

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.StreamHandler(),
    ]
)

logger = logging.getLogger('logger_name')

with escape(..., logger=logger):
    1/0

# You will see a description of the error in the console.

It works in any mode: both in the case of the context manager and the decorator.

By default only exceptions are logged. If the code block or function was executed without errors, the log will not be recorded. Also the log is recorded regardless of whether the exception was suppressed or not. However, depending on this, you will see different log messages to distinguish one situation from another.

But! You can change the standard logging behavior.

If you want the log to be recorded for any outcome, including the one where no errors occurred, specify the success_logging=True flag (messages will be recorded with the info level):

with escape(success_logging=True, logger=logger):
    pass
    # > The code block was executed successfully.

In addition, you can change the standard messages that you see in the logs. Keep in mind that this feature narrows down the variety of standard messages, which differ depending on where the error occurred (in a regular function, in a generator or asynchronous function, or perhaps in a block of code wrapped by a context manager), or whether the error was intercepted. You can define your own messages for only two types of situations: when the code was executed without exceptions, and when with an exception.

Pass your message as error_log_message if you want to see it when an error occurred inside the code:

with escape(..., error_log_message='Oh my God!', logger=logger):
    raise ValueError
    # > Oh my God!

By analogy, pass success_log_message as a message if there are no errors in the code block (but don't forget to set success_logging=True!):

with escape(success_log_message='Good news, everyone!', success_logging=True, logger=logger):
    pass
    # > Good news, everyone!

In addition, if the exception was suppressed inside the escape, the log will be recorded using the exception method - this means that the trace will be saved. Otherwise, the error method will be used - without saving the traceback, because otherwise, if you catch this exception somewhere else and pledge the traceback, there will be several duplicate tracebacks in your log file.

Callbacks

You can pass callback functions to escape, which will be automatically called when the wrapped code block or function has completed.

A callback passed as success_callback will be called when the code is executed without errors:

with escape(success_callback=lambda: print('The code block ended without errors.')):
    ...

By analogy, if you pass error_callback, this function will be called when an exception is raised inside:

with escape(error_callback=lambda: print('Attention!')):
    ...

If you pass a callback as a before parameter, it'll be called before the code block anyway:

with escape(before=lambda: print('Something is going to happen now...')):
    ...

Notice, if an error occurs in this callback that will not be suppressed, the main code will not be executed - an exception will be raised before it starts executing.

If an error occurs in one of the callbacks, the exception will be suppressed if it would have been suppressed if it had happened in a wrapped code block or function. You can see the corresponding log entry about this if you pass the logger object for registration. If the error inside the callback has been suppressed, it will not affect the logic that was wrapped by escape in any way.

Baking rules

You can set up an error escaping policy once and then reuse it in different situations. To do this, get a special object through the bake method:

escaper = escape.bake(ValueError)

Creating this object, you can pass all the same arguments as when using escape directly as a decorator or a context manager: exceptions, callbacks, or a logger. The object "remembers" these arguments until the moment you decide to use it:

with escaper:
    raise ValueError  # It will be suppressed.
@escaper
def function():
    raise ValueError  # It will be suppressed too.

function()

If necessary, you can combine "baked" arguments and arguments that are passed on demand (executing the sample code requires pre-installation of the emptylog library):

import escape
from emptylog import PrintingLogger

escaper = escape.bake(logger=PrintingLogger())

@escaper(ValueError)
def function():
    raise ValueError  # It will be suppressed too.

function()
#> 2024-09-06 14:45:19.606267 | EXCEPTION | When executing function "function", the exception "ValueError" was suppressed.

In this way, you can add additional exceptions that need to be suppressed - they will be added to the general list of suppressed ones. In addition, you can override some of the named arguments that are "baked" into the object on demand - in this case, the argument that was passed later will be used.

Arguments baking is an extremely powerful tool, useful for large programs. It allows you to get rid of multiple duplications of code that are often encountered during error handling. In addition, with its help, you can describe the error handling policy centrally, in one place for the entire program, which makes maintaining or changing the program a much easier task.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

escaping-0.0.14.tar.gz (14.4 kB view details)

Uploaded Source

Built Distribution

escaping-0.0.14-py3-none-any.whl (11.1 kB view details)

Uploaded Python 3

File details

Details for the file escaping-0.0.14.tar.gz.

File metadata

  • Download URL: escaping-0.0.14.tar.gz
  • Upload date:
  • Size: 14.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.5

File hashes

Hashes for escaping-0.0.14.tar.gz
Algorithm Hash digest
SHA256 c9faf884bfafe9dd791eb87c14881f575908f6487d767ed405a01434de07238d
MD5 c4a80a21b56e9f14646376a62d766596
BLAKE2b-256 18219f2a64fcb76e6680caded9b4699d1e17af8cc7a77940881ee423a18c5b6f

See more details on using hashes here.

File details

Details for the file escaping-0.0.14-py3-none-any.whl.

File metadata

  • Download URL: escaping-0.0.14-py3-none-any.whl
  • Upload date:
  • Size: 11.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.5

File hashes

Hashes for escaping-0.0.14-py3-none-any.whl
Algorithm Hash digest
SHA256 fa397b4e6625839c68cae9e967d1d30d2a9e4da7fe25b68961b3e89572bb5f5f
MD5 59417e84883611e9b1e5d8479d8b43d3
BLAKE2b-256 650e46736f3b4f9622ff75dfd44b363026751dee15adddc2b671651f224aa791

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page