1
1
"""Python part of the warnings subsystem."""
2
2
3
3
import sys
4
+ import itertools as _itertools
5
+ import contextvars as _contextvars
4
6
5
7
6
8
__all__ = ["warn" , "warn_explicit" , "showwarning" ,
7
9
"formatwarning" , "filterwarnings" , "simplefilter" ,
8
10
"resetwarnings" , "catch_warnings" , "deprecated" ]
9
11
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
+
10
110
def showwarning (message , category , filename , lineno , file = None , line = None ):
11
111
"""Hook to write a warning to a file; replace if you like."""
12
112
msg = WarningMessage (message , category , filename , lineno , file , line )
@@ -18,6 +118,10 @@ def formatwarning(message, category, filename, lineno, line=None):
18
118
return _formatwarnmsg_impl (msg )
19
119
20
120
def _showwarnmsg_impl (msg ):
121
+ context = get_context ()
122
+ if context .log is not None :
123
+ context ._record_warning (msg )
124
+ return
21
125
file = msg .file
22
126
if file is None :
23
127
file = sys .stderr
@@ -129,7 +233,7 @@ def _formatwarnmsg(msg):
129
233
return _formatwarnmsg_impl (msg )
130
234
131
235
def filterwarnings (action , message = "" , category = Warning , module = "" , lineno = 0 ,
132
- append = False ):
236
+ append = False , * , context = _global_context ):
133
237
"""Insert an entry into the list of warnings filters (at the front).
134
238
135
239
'action' -- one of "error", "ignore", "always", "all", "default", "module",
@@ -165,9 +269,11 @@ def filterwarnings(action, message="", category=Warning, module="", lineno=0,
165
269
else :
166
270
module = None
167
271
168
- _add_filter (action , message , category , module , lineno , append = append )
272
+ _add_filter (action , message , category , module , lineno , append = append ,
273
+ context = context )
169
274
170
- def simplefilter (action , category = Warning , lineno = 0 , append = False ):
275
+ def simplefilter (action , category = Warning , lineno = 0 , append = False , * ,
276
+ context = _global_context ):
171
277
"""Insert a simple entry into the list of warnings filters (at the front).
172
278
173
279
A simple filter matches all modules and messages.
@@ -183,10 +289,12 @@ def simplefilter(action, category=Warning, lineno=0, append=False):
183
289
raise TypeError ("lineno must be an int" )
184
290
if lineno < 0 :
185
291
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 )
187
294
188
- def _add_filter (* item , append ):
295
+ def _add_filter (* item , append , context = _global_context ):
189
296
with _lock :
297
+ filters = context ._filters
190
298
if not append :
191
299
# Remove possible duplicate filters, so new one will be placed
192
300
# in correct place. If append=True and duplicate exists, do nothing.
@@ -200,10 +308,10 @@ def _add_filter(*item, append):
200
308
filters .append (item )
201
309
_filters_mutated ()
202
310
203
- def resetwarnings ():
311
+ def resetwarnings (* , context = _global_context ):
204
312
"""Clear the list of warning filters, so that no filters are active."""
205
313
with _lock :
206
- filters [:] = []
314
+ context . _filters [:] = []
207
315
_filters_mutated ()
208
316
209
317
class _OptionError (Exception ):
@@ -371,7 +479,7 @@ def warn_explicit(message, category, filename, lineno,
371
479
if registry .get (key ):
372
480
return
373
481
# Search the filters
374
- for item in filters :
482
+ for item in _itertools . chain ( get_context (). _filters , filters ) :
375
483
action , msg , cat , mod , ln = item
376
484
if ((msg is None or msg .match (text )) and
377
485
issubclass (category , cat ) and
@@ -496,17 +604,17 @@ def __enter__(self):
496
604
self ._module ._filters_mutated ()
497
605
self ._showwarning = self ._module .showwarning
498
606
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
499
615
if self ._filter is not None :
500
616
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
510
618
511
619
def __exit__ (self , * exc_info ):
512
620
if not self ._entered :
@@ -518,6 +626,64 @@ def __exit__(self, *exc_info):
518
626
self ._module ._showwarnmsg_impl = self ._showwarnmsg_impl
519
627
520
628
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
+
521
687
class deprecated :
522
688
"""Indicate that a class, function or overload is deprecated.
523
689
@@ -704,6 +870,7 @@ def extract():
704
870
# - a line number for the line being warning, or 0 to mean any line
705
871
# If either if the compiled regexs are None, match anything.
706
872
try :
873
+ raise ImportError # FIXME: temporary, until _warnings is updated
707
874
from _warnings import (filters , _defaultaction , _onceregistry ,
708
875
warn , warn_explicit , _filters_mutated ,
709
876
_acquire_lock , _release_lock ,
0 commit comments