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+ def get_context ():
91+ try :
92+ return _warnings_context .get ()
93+ except LookupError :
94+ context = _Context ([])
95+ _warnings_context .set (context )
96+ return context
97+
98+
99+ def _set_context (context ):
100+ _warnings_context .set (context )
101+
102+
103+ def _new_context ():
104+ old_context = get_context ()
105+ new_context = old_context .copy ()
106+ _set_context (new_context )
107+ return old_context , new_context
108+
109+
10110def showwarning (message , category , filename , lineno , file = None , line = None ):
11111 """Hook to write a warning to a file; replace if you like."""
12112 msg = WarningMessage (message , category , filename , lineno , file , line )
@@ -18,6 +118,10 @@ def formatwarning(message, category, filename, lineno, line=None):
18118 return _formatwarnmsg_impl (msg )
19119
20120def _showwarnmsg_impl (msg ):
121+ context = get_context ()
122+ if context .log is not None :
123+ context ._record_warning (msg )
124+ return
21125 file = msg .file
22126 if file is None :
23127 file = sys .stderr
@@ -129,7 +233,7 @@ def _formatwarnmsg(msg):
129233 return _formatwarnmsg_impl (msg )
130234
131235def filterwarnings (action , message = "" , category = Warning , module = "" , lineno = 0 ,
132- append = False ):
236+ append = False , * , context = _global_context ):
133237 """Insert an entry into the list of warnings filters (at the front).
134238
135239 'action' -- one of "error", "ignore", "always", "all", "default", "module",
@@ -165,9 +269,11 @@ def filterwarnings(action, message="", category=Warning, module="", lineno=0,
165269 else :
166270 module = None
167271
168- _add_filter (action , message , category , module , lineno , append = append )
272+ _add_filter (action , message , category , module , lineno , append = append ,
273+ context = context )
169274
170- def simplefilter (action , category = Warning , lineno = 0 , append = False ):
275+ def simplefilter (action , category = Warning , lineno = 0 , append = False , * ,
276+ context = _global_context ):
171277 """Insert a simple entry into the list of warnings filters (at the front).
172278
173279 A simple filter matches all modules and messages.
@@ -183,10 +289,12 @@ def simplefilter(action, category=Warning, lineno=0, append=False):
183289 raise TypeError ("lineno must be an int" )
184290 if lineno < 0 :
185291 raise ValueError ("lineno must be an int >= 0" )
186- _add_filter (action , None , category , None , lineno , append = append )
292+ _add_filter (action , None , category , None , lineno , append = append ,
293+ context = context )
187294
188- def _add_filter (* item , append ):
295+ def _add_filter (* item , append , context = _global_context ):
189296 with _lock :
297+ filters = context ._filters
190298 if not append :
191299 # Remove possible duplicate filters, so new one will be placed
192300 # in correct place. If append=True and duplicate exists, do nothing.
@@ -200,10 +308,10 @@ def _add_filter(*item, append):
200308 filters .append (item )
201309 _filters_mutated ()
202310
203- def resetwarnings ():
311+ def resetwarnings (* , context = _global_context ):
204312 """Clear the list of warning filters, so that no filters are active."""
205313 with _lock :
206- filters [:] = []
314+ context . _filters [:] = []
207315 _filters_mutated ()
208316
209317class _OptionError (Exception ):
@@ -371,7 +479,7 @@ def warn_explicit(message, category, filename, lineno,
371479 if registry .get (key ):
372480 return
373481 # Search the filters
374- for item in filters :
482+ for item in _itertools . chain ( get_context (). _filters , filters ) :
375483 action , msg , cat , mod , ln = item
376484 if ((msg is None or msg .match (text )) and
377485 issubclass (category , cat ) and
@@ -496,17 +604,17 @@ def __enter__(self):
496604 self ._module ._filters_mutated ()
497605 self ._showwarning = self ._module .showwarning
498606 self ._showwarnmsg_impl = self ._module ._showwarnmsg_impl
607+ if self ._record :
608+ log = []
609+ self ._module ._showwarnmsg_impl = log .append
610+ # Reset showwarning() to the default implementation to make sure
611+ # that _showwarnmsg() calls _showwarnmsg_impl()
612+ self ._module .showwarning = self ._module ._showwarning_orig
613+ else :
614+ log = None
499615 if self ._filter is not None :
500616 simplefilter (* self ._filter )
501- if self ._record :
502- log = []
503- self ._module ._showwarnmsg_impl = log .append
504- # Reset showwarning() to the default implementation to make sure
505- # that _showwarnmsg() calls _showwarnmsg_impl()
506- self ._module .showwarning = self ._module ._showwarning_orig
507- return log
508- else :
509- return None
617+ return log
510618
511619 def __exit__ (self , * exc_info ):
512620 if not self ._entered :
@@ -518,6 +626,64 @@ def __exit__(self, *exc_info):
518626 self ._module ._showwarnmsg_impl = self ._showwarnmsg_impl
519627
520628
629+ class local_context :
630+ """A context manager that copies and restores the warnings filter upon
631+ exiting the context. This uses a context variable so that the filter
632+ changes are thread local and work as expected with asynchronous task
633+ switching.
634+
635+ The 'record' argument specifies whether warnings should be captured rather
636+ than being emitted by warnings.showwarning(). When capture is enabled, the
637+ list of warnings is available as get_context().log.
638+ """
639+ def __init__ (self , * , record = False ):
640+ self ._record = record
641+ self ._entered = False
642+
643+ def __enter__ (self ):
644+ if self ._entered :
645+ raise RuntimeError ("Cannot enter %r twice" % self )
646+ self ._entered = True
647+ self ._saved_context , context = _new_context ()
648+ if self ._record :
649+ context .log = []
650+ _filters_mutated ()
651+ return context
652+
653+ def __exit__ (self , * exc_info ):
654+ if not self ._entered :
655+ raise RuntimeError ("Cannot exit %r without entering first" % self )
656+ _warnings_context .set (self ._saved_context )
657+ _filters_mutated ()
658+
659+
660+ class _CatchManager (local_context ):
661+ """Context manager used by get_context().catch_warnings()."""
662+ def __init__ (
663+ self ,
664+ * ,
665+ record = False ,
666+ action = None ,
667+ category = Warning ,
668+ lineno = 0 ,
669+ append = False ,
670+ ):
671+ super ().__init__ (record = record )
672+ if action is None :
673+ self ._filter = None
674+ else :
675+ self ._filter = (action , category , lineno , append )
676+
677+ def __enter__ (self ):
678+ context = super ().__enter__ ()
679+ if self ._filter is not None :
680+ context .simplefilter (* self ._filter )
681+ return context .log
682+
683+ def __exit__ (self , * exc_info ):
684+ context = super ().__exit__ (* exc_info )
685+
686+
521687class deprecated :
522688 """Indicate that a class, function or overload is deprecated.
523689
@@ -704,6 +870,7 @@ def extract():
704870# - a line number for the line being warning, or 0 to mean any line
705871# If either if the compiled regexs are None, match anything.
706872try :
873+ raise ImportError # FIXME: temporary, until _warnings is updated
707874 from _warnings import (filters , _defaultaction , _onceregistry ,
708875 warn , warn_explicit , _filters_mutated ,
709876 _acquire_lock , _release_lock ,
0 commit comments