Skip to content

Commit 7fe2ebc

Browse files
committed
Merge pull request #345 from bcipolli/refactor_warning_tests
MRG: Simplify interface for testing of warnings. This is a fix for #343 New interface: suppress_warnings, clear_and_catch_warnings, and error_warnings. Deprecate ErrorWarnings, IgnoreWarnings, and catch_warn_reset; nibabel.checkwarns.
2 parents 5e10a6d + 2d7e01a commit 7fe2ebc

8 files changed

+239
-147
lines changed

nibabel/checkwarns.py

+19-35
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,23 @@
1212

1313
import warnings
1414

15+
from .testing import (error_warnings, suppress_warnings)
1516

16-
class ErrorWarnings(warnings.catch_warnings):
17-
""" Context manager to check for warnings as errors. Usually used with
18-
``assert_raises`` in the with block
19-
20-
Examples
21-
--------
22-
>>> with ErrorWarnings():
23-
... try:
24-
... warnings.warn('Message', UserWarning)
25-
... except UserWarning:
26-
... print('I consider myself warned')
27-
I consider myself warned
28-
"""
29-
filter = 'error'
30-
31-
def __init__(self, record=True, module=None):
32-
super(ErrorWarnings, self).__init__(record=record, module=module)
33-
34-
def __enter__(self):
35-
mgr = super(ErrorWarnings, self).__enter__()
36-
warnings.simplefilter(self.filter)
37-
return mgr
38-
39-
40-
class IgnoreWarnings(ErrorWarnings):
41-
""" Context manager to ignore warnings
42-
43-
Examples
44-
--------
45-
>>> with IgnoreWarnings():
46-
... warnings.warn('Message', UserWarning)
47-
48-
(and you get no warning)
49-
"""
50-
filter = 'ignore'
17+
18+
warnings.warn('The checkwarns module is deprecated and will be removed in nibabel v3.0', FutureWarning)
19+
20+
21+
class ErrorWarnings(error_warnings):
22+
def __init__(self, *args, **kwargs):
23+
warnings.warn('ErrorWarnings is deprecated and will be removed in '
24+
'nibabel v3.0; use nibabel.testing.error_warnings.',
25+
FutureWarning)
26+
super(ErrorWarnings, self).__init__(*args, **kwargs)
27+
28+
29+
class IgnoreWarnings(suppress_warnings):
30+
def __init__(self, *args, **kwargs):
31+
warnings.warn('IgnoreWarnings is deprecated and will be removed in '
32+
'nibabel v3.0; use nibabel.testing.suppress_warnings.',
33+
FutureWarning)
34+
super(IgnoreWarnings, self).__init__(*args, **kwargs)

nibabel/testing/__init__.py

+98-22
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,23 @@
77
#
88
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
99
''' Utilities for testing '''
10+
from __future__ import division, print_function
11+
12+
import sys
13+
import warnings
1014
from os.path import dirname, abspath, join as pjoin
1115

1216
import numpy as np
13-
from warnings import catch_warnings, simplefilter
14-
15-
# set path to example data
16-
data_path = abspath(pjoin(dirname(__file__), '..', 'tests', 'data'))
1717

1818
# Allow failed import of nose if not now running tests
1919
try:
20-
import nose.tools as nt
21-
except ImportError:
22-
pass
23-
else:
2420
from nose.tools import (assert_equal, assert_not_equal,
2521
assert_true, assert_false, assert_raises)
22+
except ImportError:
23+
pass
24+
25+
# set path to example data
26+
data_path = abspath(pjoin(dirname(__file__), '..', 'tests', 'data'))
2627

2728

2829
def assert_dt_equal(a, b):
@@ -56,35 +57,110 @@ def assert_allclose_safely(a, b, match_nans=True):
5657
assert_true(np.allclose(a, b))
5758

5859

