warnings.simplefilter("always") is not forcing warnings to be made in Python 2.7

Viewed 805

warnings.simplefilter('always') does not seem to cause warnings to be repeated in Python 2.7. It seems to have the same effect as 'once'.

I am trying to test some code that issues warnings. Half of the time, the warning I am trying to test has already been issued, so my test uses simplefilter() to ensure that the warning is issued again. This problem only occurs in Python 2.7. The other versions all seem to work as expected.

I have followed the example in the Python 2.7 documentation https://docs.python.org/2/library/warnings.html#testing-warnings The example works as-is, but if I add a call to fxn() right before the test code, the warning is not raised and len(w) = 0 instead of 1.

Specifically,

import warnings

def fxn():
    warnings.warn("deprecated", DeprecationWarning)

fxn()

with warnings.catch_warnings(record=True) as w:
    # Cause all warnings to always be triggered.
    warnings.simplefilter("always")
    # Trigger a warning.
    fxn()
    # Verify some things
    assert len(w) == 1
    assert issubclass(w[-1].category, DeprecationWarning)
    assert "deprecated" in str(w[-1].message)

Expected result is to run to the end with no output. Actual result in Python 2.7.14 and 2.7.15:

Traceback (most recent call last):
  File "warntest.py", line 14, in <module>
    assert len(w) == 1
AssertionError

Update: I also found that calling warnings.resetwarnings() after the first warning is issued does not work.

import warnings

def fxn():
    warnings.warn("deprecated", DeprecationWarning)

fxn()
warnings.resetwarnings()

with warnings.catch_warnings(record=True) as w:
    # Cause all warnings to always be triggered.
    warnings.simplefilter("always")
    # Trigger a warning.
    fxn()
    # Verify some things
    assert len(w) == 1
    assert issubclass(w[-1].category, DeprecationWarning)
    assert "deprecated" in str(w[-1].message)

fxn()

Result:

Traceback (most recent call last):
  File "warntest.py", line 15, in <module>
    assert len(w) == 1
AssertionError
2 Answers

This is definitely a bug with Python 2.7, but I implemented a work-around based on Tom's recommendation. The workaround causes my unit tests to emit a ton of warnings for Python 2.7, but it passes. Basically, when the Python major version is 2, I added

warnings.simplefilter('always')

before the test begins, and I skip the individual

warnings.simplefilter('ignore')

I had for in the tests that cause warnings that should be ignored.

I'm in a situation where I can't control the filter before the warning might be raised the first time. What I needed to do for a workaround is smash the record of the warning having been raised. This is stored in __warningregistry__ in the module where the warning was raised. The key is a tuple of (warning string, warning type, line number). If the value is truthy the warning will be skipped; if falsey or there is no such key, it'll be issued. In practice the warnings module never seems to set it to anything not truthy.

So if you're calling something in module that potentially raises a warning, the necessary steps are:

if sys.version_info[0:2] == (2,7) and hasattr(module, '__warningregistry__'):
    for k in module.__warningregistry__.keys():
        if k[0].startswith(
            'Expected warning string') \
            and k[1] is UserWarning:
                del module.__warningregistry__[k]
                    break
warnings.simplefilter('always')

I freely admit this is simply awful.

Related