Skip to content

Commit

Permalink
Merge pull request #9 from pomponchik/develop
Browse files Browse the repository at this point in the history
0.0.9
  • Loading branch information
pomponchik authored Mar 2, 2024
2 parents ffd1d4d + 9828774 commit 9beb1af
Show file tree
Hide file tree
Showing 19 changed files with 1,630 additions and 153 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ jobs:
shell: bash
run: mypy escape --strict

- name: Run mypy for tests
shell: bash
run: mypy tests

- name: Run ruff
shell: bash
run: ruff escape

- name: Run ruff for tests
shell: bash
run: ruff tests
98 changes: 53 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# exception_escaping
![logo](https://raw.githubusercontent.com/pomponchik/exception_escaping/develop/docs/assets/logo_9.svg)

[![Downloads](https://static.pepy.tech/badge/exception_escaping/month)](https://pepy.tech/project/exception_escaping)
[![Downloads](https://static.pepy.tech/badge/exception_escaping)](https://pepy.tech/project/exception_escaping)
Expand All @@ -20,7 +20,6 @@ If you've just confessed and you can't wait to sin again, try this package. It w
- [**Quick start**](#quick-start)
- [**Decorator mode**](#decorator-mode)
- [**Context manager mode**](#context-manager-mode)
- [**Which exceptions are escaped?**](#which-exceptions-are-escaped)
- [**Logging**](#logging)


Expand Down Expand Up @@ -49,92 +48,101 @@ Read about other library features below.

## Decorator mode

You can hang the `escape` decorator on any function, including a coroutine one. Exceptions that occur internally will be suppressed.

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

```python
@escape
import asyncio
import escape

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

And with coroutine one:
@escape(ValueError, ZeroDivisionError)
async def async_function():
raise ZeroDivisionError('oh!')

```python
@escape
async def coroutine_function():
raise ValueError
function() # Silence.
asyncio.run(async_function()) # Silence.
```

The decorator will work both with and without brackets:
If you use `@escape` with parentheses but do not pass any exception types, no exceptions will be suppressed:

```python
@escape() # This will work too.
@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:

```python
@escape(default='some 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.

## Context manager mode
```python
@escape
def function():
raise ValueError

You can use `escape` as a context manager. It works almost the same way as [`contextlib.suppress`](https://docs.python.org/3/library/contextlib.html#contextlib.suppress) from the standard library. However, in this case, you can choose whether to use the context manager with or without brackets:
function() # Silence still.
```

```python
# Both options work the same way.
In this mode, not all exceptions from the [hierarchy](https://docs.python.org/3/library/exceptions.html#exception-hierarchy) are suppressed, but only those that can be expected in the user code. [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception) and all its descendants are suppressed, as well as, starting with `Python 3.11`, [groups of exceptions](https://docs.python.org/3/library/exceptions.html#exception-groups). However, exceptions [`GeneratorExit`](https://docs.python.org/3/library/exceptions.html#GeneratorExit), [`KeyboardInterrupt`](https://docs.python.org/3/library/exceptions.html#KeyboardInterrupt) and [`SystemExit`](https://docs.python.org/3/library/exceptions.html#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.

with escape:
You can also use the same set of exceptions in parenthesis mode as without parentheses. To do this, use the [`Ellipsis`](https://docs.python.org/dev/library/constants.html#Ellipsis) (three dots):

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

with escape():
@escape
def function_2():
raise ValueError

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

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:
`Ellipsis` can also be used in enumeration, along with other exceptions:

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

# escape.errors.SetDefaultReturnValueForDecoratorError: You cannot set a default value for the context manager. This is only possible for the decorator.
@escape(GeneratorExit, ...)
```

## Which exceptions are escaped?

By default, not all exceptions from the [hierarchy](https://docs.python.org/3/library/exceptions.html#exception-hierarchy) are escaped. This only applies to [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception) and all its descendants. Starting with Python 3.11, [groups of exceptions](https://docs.python.org/3/library/exceptions.html#exception-groups) appear - and they are also escaped by default. However, exceptions [`GeneratorExit`](https://docs.python.org/3/library/exceptions.html#GeneratorExit), [`KeyboardInterrupt`](https://docs.python.org/3/library/exceptions.html#KeyboardInterrupt) and [`SystemExit`](https://docs.python.org/3/library/exceptions.html#SystemExit) are not escaped by default. 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.

If you want to expand or narrow the range of escaped exceptions, use the `exceptions` argument. You must pass a list or tuple of exception types.
## Context manager mode

It works for the [decorator mode](#decorator-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](#decorator-mode) - pass exceptions or ellipsis there. It also works almost the same way as [`contextlib.suppress`](https://docs.python.org/3/library/contextlib.html#contextlib.suppress) from the standard library, but with a bit more opportunities. Some examples:

```python
@escape(exceptions=[ValueError]):
def function():
raise ValueError # It will be suppressed.
with escape(ValueError):
raise ValueError

@escape(exceptions=[ValueError]):
def function():
raise KeyError # And this is not.
with escape:
raise ValueError

with escape(...):
raise ValueError
```

... and for the [context manager mode](#context-manager-mode):
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:

```python
with escape(exceptions=[ValueError]):
raise ValueError # It will be suppressed.
with escape(default='some value'):
...

with escape(exceptions=[ValueError]):
raise KeyError # And this is not.
# > escape.errors.SetDefaultReturnValueForContextManagerError: You cannot set a default value for the context manager. This is only possible for the decorator.
```


Expand All @@ -156,7 +164,7 @@ logging.basicConfig(

logger = logging.getLogger('logger_name')

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

# You will see a description of the error in the console.
Expand Down
Binary file added docs/assets/logo_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 9beb1af

Please sign in to comment.