11"""Python part of the warnings subsystem."""
22
33import sys
4+ import itertools as _itertools
5+ import contextvars as _contextvars
46
57
68__all__ = ["warn" , "warn_explicit" , "showwarning" ,
79 "formatwarning" , "filterwarnings" , "simplefilter" ,
810 "resetwarnings" , "catch_warnings" , "deprecated" ]
911
12+ class _Context :
13+ def __init__ (self , filters ):
14+ self ._filters = filters
15+ self .log = None # if set to a list, logging is enabled
16+
17+ def copy (self ):
18+ context = _Context (self ._filters [:])
19+ return context
20+
21+ def _record_warning (self , msg ):
22+ self .log .append (msg )
23+
24+ def filterwarnings (
25+ self ,
26+ action ,
27+ message = "" ,
28+ category = Warning ,
29+ module = "" ,
30+ lineno = 0 ,
31+ append = False ,
32+ ):
33+ filterwarnings (
34+ action ,
35+ message = message ,
36+ category = category ,
37+ module = module ,
38+ lineno = lineno ,
39+ append = append ,
40+ context = self ,
41+ )
42+
43+ def simplefilter (self , action , category = Warning , lineno = 0 , append = False ):
44+ simplefilter (
45+ action ,
46+ category = category ,
47+ lineno = lineno ,
48+ append = append ,
49+ context = self ,
50+ )
51+
52+ def resetwarnings (self ):
53+ resetwarnings (context = self )
54+
55+ def catch_warnings (
56+ self ,
57+ * ,
58+ record = False ,
59+ action = None ,
60+ category = Warning ,
61+ lineno = 0 ,
62+ append = False ,
63+ ):
64+ # For easier backwards compatibility.
65+ return _CatchManager (
66+ record = record ,
67+ action = action ,
68+ category = category ,
69+ lineno = lineno ,
70+ append = append ,
71+ )
72+
73+
74+ class _GlobalContext (_Context ):
75+ def __init__ (self ):
76+ self .log = None
77+
78+ @property
79+ def _filters (self ):
80+ # Since there is quite a lot of code that assigns to
81+ # warnings.filters, this needs to return the current value of
82+ # the module global.
83+ return filters
84+
85+
86+ _global_context = _GlobalContext ()
87+
88+ _warnings_context = _contextvars .ContextVar ('warnings_context' )
89+
90+
91+ def get_context ():
92+ try :
93+ return _warnings_context .get ()
94+ except LookupError :
95+ context = _Context ([])
96+ _warnings_context .set (context )
97+ return context
98+
99+
100+ def _set_context (context ):
101+ _warnings_context .set (context )
102+
103+
104+ def _new_context ():
105+ old_context = get_context ()
106+ new_context = old_context .copy ()
107+ _set_context (new_context )
108+ return old_context , new_context
109+
110+
10111def showwarning (message , category , filename , lineno , file = None , line = None ):
11112 """Hook to write a warning to a file; replace if you like."""
12113 msg = WarningMessage (message , category , filename , lineno , file , line )
@@ -18,6 +119,10 @@ def formatwarning(message, category, filename, lineno, line=None):
18119 return _formatwarnmsg_impl (msg )
19120
20121def _showwarnmsg_impl (msg ):
122+ context = get_context ()
123+ if context .log is not None :
124+ context ._record_warning (msg )
125+ return
21126 file = msg .file
22127 if file is None :
23128 file = sys .stderr
@@ -129,7 +234,7 @@ def _formatwarnmsg(msg):
129234 return _formatwarnmsg_impl (msg )
130235
131236def filterwarnings (action , message = "" , category = Warning , module = "" , lineno = 0 ,
132- append = False ):
237+ append = False , * , context = _global_context ):
133238 """Insert an entry into the list of warnings filters (at the front).
134239
135240 'action' -- one of "error", "ignore", "always", "all", "default", "module",
@@ -165,9 +270,11 @@ def filterwarnings(action, message="", category=Warning, module="", lineno=0,
165270 else :
166271 module = None
167272
168- _add_filter (action , message , category , module , lineno , append = append )
273+ _add_filter (action , message , category , module , lineno , append = append ,
274+ context = context )
169275
170- def simplefilter (action , category = Warning , lineno = 0 , append = False ):
276+ def simplefilter (action , category = Warning , lineno = 0 , append = False , * ,
277+ context = _global_context ):
171278 """Insert a simple entry into the list of warnings filters (at the front).
172279
173280 A simple filter matches all modules and messages.
@@ -183,10 +290,12 @@ def simplefilter(action, category=Warning, lineno=0, append=False):
183290 raise TypeError ("lineno must be an int" )
184291 if lineno < 0 :
185292 raise ValueError ("lineno must be an int >= 0" )
186- _add_filter (action , None , category , None , lineno , append = append )
293+ _add_filter (action , None , category , None , lineno , append = append ,
294+ context = context )
187295
188- def _add_filter (* item , append ):
296+ def _add_filter (* item , append , context = _global_context ):
189297 with _lock :
298+ filters = context ._filters
190299 if not append :
191300 # Remove possible duplicate filters, so new one will be placed
192301 # in correct place. If append=True and duplicate exists, do nothing.
@@ -200,10 +309,10 @@ def _add_filter(*item, append):
200309 filters .append (item )
201310 _filters_mutated ()
202311
203- def resetwarnings ():
312+ def resetwarnings (* , context = _global_context ):
204313 """Clear the list of warning filters, so that no filters are active."""
205314 with _lock :
206- filters [:] = []
315+ context . _filters [:] = []
207316 _filters_mutated ()
208317
209318class _OptionError (Exception ):
@@ -347,7 +456,7 @@ def warn(message, category=None, stacklevel=1, source=None,
347456 warn_explicit (message , category , filename , lineno , module , registry ,
348457 globals , source )
349458
350- def warn_explicit (message , category , filename , lineno ,
459+ def _warn_explicit_impl (message , category , filename , lineno ,
351460 module = None , registry = None , module_globals = None ,
352461 source = None ):
353462 lineno = int (lineno )
@@ -371,7 +480,7 @@ def warn_explicit(message, category, filename, lineno,
371480 if registry .get (key ):
372481 return
373482 # Search the filters
374- for item in filters :
483+ for item in _itertools . chain ( get_context (). _filters , filters ) :
375484 action , msg , cat , mod , ln = item
376485 if ((msg is None or msg .match (text )) and
377486 issubclass (category , cat ) and
@@ -418,6 +527,11 @@ def warn_explicit(message, category, filename, lineno,
418527 _showwarnmsg (msg )
419528
420529
530+ def warn_explicit (* args , ** kwargs ):
531+ with _lock :
532+ return _warn_explicit_impl (* args , ** kwargs )
533+
534+
421535class WarningMessage (object ):
422536
423537 _WARNING_DETAILS = ("message" , "category" , "filename" , "lineno" , "file" ,
@@ -518,6 +632,64 @@ def __exit__(self, *exc_info):
518632 self ._module ._showwarnmsg_impl = self ._showwarnmsg_impl
519633
520634
635+ class local_context :
636+ """A context manager that copies and restores the warnings filter upon
637+ exiting the context. This uses a context variable so that the filter
638+ changes are thread local and work as expected with asynchronous task
639+ switching.
640+
641+ The 'record' argument specifies whether warnings should be captured rather
642+ than being emitted by warnings.showwarning(). When capture is enabled, the
643+ list of warnings is available as get_context().log.
644+ """
645+ def __init__ (self , * , record = False ):
646+ self ._record = record
647+ self ._entered = False
648+
649+ def __enter__ (self ):
650+ if self ._entered :
651+ raise RuntimeError ("Cannot enter %r twice" % self )
652+ self ._entered = True
653+ self ._saved_context , context = _new_context ()
654+ if self ._record :
655+ context .log = []
656+ _filters_mutated ()
657+ return context
658+
659+ def __exit__ (self , * exc_info ):
660+ if not self ._entered :
661+ raise RuntimeError ("Cannot exit %r without entering first" % self )
662+ _warnings_context .set (self ._saved_context )
663+ _filters_mutated ()
664+
665+
666+ class _CatchManager (local_context ):
667+ """Context manager used by get_context().catch_warnings()."""
668+ def __init__ (
669+ self ,
670+ * ,
671+ record = False ,
672+ action = None ,
673+ category = Warning ,
674+ lineno = 0 ,
675+ append = False ,
676+ ):
677+ super ().__init__ (record = record )
678+ if action is None :
679+ self ._filter = None
680+ else :
681+ self ._filter = (action , category , lineno , append )
682+
683+ def __enter__ (self ):
684+ context = super ().__enter__ ()
685+ if self ._filter is not None :
686+ context .simplefilter (* self ._filter )
687+ return context .log
688+
689+ def __exit__ (self , * exc_info ):
690+ context = super ().__exit__ (* exc_info )
691+
692+
521693class deprecated :
522694 """Indicate that a class, function or overload is deprecated.
523695
@@ -704,6 +876,7 @@ def extract():
704876# - a line number for the line being warning, or 0 to mean any line
705877# If either if the compiled regexs are None, match anything.
706878try :
879+ raise ImportError # FIXME: temporary, until _warnings is updated
707880 from _warnings import (filters , _defaultaction , _onceregistry ,
708881 warn , warn_explicit , _filters_mutated ,
709882 _acquire_lock , _release_lock ,
0 commit comments