[Python-checkins] CVS: python/nondist/peps pep-0230.txt,1.1,1.2

Guido van Rossum python-dev@python.org
Thu, 7 Dec 2000 09:38:53 -0800


Update of /cvsroot/python/python/nondist/peps
In directory slayer.i.sourceforge.net:/tmp/cvs-serv24990

Modified Files:
	pep-0230.txt 
Log Message:
First stab at the Warnings PEP.  I'm still working on the
implementation...


Index: pep-0230.txt
===================================================================
RCS file: /cvsroot/python/python/nondist/peps/pep-0230.txt,v
retrieving revision 1.1
retrieving revision 1.2
diff -C2 -r1.1 -r1.2
*** pep-0230.txt	2000/11/28 22:23:25	1.1
--- pep-0230.txt	2000/12/07 17:38:50	1.2
***************
*** 20,23 ****
--- 20,351 ----
  
  
+ 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:
+ 
+       sys.warn(message[, category])
+ 
+       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.
+ 
+     - To issue a warning from C:
+ 
+       int Py_Warn(char *message, PyObject *category)
+ 
+       Return 0 normally, 1 if an exception is raised.  The category
+       argument must be a warning category class (see below).  When
+       Py_Warn() function returns 1, the caller should do normal
+       exception handling.  [Question: what about issuing warnings
+       during lexing or parsing, which don't have the exception
+       machinery available?]
+ 
+ 
+ Warnings Categories
+ 
+     The "warnings" module defines classes representing warning
+     categories.  This categorization is useful to be able to filter
+     out groups of warnings.  The classes defined in this module have
+     no semantics attached to them and are never instantiated -- only
+     their names are used for filtering (see the section on the
+     warnings filter below).  The following warnings category classes
+     are currently defined:
+ 
+     - Warning -- this is the base class of all warning category
+       classes.  A warning category must always be a subclass of this
+       class.
+ 
+     - UserWarning -- the default category for sys.warn()
+ 
+     - DeprecationWarning -- base category for warnings about deprecated
+       features
+ 
+     - SyntaxWarning -- base category for warnings about dubious
+       syntactic features
+ 
+     Other categories may be proposed during the review period for this
+     PEP.
+ 
+ 
+ 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 Py_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 PyEval_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 regular expression that the warning message must
+       match
+ 
+     - module is a 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
+ 
+     The Warning class is derived from the built-in Exception class, so
+     that to turn a warning into an error we raise category(message).
+ 
+ 
+ The Warnings Output Hook
+ 
+     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 sys.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.
+ 
+ 
+ TO DO
+ 
+     - There should be a function sys.insertwarningfilter(message,
+       category, filename, lineno) that inserts items into the warnings
+       filter after some sanity checking.
+ 
+     - 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
+ 
+ 
+ Implementation
+ 
+     Here is a mostly functional implementation:
+ 
+         """Prototype implementation of sys.warn() and related stuff."""
+ 
+         import sys, re, linecache, getopt
+ 
+         class Warning(Exception):
+             """Base class for warning categories.
+ 
+             All warning categories must be subclasses of this.
+             """
+             pass
+ 
+         class UserWarning(Warning):
+             """Base class for warnings generated by user code."""
+             pass
+ 
+         class DeprecationWarning(Warning):
+             """Base class for warnings about deprecated features."""
+             pass
+ 
+         class SyntaxWarning(Warning):
+             """Base class for warnings about dubious syntax."""
+             pass
+ 
+         defaultaction = "default"
+         filter = []
+         onceregistry = {}
+ 
+         def warn(message, category=None, level=1):
+             """Issue a warning, or maybe ignore it or raise an exception."""
+             # Check category argument
+             if category is None:
+                 category = UserWarning
+             assert issubclass(category, Warning)
+             # Get context information
+             caller = sys._getframe(level)
+             globals = caller.f_globals
+             lineno = caller.f_lineno
+             module = globals['__name__']
+             filename = globals.get('__file__')
+             if not filename:
+                 if module == "__main__":
+                     filename = sys.argv[0]
+                 if not filename:
+                     filename = module
+             # Quick test for common case
+             registry = globals.setdefault("__warningregistry__", {})
+             key = (message, category, lineno)
+             if registry.get(key):
+                 return
+             # Search the filter
+             for msg, cat, mod, ln, action in filter:
+                 if (re.match(msg, message) and
+                     issubclass(category, cat) and
+                     re.match(mod, module) and
+                     (ln == 0 or lineno == ln)):
+                     break
+             else:
+                 action = defaultaction
+             # Early exit actions
+             if action == "ignore":
+                 registry[key] = 1
+                 return
+             if action == "error":
+                 raise category(message)
+             # Other actions
+             if action == "once":
+                 registry[key] = 1
+                 oncekey = (message, category)
+                 if onceregistry.get(oncekey):
+                     return
+                 onceregistry[oncekey] = 1
+             elif action == "always":
+                 pass
+             elif action == "module":
+                 registry[key] = 1
+                 altkey = (message, category, 0)
+                 if registry.get(altkey):
+                     return
+                 registry[altkey] = 1
+             elif action == "default":
+                 registry[key] = 1
+             else:
+                 # Unrecognized actions are errors
+                 raise RuntimeError(
+                       "Unrecognized action (%s) in warnings.filter:\n %s" %
+                       (`action`, str((cat, msg, mod, ln, action))))
+             # Print message and context
+             showwarning(message, category, filename, lineno)
+ 
+         def showwarning(message, category, filename, lineno):
+             print >>sys.stderr, "%s:%s: %s: %s" % (filename, lineno,
+                                                    category.__name__, message)
+             line = linecache.getline(filename, lineno).strip()
+             if line:
+                 print >>sys.stderr, "  " + line
+ 
+         def setupfilter(args):
+             """Set up the warnings filter based upon command line options.
+ 
+             Return remaining command line arguments.
+             Raise getopt.error if an unrecognized option is found.
+             """
+             opts, args = getopt.getop(args, "")
+             return args
+ 
+         # Self-test
+ 
+         def _test():
+             hello = "hello world"
+             warn(hello)
+             warn(hello, UserWarning)
+             warn(hello, DeprecationWarning)
+             for i in range(3):
+                 warn(hello)
+             filter.append(("", Warning, "", 0, "error"))
+             try:
+                 warn(hello)
+             except Exception, msg:
+                 print "Caught", msg.__class__.__name__ + ":", msg
+             else:
+                 print "No exception"
+             filter[:] = []
+             filter.append(("", Warning, "", 0, "booh"))
+             try:
+                 warn(hello)
+             except Exception, msg:
+                 print "Caught", msg.__class__.__name__ + ":", msg
+             else:
+                 print "No exception"
+ 
+         if __name__ == "__main__":
+             args = setupfilter(sys.argv[1:])
+             _test()
+ 
+ 
  
  Local Variables: