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