From 03e805f9d1036d1d34cb5eae9f6c99f07d64adfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <piotr.maslanka@henrietta.com.pl> Date: Sun, 4 Apr 2021 21:00:08 +0200 Subject: [PATCH] added expect_exception --- CHANGELOG.md | 2 ++ docs/coding/functions.rst | 2 ++ satella/__init__.py | 2 +- satella/coding/__init__.py | 2 ++ satella/coding/expect_exception.py | 40 ++++++++++++++++++++++++++++++ tests/test_coding/test_debug.py | 19 +++++++++++++- 6 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 satella/coding/expect_exception.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7188f05e..1ef88df5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,3 @@ # v2.15.5 + +* added `expect_exception` diff --git a/docs/coding/functions.rst b/docs/coding/functions.rst index d0fd22cf..c68e74ee 100644 --- a/docs/coding/functions.rst +++ b/docs/coding/functions.rst @@ -2,6 +2,8 @@ Functions and decorators ======================== +.. autoclass:: satella.coding.expect_exception + .. autofunction:: satella.coding.enum_value .. autofunction:: satella.coding.contains diff --git a/satella/__init__.py b/satella/__init__.py index 08ca7568..c9353a9f 100644 --- a/satella/__init__.py +++ b/satella/__init__.py @@ -1 +1 @@ -__version__ = '2.15.5a1' +__version__ = '2.15.5' diff --git a/satella/coding/__init__.py b/satella/coding/__init__.py index 089da2f4..fa8cfbfa 100644 --- a/satella/coding/__init__.py +++ b/satella/coding/__init__.py @@ -19,9 +19,11 @@ from .misc import update_if_not_none, update_key_if_none, update_attr_if_none, q from .overloading import overload, class_or_instancemethod from .recast_exceptions import rethrow_as, silence_excs, catch_exception, log_exceptions, \ raises_exception +from .expect_exception import expect_exception __all__ = [ 'Closeable', 'contains', 'enum_value', + 'expect_exception', 'overload', 'class_or_instancemethod', 'update_if_not_none', 'DocsFromParent', 'update_key_if_none', 'queue_iterator', 'update_attr_if_none', 'update_key_if_not_none', 'source_to_function', diff --git a/satella/coding/expect_exception.py b/satella/coding/expect_exception.py new file mode 100644 index 00000000..06b2cc65 --- /dev/null +++ b/satella/coding/expect_exception.py @@ -0,0 +1,40 @@ +from satella.coding.typing import ExceptionList +import typing as tp + + +class expect_exception: + """ + A decorator to use as following: + + >>> a = {'test': 2} + >>> with expect_exception(KeyError, ValueError, 'KeyError not raised'): + >>> a['test2'] + + If other exception than the expected is raised, it is passed through + + :param exc_to_except: a list of exceptions or a single exception to expect + :param else_raise: raise a particular exception if no exception is raised. + This should be a callable that accepts provided args and kwargs and returns + an exception instance. + :param args: args to provide to constructor + :param kwargs: kwargs to provide to constructor + """ + __slots__ = ('exc_to_except', 'else_raise', 'else_raise_args', 'else_raise_kwargs') + + def __init__(self, exc_to_except: ExceptionList, else_raise: tp.Type[Exception], + *args, **kwargs): + self.exc_to_except = exc_to_except + self.else_raise = else_raise + self.else_raise_args = args + self.else_raise_kwargs = kwargs + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type is None: + raise self.else_raise(*self.else_raise_args, + **self.else_raise_kwargs) + elif not isinstance(exc_val, self.exc_to_except): + return False + return True diff --git a/tests/test_coding/test_debug.py b/tests/test_coding/test_debug.py index b08c641a..23d40509 100644 --- a/tests/test_coding/test_debug.py +++ b/tests/test_coding/test_debug.py @@ -1,13 +1,30 @@ import unittest from satella.coding import precondition, short_none, has_keys, update_if_not_none, postcondition, \ - get_arguments + get_arguments, expect_exception from satella.coding.decorators import for_argument from satella.exceptions import PreconditionError class TestTypecheck(unittest.TestCase): + def test_except_exception(self): + def expect_exception_1(): + with expect_exception(KeyError, ValueError, 'Hello world!'): + pass + + self.assertRaises(ValueError, expect_exception_1) + + a = {} + with expect_exception(KeyError, ValueError, 'Hello world!'): + a['test'] + + def expect_exception_2(): + with expect_exception(KeyError, ValueError, 'Hello world!'): + raise TypeError() + + self.assertRaises(TypeError, expect_exception_2) + def test_for_argument_bug(self): class Device: @for_argument(None, str) -- GitLab