PEP: 230
Title: Warning Framework
Version: $Revision: 638 $
Author: Guido van Rossum <guido at python.org>
Status: Draft
Type: Standards Track
Python-Version: 2.1
Status: Incomplete
Post-History: 05-Nov-2000

Abstract

    This PEP proposes a C and Python level API, as well as command
    line flags, to issue warning messages and control what happens to
    them.  This is mostly based on GvR's proposal posted to python-dev
    on 05-Nov-2000, with some ideas (such as using classes to
    categorize warnings) merged in from Paul Prescod's
    counter-proposal posted on the same date.  Also, an attempt to
    implement the proposal caused several small tweaks.


Motivation

    With Python 3000 looming, it is necessary to start issuing
    warnings about the use of obsolete or deprecated features, in
    addition to errors.  There are also lots of other reasons to be
    able to issue warnings, both from C and from Python code, both at
    compile time and at run time.

    Warnings aren't fatal, and thus it's possible that a program
    triggers the same warning many times during a single execution.
    It would be annoying if a program emitted an endless stream of
    identical warnings.  Therefore, a mechanism is needed that
    suppresses multiple identical warnings.

    It is also desirable to have user control over which warnings are
    printed.  While in general it is useful to see all warnings all
    the time, there may be times where it is impractical to fix the
    code right away in a production program.  In this case, there
    should be a way to suppress warnings.

    It is also useful to be able to suppress specific warnings during
    program development, e.g. when a warning is generated by a piece
    of 3rd party code that cannot be fixed right away, or when there
    is no way to fix the code (possibly a warning message is generated
    for a perfectly fine piece of code).  It would be unwise to offer
    to suppress all warnings in such cases: the developer would miss
    warnings about the rest of the code.

    On the other hand, there are also situations conceivable where
    some or all warnings are better treated as errors.  For example,
    it may be a local coding standard that a particular deprecated
    feature should not be used.  In order to enforce this, it is
    useful to be able to turn the warning about this particular
    feature into an error, raising an exception (without necessarily
    turning all warnings into errors).

    Therefore, I propose to introduce a flexible "warning filter"
    which can filter out warnings or change them into exceptions,
    based on:

    - Where in the code they are generated (per package, module, or
      function)

    - The warning category (warning categories are discussed below)

    - A specific warning message

    The warning filter must be controllable both from the command line
    and from Python code.