59-
class suppress_warnings(catch_warnings):
60-
""" Version of ``catch_warnings`` class that suppresses warnings
61-
"""
62-
def __enter__(self):
63-
res = super(suppress_warnings, self).__enter__()
64-
simplefilter('ignore')
65-
return res
60+
def get_fresh_mod(mod_name=__name__):
61+
# Get this module, with warning registry empty
62+
my_mod = sys.modules[mod_name]
63+
try:
64+
my_mod.__warningregistry__.clear()
65+
except AttributeError:
66+
pass
67+
return my_mod
68+
69+
70+
class clear_and_catch_warnings(warnings.catch_warnings):
71+
""" Context manager that resets warning registry for catching warnings
72+
73+
Warnings can be slippery, because, whenever a warning is triggered, Python
74+
adds a ``__warningregistry__`` member to the *calling* module. This makes
75+
it impossible to retrigger the warning in this module, whatever you put in
76+
the warnings filters. This context manager accepts a sequence of `modules`
77+
as a keyword argument to its constructor and:
78+
79+
* stores and removes any ``__warningregistry__`` entries in given `modules`
80+
on entry;
81+
* resets ``__warningregistry__`` to its previous state on exit.
82+
83+
This makes it possible to trigger any warning afresh inside the context
84+
manager without disturbing the state of warnings outside.
6685
86+
For compatibility with Python 3.0, please consider all arguments to be
87+
keyword-only.
6788
68-
class catch_warn_reset(catch_warnings):
69-
""" Version of ``catch_warnings`` class that resets warning registry
89+
Parameters
90+
----------
91+
record : bool, optional
92+
Specifies whether warnings should be captured by a custom
93+
implementation of ``warnings.showwarning()`` and be appended to a list
94+
returned by the context manager. Otherwise None is returned by the
95+
context manager. The objects appended to the list are arguments whose
96+
attributes mirror the arguments to ``showwarning()``.
97+
98+
NOTE: nibabel difference from numpy: default is True
99+
100+
modules : sequence, optional
101+
Sequence of modules for which to reset warnings registry on entry and
102+
restore on exit
103+
104+
Examples
105+
--------
106+
>>> import warnings
107+
>>> with clear_and_catch_warnings(modules=[np.core.fromnumeric]):
108+
... warnings.simplefilter('always')
109+
... # do something that raises a warning in np.core.fromnumeric
70110
"""
71-
def __init__(self, *args, **kwargs):
72-
self.modules = kwargs.pop('modules', [])
111+
class_modules = ()
112+
113+
def __init__(self, record=True, modules=()):
114+
self.modules = set(modules).union(self.class_modules)
73115
self._warnreg_copies = {}
74-
super(catch_warn_reset, self).__init__(*args, **kwargs)
116+
super(clear_and_catch_warnings, self).__init__(record=record)
75117

76118
def __enter__(self):
77119
for mod in self.modules:
78120
if hasattr(mod, '__warningregistry__'):
79121
mod_reg = mod.__warningregistry__
80122
self._warnreg_copies[mod] = mod_reg.copy()
81123
mod_reg.clear()
82-
return super(catch_warn_reset, self).__enter__()
124+
return super(clear_and_catch_warnings, self).__enter__()
83125

84126
def __exit__(self, *exc_info):
85-
super(catch_warn_reset, self).__exit__(*exc_info)
127+
super(clear_and_catch_warnings, self).__exit__(*exc_info)
86128
for mod in self.modules:
87129
if hasattr(mod, '__warningregistry__'):
88130
mod.__warningregistry__.clear()
89131
if mod in self._warnreg_copies:
90132
mod.__warningregistry__.update(self._warnreg_copies[mod])
133+
134+
135+
class error_warnings(clear_and_catch_warnings):
136+
""" Context manager to check for warnings as errors. Usually used with
137+
``assert_raises`` in the with block
138+
139+
Examples
140+
--------
141+
>>> with error_warnings():
142+
... try:
143+
... warnings.warn('Message', UserWarning)
144+
... except UserWarning:
145+
... print('I consider myself warned')
146+
I consider myself warned
147+
"""
148+
filter = 'error'
149+
150+
def __enter__(self):
151+
mgr = super(error_warnings, self).__enter__()
152+
warnings.simplefilter(self.filter)
153+
return mgr
154+
155+
156+
class suppress_warnings(error_warnings):
157+
""" Version of ``catch_warnings`` class that suppresses warnings
158+
"""
159+
filter = 'ignore'
160+
161+
162+
class catch_warn_reset(clear_and_catch_warnings):
163+
def __init__(self, *args, **kwargs):
164+
warnings.warn('catch_warn_reset is deprecated and will be removed in '
165+
'nibabel v3.0; use nibabel.testing.clear_and_catch_warnings.',
166+
FutureWarning)

nibabel/tests/test_arraywriters.py

+4-9
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,19 @@
1111
import numpy as np
1212

1313
from ..externals.six import BytesIO
14-
1514
from ..arraywriters import (SlopeInterArrayWriter, SlopeArrayWriter,
1615
WriterError, ScalingError, ArrayWriter,
1716
make_array_writer, get_slope_inter)
18-
1917
from ..casting import int_abs, type_info, shared_range, on_powerpc
20-
2118
from ..volumeutils import array_from_file, apply_read_scaling, _dt_min_max
2219

2320
from numpy.testing import (assert_array_almost_equal,
2421
assert_array_equal)
25-
2622
from nose.tools import (assert_true, assert_false,
2723
assert_equal, assert_not_equal,
2824
assert_raises)
29-
30-
from ..testing import assert_allclose_safely, suppress_warnings
31-
from ..checkwarns import ErrorWarnings
25+
from ..testing import (assert_allclose_safely, suppress_warnings,
26+
error_warnings)
3227

3328

3429
FLOAT_TYPES = np.sctypes['float']
@@ -524,7 +519,7 @@ def test_nan2zero():
524519
data_back = round_trip(aw)
525520
assert_array_equal(np.isnan(data_back), [True, False])
526521
# Deprecation warning for nan2zero as argument to `to_fileobj`
527-
with ErrorWarnings():
522+
with error_warnings():
528523
assert_raises(DeprecationWarning,
529524
aw.to_fileobj, BytesIO(), 'F', True)
530525
assert_raises(DeprecationWarning,
@@ -545,7 +540,7 @@ def test_nan2zero():
545540
astype_res = np.array(np.nan).astype(np.int32)
546541
assert_array_equal(data_back, [astype_res, 99])
547542
# Deprecation warning for nan2zero as argument to `to_fileobj`
548-
with ErrorWarnings():
543+
with error_warnings():
549544
assert_raises(DeprecationWarning,
550545
aw.to_fileobj, BytesIO(), 'F', False)
551546
assert_raises(DeprecationWarning,

nibabel/tests/test_checkwarns.py

+12-33
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,21 @@
11
""" Tests for warnings context managers
22
"""
3-
43
from __future__ import division, print_function, absolute_import
54

6-
from warnings import warn, simplefilter, filters
7-
8-
from ..checkwarns import ErrorWarnings, IgnoreWarnings
9-
105
from nose.tools import assert_true, assert_equal, assert_raises
6+
from ..testing import clear_and_catch_warnings, suppress_warnings
117

128

13-
def test_warn_error():
14-
# Check warning error context manager
15-
n_warns = len(filters)
16-
with ErrorWarnings():
17-
assert_raises(UserWarning, warn, 'A test')
18-
with ErrorWarnings() as w: # w not used for anything
19-
assert_raises(UserWarning, warn, 'A test')
20-
assert_equal(n_warns, len(filters))
21-
# Check other errors are propagated
22-
def f():
23-
with ErrorWarnings():
24-
raise ValueError('An error')
25-
assert_raises(ValueError, f)
9+
def test_ignore_and_error_warnings():
10+
with suppress_warnings():
11+
from .. import checkwarns
2612

13+
with clear_and_catch_warnings() as w:
14+
checkwarns.IgnoreWarnings()
15+
assert_equal(len(w), 1)
16+
assert_equal(w[0].category, FutureWarning)
2717

28-
def test_warn_ignore():
29-
# Check warning ignore context manager
30-
n_warns = len(filters)
31-
with IgnoreWarnings():
32-
warn('Here is a warning, you will not see it')
33-
warn('Nor this one', DeprecationWarning)
34-
with IgnoreWarnings() as w: # w not used
35-
warn('Here is a warning, you will not see it')
36-
warn('Nor this one', DeprecationWarning)
37-
assert_equal(n_warns, len(filters))
38-
# Check other errors are propagated
39-
def f():
40-
with IgnoreWarnings():
41-
raise ValueError('An error')
42-
assert_raises(ValueError, f)
18+
with clear_and_catch_warnings() as w:
19+
checkwarns.ErrorWarnings()
20+
assert_equal(len(w), 1)
21+
assert_equal(w[0].category, FutureWarning)

nibabel/tests/test_openers.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@
1212
from bz2 import BZ2File
1313
from io import BytesIO, UnsupportedOperation
1414

15-
from ..checkwarns import ErrorWarnings
1615
from ..py3k import asstr, asbytes
1716
from ..openers import Opener, ImageOpener
1817
from ..tmpdirs import InTemporaryDirectory
1918
from ..volumeutils import BinOpener
2019

2120
from nose.tools import (assert_true, assert_false, assert_equal,
2221
assert_not_equal, assert_raises)
22+
from ..testing import error_warnings
23+
2324

2425
class Lunk(object):
2526
# bare file-like for testing
@@ -84,7 +85,7 @@ def test_Opener_various():
8485
assert_not_equal(fobj.fileno(), 0)
8586

8687
def test_BinOpener():
87-
with ErrorWarnings():
88+
with error_warnings():
8889
assert_raises(DeprecationWarning,
8990
BinOpener, 'test.txt', 'r')
9091

nibabel/tests/test_parrec.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from nose.tools import (assert_true, assert_false, assert_raises,
2424
assert_equal)
2525

26-
from ..testing import catch_warn_reset, suppress_warnings
26+
from ..testing import clear_and_catch_warnings, suppress_warnings
2727

2828
from .test_arrayproxy import check_mmap
2929
from . import test_spatialimages as tsi
@@ -237,7 +237,7 @@ def test_affine_regression():
237237

238238
def test_get_voxel_size_deprecated():
239239
hdr = PARRECHeader(HDR_INFO, HDR_DEFS)
240-
with catch_warn_reset(modules=[parrec], record=True) as wlist:
240+
with clear_and_catch_warnings(modules=[parrec], record=True) as wlist:
241241
simplefilter('always')
242242
hdr.get_voxel_size()
243243
assert_equal(wlist[0].category, DeprecationWarning)
@@ -255,7 +255,7 @@ def test_get_sorted_slice_indices():
255255
17, 16, 15, 14, 13, 12, 11, 10, 9,
256256
26, 25, 24, 23, 22, 21, 20, 19, 18])
257257
# Omit last slice, only two volumes
258-
with catch_warn_reset(modules=[parrec], record=True):
258+
with clear_and_catch_warnings(modules=[parrec], record=True):
259259
hdr = PARRECHeader(HDR_INFO, HDR_DEFS[:-1], permit_truncated=True)
260260
assert_array_equal(hdr.get_sorted_slice_indices(), range(n_slices - 9))
261261

@@ -300,7 +300,7 @@ def test_truncated_load():
300300
with open(TRUNC_PAR, 'rt') as fobj:
301301
gen_info, slice_info = parse_PAR_header(fobj)
302302
assert_raises(PARRECError, PARRECHeader, gen_info, slice_info)
303-
with catch_warn_reset(record=True) as wlist:
303+
with clear_and_catch_warnings(record=True) as wlist:
304304
hdr = PARRECHeader(gen_info, slice_info, True)
305305
assert_equal(len(wlist), 1)
306306

@@ -373,7 +373,7 @@ def test_truncations():
373373
# Drop one line, raises error
374374
assert_raises(PARRECError, PARRECHeader, gen_info, slice_info[:-1])
375375
# When we are permissive, we raise a warning, and drop a volume
376-
with catch_warn_reset(modules=[parrec], record=True) as wlist:
376+
with clear_and_catch_warnings(modules=[parrec], record=True) as wlist:
377377
hdr = PARRECHeader(gen_info, slice_info[:-1], permit_truncated=True)
378378
assert_equal(len(wlist), 1)
379379
assert_equal(hdr.get_data_shape(), (80, 80, 10))

0 commit comments

Comments
 (0)