1
+ """
2
+ Shim for NumPy's suppress_warnings
3
+ """
4
+
5
+
6
+ try :
7
+ from numpy .testing import suppress_warnings
8
+ except ImportError :
9
+
10
+ # The following two classes are copied from python 2.6 warnings module (context
11
+ # manager)
12
+ class WarningMessage (object ):
13
+
14
+ """
15
+ Holds the result of a single showwarning() call.
16
+ Deprecated in 1.8.0
17
+ Notes
18
+ -----
19
+ `WarningMessage` is copied from the Python 2.6 warnings module,
20
+ so it can be used in NumPy with older Python versions.
21
+ """
22
+
23
+ _WARNING_DETAILS = ("message" , "category" , "filename" , "lineno" , "file" ,
24
+ "line" )
25
+
26
+ def __init__ (self , message , category , filename , lineno , file = None ,
27
+ line = None ):
28
+ local_values = locals ()
29
+ for attr in self ._WARNING_DETAILS :
30
+ setattr (self , attr , local_values [attr ])
31
+ if category :
32
+ self ._category_name = category .__name__
33
+ else :
34
+ self ._category_name = None
35
+
36
+ def __str__ (self ):
37
+ return ("{message : %r, category : %r, filename : %r, lineno : %s, "
38
+ "line : %r}" % (self .message , self ._category_name ,
39
+ self .filename , self .lineno , self .line ))
40
+
41
+ import re
42
+ import warnings
43
+ from functools import wraps
44
+
45
+ class suppress_warnings (object ):
46
+ """
47
+ Context manager and decorator doing much the same as
48
+ ``warnings.catch_warnings``.
49
+ However, it also provides a filter mechanism to work around
50
+ http://bugs.python.org/issue4180.
51
+ This bug causes Python before 3.4 to not reliably show warnings again
52
+ after they have been ignored once (even within catch_warnings). It
53
+ means that no "ignore" filter can be used easily, since following
54
+ tests might need to see the warning. Additionally it allows easier
55
+ specificity for testing warnings and can be nested.
56
+ Parameters
57
+ ----------
58
+ forwarding_rule : str, optional
59
+ One of "always", "once", "module", or "location". Analogous to
60
+ the usual warnings module filter mode, it is useful to reduce
61
+ noise mostly on the outmost level. Unsuppressed and unrecorded
62
+ warnings will be forwarded based on this rule. Defaults to "always".
63
+ "location" is equivalent to the warnings "default", match by exact
64
+ location the warning warning originated from.
65
+ Notes
66
+ -----
67
+ Filters added inside the context manager will be discarded again
68
+ when leaving it. Upon entering all filters defined outside a
69
+ context will be applied automatically.
70
+ When a recording filter is added, matching warnings are stored in the
71
+ ``log`` attribute as well as in the list returned by ``record``.
72
+ If filters are added and the ``module`` keyword is given, the
73
+ warning registry of this module will additionally be cleared when
74
+ applying it, entering the context, or exiting it. This could cause
75
+ warnings to appear a second time after leaving the context if they
76
+ were configured to be printed once (default) and were already
77
+ printed before the context was entered.
78
+ Nesting this context manager will work as expected when the
79
+ forwarding rule is "always" (default). Unfiltered and unrecorded
80
+ warnings will be passed out and be matched by the outer level.
81
+ On the outmost level they will be printed (or caught by another
82
+ warnings context). The forwarding rule argument can modify this
83
+ behaviour.
84
+ Like ``catch_warnings`` this context manager is not threadsafe.
85
+ Examples
86
+ --------
87
+ >>> with suppress_warnings() as sup:
88
+ ... sup.filter(DeprecationWarning, "Some text")
89
+ ... sup.filter(module=np.ma.core)
90
+ ... log = sup.record(FutureWarning, "Does this occur?")
91
+ ... command_giving_warnings()
92
+ ... # The FutureWarning was given once, the filtered warnings were
93
+ ... # ignored. All other warnings abide outside settings (may be
94
+ ... # printed/error)
95
+ ... assert_(len(log) == 1)
96
+ ... assert_(len(sup.log) == 1) # also stored in log attribute
97
+ Or as a decorator:
98
+ >>> sup = suppress_warnings()
99
+ >>> sup.filter(module=np.ma.core) # module must match exact
100
+ >>> @sup
101
+ >>> def some_function():
102
+ ... # do something which causes a warning in np.ma.core
103
+ ... pass
104
+ """
105
+ def __init__ (self , forwarding_rule = "always" ):
106
+ self ._entered = False
107
+
108
+ # Suppressions are either instance or defined inside one with block:
109
+ self ._suppressions = []
110
+
111
+ if forwarding_rule not in {"always" , "module" , "once" , "location" }:
112
+ raise ValueError ("unsupported forwarding rule." )
113
+ self ._forwarding_rule = forwarding_rule
114
+
115
+ def _clear_registries (self ):
116
+ if hasattr (warnings , "_filters_mutated" ):
117
+ # clearing the registry should not be necessary on new pythons,
118
+ # instead the filters should be mutated.
119
+ warnings ._filters_mutated ()
120
+ return
121
+ # Simply clear the registry, this should normally be harmless,
122
+ # note that on new pythons it would be invalidated anyway.
123
+ for module in self ._tmp_modules :
124
+ if hasattr (module , "__warningregistry__" ):
125
+ module .__warningregistry__ .clear ()
126
+
127
+ def _filter (self , category = Warning , message = "" , module = None , record = False ):
128
+ if record :
129
+ record = [] # The log where to store warnings
130
+ else :
131
+ record = None
132
+ if self ._entered :
133
+ if module is None :
134
+ warnings .filterwarnings (
135
+ "always" , category = category , message = message )
136
+ else :
137
+ module_regex = module .__name__ .replace ('.' , '\.' ) + '$'
138
+ warnings .filterwarnings (
139
+ "always" , category = category , message = message ,
140
+ module = module_regex )
141
+ self ._tmp_modules .add (module )
142
+ self ._clear_registries ()
143
+
144
+ self ._tmp_suppressions .append (
145
+ (category , message , re .compile (message , re .I ), module , record ))
146
+ else :
147
+ self ._suppressions .append (
148
+ (category , message , re .compile (message , re .I ), module , record ))
149
+
150
+ return record
151
+
152
+ def filter (self , category = Warning , message = "" , module = None ):
153
+ """
154
+ Add a new suppressing filter or apply it if the state is entered.
155
+ Parameters
156
+ ----------
157
+ category : class, optional
158
+ Warning class to filter
159
+ message : string, optional
160
+ Regular expression matching the warning message.
161
+ module : module, optional
162
+ Module to filter for. Note that the module (and its file)
163
+ must match exactly and cannot be a submodule. This may make
164
+ it unreliable for external modules.
165
+ Notes
166
+ -----
167
+ When added within a context, filters are only added inside
168
+ the context and will be forgotten when the context is exited.
169
+ """
170
+ self ._filter (category = category , message = message , module = module ,
171
+ record = False )
172
+
173
+ def record (self , category = Warning , message = "" , module = None ):
174
+ """
175
+ Append a new recording filter or apply it if the state is entered.
176
+ All warnings matching will be appended to the ``log`` attribute.
177
+ Parameters
178
+ ----------
179
+ category : class, optional
180
+ Warning class to filter
181
+ message : string, optional
182
+ Regular expression matching the warning message.
183
+ module : module, optional
184
+ Module to filter for. Note that the module (and its file)
185
+ must match exactly and cannot be a submodule. This may make
186
+ it unreliable for external modules.
187
+ Returns
188
+ -------
189
+ log : list
190
+ A list which will be filled with all matched warnings.
191
+ Notes
192
+ -----
193
+ When added within a context, filters are only added inside
194
+ the context and will be forgotten when the context is exited.
195
+ """
196
+ return self ._filter (category = category , message = message , module = module ,
197
+ record = True )
198
+
199
+ def __enter__ (self ):
200
+ if self ._entered :
201
+ raise RuntimeError ("cannot enter suppress_warnings twice." )
202
+
203
+ self ._orig_show = warnings .showwarning
204
+ if hasattr (warnings , "_showwarnmsg" ):
205
+ self ._orig_showmsg = warnings ._showwarnmsg
206
+ self ._filters = warnings .filters
207
+ warnings .filters = self ._filters [:]
208
+
209
+ self ._entered = True
210
+ self ._tmp_suppressions = []
211
+ self ._tmp_modules = set ()
212
+ self ._forwarded = set ()
213
+
214
+ self .log = [] # reset global log (no need to keep same list)
215
+
216
+ for cat , mess , _ , mod , log in self ._suppressions :
217
+ if log is not None :
218
+ del log [:] # clear the log
219
+ if mod is None :
220
+ warnings .filterwarnings (
221
+ "always" , category = cat , message = mess )
222
+ else :
223
+ module_regex = mod .__name__ .replace ('.' , '\.' ) + '$'
224
+ warnings .filterwarnings (
225
+ "always" , category = cat , message = mess ,
226
+ module = module_regex )
227
+ self ._tmp_modules .add (mod )
228
+ warnings .showwarning = self ._showwarning
229
+ if hasattr (warnings , "_showwarnmsg" ):
230
+ warnings ._showwarnmsg = self ._showwarnmsg
231
+ self ._clear_registries ()
232
+
233
+ return self
234
+
235
+ def __exit__ (self , * exc_info ):
236
+ warnings .showwarning = self ._orig_show
237
+ if hasattr (warnings , "_showwarnmsg" ):
238
+ warnings ._showwarnmsg = self ._orig_showmsg
239
+ warnings .filters = self ._filters
240
+ self ._clear_registries ()
241
+ self ._entered = False
242
+ del self ._orig_show
243
+ del self ._filters
244
+
245
+ def _showwarnmsg (self , msg ):
246
+ self ._showwarning (msg .message , msg .category , msg .filename , msg .lineno ,
247
+ msg .file , msg .line , use_warnmsg = msg )
248
+
249
+ def _showwarning (self , message , category , filename , lineno ,
250
+ * args , ** kwargs ):
251
+ use_warnmsg = kwargs .pop ("use_warnmsg" , None )
252
+ for cat , _ , pattern , mod , rec in (
253
+ self ._suppressions + self ._tmp_suppressions )[::- 1 ]:
254
+ if (issubclass (category , cat ) and
255
+ pattern .match (message .args [0 ]) is not None ):
256
+ if mod is None :
257
+ # Message and category match, either recorded or ignored
258
+ if rec is not None :
259
+ msg = WarningMessage (message , category , filename ,
260
+ lineno , ** kwargs )
261
+ self .log .append (msg )
262
+ rec .append (msg )
263
+ return
264
+ # Use startswith, because warnings strips the c or o from
265
+ # .pyc/.pyo files.
266
+ elif mod .__file__ .startswith (filename ):
267
+ # The message and module (filename) match
268
+ if rec is not None :
269
+ msg = WarningMessage (message , category , filename ,
270
+ lineno , ** kwargs )
271
+ self .log .append (msg )
272
+ rec .append (msg )
273
+ return
274
+
275
+ # There is no filter in place, so pass to the outside handler
276
+ # unless we should only pass it once
277
+ if self ._forwarding_rule == "always" :
278
+ if use_warnmsg is None :
279
+ self ._orig_show (message , category , filename , lineno ,
280
+ * args , ** kwargs )
281
+ else :
282
+ self ._orig_showmsg (use_warnmsg )
283
+ return
284
+
285
+ if self ._forwarding_rule == "once" :
286
+ signature = (message .args , category )
287
+ elif self ._forwarding_rule == "module" :
288
+ signature = (message .args , category , filename )
289
+ elif self ._forwarding_rule == "location" :
290
+ signature = (message .args , category , filename , lineno )
291
+
292
+ if signature in self ._forwarded :
293
+ return
294
+ self ._forwarded .add (signature )
295
+ if use_warnmsg is None :
296
+ self ._orig_show (message , category , filename , lineno , * args ,
297
+ ** kwargs )
298
+ else :
299
+ self ._orig_showmsg (use_warnmsg )
300
+
301
+ def __call__ (self , func ):
302
+ """
303
+ Function decorator to apply certain suppressions to a whole
304
+ function.
305
+ """
306
+ @wraps (func )
307
+ def new_func (* args , ** kwargs ):
308
+ with self :
309
+ return func (* args , ** kwargs )
310
+
311
+ return new_func
0 commit comments