APIs For Issuing Warnings

    - To issue a warning from Python:

        import warnings
        warnings.warn(message[, category[, stacklevel]])

      The category argument, if given, must be a warning category
      class (see below); it defaults to warnings.UserWarning.  This
      may raise an exception if the particular warning issued is
      changed into an error by the warnings filter.  The stacklevel
      can be used by wrapper functions written in Python, like this:

      def deprecation(message):
          warn(message, DeprecationWarning, level=2)

      This makes the warning refer to the deprecation()'s caller,
      rather than to the source of deprecation() itself (since the
      latter would defeat the purpose of the warning message).

    - To issue a warning from C:

        int PyErr_Warn(PyObject *category, char *message);

      Return 0 normally, 1 if an exception is raised (either because
      the warning was transformed into an exception, or because of a
      malfunction in the implementation, such as running out of
      memory).  The category argument must be a warning category class
      (see below) or NULL, in which case it defaults to
      PyExc_RuntimeWarning.  When PyErr_Warn() function returns 1, the
      caller should do normal exception handling.

      The current C implementation of PyErr_Warn() imports the
      warnings module (implemented in Python) and calls its warn()
      function.  This minimizes the amount of C code that needs to be
      added to implement the warning feature.

      [XXX Open Issue: what about issuing warnings during lexing or
      parsing, which don't have the exception machinery available?]


Warnings Categories

    There are a number of built-in exceptions that represent warning
    categories.  This categorization is useful to be able to filter
    out groups of warnings.  The following warnings category classes
    are currently defined:

    - Warning -- this is the base class of all warning category
      classes and it itself a subclass of Exception

    - UserWarning -- the default category for warnings.warn()

    - DeprecationWarning -- base category for warnings about deprecated
      features

    - SyntaxWarning -- base category for warnings about dubious
      syntactic features

    - RuntimeWarning -- base category for warnings about dubious
      runtime features

    [XXX: Other warning categories may be proposed during the review
    period for this PEP.]

    These standard warning categories are available from C as
    PyExc_Warning, PyExc_UserWarning, etc.  From Python, they are
    available in the __builtin__ module, so no import is necessary.

    User code can define additional warning categories by subclassing
    one of the standard warning categories.  A warning category must
    always be a subclass of the Warning class.


The Warnings Filter

    The warnings filter control whether warnings are ignored,
    displayed, or turned into errors (raising an exception).

    There are three sides to the warnings filter:

    - The data structures used to efficiently determine the
      disposition of a particular warnings.warn() or PyErr_Warn()
      call.

    - The API to control the filter from Python source code.

    - The command line switches to control the filter.

    The warnings filter works in several stages.  It is optimized for
    the (expected to be common) case where the same warning is issued
    from the same place in the code over and over.

    First, the warning filter collects the module and line number
    where the warning is issued; this information is readily available
    through sys._getframe().

    Conceptually, the warnings filter maintains an ordered list of
    filter specifications; any specific warning is matched against
    each filter specification in the list in turn until a match is
    found; the match determines the disposition of the match.  Each
    entry is a tuple as follows:

      (category, message, module, lineno, action)

    - category is a class (a subclass of warnings.Warning) of which
      the warning category must be a subclass in order to match

    - message is a compiled regular expression that the warning
      message must match (the match is case-insensitive)

    - module is a compiled regular expression that the module name
      must match

    - lineno is an integer that the line number where the warning
      occurred must match, or 0 to match all line numbers

    - action is one of the following strings:

        - "error" -- turn matching warnings into exceptions

        - "ignore" -- never print matching warnings

        - "always" -- always print matching warnings

        - "default" -- print the first occurrence of matching warnings
          for each location where the warning is issued

        - "module" -- print the first occurrence of matching warnings
          for each module where the warning is issued

        - "once" -- print only the first occurrence of matching
          warnings

    Since the Warning class is derived from the built-in Exception
    class, to turn a warning into an error we simply raise
    category(message).


Warnings Output And Formatting Hooks

    When the warnings filter decides to issue a warning (but not when
    it decides to raise an exception), it passes the information about
    the function warnings.showwarning(message, category, filename, lineno).
    The default implementation of this function writes the warning text
    to sys.stderr, and shows the source line of the filename.  It has
    an optional 5th argument which can be used to specify a different
    file than sys.stderr.

    The formatting of warnings is done by a separate function,
    warnings.formatwarning(message, category, filename, lineno).  This
    returns a string (that may contain newlines and ends in a newline)
    that can be printed to get the identical effect of the
    showwarning() function.


API For Manipulating Warning Filters

      warnings.filterwarnings(message, category, module, lineno, action)

    This checks the types of the arguments, compiles the message and
    module regular expressions, and inserts them as a tuple in front
    of the warnings filter.

      warnings.resetwarnings()

    Reset the warnings filter to empty.


Command Line Syntax

    There should be command line options to specify the most common
    filtering actions, which I expect to include at least:

    - suppress all warnings

    - suppress a particular warning message everywhere

    - suppress all warnings in a particular module

    - turn all warnings into exceptions

    I propose the following command line option syntax:

    -Waction[:message[:category[:module[:lineno]]]]

    Where:

    - 'action' is an abbreviation of one of the allowed actions
      ("error", "default", "ignore", "always", "once", or "module")

    - 'message' is a message string; matches warnings whose message
      text is an initial substring of 'message' (matching is
      case-insensitive)

    - 'category' is an abbreviation of a standard warning category
      class name *or* a fully-qualified name for a user-defined
      warning category class of the form [package.]module.classname

    - 'module' is a module name (possibly package.module)

    - 'lineno' is an integral line number

    All parts except 'action' may be omitted, where an empty value
    after stripping whitespace is the same as an omitted value.

    The C code that parses the Python command line saves the body of
    all -W options in a list of strings, which is made available to
    the warnings module as sys.warnoptions.  The warnings module
    parses these when it is first imported.  Errors detected during
    the parsing of sys.warnoptions are not fatal; a message is written
    to sys.stderr and processing continues with the option.

    Examples:

    -Werror
        Turn all warnings into errors

    -Wall
        Show all warnings

    -Wignore
        Ignore all warnings

    -Wi:hello
        Ignore warnings whose message text starts with "hello"

    -We::Deprecation
        Turn deprecation warnings into errors

    -Wi:::spam:10
        Ignore all warnings on line 10 of module spam

    -Wi:::spam -Wd:::spam:10
        Ignore all warnings in module spam except on line 10

    -We::Deprecation -Wd::Deprecation:spam
        Turn deprecation warnings into errors except in module spam


Open Issues

    Some open issues off the top of my head:

    - What about issuing warnings during lexing or parsing, which
      don't have the exception machinery available?

    - The proposed command line syntax is a bit ugly (although the
      simple cases aren't so bad: -Werror, -Wignore, etc.).  Anybody
      got a better idea?

    - I'm a bit worried that the filter specifications are too
      complex.  Perhaps filtering only on category and module (not on
      message text and line number) would be enough?

    - There's a bit of confusion between module names and file names.
      The reporting uses file names, but the filter specification uses
      module names.  Maybe it should allow filenames as well?

    - I'm not at all convinced that packages are handled right.

    - Do we need more standard warning categories?  Fewer?

    - In order to minimize the start-up overhead, the warnings module
      is imported by the first call to PyErr_Warn().  It does the
      command line parsing for -W options upon import.  Therefore, it
      is possible that warning-free programs will not complain about
      invalid -W options.


Rejected Concerns

    Paul Prescod, Barry Warsaw and Fred Drake have brought up several
    additional concerns that I feel aren't critical.  I address them
    here (the concerns are paraphrased, not exactly their words):

    - Paul: warn() should be a built-in or a statement to make it easily
      available.

      Response: "from warnings import warn" is easy enough.

    - Paul: What if I have a speed-critical module that triggers
      warnings in an inner loop.  It should be possible to disable the
      overhead for detecting the warning (not just suppress the
      warning).

      Response: rewrite the inner loop to avoid triggering the
      warning.

    - Paul: What if I want to see the full context of a warning?

      Response: use -Werror to turn it into an exception.

    - Paul: I prefer ":*:*:" to ":::" for leaving parts of the warning
      spec out.

      Response: I don't.

    - Barry: It would be nice if lineno can be a range specification.

      Response: Too much complexity already.

    - Barry: I'd like to add my own warning action.  Maybe if `action'
      could be a callable as well as a string.  Then in my IDE, I
      could set that to "mygui.popupWarningsDialog".

      Response: For that purpose you would override
      warnings.showwarning().

    - Fred: why do the Warning category classes have to be in
      __builtin__?

      Response: that's the simplest implementation, given that the
      warning categories must be available in C before the first
      PyErr_Warn() call, which imports the warnings module.  I see no
      problem with making them available as built-ins.


Implementation

    Here's a prototype implementation:

  http://sourceforge.net/patch/?func=detailpatch&patch_id=102715&group_id=5470