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:
parent
5d702e543f
commit
aaef244947
274
pep-0230.txt
274
pep-0230.txt
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue