1"""Python part of the warnings subsystem."""
2
3import sys
4
5
6__all__ = ["warn", "warn_explicit", "showwarning",
7           "formatwarning", "filterwarnings", "simplefilter",
8           "resetwarnings", "catch_warnings"]
9
10def showwarning(message, category, filename, lineno, file=None, line=None):
11    """Hook to write a warning to a file; replace if you like."""
12    msg = WarningMessage(message, category, filename, lineno, file, line)
13    _showwarnmsg_impl(msg)
14
15def formatwarning(message, category, filename, lineno, line=None):
16    """Function to format a warning the standard way."""
17    msg = WarningMessage(message, category, filename, lineno, None, line)
18    return _formatwarnmsg_impl(msg)
19
20def _showwarnmsg_impl(msg):
21    file = msg.file
22    if file is None:
23        file = sys.stderr
24        if file is None:
25            # sys.stderr is None when run with pythonw.exe:
26            # warnings get lost
27            return
28    text = _formatwarnmsg(msg)
29    try:
30        file.write(text)
31    except OSError:
32        # the file (probably stderr) is invalid - this warning gets lost.
33        pass
34
35def _formatwarnmsg_impl(msg):
36    s =  ("%s:%s: %s: %s\n"
37          % (msg.filename, msg.lineno, msg.category.__name__,
38             msg.message))
39
40    if msg.line is None:
41        try:
42            import linecache
43            line = linecache.getline(msg.filename, msg.lineno)
44        except Exception:
45            # When a warning is logged during Python shutdown, linecache
46            # and the import machinery don't work anymore
47            line = None
48            linecache = None
49    else:
50        line = msg.line
51    if line:
52        line = line.strip()
53        s += "  %s\n" % line
54
55    if msg.source is not None:
56        try:
57            import tracemalloc
58            tb = tracemalloc.get_object_traceback(msg.source)
59        except Exception:
60            # When a warning is logged during Python shutdown, tracemalloc
61            # and the import machinery don't work anymore
62            tb = None
63
64        if tb is not None:
65            s += 'Object allocated at (most recent call first):\n'
66            for frame in tb:
67                s += ('  File "%s", lineno %s\n'
68                      % (frame.filename, frame.lineno))
69
70                try:
71                    if linecache is not None:
72                        line = linecache.getline(frame.filename, frame.lineno)
73                    else:
74                        line = None
75                except Exception:
76                    line = None
77                if line:
78                    line = line.strip()
79                    s += '    %s\n' % line
80    return s
81
82# Keep a reference to check if the function was replaced
83_showwarning_orig = showwarning
84
85def _showwarnmsg(msg):
86    """Hook to write a warning to a file; replace if you like."""
87    try:
88        sw = showwarning
89    except NameError:
90        pass
91    else:
92        if sw is not _showwarning_orig:
93            # warnings.showwarning() was replaced
94            if not callable(sw):
95                raise TypeError("warnings.showwarning() must be set to a "
96                                "function or method")
97
98            sw(msg.message, msg.category, msg.filename, msg.lineno,
99               msg.file, msg.line)
100            return
101    _showwarnmsg_impl(msg)
102
103# Keep a reference to check if the function was replaced
104_formatwarning_orig = formatwarning
105
106def _formatwarnmsg(msg):
107    """Function to format a warning the standard way."""
108    try:
109        fw = formatwarning
110    except NameError:
111        pass
112    else:
113        if fw is not _formatwarning_orig:
114            # warnings.formatwarning() was replaced
115            return fw(msg.message, msg.category,
116                      msg.filename, msg.lineno, line=msg.line)
117    return _formatwarnmsg_impl(msg)
118
119def filterwarnings(action, message="", category=Warning, module="", lineno=0,
120                   append=False):
121    """Insert an entry into the list of warnings filters (at the front).
122
123    'action' -- one of "error", "ignore", "always", "default", "module",
124                or "once"
125    'message' -- a regex that the warning message must match
126    'category' -- a class that the warning must be a subclass of
127    'module' -- a regex that the module name must match
128    'lineno' -- an integer line number, 0 matches all warnings
129    'append' -- if true, append to the list of filters
130    """
131    import re
132    assert action in ("error", "ignore", "always", "default", "module",
133                      "once"), "invalid action: %r" % (action,)
134    assert isinstance(message, str), "message must be a string"
135    assert isinstance(category, type), "category must be a class"
136    assert issubclass(category, Warning), "category must be a Warning subclass"
137    assert isinstance(module, str), "module must be a string"
138    assert isinstance(lineno, int) and lineno >= 0, \
139           "lineno must be an int >= 0"
140    _add_filter(action, re.compile(message, re.I), category,
141            re.compile(module), lineno, append=append)
142
143def simplefilter(action, category=Warning, lineno=0, append=False):
144    """Insert a simple entry into the list of warnings filters (at the front).
145
146    A simple filter matches all modules and messages.
147    'action' -- one of "error", "ignore", "always", "default", "module",
148                or "once"
149    'category' -- a class that the warning must be a subclass of
150    'lineno' -- an integer line number, 0 matches all warnings
151    'append' -- if true, append to the list of filters
152    """
153    assert action in ("error", "ignore", "always", "default", "module",
154                      "once"), "invalid action: %r" % (action,)
155    assert isinstance(lineno, int) and lineno >= 0, \
156           "lineno must be an int >= 0"
157    _add_filter(action, None, category, None, lineno, append=append)
158
159def _add_filter(*item, append):
160    # Remove possible duplicate filters, so new one will be placed
161    # in correct place. If append=True and duplicate exists, do nothing.
162    if not append:
163        try:
164            filters.remove(item)
165        except ValueError:
166            pass
167        filters.insert(0, item)
168    else:
169        if item not in filters:
170            filters.append(item)
171    _filters_mutated()
172
173def resetwarnings():
174    """Clear the list of warning filters, so that no filters are active."""
175    filters[:] = []
176    _filters_mutated()
177
178class _OptionError(Exception):
179    """Exception used by option processing helpers."""
180    pass
181
182# Helper to process -W options passed via sys.warnoptions
183def _processoptions(args):
184    for arg in args:
185        try:
186            _setoption(arg)
187        except _OptionError as msg:
188            print("Invalid -W option ignored:", msg, file=sys.stderr)
189
190# Helper for _processoptions()
191def _setoption(arg):
192    import re
193    parts = arg.split(':')
194    if len(parts) > 5:
195        raise _OptionError("too many fields (max 5): %r" % (arg,))
196    while len(parts) < 5:
197        parts.append('')
198    action, message, category, module, lineno = [s.strip()
199                                                 for s in parts]
200    action = _getaction(action)
201    message = re.escape(message)
202    category = _getcategory(category)
203    module = re.escape(module)
204    if module:
205        module = module + '$'
206    if lineno:
207        try:
208            lineno = int(lineno)
209            if lineno < 0:
210                raise ValueError
211        except (ValueError, OverflowError):
212            raise _OptionError("invalid lineno %r" % (lineno,))
213    else:
214        lineno = 0
215    filterwarnings(action, message, category, module, lineno)
216
217# Helper for _setoption()
218def _getaction(action):
219    if not action:
220        return "default"
221    if action == "all": return "always" # Alias
222    for a in ('default', 'always', 'ignore', 'module', 'once', 'error'):
223        if a.startswith(action):
224            return a
225    raise _OptionError("invalid action: %r" % (action,))
226
227# Helper for _setoption()
228def _getcategory(category):
229    import re
230    if not category:
231        return Warning
232    if re.match("^[a-zA-Z0-9_]+$", category):
233        try:
234            cat = eval(category)
235        except NameError:
236            raise _OptionError("unknown warning category: %r" % (category,))
237    else:
238        i = category.rfind(".")
239        module = category[:i]
240        klass = category[i+1:]
241        try:
242            m = __import__(module, None, None, [klass])
243        except ImportError:
244            raise _OptionError("invalid module name: %r" % (module,))
245        try:
246            cat = getattr(m, klass)
247        except AttributeError:
248            raise _OptionError("unknown warning category: %r" % (category,))
249    if not issubclass(cat, Warning):
250        raise _OptionError("invalid warning category: %r" % (category,))
251    return cat
252
253
254def _is_internal_frame(frame):
255    """Signal whether the frame is an internal CPython implementation detail."""
256    filename = frame.f_code.co_filename
257    return 'importlib' in filename and '_bootstrap' in filename
258
259
260def _next_external_frame(frame):
261    """Find the next frame that doesn't involve CPython internals."""
262    frame = frame.f_back
263    while frame is not None and _is_internal_frame(frame):
264        frame = frame.f_back
265    return frame
266
267
268# Code typically replaced by _warnings
269def warn(message, category=None, stacklevel=1, source=None):
270    """Issue a warning, or maybe ignore it or raise an exception."""
271    # Check if message is already a Warning object
272    if isinstance(message, Warning):
273        category = message.__class__
274    # Check category argument
275    if category is None:
276        category = UserWarning
277    if not (isinstance(category, type) and issubclass(category, Warning)):
278        raise TypeError("category must be a Warning subclass, "
279                        "not '{:s}'".format(type(category).__name__))
280    # Get context information
281    try:
282        if stacklevel <= 1 or _is_internal_frame(sys._getframe(1)):
283            # If frame is too small to care or if the warning originated in
284            # internal code, then do not try to hide any frames.
285            frame = sys._getframe(stacklevel)
286        else:
287            frame = sys._getframe(1)
288            # Look for one frame less since the above line starts us off.
289            for x in range(stacklevel-1):
290                frame = _next_external_frame(frame)
291                if frame is None:
292                    raise ValueError
293    except ValueError:
294        globals = sys.__dict__
295        lineno = 1
296    else:
297        globals = frame.f_globals
298        lineno = frame.f_lineno
299    if '__name__' in globals:
300        module = globals['__name__']
301    else:
302        module = "<string>"
303    filename = globals.get('__file__')
304    if filename:
305        fnl = filename.lower()
306        if fnl.endswith(".pyc"):
307            filename = filename[:-1]
308    else:
309        if module == "__main__":
310            try:
311                filename = sys.argv[0]
312            except AttributeError:
313                # embedded interpreters don't have sys.argv, see bug #839151
314                filename = '__main__'
315        if not filename:
316            filename = module
317    registry = globals.setdefault("__warningregistry__", {})
318    warn_explicit(message, category, filename, lineno, module, registry,
319                  globals, source)
320
321def warn_explicit(message, category, filename, lineno,
322                  module=None, registry=None, module_globals=None,
323                  source=None):
324    lineno = int(lineno)
325    if module is None:
326        module = filename or "<unknown>"
327        if module[-3:].lower() == ".py":
328            module = module[:-3] # XXX What about leading pathname?
329    if registry is None:
330        registry = {}
331    if registry.get('version', 0) != _filters_version:
332        registry.clear()
333        registry['version'] = _filters_version
334    if isinstance(message, Warning):
335        text = str(message)
336        category = message.__class__
337    else:
338        text = message
339        message = category(message)
340    key = (text, category, lineno)
341    # Quick test for common case
342    if registry.get(key):
343        return
344    # Search the filters
345    for item in filters:
346        action, msg, cat, mod, ln = item
347        if ((msg is None or msg.match(text)) and
348            issubclass(category, cat) and
349            (mod is None or mod.match(module)) and
350            (ln == 0 or lineno == ln)):
351            break
352    else:
353        action = defaultaction
354    # Early exit actions
355    if action == "ignore":
356        registry[key] = 1
357        return
358
359    # Prime the linecache for formatting, in case the
360    # "file" is actually in a zipfile or something.
361    import linecache
362    linecache.getlines(filename, module_globals)
363
364    if action == "error":
365        raise message
366    # Other actions
367    if action == "once":
368        registry[key] = 1
369        oncekey = (text, category)
370        if onceregistry.get(oncekey):
371            return
372        onceregistry[oncekey] = 1
373    elif action == "always":
374        pass
375    elif action == "module":
376        registry[key] = 1
377        altkey = (text, category, 0)
378        if registry.get(altkey):
379            return
380        registry[altkey] = 1
381    elif action == "default":
382        registry[key] = 1
383    else:
384        # Unrecognized actions are errors
385        raise RuntimeError(
386              "Unrecognized action (%r) in warnings.filters:\n %s" %
387              (action, item))
388    # Print message and context
389    msg = WarningMessage(message, category, filename, lineno, source)
390    _showwarnmsg(msg)
391
392
393class WarningMessage(object):
394
395    _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
396                        "line", "source")
397
398    def __init__(self, message, category, filename, lineno, file=None,
399                 line=None, source=None):
400        local_values = locals()
401        for attr in self._WARNING_DETAILS:
402            setattr(self, attr, local_values[attr])
403        self._category_name = category.__name__ if category else None
404
405    def __str__(self):
406        return ("{message : %r, category : %r, filename : %r, lineno : %s, "
407                    "line : %r}" % (self.message, self._category_name,
408                                    self.filename, self.lineno, self.line))
409
410
411class catch_warnings(object):
412
413    """A context manager that copies and restores the warnings filter upon
414    exiting the context.
415
416    The 'record' argument specifies whether warnings should be captured by a
417    custom implementation of warnings.showwarning() and be appended to a list
418    returned by the context manager. Otherwise None is returned by the context
419    manager. The objects appended to the list are arguments whose attributes
420    mirror the arguments to showwarning().
421
422    The 'module' argument is to specify an alternative module to the module
423    named 'warnings' and imported under that name. This argument is only useful
424    when testing the warnings module itself.
425
426    """
427
428    def __init__(self, *, record=False, module=None):
429        """Specify whether to record warnings and if an alternative module
430        should be used other than sys.modules['warnings'].
431
432        For compatibility with Python 3.0, please consider all arguments to be
433        keyword-only.
434
435        """
436        self._record = record
437        self._module = sys.modules['warnings'] if module is None else module
438        self._entered = False
439
440    def __repr__(self):
441        args = []
442        if self._record:
443            args.append("record=True")
444        if self._module is not sys.modules['warnings']:
445            args.append("module=%r" % self._module)
446        name = type(self).__name__
447        return "%s(%s)" % (name, ", ".join(args))
448
449    def __enter__(self):
450        if self._entered:
451            raise RuntimeError("Cannot enter %r twice" % self)
452        self._entered = True
453        self._filters = self._module.filters
454        self._module.filters = self._filters[:]
455        self._module._filters_mutated()
456        self._showwarning = self._module.showwarning
457        self._showwarnmsg_impl = self._module._showwarnmsg_impl
458        if self._record:
459            log = []
460            self._module._showwarnmsg_impl = log.append
461            # Reset showwarning() to the default implementation to make sure
462            # that _showwarnmsg() calls _showwarnmsg_impl()
463            self._module.showwarning = self._module._showwarning_orig
464            return log
465        else:
466            return None
467
468    def __exit__(self, *exc_info):
469        if not self._entered:
470            raise RuntimeError("Cannot exit %r without entering first" % self)
471        self._module.filters = self._filters
472        self._module._filters_mutated()
473        self._module.showwarning = self._showwarning
474        self._module._showwarnmsg_impl = self._showwarnmsg_impl
475
476
477# filters contains a sequence of filter 5-tuples
478# The components of the 5-tuple are:
479# - an action: error, ignore, always, default, module, or once
480# - a compiled regex that must match the warning message
481# - a class representing the warning category
482# - a compiled regex that must match the module that is being warned
483# - a line number for the line being warning, or 0 to mean any line
484# If either if the compiled regexs are None, match anything.
485_warnings_defaults = False
486try:
487    from _warnings import (filters, _defaultaction, _onceregistry,
488                           warn, warn_explicit, _filters_mutated)
489    defaultaction = _defaultaction
490    onceregistry = _onceregistry
491    _warnings_defaults = True
492except ImportError:
493    filters = []
494    defaultaction = "default"
495    onceregistry = {}
496
497    _filters_version = 1
498
499    def _filters_mutated():
500        global _filters_version
501        _filters_version += 1
502
503
504# Module initialization
505_processoptions(sys.warnoptions)
506if not _warnings_defaults:
507    silence = [ImportWarning, PendingDeprecationWarning]
508    silence.append(DeprecationWarning)
509    for cls in silence:
510        simplefilter("ignore", category=cls)
511    bytes_warning = sys.flags.bytes_warning
512    if bytes_warning > 1:
513        bytes_action = "error"
514    elif bytes_warning:
515        bytes_action = "default"
516    else:
517        bytes_action = "ignore"
518    simplefilter(bytes_action, category=BytesWarning, append=1)
519    # resource usage warnings are enabled by default in pydebug mode
520    if hasattr(sys, 'gettotalrefcount'):
521        resource_action = "always"
522    else:
523        resource_action = "ignore"
524    simplefilter(resource_action, category=ResourceWarning, append=1)
525
526del _warnings_defaults
527