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_unlocked ()
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_unlocked ()
208
316
209
317
class _OptionError (Exception ):
@@ -372,7 +480,7 @@ def warn_explicit(message, category, filename, lineno,
372
480
if registry .get (key ):
373
481
return
374
482
# Search the filters
375
- for item in filters :
483
+ for item in _itertools . chain ( get_context (). _filters , filters ) :
376
484
action , msg , cat , mod , ln = item
377
485
if ((msg is None or msg .match (text )) and
378
486
issubclass (category , cat ) and
@@ -498,17 +606,17 @@ def __enter__(self):
498
606
self ._module ._filters_mutated_unlocked ()
499
607
self ._showwarning = self ._module .showwarning
500
608
self ._showwarnmsg_impl = self ._module ._showwarnmsg_impl
609
+ if self ._record :
610
+ log = []
611
+ self ._module ._showwarnmsg_impl = log .append
612
+ # Reset showwarning() to the default implementation to make sure
613
+ # that _showwarnmsg() calls _showwarnmsg_impl()
614
+ self ._module .showwarning = self ._module ._showwarning_orig
615
+ else :
616
+ log = None
501
617
if self ._filter is not None :
502
618
simplefilter (* self ._filter )
503
- if self ._record :
504
- log = []
505
- self ._module ._showwarnmsg_impl = log .append
506
- # Reset showwarning() to the default implementation to make sure
507
- # that _showwarnmsg() calls _showwarnmsg_impl()
508
- self ._module .showwarning = self ._module ._showwarning_orig
509
- return log
510
- else :
511
- return None
619
+ return log
512
620
513
621
def __exit__ (self , * exc_info ):
514
622
if not self ._entered :
@@ -520,6 +628,64 @@ def __exit__(self, *exc_info):
520
628
self ._module ._showwarnmsg_impl = self ._showwarnmsg_impl
521
629
522
630
631
+ class local_context :
632
+ """A context manager that copies and restores the warnings filter upon
633
+ exiting the context. This uses a context variable so that the filter
634
+ changes are thread local and work as expected with asynchronous task
635
+ switching.
636
+
637
+ The 'record' argument specifies whether warnings should be captured rather
638
+ than being emitted by warnings.showwarning(). When capture is enabled, the
639
+ list of warnings is available as get_context().log.
640
+ """
641
+ def __init__ (self , * , record = False ):
642
+ self ._record = record
643
+ self ._entered = False
644
+
645
+ def __enter__ (self ):
646
+ if self ._entered :
647
+ raise RuntimeError ("Cannot enter %r twice" % self )
648
+ self ._entered = True
649
+ self ._saved_context , context = _new_context ()
650
+ if self ._record :
651
+ context .log = []
652
+ _filters_mutated ()
653
+ return context
654
+
655
+ def __exit__ (self , * exc_info ):
656
+ if not self ._entered :
657
+ raise RuntimeError ("Cannot exit %r without entering first" % self )
658
+ _warnings_context .set (self ._saved_context )
659
+ _filters_mutated ()
660
+
661
+
662
+ class _CatchManager (local_context ):
663
+ """Context manager used by get_context().catch_warnings()."""
664
+ def __init__ (
665
+ self ,
666
+ * ,
667
+ record = False ,
668
+ action = None ,
669
+ category = Warning ,
670
+ lineno = 0 ,
671
+ append = False ,
672
+ ):
673
+ super ().__init__ (record = record )
674
+ if action is None :
675
+ self ._filter = None
676
+ else :
677
+ self ._filter = (action , category , lineno , append )
678
+
679
+ def __enter__ (self ):
680
+ context = super ().__enter__ ()
681
+ if self ._filter is not None :
682
+ context .simplefilter (* self ._filter )
683
+ return context .log
684
+
685
+ def __exit__ (self , * exc_info ):
686
+ context = super ().__exit__ (* exc_info )
687
+
688
+
523
689
class deprecated :
524
690
"""Indicate that a class, function or overload is deprecated.
525
691
@@ -706,6 +872,7 @@ def extract():
706
872
# - a line number for the line being warning, or 0 to mean any line
707
873
# If either if the compiled regexs are None, match anything.
708
874
try :
875
+ raise ImportError # FIXME: temporary, until _warnings is updated
709
876
from _warnings import (filters , _defaultaction , _onceregistry ,
710
877
warn , warn_explicit ,
711
878
_filters_mutated_unlocked ,
0 commit comments