PEP: 230 Title: Warning Framework Version: $Revision$ Last-Modified:
$Date$ Author: guido\@python.org (Guido van Rossum) Status: Final Type:
Standards Track Content-Type: text/x-rst Created: Python-Version: 2.1
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

.. Local Variables: mode: indented-text indent-tabs-mode: nil End:
