Skip to content

pytest.raises 3.5.0 breaks for exception classes that look iterable (e.g. spyne.error.RequestNotAllowed) #3372

@backbord

Description

@backbord

Hi,

I recently update to pytest 3.5.0 and use it to test applications that use spyne.

Unfortunately, my tests that have code like

import pytest
from spyne.errors import RequestNotAllowed, InvalidCredentialsError

import mymodule

def test_a():
    with pytest.raises(RequestNotAllowed):
        mymodule.func_that_raises_request_not_allowed()

def test_b():
    with pytest.raises(InvalidCredentialsError):
        mymodule.func_that_raises_invalid_credentials_error()

now break in the lines starting on with pytest.raises and give a traceback similar to this one:

___ test_detect_replay_nonce[example_forecast.xml.gz] ___

    def test_a():
>       with pytest.raises(RequestNotAllowed):

tests/test_client.py:130: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/foobar/lib/python3.6/site-packages/_pytest/python_api.py:587: in raises
    for exc in filterfalse(isclass, always_iterable(expected_exception)):
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <class 'spyne.error.RequestNotAllowed'>, item = 0

    def __getitem__(self, item):
>       return self.customize(**item)
E       TypeError: customize() argument after ** must be a mapping, not int

/foobar/lib/python3.6/site-packages/spyne/model/_base.py:179: TypeError

The issue didn't come up in previous versions of pytest so I dug a bit and found that

  • pytest.raises changed in version 3.5.0 to use always_iterable,
  • the exception classes used by spyne both have a __len__ and a __getitem__ function,
  • python thinks that the exception class is iterable,
    and that this is the cause of these unexpected problems.

I don't know whether exception classes that look iterable are generally disallowed or if this is something that should be supported/fixed in pytest.
I didn't find this behavior change to be mentioned in the changelog but I may have not looked thoroughly enough.

Perhaps you can advise?

Thanks!
Tim


Here is some IPython output from my digging:

Python 3.6.5
Type 'copyright', 'credits' or 'license' for more information
IPython 6.3.0 -- An enhanced Interactive Python. Type '?' for help.
Warning: disable autoreload in ipython_config.py to improve performance.

In [1]: from spyne.error import InvalidCredentialsError

In [2]: hasattr(InvalidCredentialsError, '__getitem__')
Out[2]: True

In [3]: hasattr(InvalidCredentialsError, '__len__')
Out[3]: True

In [4]: from more_itertools.more import always_iterable

In [5]: next(always_iterable(InvalidCredentialsError))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-24d61822a93e> in <module>()
----> 1 next(always_iterable(InvalidCredentialsError))

/foobar/lib/python3.6/site-packages/spyne/model/_base.py in __getitem__(self, item)
    177 class ModelBaseMeta(type(object)):
    178     def __getitem__(self, item):
--> 179         return self.customize(**item)
    180
    181     def customize(self, **kwargs):

TypeError: customize() argument after ** must be a mapping, not int

always_iterable attempts to create an iterable by executing

try:
    return iter(obj)
except TypeError:
    return iter((obj,))
In [6]: next(iter(InvalidCredentialsError))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-5759e2f7cde0> in <module>()
----> 1 next(iter(InvalidCredentialsError))

/foobar/lib/python3.6/site-packages/spyne/model/_base.py in __getitem__(self, item)
    177 class ModelBaseMeta(type(object)):
    178     def __getitem__(self, item):
--> 179         return self.customize(**item)
    180
    181     def customize(self, **kwargs):

TypeError: customize() argument after ** must be a mapping, not int

I'm using

  • python 3.6.5
  • pytest 3.5.0
  • spyne 2.12.14

It works in pytest 3.4.2 where always_iterable isn't used in functionraises of _pytest/python_api.py.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: bugproblem that needs to be addressedtype: regressionindicates a problem that was introduced in a release which was working previously

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions