Revised version, completing a bunch of things that weren't resolved

before.  The implementation was removed from the PEP, it's now in the
SF patch manager; I'm pretty happy with it! :-)
This commit is contained in:
Guido van Rossum 2000-12-07 21:52:14 +00:00
parent 5d702e543f
commit aaef244947
1 changed files with 129 additions and 145 deletions

View File

@ -190,162 +190,146 @@ The Warnings Output Hook
to sys.stderr, and shows the source line of the filename.
TO DO
API For Manipulating Warning Filters
- There should be a function sys.insertwarningfilter(message,
category, filename, lineno) that inserts items into the warnings
filter after some sanity checking.
sys.filterwarnings(message, category, module, lineno, action)
- There should be command line options to specify the most common
filtering actions, which I expect to include at least:
This checks the types of the arguments and inserts them as a tuple
in front of the warnings filter.
- suppress all warnings
sys.resetwarnings()
- suppress a particular warning message everywhere
Reset the warnings filter to empty.
- suppress all warnings in a particular module
sys.setupwarnings(args)
- turn all warnings into exceptions
Parse command line options and initialize the warnings filter
accordingly. The argument should be sys.argv[1:] or equivalent.
Unrecognized options raise getopt.error. The return value is a
list containing the remaining (non-option) arguments.
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-sensitive)
- '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.
Each -W option results into a call to sys.filterwarnings(); thus
later -W options override earlier -W options for warnings they
both match.
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:
- The proposal has all the Python API functions in the sys module,
except that the warning categories are in the warnings module.
Perhaps everything should be in the warnings module (like the
prototype implementation)? Or perhaps warn() should be promoted
to a built-in (i.e. in the __builtin__ module)?
- It's tempting to leave the implementation in Python and add an
absolute minimal amount of C code, only to make the standard
warning categories available from C code. The Py_Warn()
function could call warnings.warn(). Similarly, the Python
main() function could collect -W options and pass them to
warnings.setupwarnings().
- The prototype implements a third argument to warn():
warn(message, category=UserWarning, level=1)
The 'level' argument could 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 (the latter
would defeat the purpose of the warning message).
- The proposed command line syntax is 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.
- Better names for the various API functions?
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()
Here's a prototype implementation:
http://sourceforge.net/patch/?func=detailpatch&patch_id=102715&group_id=5470
Local Variables: