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+ if self .log is not None :
20+ context .log = self .log
21+ return context
22+
23+ def _record_warning (self , msg ):
24+ self .log .append (msg )
25+
26+
27+ class _GlobalContext (_Context ):
28+ def __init__ (self ):
29+ self .log = None
30+
31+ @property
32+ def _filters (self ):
33+ # Since there is quite a lot of code that assigns to
34+ # warnings.filters, this needs to return the current value of
35+ # the module global.
36+ try :
37+ return filters
38+ except NameError :
39+ # 'filters' global was deleted. Do we need to actually handle this case?
40+ return []
41+
42+ _global_context = _GlobalContext ()
43+ _warnings_context = _contextvars .ContextVar ('warnings_context' )
44+
45+ def get_context ():
46+ try :
47+ context = _warnings_context .get ()
48+ except LookupError :
49+ context = _global_context
50+ _warnings_context .set (context )
51+ return context
52+
53+
54+ def _set_context (context ):
55+ _warnings_context .set (context )
56+
57+
58+ def _new_context ():
59+ old_context = get_context ()
60+ new_context = old_context .copy ()
61+ _set_context (new_context )
62+ return old_context , new_context
63+
64+
65+ def _get_filters ():
66+ """Return the current list of filters. This is a non-public API used by
67+ the unit tests."""
68+ return get_context ()._filters
69+
70+
1071def showwarning (message , category , filename , lineno , file = None , line = None ):
1172 """Hook to write a warning to a file; replace if you like."""
1273 msg = WarningMessage (message , category , filename , lineno , file , line )
@@ -18,6 +79,10 @@ def formatwarning(message, category, filename, lineno, line=None):
1879 return _formatwarnmsg_impl (msg )
1980
2081def _showwarnmsg_impl (msg ):
82+ context = get_context ()
83+ if context .log is not None :
84+ context ._record_warning (msg )
85+ return
2186 file = msg .file
2287 if file is None :
2388 file = sys .stderr
@@ -129,7 +194,7 @@ def _formatwarnmsg(msg):
129194 return _formatwarnmsg_impl (msg )
130195
131196def filterwarnings (action , message = "" , category = Warning , module = "" , lineno = 0 ,
132- append = False ):
197+ append = False , * , context = None ):
133198 """Insert an entry into the list of warnings filters (at the front).
134199
135200 'action' -- one of "error", "ignore", "always", "all", "default", "module",
@@ -165,9 +230,11 @@ def filterwarnings(action, message="", category=Warning, module="", lineno=0,
165230 else :
166231 module = None
167232
168- _add_filter (action , message , category , module , lineno , append = append )
233+ _add_filter (action , message , category , module , lineno , append = append ,
234+ context = context )
169235
170- def simplefilter (action , category = Warning , lineno = 0 , append = False ):
236+ def simplefilter (action , category = Warning , lineno = 0 , append = False , * ,
237+ context = None ):
171238 """Insert a simple entry into the list of warnings filters (at the front).
172239
173240 A simple filter matches all modules and messages.
@@ -183,16 +250,20 @@ def simplefilter(action, category=Warning, lineno=0, append=False):
183250 raise TypeError ("lineno must be an int" )
184251 if lineno < 0 :
185252 raise ValueError ("lineno must be an int >= 0" )
186- _add_filter (action , None , category , None , lineno , append = append )
253+ _add_filter (action , None , category , None , lineno , append = append ,
254+ context = context )
187255
188256def _filters_mutated ():
189257 # Even though this function is part of the public API, it's used by
190258 # a fair amount of user code.
191259 with _lock :
192260 _filters_mutated_lock_held ()
193261
194- def _add_filter (* item , append ):
262+ def _add_filter (* item , append , context = None ):
195263 with _lock :
264+ if context is None :
265+ context = get_context ()
266+ filters = context ._filters
196267 if not append :
197268 # Remove possible duplicate filters, so new one will be placed
198269 # in correct place. If append=True and duplicate exists, do nothing.
@@ -206,10 +277,12 @@ def _add_filter(*item, append):
206277 filters .append (item )
207278 _filters_mutated_lock_held ()
208279
209- def resetwarnings ():
280+ def resetwarnings (* , context = None ):
210281 """Clear the list of warning filters, so that no filters are active."""
211282 with _lock :
212- filters [:] = []
283+ if context is None :
284+ context = get_context ()
285+ del context ._filters [:]
213286 _filters_mutated_lock_held ()
214287
215288class _OptionError (Exception ):
@@ -378,7 +451,7 @@ def warn_explicit(message, category, filename, lineno,
378451 if registry .get (key ):
379452 return
380453 # Search the filters
381- for item in filters :
454+ for item in get_context (). _filters :
382455 action , msg , cat , mod , ln = item
383456 if ((msg is None or msg .match (text )) and
384457 issubclass (category , cat ) and
@@ -499,31 +572,28 @@ def __enter__(self):
499572 raise RuntimeError ("Cannot enter %r twice" % self )
500573 self ._entered = True
501574 with _lock :
502- self ._filters = self ._module .filters
503- self ._module .filters = self ._filters [:]
504- self ._module ._filters_mutated_lock_held ()
575+ self ._saved_context , context = self ._module ._new_context ()
505576 self ._showwarning = self ._module .showwarning
506577 self ._showwarnmsg_impl = self ._module ._showwarnmsg_impl
578+ if self ._record :
579+ context .log = log = []
580+ # Reset showwarning() to the default implementation to make sure
581+ # that _showwarnmsg() calls _showwarnmsg_impl()
582+ self ._module .showwarning = self ._module ._showwarning_orig
583+ else :
584+ log = None
507585 if self ._filter is not None :
508- simplefilter (* self ._filter )
509- if self ._record :
510- log = []
511- self ._module ._showwarnmsg_impl = log .append
512- # Reset showwarning() to the default implementation to make sure
513- # that _showwarnmsg() calls _showwarnmsg_impl()
514- self ._module .showwarning = self ._module ._showwarning_orig
515- return log
516- else :
517- return None
586+ self ._module .simplefilter (* self ._filter , context = context )
587+ return log
518588
519589 def __exit__ (self , * exc_info ):
520590 if not self ._entered :
521591 raise RuntimeError ("Cannot exit %r without entering first" % self )
522592 with _lock :
523- self ._module .filters = self ._filters
524- self ._module ._filters_mutated_lock_held ()
593+ self ._module ._warnings_context .set (self ._saved_context )
525594 self ._module .showwarning = self ._showwarning
526595 self ._module ._showwarnmsg_impl = self ._showwarnmsg_impl
596+ self ._module ._filters_mutated_lock_held ()
527597
528598
529599class deprecated :
@@ -762,3 +832,9 @@ def _filters_mutated_lock_held():
762832 simplefilter ("ignore" , category = ResourceWarning , append = 1 )
763833
764834del _warnings_defaults
835+
836+ #def __getattr__(name):
837+ # if name == "filters":
838+ # warn('Accessing warnings.filters is likely not thread-safe.', DeprecationWarning, stacklevel=2)
839+ # return get_context()._filters
840+ # raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
0 commit comments