OILS / opy / _regtest / src / warnings.py View on Github | oilshell.org

422 lines, 299 significant
1"""Python part of the warnings subsystem."""
2
3# Note: function level imports should *not* be used
4# in this module as it may cause import lock deadlock.
5# See bug 683658.
6import linecache
7import sys
8import types
9
10__all__ = ["warn", "warn_explicit", "showwarning",
11 "formatwarning", "filterwarnings", "simplefilter",
12 "resetwarnings", "catch_warnings"]
13
14
15def warnpy3k(message, category=None, stacklevel=1):
16 """Issue a deprecation warning for Python 3.x related changes.
17
18 Warnings are omitted unless Python is started with the -3 option.
19 """
20 if sys.py3kwarning:
21 if category is None:
22 category = DeprecationWarning
23 warn(message, category, stacklevel+1)
24
25def _show_warning(message, category, filename, lineno, file=None, line=None):
26 """Hook to write a warning to a file; replace if you like."""
27 if file is None:
28 file = sys.stderr
29 if file is None:
30 # sys.stderr is None - warnings get lost
31 return
32 try:
33 file.write(formatwarning(message, category, filename, lineno, line))
34 except (IOError, UnicodeError):
35 pass # the file (probably stderr) is invalid - this warning gets lost.
36# Keep a working version around in case the deprecation of the old API is
37# triggered.
38showwarning = _show_warning
39
40def formatwarning(message, category, filename, lineno, line=None):
41 """Function to format a warning the standard way."""
42 try:
43 unicodetype = unicode
44 except NameError:
45 unicodetype = ()
46 try:
47 message = str(message)
48 except UnicodeEncodeError:
49 pass
50 s = "%s: %s: %s\n" % (lineno, category.__name__, message)
51 line = linecache.getline(filename, lineno) if line is None else line
52 if line:
53 line = line.strip()
54 if isinstance(s, unicodetype) and isinstance(line, str):
55 line = unicode(line, 'latin1')
56 s += " %s\n" % line
57 if isinstance(s, unicodetype) and isinstance(filename, str):
58 enc = sys.getfilesystemencoding()
59 if enc:
60 try:
61 filename = unicode(filename, enc)
62 except UnicodeDecodeError:
63 pass
64 s = "%s:%s" % (filename, s)
65 return s
66
67def filterwarnings(action, message="", category=Warning, module="", lineno=0,
68 append=0):
69 """Insert an entry into the list of warnings filters (at the front).
70
71 'action' -- one of "error", "ignore", "always", "default", "module",
72 or "once"
73 'message' -- a regex that the warning message must match
74 'category' -- a class that the warning must be a subclass of
75 'module' -- a regex that the module name must match
76 'lineno' -- an integer line number, 0 matches all warnings
77 'append' -- if true, append to the list of filters
78 """
79 import re
80 assert action in ("error", "ignore", "always", "default", "module",
81 "once"), "invalid action: %r" % (action,)
82 assert isinstance(message, basestring), "message must be a string"
83 assert isinstance(category, (type, types.ClassType)), \
84 "category must be a class"
85 assert issubclass(category, Warning), "category must be a Warning subclass"
86 assert isinstance(module, basestring), "module must be a string"
87 assert isinstance(lineno, int) and lineno >= 0, \
88 "lineno must be an int >= 0"
89 item = (action, re.compile(message, re.I), category,
90 re.compile(module), lineno)
91 if append:
92 filters.append(item)
93 else:
94 filters.insert(0, item)
95
96def simplefilter(action, category=Warning, lineno=0, append=0):
97 """Insert a simple entry into the list of warnings filters (at the front).
98
99 A simple filter matches all modules and messages.
100 'action' -- one of "error", "ignore", "always", "default", "module",
101 or "once"
102 'category' -- a class that the warning must be a subclass of
103 'lineno' -- an integer line number, 0 matches all warnings
104 'append' -- if true, append to the list of filters
105 """
106 assert action in ("error", "ignore", "always", "default", "module",
107 "once"), "invalid action: %r" % (action,)
108 assert isinstance(lineno, int) and lineno >= 0, \
109 "lineno must be an int >= 0"
110 item = (action, None, category, None, lineno)
111 if append:
112 filters.append(item)
113 else:
114 filters.insert(0, item)
115
116def resetwarnings():
117 """Clear the list of warning filters, so that no filters are active."""
118 filters[:] = []
119
120class _OptionError(Exception):
121 """Exception used by option processing helpers."""
122 pass
123
124# Helper to process -W options passed via sys.warnoptions
125def _processoptions(args):
126 for arg in args:
127 try:
128 _setoption(arg)
129 except _OptionError, msg:
130 print >>sys.stderr, "Invalid -W option ignored:", msg
131
132# Helper for _processoptions()
133def _setoption(arg):
134 import re
135 parts = arg.split(':')
136 if len(parts) > 5:
137 raise _OptionError("too many fields (max 5): %r" % (arg,))
138 while len(parts) < 5:
139 parts.append('')
140 action, message, category, module, lineno = [s.strip()
141 for s in parts]
142 action = _getaction(action)
143 message = re.escape(message)
144 category = _getcategory(category)
145 module = re.escape(module)
146 if module:
147 module = module + '$'
148 if lineno:
149 try:
150 lineno = int(lineno)
151 if lineno < 0:
152 raise ValueError
153 except (ValueError, OverflowError):
154 raise _OptionError("invalid lineno %r" % (lineno,))
155 else:
156 lineno = 0
157 filterwarnings(action, message, category, module, lineno)
158
159# Helper for _setoption()
160def _getaction(action):
161 if not action:
162 return "default"
163 if action == "all": return "always" # Alias
164 for a in ('default', 'always', 'ignore', 'module', 'once', 'error'):
165 if a.startswith(action):
166 return a
167 raise _OptionError("invalid action: %r" % (action,))
168
169# Helper for _setoption()
170def _getcategory(category):
171 import re
172 if not category:
173 return Warning
174 if re.match("^[a-zA-Z0-9_]+$", category):
175 try:
176 cat = eval(category)
177 except NameError:
178 raise _OptionError("unknown warning category: %r" % (category,))
179 else:
180 i = category.rfind(".")
181 module = category[:i]
182 klass = category[i+1:]
183 try:
184 m = __import__(module, None, None, [klass])
185 except ImportError:
186 raise _OptionError("invalid module name: %r" % (module,))
187 try:
188 cat = getattr(m, klass)
189 except AttributeError:
190 raise _OptionError("unknown warning category: %r" % (category,))
191 if not issubclass(cat, Warning):
192 raise _OptionError("invalid warning category: %r" % (category,))
193 return cat
194
195
196# Code typically replaced by _warnings
197def warn(message, category=None, stacklevel=1):
198 """Issue a warning, or maybe ignore it or raise an exception."""
199 # Check if message is already a Warning object
200 if isinstance(message, Warning):
201 category = message.__class__
202 # Check category argument
203 if category is None:
204 category = UserWarning
205 assert issubclass(category, Warning)
206 # Get context information
207 try:
208 caller = sys._getframe(stacklevel)
209 except ValueError:
210 globals = sys.__dict__
211 lineno = 1
212 else:
213 globals = caller.f_globals
214 lineno = caller.f_lineno
215 if '__name__' in globals:
216 module = globals['__name__']
217 else:
218 module = "<string>"
219 filename = globals.get('__file__')
220 if filename:
221 fnl = filename.lower()
222 if fnl.endswith((".pyc", ".pyo")):
223 filename = filename[:-1]
224 else:
225 if module == "__main__":
226 try:
227 filename = sys.argv[0]
228 except AttributeError:
229 # embedded interpreters don't have sys.argv, see bug #839151
230 filename = '__main__'
231 if not filename:
232 filename = module
233 registry = globals.setdefault("__warningregistry__", {})
234 warn_explicit(message, category, filename, lineno, module, registry,
235 globals)
236
237def warn_explicit(message, category, filename, lineno,
238 module=None, registry=None, module_globals=None):
239 lineno = int(lineno)
240 if module is None:
241 module = filename or "<unknown>"
242 if module[-3:].lower() == ".py":
243 module = module[:-3] # XXX What about leading pathname?
244 if registry is None:
245 registry = {}
246 if isinstance(message, Warning):
247 text = str(message)
248 category = message.__class__
249 else:
250 text = message
251 message = category(message)
252 key = (text, category, lineno)
253 # Quick test for common case
254 if registry.get(key):
255 return
256 # Search the filters
257 for item in filters:
258 action, msg, cat, mod, ln = item
259 if ((msg is None or msg.match(text)) and
260 issubclass(category, cat) and
261 (mod is None or mod.match(module)) and
262 (ln == 0 or lineno == ln)):
263 break
264 else:
265 action = defaultaction
266 # Early exit actions
267 if action == "ignore":
268 registry[key] = 1
269 return
270
271 # Prime the linecache for formatting, in case the
272 # "file" is actually in a zipfile or something.
273 linecache.getlines(filename, module_globals)
274
275 if action == "error":
276 raise message
277 # Other actions
278 if action == "once":
279 registry[key] = 1
280 oncekey = (text, category)
281 if onceregistry.get(oncekey):
282 return
283 onceregistry[oncekey] = 1
284 elif action == "always":
285 pass
286 elif action == "module":
287 registry[key] = 1
288 altkey = (text, category, 0)
289 if registry.get(altkey):
290 return
291 registry[altkey] = 1
292 elif action == "default":
293 registry[key] = 1
294 else:
295 # Unrecognized actions are errors
296 raise RuntimeError(
297 "Unrecognized action (%r) in warnings.filters:\n %s" %
298 (action, item))
299 # Print message and context
300 showwarning(message, category, filename, lineno)
301
302
303class WarningMessage(object):
304
305 """Holds the result of a single showwarning() call."""
306
307 _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
308 "line")
309
310 def __init__(self, message, category, filename, lineno, file=None,
311 line=None):
312 local_values = locals()
313 for attr in self._WARNING_DETAILS:
314 setattr(self, attr, local_values[attr])
315 self._category_name = category.__name__ if category else None
316
317 def __str__(self):
318 return ("{message : %r, category : %r, filename : %r, lineno : %s, "
319 "line : %r}" % (self.message, self._category_name,
320 self.filename, self.lineno, self.line))
321
322
323class catch_warnings(object):
324
325 """A context manager that copies and restores the warnings filter upon
326 exiting the context.
327
328 The 'record' argument specifies whether warnings should be captured by a
329 custom implementation of warnings.showwarning() and be appended to a list
330 returned by the context manager. Otherwise None is returned by the context
331 manager. The objects appended to the list are arguments whose attributes
332 mirror the arguments to showwarning().
333
334 The 'module' argument is to specify an alternative module to the module
335 named 'warnings' and imported under that name. This argument is only useful
336 when testing the warnings module itself.
337
338 """
339
340 def __init__(self, record=False, module=None):
341 """Specify whether to record warnings and if an alternative module
342 should be used other than sys.modules['warnings'].
343
344 For compatibility with Python 3.0, please consider all arguments to be
345 keyword-only.
346
347 """
348 self._record = record
349 self._module = sys.modules['warnings'] if module is None else module
350 self._entered = False
351
352 def __repr__(self):
353 args = []
354 if self._record:
355 args.append("record=True")
356 if self._module is not sys.modules['warnings']:
357 args.append("module=%r" % self._module)
358 name = type(self).__name__
359 return "%s(%s)" % (name, ", ".join(args))
360
361 def __enter__(self):
362 if self._entered:
363 raise RuntimeError("Cannot enter %r twice" % self)
364 self._entered = True
365 self._filters = self._module.filters
366 self._module.filters = self._filters[:]
367 self._showwarning = self._module.showwarning
368 if self._record:
369 log = []
370 def showwarning(*args, **kwargs):
371 log.append(WarningMessage(*args, **kwargs))
372 self._module.showwarning = showwarning
373 return log
374 else:
375 return None
376
377 def __exit__(self, *exc_info):
378 if not self._entered:
379 raise RuntimeError("Cannot exit %r without entering first" % self)
380 self._module.filters = self._filters
381 self._module.showwarning = self._showwarning
382
383
384# filters contains a sequence of filter 5-tuples
385# The components of the 5-tuple are:
386# - an action: error, ignore, always, default, module, or once
387# - a compiled regex that must match the warning message
388# - a class representing the warning category
389# - a compiled regex that must match the module that is being warned
390# - a line number for the line being warning, or 0 to mean any line
391# If either if the compiled regexs are None, match anything.
392_warnings_defaults = False
393try:
394 from _warnings import (filters, default_action, once_registry,
395 warn, warn_explicit)
396 defaultaction = default_action
397 onceregistry = once_registry
398 _warnings_defaults = True
399except ImportError:
400 filters = []
401 defaultaction = "default"
402 onceregistry = {}
403
404
405# Module initialization
406_processoptions(sys.warnoptions)
407if not _warnings_defaults:
408 silence = [ImportWarning, PendingDeprecationWarning]
409 # Don't silence DeprecationWarning if -3 or -Q was used.
410 if not sys.py3kwarning and not sys.flags.division_warning:
411 silence.append(DeprecationWarning)
412 for cls in silence:
413 simplefilter("ignore", category=cls)
414 bytes_warning = sys.flags.bytes_warning
415 if bytes_warning > 1:
416 bytes_action = "error"
417 elif bytes_warning:
418 bytes_action = "default"
419 else:
420 bytes_action = "ignore"
421 simplefilter(bytes_action, category=BytesWarning, append=1)
422del _warnings_defaults