diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 22b58d6..1aec3cf 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -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
diff --git a/README.md b/README.md
index 6369ca7..6d14c8a 100644
--- a/README.md
+++ b/README.md
@@ -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)
@@ -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)
@@ -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.
```
@@ -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.
diff --git a/docs/assets/logo_1.png b/docs/assets/logo_1.png
new file mode 100644
index 0000000..3ffdf2d
Binary files /dev/null and b/docs/assets/logo_1.png differ
diff --git a/docs/assets/logo_2.svg b/docs/assets/logo_2.svg
new file mode 100644
index 0000000..e3d784b
--- /dev/null
+++ b/docs/assets/logo_2.svg
@@ -0,0 +1,301 @@
+
\ No newline at end of file
diff --git a/docs/assets/logo_3.png b/docs/assets/logo_3.png
new file mode 100644
index 0000000..f9fe867
Binary files /dev/null and b/docs/assets/logo_3.png differ
diff --git a/docs/assets/logo_4.svg b/docs/assets/logo_4.svg
new file mode 100644
index 0000000..e3d784b
--- /dev/null
+++ b/docs/assets/logo_4.svg
@@ -0,0 +1,301 @@
+
\ No newline at end of file
diff --git a/docs/assets/logo_5.png b/docs/assets/logo_5.png
new file mode 100644
index 0000000..443e799
Binary files /dev/null and b/docs/assets/logo_5.png differ
diff --git a/docs/assets/logo_6.svg b/docs/assets/logo_6.svg
new file mode 100644
index 0000000..c4efa3b
--- /dev/null
+++ b/docs/assets/logo_6.svg
@@ -0,0 +1,278 @@
+
\ No newline at end of file
diff --git a/docs/assets/logo_7.svg b/docs/assets/logo_7.svg
new file mode 100644
index 0000000..9d96925
--- /dev/null
+++ b/docs/assets/logo_7.svg
@@ -0,0 +1,63 @@
+
+
diff --git a/docs/assets/logo_8.svg b/docs/assets/logo_8.svg
new file mode 100644
index 0000000..e0aed2e
--- /dev/null
+++ b/docs/assets/logo_8.svg
@@ -0,0 +1,100 @@
+
+
diff --git a/docs/assets/logo_9.svg b/docs/assets/logo_9.svg
new file mode 100644
index 0000000..8b8e296
--- /dev/null
+++ b/docs/assets/logo_9.svg
@@ -0,0 +1,96 @@
+
+
diff --git a/escape/errors.py b/escape/errors.py
index a9f4e35..6a4f8cf 100644
--- a/escape/errors.py
+++ b/escape/errors.py
@@ -1,2 +1,2 @@
-class SetDefaultReturnValueForDecoratorError(Exception):
+class SetDefaultReturnValueForContextManagerError(Exception):
pass
diff --git a/escape/proxy_module.py b/escape/proxy_module.py
index 97e0597..0c82a95 100644
--- a/escape/proxy_module.py
+++ b/escape/proxy_module.py
@@ -1,7 +1,13 @@
import sys
-from typing import Type, Tuple, List, Callable, Union, Optional, Any
+from typing import Type, Tuple, Callable, Union, Optional, Any
from types import TracebackType
from inspect import isclass
+from itertools import chain
+
+try:
+ from types import EllipsisType # type: ignore[attr-defined]
+except ImportError: # pragma: no cover
+ EllipsisType = type(...) # pragma: no cover
from emptylog import LoggerProtocol, EmptyLogger
@@ -14,26 +20,26 @@
muted_by_default_exceptions = (Exception, BaseExceptionGroup)
class ProxyModule(sys.modules[__name__].__class__): # type: ignore[misc]
- def __call__(self, *args: Callable[..., Any], default: Any = None, exceptions: Union[Tuple[Type[BaseException], ...], List[Type[BaseException]]] = muted_by_default_exceptions, logger: LoggerProtocol = EmptyLogger()) -> Union[Callable[..., Any], Callable[[Callable[..., Any]], Callable[..., Any]]]:
+ def __call__(self, *args: Union[Callable[..., Any], Type[BaseException], EllipsisType], default: Any = None, logger: LoggerProtocol = EmptyLogger()) -> Union[Callable[..., Any], Callable[[Callable[..., Any]], Callable[..., Any]]]:
"""
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
"""
- if not isinstance(exceptions, tuple) and not isinstance(exceptions, list):
- raise ValueError('The list of exception types can be of the list or tuple type.')
- elif not all(isclass(x) and issubclass(x, BaseException) for x in exceptions):
- raise ValueError('The list of exception types can contain only exception types.')
-
- if isinstance(exceptions, list):
- converted_exceptions: Tuple[Type[BaseException], ...] = tuple(exceptions)
+ if self.are_it_function(args):
+ exceptions: Tuple[Type[BaseException], ...] = muted_by_default_exceptions
else:
- converted_exceptions = exceptions
+ if self.is_there_ellipsis(args):
+ exceptions = tuple(chain((x for x in args if x is not Ellipsis), muted_by_default_exceptions)) # type: ignore[misc]
+ else:
+ exceptions = args # type: ignore[assignment]
- wrapper_of_wrappers = Wrapper(default, converted_exceptions, logger)
+ wrapper_of_wrappers = Wrapper(default, exceptions, logger)
- if len(args) == 1 and callable(args[0]):
- return wrapper_of_wrappers(args[0])
- elif len(args) == 0:
+ if self.are_it_exceptions(args):
return wrapper_of_wrappers
+
+ elif self.are_it_function(args):
+ return wrapper_of_wrappers(args[0])
+
else:
raise ValueError('You are using the decorator for the wrong purpose.')
@@ -47,3 +53,15 @@ def __exit__(self, exception_type: Optional[Type[BaseException]], exception_valu
return True
return False
+
+ @staticmethod
+ def is_there_ellipsis(args: Tuple[Union[Type[BaseException], Callable[..., Any], EllipsisType], ...]) -> bool:
+ return any(x is Ellipsis for x in args)
+
+ @staticmethod
+ def are_it_exceptions(args: Tuple[Union[Type[BaseException], Callable[..., Any], EllipsisType], ...]) -> bool:
+ return all((x is Ellipsis) or (isclass(x) and issubclass(x, BaseException)) for x in args)
+
+ @staticmethod
+ def are_it_function(args: Tuple[Union[Type[BaseException], Callable[..., Any], EllipsisType], ...]) -> bool:
+ return len(args) == 1 and callable(args[0]) and not (isclass(args[0]) and issubclass(args[0], BaseException))
diff --git a/escape/wrapper.py b/escape/wrapper.py
index 766ca35..f573ef8 100644
--- a/escape/wrapper.py
+++ b/escape/wrapper.py
@@ -5,7 +5,7 @@
from emptylog import LoggerProtocol
-from escape.errors import SetDefaultReturnValueForDecoratorError
+from escape.errors import SetDefaultReturnValueForContextManagerError
class Wrapper:
@@ -47,7 +47,7 @@ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
def __enter__(self) -> 'Wrapper':
if self.default is not None:
- raise SetDefaultReturnValueForDecoratorError('You cannot set a default value for the context manager. This is only possible for the decorator.')
+ raise SetDefaultReturnValueForContextManagerError('You cannot set a default value for the context manager. This is only possible for the decorator.')
return self
diff --git a/pyproject.toml b/pyproject.toml
index 0b47196..dedd9cb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = 'setuptools.build_meta'
[project]
name = 'exception_escaping'
-version = '0.0.8'
+version = '0.0.9'
authors = [
{ name='Evgeniy Blinov', email='zheni-b@yandex.ru' },
]
@@ -29,6 +29,7 @@ classifiers = [
'License :: OSI Approved :: MIT License',
'Intended Audience :: Developers',
'Topic :: Software Development :: Libraries',
+ 'Typing :: Typed',
]
[tool.setuptools.package-data]
diff --git a/tests/documentation/__init__.py b/tests/documentation/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/documentation/test_readme.py b/tests/documentation/test_readme.py
new file mode 100644
index 0000000..e43aa30
--- /dev/null
+++ b/tests/documentation/test_readme.py
@@ -0,0 +1,91 @@
+import asyncio
+
+import pytest
+import full_match
+
+import escape
+
+
+def test_quick_start():
+ @escape
+ def function():
+ raise ValueError
+
+ function() # The exception is suppressed.
+
+
+def test_decorator_mode_passing_exceptions():
+ @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.
+
+
+def test_decorator_mode_not_suppressing_exception():
+ @escape()
+ def function():
+ raise ValueError('oh!')
+
+ with pytest.raises(ValueError, match='oh!'):
+ function()
+ # > ValueError: oh!
+
+
+def test_decorator_mode_default_value():
+ @escape(ValueError, default='some value')
+ def function():
+ raise ValueError
+
+ assert function() == 'some value' # It's going to work.
+
+
+def test_decorator_mode_with_empty_breackets():
+ @escape
+ def function():
+ raise ValueError
+
+ function() # Silence still.
+
+
+def test_decorator_more_equivalents():
+ @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.
+
+
+def test_decorator_mode_exception_and_ellipsis_separated_by_comma():
+ @escape(GeneratorExit, ...)
+ def function():
+ raise GeneratorExit
+
+ function()
+
+
+def test_context_manager_basic_examples():
+ with escape(ValueError):
+ raise ValueError
+
+ with escape:
+ raise ValueError
+
+ with escape(...):
+ raise ValueError
+
+
+def test_context_manager_attempt_to_set_default_value():
+ with pytest.raises(escape.errors.SetDefaultReturnValueForContextManagerError, match=full_match('You cannot set a default value for the context manager. This is only possible for the decorator.')):
+ with escape(default='some value'):
+ ...
diff --git a/tests/units/__init__.py b/tests/units/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_proxy_module.py b/tests/units/test_proxy_module.py
similarity index 54%
rename from tests/test_proxy_module.py
rename to tests/units/test_proxy_module.py
index 6dab1a3..c88b616 100644
--- a/tests/test_proxy_module.py
+++ b/tests/units/test_proxy_module.py
@@ -5,7 +5,7 @@
from emptylog import MemoryLogger
import escape
-from escape.errors import SetDefaultReturnValueForDecoratorError
+from escape.errors import SetDefaultReturnValueForContextManagerError
def test_run_simple_function():
@@ -87,8 +87,8 @@ def function(a, b, c=5):
assert function(1, 2, c=8) == 11
-def test_run_function_with_exception_with_empty_brackets():
- @escape()
+def test_run_function_with_exception_with_empty_brackets_with_ellipsis():
+ @escape(...)
def function(a, b, c=5):
raise ValueError
@@ -116,8 +116,8 @@ async def function(a, b, c=5):
assert asyncio.run(function(1, 2, c=8)) == 11
-def test_run_coroutine_function_with_exception_with_empty_brackets():
- @escape()
+def test_run_coroutine_function_with_exception_with_empty_brackets_with_ellipsis():
+ @escape(...)
async def function(a, b, c=5):
raise ValueError
@@ -145,10 +145,10 @@ def function(a, b, c=5):
assert function(1, 2, c=8) == 11
-def test_run_function_with_exception_with_default_return():
+def test_run_function_with_exception_with_default_return_with_ellipsis():
default_value = 13
- @escape(default=default_value)
+ @escape(..., default=default_value)
def function(a, b, c=5):
raise ValueError
@@ -176,10 +176,10 @@ async def function(a, b, c=5):
assert asyncio.run(function(1, 2, c=8)) == 11
-def test_run_coroutine_function_with_exception_with_default_return():
+def test_run_coroutine_function_with_exception_with_default_return_with_ellipsis():
default_value = 13
- @escape(default=default_value)
+ @escape(..., default=default_value)
async def function(a, b, c=5):
raise ValueError
@@ -191,8 +191,8 @@ def test_wrong_argument_to_decorator():
escape('kek')
-def test_context_manager_with_empty_brackets_muted_by_default_exception():
- with escape():
+def test_context_manager_with_empty_brackets_muted_by_default_exception_with_ellipsis():
+ with escape(...):
raise ValueError
@@ -205,21 +205,21 @@ def test_context_manager_with_empty_brackets_not_muted_by_default_exception():
def test_context_manager_with_exceptions_parameter_not_muted_exception():
with pytest.raises(ValueError):
- with escape(exceptions=(ZeroDivisionError,)):
+ with escape(ZeroDivisionError):
raise ValueError
with pytest.raises(ValueError):
- with escape(exceptions=[ZeroDivisionError]):
+ with escape(ZeroDivisionError):
raise ValueError
def test_context_manager_with_exceptions_parameter_muted_exception():
for muted_exception in (GeneratorExit, KeyboardInterrupt, SystemExit):
- with escape(exceptions=(muted_exception,)):
+ with escape(muted_exception):
raise muted_exception
for muted_exception in (GeneratorExit, KeyboardInterrupt, SystemExit):
- with escape(exceptions=[muted_exception]):
+ with escape(muted_exception):
raise muted_exception
@@ -253,47 +253,30 @@ async def function():
def test_context_manager_with_default_return_value():
- with pytest.raises(SetDefaultReturnValueForDecoratorError, match=full_match('You cannot set a default value for the context manager. This is only possible for the decorator.')):
+ with pytest.raises(SetDefaultReturnValueForContextManagerError, match=full_match('You cannot set a default value for the context manager. This is only possible for the decorator.')):
with escape(default='lol'):
...
def test_set_exceptions_types_with_bad_typed_value():
- with pytest.raises(ValueError, match=full_match('The list of exception types can be of the list or tuple type.')):
- escape(exceptions='lol')
+ with pytest.raises(ValueError, match=full_match('You are using the decorator for the wrong purpose.')):
+ escape('lol')
def test_set_exceptions_types_with_bad_typed_exceptions_in_list():
- with pytest.raises(ValueError, match=full_match('The list of exception types can contain only exception types.')):
- escape(exceptions=[ValueError, 'lol'])
-
-
-def test_decorator_with_list_of_muted_exceptions():
- @escape(exceptions=[ValueError])
- def function():
- raise ValueError
-
- function()
-
-
-def test_decorator_with_list_of_not_muted_exceptions():
- @escape(exceptions=[ValueError])
- def function():
- raise KeyError
-
- with pytest.raises(KeyError):
- function()
+ with pytest.raises(ValueError, match=full_match('You are using the decorator for the wrong purpose.')):
+ escape(ValueError, 'lol')
-def test_decorator_with_tuple_of_muted_exceptions():
- @escape(exceptions=(ValueError, ))
+def test_decorator_with_muted_exceptions():
+ @escape(ValueError)
def function():
raise ValueError
function()
-def test_decorator_with_list_of_not_muted_exceptions():
- @escape(exceptions=(ValueError,))
+def test_decorator_with_not_muted_exceptions():
+ @escape(ValueError)
def function():
raise KeyError
@@ -301,33 +284,16 @@ def function():
function()
-def test_async_decorator_with_list_of_muted_exceptions():
- @escape(exceptions=[ValueError])
+def test_async_decorator_with_muted_exceptions():
+ @escape(ValueError)
async def function():
raise ValueError
asyncio.run(function())
-def test_async_decorator_with_list_of_not_muted_exceptions():
- @escape(exceptions=[ValueError])
- async def function():
- raise KeyError
-
- with pytest.raises(KeyError):
- asyncio.run(function())
-
-
-def test_async_decorator_with_tuple_of_muted_exceptions():
- @escape(exceptions=(ValueError, ))
- async def function():
- raise ValueError
-
- asyncio.run(function())
-
-
-def test_async_decorator_with_list_of_not_muted_exceptions():
- @escape(exceptions=(ValueError,))
+def test_async_decorator_with_not_muted_exceptions():
+ @escape(ValueError)
async def function():
raise KeyError
@@ -336,7 +302,7 @@ async def function():
def test_default_default_value_is_none():
- @escape(exceptions=(ValueError,))
+ @escape(ValueError)
def function():
raise ValueError
@@ -344,7 +310,7 @@ def function():
def test_default_default_value_is_none_in_async_case():
- @escape(exceptions=(ValueError,))
+ @escape(ValueError)
async def function():
raise ValueError
@@ -358,10 +324,17 @@ def test_context_manager_normal_way():
assert variable
-def test_logging_catched_exception_without_message_usual_function():
+def test_context_manager_normal_way_with_empty_breackets():
+ with escape():
+ variable = True
+
+ assert variable
+
+
+def test_logging_catched_exception_without_message_usual_function_with_ellipsis():
logger = MemoryLogger()
- @escape(logger=logger, default='kek')
+ @escape(..., logger=logger, default='kek')
def function():
raise ValueError
@@ -369,13 +342,13 @@ def function():
assert len(logger.data.exception) == 1
assert len(logger.data) == 1
- assert logger.data.exception[0].message == f'When executing function "function", the exception "ValueError" was suppressed.'
+ assert logger.data.exception[0].message == 'When executing function "function", the exception "ValueError" was suppressed.'
-def test_logging_catched_exception_with_message_usual_function():
+def test_logging_catched_exception_with_message_usual_function_with_ellipsis():
logger = MemoryLogger()
- @escape(logger=logger, default='kek')
+ @escape(..., logger=logger, default='kek')
def function():
raise ValueError('lol kek cheburek')
@@ -383,13 +356,13 @@ def function():
assert len(logger.data.exception) == 1
assert len(logger.data) == 1
- assert logger.data.exception[0].message == f'When executing function "function", the exception "ValueError" ("lol kek cheburek") was suppressed.'
+ assert logger.data.exception[0].message == 'When executing function "function", the exception "ValueError" ("lol kek cheburek") was suppressed.'
def test_logging_not_catched_exception_without_message_usual_function():
logger = MemoryLogger()
- @escape(logger=logger, default='kek', exceptions=[ZeroDivisionError])
+ @escape(ZeroDivisionError, logger=logger, default='kek')
def function():
raise ValueError
@@ -398,13 +371,13 @@ def function():
assert len(logger.data.exception) == 1
assert len(logger.data) == 1
- assert logger.data.exception[0].message == f'When executing function "function", the exception "ValueError" was not suppressed.'
+ assert logger.data.exception[0].message == 'When executing function "function", the exception "ValueError" was not suppressed.'
def test_logging_not_catched_exception_with_message_usual_function():
logger = MemoryLogger()
- @escape(logger=logger, default='kek', exceptions=[ZeroDivisionError])
+ @escape(ZeroDivisionError, logger=logger, default='kek')
def function():
raise ValueError('lol kek cheburek')
@@ -413,13 +386,13 @@ def function():
assert len(logger.data.exception) == 1
assert len(logger.data) == 1
- assert logger.data.exception[0].message == f'When executing function "function", the exception "ValueError" ("lol kek cheburek") was not suppressed.'
+ assert logger.data.exception[0].message == 'When executing function "function", the exception "ValueError" ("lol kek cheburek") was not suppressed.'
-def test_logging_catched_exception_without_message_coroutine_function():
+def test_logging_catched_exception_without_message_coroutine_function_with_ellipsis():
logger = MemoryLogger()
- @escape(logger=logger, default='kek')
+ @escape(..., logger=logger, default='kek')
async def function():
raise ValueError
@@ -427,13 +400,13 @@ async def function():
assert len(logger.data.exception) == 1
assert len(logger.data) == 1
- assert logger.data.exception[0].message == f'When executing coroutine function "function", the exception "ValueError" was suppressed.'
+ assert logger.data.exception[0].message == 'When executing coroutine function "function", the exception "ValueError" was suppressed.'
-def test_logging_catched_exception_with_message_coroutine_function():
+def test_logging_catched_exception_with_message_coroutine_function_with_ellipsis():
logger = MemoryLogger()
- @escape(logger=logger, default='kek')
+ @escape(..., logger=logger, default='kek')
async def function():
raise ValueError('lol kek cheburek')
@@ -441,13 +414,13 @@ async def function():
assert len(logger.data.exception) == 1
assert len(logger.data) == 1
- assert logger.data.exception[0].message == f'When executing coroutine function "function", the exception "ValueError" ("lol kek cheburek") was suppressed.'
+ assert logger.data.exception[0].message == 'When executing coroutine function "function", the exception "ValueError" ("lol kek cheburek") was suppressed.'
def test_logging_not_catched_exception_without_message_coroutine_function():
logger = MemoryLogger()
- @escape(logger=logger, default='kek', exceptions=[ZeroDivisionError])
+ @escape(ZeroDivisionError, logger=logger, default='kek')
async def function():
raise ValueError
@@ -456,13 +429,13 @@ async def function():
assert len(logger.data.exception) == 1
assert len(logger.data) == 1
- assert logger.data.exception[0].message == f'When executing coroutine function "function", the exception "ValueError" was not suppressed.'
+ assert logger.data.exception[0].message == 'When executing coroutine function "function", the exception "ValueError" was not suppressed.'
def test_logging_not_catched_exception_with_message_coroutine_function():
logger = MemoryLogger()
- @escape(logger=logger, default='kek', exceptions=[ZeroDivisionError])
+ @escape(ZeroDivisionError, logger=logger, default='kek')
async def function():
raise ValueError('lol kek cheburek')
@@ -471,13 +444,13 @@ async def function():
assert len(logger.data.exception) == 1
assert len(logger.data) == 1
- assert logger.data.exception[0].message == f'When executing coroutine function "function", the exception "ValueError" ("lol kek cheburek") was not suppressed.'
+ assert logger.data.exception[0].message == 'When executing coroutine function "function", the exception "ValueError" ("lol kek cheburek") was not suppressed.'
-def test_logging_suppressed_in_a_context_exception_without_message():
+def test_logging_suppressed_in_a_context_exception_with_ellipsis_without_message():
logger = MemoryLogger()
- with escape(logger=logger):
+ with escape(..., logger=logger):
raise ValueError
assert len(logger.data.exception) == 1
@@ -485,10 +458,10 @@ def test_logging_suppressed_in_a_context_exception_without_message():
assert logger.data.exception[0].message == 'The "ValueError" exception was suppressed inside the context.'
-def test_logging_suppressed_in_a_context_exception_with_message():
+def test_logging_suppressed_in_a_context_exception_with_ellipsis_with_message():
logger = MemoryLogger()
- with escape(logger=logger):
+ with escape(..., logger=logger):
raise ValueError('lol kek cheburek')
assert len(logger.data.exception) == 1
@@ -500,7 +473,7 @@ def test_logging_not_suppressed_in_a_context_exception_without_message():
logger = MemoryLogger()
with pytest.raises(ValueError):
- with escape(logger=logger, exceptions=[ZeroDivisionError]):
+ with escape(ZeroDivisionError, logger=logger):
raise ValueError
assert len(logger.data.exception) == 1
@@ -512,9 +485,248 @@ def test_logging_not_suppressed_in_a_context_exception_with_message():
logger = MemoryLogger()
with pytest.raises(ValueError, match='lol kek cheburek'):
- with escape(logger=logger, exceptions=[ZeroDivisionError]):
+ with escape(ZeroDivisionError, logger=logger):
raise ValueError('lol kek cheburek')
assert len(logger.data.exception) == 1
assert len(logger.data) == 1
assert logger.data.exception[0].message == 'The "ValueError" ("lol kek cheburek") exception was not suppressed inside the context.'
+
+
+@pytest.mark.parametrize(
+ 'exception_type',
+ [
+ ValueError,
+ ZeroDivisionError,
+ Exception,
+ BaseException,
+ TypeError,
+ ],
+)
+def test_decorator_just_empty_breackets_when_exception(exception_type):
+ @escape()
+ def function():
+ raise exception_type('text')
+
+ with pytest.raises(exception_type, match='text'):
+ assert function() is None
+
+
+@pytest.mark.parametrize(
+ 'exception_type',
+ [
+ ValueError,
+ ZeroDivisionError,
+ Exception,
+ BaseException,
+ TypeError,
+ ],
+)
+def test_async_decorator_just_empty_breackets_when_exception(exception_type):
+ @escape()
+ async def function():
+ raise exception_type('text')
+
+ with pytest.raises(exception_type, match='text'):
+ assert asyncio.run(function()) is None
+
+
+@pytest.mark.parametrize(
+ 'exception_type',
+ [
+ ValueError,
+ ZeroDivisionError,
+ Exception,
+ BaseException,
+ TypeError,
+ ],
+)
+def test_decorator_just_empty_breackets_without_exceptions(exception_type):
+ @escape()
+ def function():
+ raise exception_type('text')
+
+ with pytest.raises(exception_type, match='text'):
+ assert function() is None
+
+
+@pytest.mark.parametrize(
+ 'exception_type',
+ [
+ ValueError,
+ ZeroDivisionError,
+ Exception,
+ BaseException,
+ TypeError,
+ ],
+)
+def test_async_decorator_just_empty_breackets_without_exceptions(exception_type):
+ @escape()
+ async def function():
+ raise exception_type('text')
+
+ with pytest.raises(exception_type, match='text'):
+ assert asyncio.run(function()) is None
+
+
+@pytest.mark.parametrize(
+ 'exception_type',
+ [
+ ValueError,
+ ZeroDivisionError,
+ Exception,
+ BaseException,
+ TypeError,
+ ],
+)
+def test_context_manager_with_empty_breackets_when_exception(exception_type):
+ with pytest.raises(exception_type, match='text'):
+ with escape():
+ raise exception_type('text')
+
+
+@pytest.mark.parametrize(
+ 'exception_type',
+ [
+ ValueError,
+ ZeroDivisionError,
+ Exception,
+ TypeError,
+ ],
+)
+def test_context_manager_with_just_ellipsis_when_escaped_by_default_exception(exception_type):
+ with escape(...):
+ raise exception_type('text')
+
+
+@pytest.mark.parametrize(
+ 'exception_type',
+ [
+ BaseException,
+ GeneratorExit,
+ KeyboardInterrupt,
+ SystemExit,
+ ],
+)
+def test_context_manager_with_just_ellipsis_when_not_escaped_by_default_exception(exception_type):
+ with pytest.raises(exception_type, match='text'):
+ with escape(...):
+ raise exception_type('text')
+
+
+@pytest.mark.parametrize(
+ 'exception_type',
+ [
+ ValueError,
+ ZeroDivisionError,
+ Exception,
+ TypeError,
+ ],
+)
+def test_decorator_with_just_ellipsis_when_escaped_by_default_exception(exception_type):
+ @escape(...)
+ def function():
+ raise exception_type('text')
+
+ assert function() is None
+
+
+@pytest.mark.parametrize(
+ 'exception_type',
+ [
+ ValueError,
+ ZeroDivisionError,
+ Exception,
+ TypeError,
+ ],
+)
+def test_async_decorator_with_just_ellipsis_when_escaped_by_default_exception(exception_type):
+ @escape(...)
+ async def function():
+ raise exception_type('text')
+
+ assert asyncio.run(function()) is None
+
+
+def test_simple_decorator_normal_way():
+ @escape
+ def function(a, b, c):
+ return a + b + c
+
+ assert function(1, 2, 3) == 6
+
+
+def test_decorator_with_empty_breackets_normal_way():
+ @escape()
+ def function(a, b, c):
+ return a + b + c
+
+ assert function(1, 2, 3) == 6
+
+
+def test_decorator_with_ellipsis_normal_way():
+ @escape(...)
+ def function(a, b, c):
+ return a + b + c
+
+ assert function(1, 2, 3) == 6
+
+
+def test_simple_async_decorator_normal_way():
+ @escape
+ async def function(a, b, c):
+ return a + b + c
+
+ assert asyncio.run(function(1, 2, 3)) == 6
+
+
+def test_async_decorator_with_empty_breackets_normal_way():
+ @escape()
+ async def function(a, b, c):
+ return a + b + c
+
+ assert asyncio.run(function(1, 2, 3)) == 6
+
+
+def test_async_decorator_with_ellipsis_normal_way():
+ @escape(...)
+ async def function(a, b, c):
+ return a + b + c
+
+ assert asyncio.run(function(1, 2, 3)) == 6
+
+
+@pytest.mark.parametrize(
+ 'exception_type',
+ [
+ BaseException,
+ GeneratorExit,
+ KeyboardInterrupt,
+ SystemExit,
+ ],
+)
+def test_decorator_with_just_ellipsis_when_not_escaped_by_default_exception(exception_type):
+ @escape(...)
+ def function():
+ raise exception_type('text')
+
+ with pytest.raises(exception_type, match='text'):
+ function()
+
+
+@pytest.mark.parametrize(
+ 'exception_type',
+ [
+ BaseException,
+ GeneratorExit,
+ KeyboardInterrupt,
+ SystemExit,
+ ],
+)
+def test_async_decorator_with_just_ellipsis_when_not_escaped_by_default_exception(exception_type):
+ @escape(...)
+ async def function():
+ raise exception_type('text')
+
+ with pytest.raises(exception_type, match='text'):
+ asyncio.run(function())