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
+
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
+
10
111
def showwarning (message , category , filename , lineno , file = None , line = None ):
11
112
"""Hook to write a warning to a file; replace if you like."""
12
113
msg = WarningMessage (message , category , filename , lineno , file , line )
@@ -18,6 +119,10 @@ def formatwarning(message, category, filename, lineno, line=None):
18
119
return _formatwarnmsg_impl (msg )
19
120
20
121
def _showwarnmsg_impl (msg ):
122
+ context = get_context ()
123
+ if context .log is not None :
124
+ context ._record_warning (msg )
125
+ return
21
126
file = msg .file
22
127
if file is None :
23
128
file = sys .stderr
@@ -129,7 +234,7 @@ def _formatwarnmsg(msg):
129
234
return _formatwarnmsg_impl (msg )
130
235
131
236
def filterwarnings (action , message = "" , category = Warning , module = "" , lineno = 0 ,
132
- append = False ):
237
+ append = False , * , context = _global_context ):
133
238
"""Insert an entry into the list of warnings filters (at the front).
134
239
135
240
'action' -- one of "error", "ignore", "always", "all", "default", "module",
@@ -165,9 +270,11 @@ def filterwarnings(action, message="", category=Warning, module="", lineno=0,
165
270
else :
166
271
module = None
167
272
168
- _add_filter (action , message , category , module , lineno , append = append )
273
+ _add_filter (action , message , category , module , lineno , append = append ,
274
+ context = context )
169
275
170
- def simplefilter (action , category = Warning , lineno = 0 , append = False ):
276
+ def simplefilter (action , category = Warning , lineno = 0 , append = False , * ,
277
+ context = _global_context ):
171
278
"""Insert a simple entry into the list of warnings filters (at the front).
172
279
173
280
A simple filter matches all modules and messages.
@@ -183,10 +290,12 @@ def simplefilter(action, category=Warning, lineno=0, append=False):
183
290
raise TypeError ("lineno must be an int" )
184
291
if lineno < 0 :
185
292
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 )
187
295
188
- def _add_filter (* item , append ):
296
+ def _add_filter (* item , append , context = _global_context ):
189
297
with _lock :
298
+ filters = context ._filters
190
299
if not append :
191
300
# Remove possible duplicate filters, so new one will be placed
192
301
# in correct place. If append=True and duplicate exists, do nothing.
@@ -200,10 +309,10 @@ def _add_filter(*item, append):
200
309
filters .append (item )
201
310
_filters_mutated ()
202
311
203
- def resetwarnings ():
312
+ def resetwarnings (* , context = _global_context ):
204
313
"""Clear the list of warning filters, so that no filters are active."""
205
314
with _lock :
206
- filters [:] = []
315
+ context . _filters [:] = []
207
316
_filters_mutated ()
208
317
209
318
class _OptionError (Exception ):
@@ -347,7 +456,7 @@ def warn(message, category=None, stacklevel=1, source=None,
347
456
warn_explicit (message , category , filename , lineno , module , registry ,
348
457
globals , source )
349
458
350
- def warn_explicit (message , category , filename , lineno ,
459
+ def _warn_explicit_impl (message , category , filename , lineno ,
351
460
module = None , registry = None , module_globals = None ,
352
461
source = None ):
353
462
lineno = int (lineno )
@@ -371,7 +480,7 @@ def warn_explicit(message, category, filename, lineno,
371
480
if registry .get (key ):
372
481
return
373
482
# Search the filters
374
- for item in filters :
483
+ for item in _itertools . chain ( get_context (). _filters , filters ) :
375
484
action , msg , cat , mod , ln = item
376
485
if ((msg is None or msg .match (text )) and
377
486
issubclass (category , cat ) and
@@ -418,6 +527,11 @@ def warn_explicit(message, category, filename, lineno,
418
527
_showwarnmsg (msg )
419
528
420
529
530
+ def warn_explicit (* args , ** kwargs ):
531
+ with _lock :
532
+ return _warn_explicit_impl (* args , ** kwargs )
533
+
534
+
421
535
class WarningMessage (object ):
422
536
423
537
_WARNING_DETAILS = ("message" , "category" , "filename" , "lineno" , "file" ,
@@ -518,6 +632,64 @@ def __exit__(self, *exc_info):
518
632
self ._module ._showwarnmsg_impl = self ._showwarnmsg_impl
519
633
520
634
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
+
521
693
class deprecated :
522
694
"""Indicate that a class, function or overload is deprecated.
523
695
@@ -704,6 +876,7 @@ def extract():
704
876
# - a line number for the line being warning, or 0 to mean any line
705
877
# If either if the compiled regexs are None, match anything.
706
878
try :
879
+ raise ImportError # FIXME: temporary, until _warnings is updated
707
880
from _warnings import (filters , _defaultaction , _onceregistry ,
708
881
warn , warn_explicit , _filters_mutated ,
709
882
_acquire_lock , _release_lock ,
0 commit comments