PKN+pytest_raisin.py"""Plugin enabling the use of exception instances with pytest.raises""" import logging import sys import pytest __version__ = "0.3" log = logging.getLogger(__name__) original = pytest.raises # Mapping of exception subclasses (keys) to callables (values). exception_comparers = {} def register_exception_compare(exception_class): """Plugin users may register their own errors/callables here by placing pytest-raisin's decorator factory above a function: @pytest.register_exception_compare(MyError) def my_error_compare(exc_actual, exc_expected): ... The arguments exc_actual and exc_expected should be instances of MyError. The function should raise an AssertionError with useful context message should they fail to match. It should return None if the exceptions should be considered equivalent. """ exception_classes = exception_class if not isinstance(exception_class, tuple): exception_classes = (exception_class,) for exception_class in exception_classes: if not isinstance(exception_class, type) or not issubclass(exception_class, BaseException): msg = "Can not register {!r}, it's not an Exception subclass" msg = msg.format(exception_class) raise TypeError(msg) def decorator(func): if not callable(func): raise TypeError('You are decorating a non callable: {!r}'.format(func)) for exception_class in exception_classes: if exception_class in exception_comparers: log.warning("%r was registered multiple times", exception_class) exception_comparers[exception_class] = func log.debug("Registered %r to handle %r comparisons", func, exception_class) return func return decorator def raises(expected_exception, *args, **kwargs): """Monkeypatched in by pytest-raisin plugin""" if isinstance(expected_exception, BaseException) and not args and "message" not in kwargs: message = "DID NOT RAISE {!r}".format(expected_exception) return RaisesContext(expected_exception, message, kwargs.get("match")) # There are some strange non-context usages of raises, e.g. passing a callable along # with args/kwargs or passing a code string to be exec. I don't want to support those # weird use-cases, so just fall-back to original implementation if we received any # positional values i.e. a non-empty *args return original(expected_exception, *args, **kwargs) def default_compare(exc_actual, exc_expected): __tracebackhide__ = True actual_args = getattr(exc_actual, "args", None) expected_args = getattr(exc_expected, "args", None) if actual_args == expected_args: return msg = "{} args do not match!\n Actual: {}\n Expected: {}" msg = msg.format(type(exc_expected).__name__, actual_args, expected_args) err = AssertionError(msg) err.__cause__ = None # as a side-effect, this also sets __suppress_context__ # that's a cross-compatible way of disabling chaining i.e. squashing the message # "During handling of the above exception, another exception occurred" raise err class RaisesContext(type(pytest.raises(Exception))): def __exit__(self, *tp): __tracebackhide__ = True if tp[0] is None: pytest.fail(self.message) self.excinfo.__init__(tp) # note: subclasses are not allowed here! suppress_exception = self.excinfo.type is type(self.expected_exception) if sys.version_info < (3,) and suppress_exception: sys.exc_clear() if suppress_exception: compare = exception_comparers.get(type(self.expected_exception), default_compare) compare(self.excinfo.value, self.expected_exception) if self.match_expr is not None and suppress_exception: self.excinfo.match(self.match_expr) return suppress_exception def pytest_configure(config): # this is called once after command line args have been parsed pytest.raises = raises pytest.register_exception_compare = register_exception_compare def pytest_unconfigure(config): pytest.raises = original vars(pytest).pop("register_exception_compare", None) pytest.register_exception_compare = register_exception_compare PK!H(,pytest_raisin-0.3.dist-info/entry_points.txt.,I-.14傰t33l!x PK!HMuSa!pytest_raisin-0.3.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UD"PK!Hj$pytest_raisin-0.3.dist-info/METADATAWQs6~ئ4"eqji8q2MGHPDM,J-HJb&lX.vvZ:'ŸJ z̓QUօF(`/puQ,m^oTI\rJ)ɇXVGH։2veވJG23*6QN\l*őnyMpYL 7VBYCluJ(_RN.k*tBc]2֪TIhLbAon鲪ʄ__ۃ#q= 5z(75Љfʟ?ke u}ϪԟKmsoΈSD2 !qpd3%FJXh. kX$Elfo2v仳,օ^`Z)&kzmPE!jTa>VޫwLm"WⶆOgϞOO?>*|T=? O|. ,<{=9M$GUǿ ^qa &70eSKrVӿzۨ|r ~xqQ3](*TL=e\;ywm2x&=LHXMڊSe*h<$D۞]-##H3+y`#/0ZpΨudsD\H(dA@D_қ< gmJ*^=(7ąml$>y"È ͧ؍Q])9VFUPਸ਼S@H(FvSmWo"SUbA%[c. b[J4 B#+dL'_-LjdpyQ8H>JUm'szf B0p]CrW#&/( -QCX. <],4:GG :?vZ7-v槇zyű=B.{NNw^5Q7qكݫ\P|&J.ξ ңrId:7_u_`w狼O0t%3o_;Llnя׷ M0UKʶ*~" <@l*1gxZ1TA |&;*.*mӼςgǭtLaL,Y-7;qRe;HSJK)k*;BDrYy3 Ya@ ?vv-e辌h_Ja>xD4svb]O?ՒbE[t48 Ig&k\s`z.jE6 8JygٓF2Dt*QXgd;{(Vcߑ]TS;ڡxf<0:]оb\6ܱ~Ϝb͞پBvev3[% >JU[óZu 'z䕊f] ȲSovзi gl0M͵?^PKj?aZl:{<}yt)v/ I P4xxG:ܩJXe쿇)kY쑋Һ0^)¦ZL[ Z'HE2Bmڰ]J.~G<dr\L&ty؊9 Uܲ$j|c{?Km]֣O{PlV{`VfSPK!Hec"pytest_raisin-0.3.dist-info/RECORD}n022(Q .a%X&OufK3-g8LD΅|S \ kAUʍ o~*R(%R#F>f(zV'qZ\|9..q& 5|#>̉JKh00 ]XuClZiO#72_p*ngLF4mqJ-]ʼn PKN+pytest_raisin.pyPK!H(,(pytest_raisin-0.3.dist-info/entry_points.txtPK!HMuSa!pytest_raisin-0.3.dist-info/WHEELPK!Hj$!pytest_raisin-0.3.dist-info/METADATAPK!Hec"Kpytest_raisin-0.3.dist-info/RECORDPK