Commit be86c7e0 authored by Hanno Schlichting's avatar Hanno Schlichting

Moved the ``zExceptions`` package into its own distribution.

parent dda65673
......@@ -46,6 +46,7 @@ eggs =
RestrictedPython
initgroups
tempstorage
zExceptions
ZopeUndo
zope.browser
zope.browsermenu
......
......@@ -11,6 +11,8 @@ Trunk (unreleased)
Restructuring
+++++++++++++
- Moved the ``zExceptions`` package into its own distribution.
- Completely refactored ``ZPublisher.WSGIResponse`` in order to provide
non-broken support for running Zope under arbitrary WSGI servers. In this
(alternate) scenario, transaction handling, request retry, error handling,
......
......@@ -76,6 +76,7 @@ setup(name='Zope2',
'tempstorage',
'transaction',
'zdaemon',
'zExceptions',
'zope.browser',
'zope.browsermenu',
'zope.browserpage',
......
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""An exception formatter that shows traceback supplements and traceback info,
optionally in HTML.
$Id$
"""
import sys
import cgi
DEBUG_EXCEPTION_FORMATTER = 1
class TextExceptionFormatter:
line_sep = '\n'
show_revisions = 0
def __init__(self, limit=None):
self.limit = limit
def escape(self, s):
return s
def getPrefix(self):
return 'Traceback (innermost last):'
def getLimit(self):
limit = self.limit
if limit is None:
limit = getattr(sys, 'tracebacklimit', None)
return limit
def getRevision(self, globals):
if not self.show_revisions:
return None
revision = globals.get('__revision__', None)
if revision is None:
# Incorrect but commonly used spelling
revision = globals.get('__version__', None)
if revision is not None:
try:
revision = str(revision).strip()
except:
revision = '???'
return revision
def formatSupplementLine(self, line):
return ' - %s' % line
def formatObject(self, object):
return [self.formatSupplementLine(repr(object))]
def formatSourceURL(self, url):
return [self.formatSupplementLine('URL: %s' % url)]
def formatSupplement(self, supplement, tb):
result = []
fmtLine = self.formatSupplementLine
object = getattr(supplement, 'object', None)
if object is not None:
result.extend(self.formatObject(object))
url = getattr(supplement, 'source_url', None)
if url is not None:
result.extend(self.formatSourceURL(url))
line = getattr(supplement, 'line', 0)
if line == -1:
line = tb.tb_lineno
col = getattr(supplement, 'column', -1)
if line:
if col is not None and col >= 0:
result.append(fmtLine('Line %s, Column %s' % (
line, col)))
else:
result.append(fmtLine('Line %s' % line))
elif col is not None and col >= 0:
result.append(fmtLine('Column %s' % col))
expr = getattr(supplement, 'expression', None)
if expr:
result.append(fmtLine('Expression: %s' % expr))
warnings = getattr(supplement, 'warnings', None)
if warnings:
for warning in warnings:
result.append(fmtLine('Warning: %s' % warning))
extra = self.formatExtraInfo(supplement)
if extra:
result.append(extra)
return result
def formatExtraInfo(self, supplement):
getInfo = getattr(supplement, 'getInfo', None)
if getInfo is not None:
extra = getInfo()
if extra:
return extra
return None
def formatTracebackInfo(self, tbi):
return self.formatSupplementLine('__traceback_info__: %s' % (tbi,))
def formatLine(self, tb):
f = tb.tb_frame
lineno = tb.tb_lineno
co = f.f_code
filename = co.co_filename
name = co.co_name
locals = f.f_locals
globals = f.f_globals
modname = globals.get('__name__', filename)
s = ' Module %s, line %d' % (modname, lineno)
revision = self.getRevision(globals)
if revision:
s = s + ', rev. %s' % revision
s = s + ', in %s' % name
result = []
result.append(self.escape(s))
# Output a traceback supplement, if any.
if locals.has_key('__traceback_supplement__'):
# Use the supplement defined in the function.
tbs = locals['__traceback_supplement__']
elif globals.has_key('__traceback_supplement__'):
# Use the supplement defined in the module.
# This is used by Scripts (Python).
tbs = globals['__traceback_supplement__']
else:
tbs = None
if tbs is not None:
factory = tbs[0]
args = tbs[1:]
try:
supp = factory(*args)
result.extend(self.formatSupplement(supp, tb))
except:
if DEBUG_EXCEPTION_FORMATTER:
import traceback
traceback.print_exc()
# else just swallow the exception.
try:
tbi = locals.get('__traceback_info__', None)
if tbi is not None:
result.append(self.formatTracebackInfo(tbi))
except:
pass
return self.line_sep.join(result)
def formatExceptionOnly(self, etype, value):
import traceback
return self.line_sep.join(
traceback.format_exception_only(etype, value))
def formatLastLine(self, exc_line):
return self.escape(exc_line)
def formatException(self, etype, value, tb, limit=None):
# The next line provides a way to detect recursion.
__exception_formatter__ = 1
result = [self.getPrefix() + '\n']
if limit is None:
limit = self.getLimit()
n = 0
while tb is not None and (limit is None or n < limit):
if tb.tb_frame.f_locals.get('__exception_formatter__'):
# Stop recursion.
result.append('(Recursive formatException() stopped)\n')
break
line = self.formatLine(tb)
result.append(line + '\n')
tb = tb.tb_next
n = n + 1
exc_line = self.formatExceptionOnly(etype, value)
result.append(self.formatLastLine(exc_line))
return result
class HTMLExceptionFormatter (TextExceptionFormatter):
line_sep = '<br />\r\n'
def escape(self, s):
return cgi.escape(s)
def getPrefix(self):
return '<p>Traceback (innermost last):</p>\r\n<ul>'
def formatSupplementLine(self, line):
return '<b>%s</b>' % self.escape(str(line))
def formatTracebackInfo(self, tbi):
s = self.escape(str(tbi))
s = s.replace('\n', self.line_sep)
return '__traceback_info__: %s' % s
def formatLine(self, tb):
line = TextExceptionFormatter.formatLine(self, tb)
return '<li>%s</li>' % line
def formatLastLine(self, exc_line):
return '</ul><p>%s</p>' % self.escape(exc_line)
def formatExtraInfo(self, supplement):
getInfo = getattr(supplement, 'getInfo', None)
if getInfo is not None:
extra = getInfo(1)
if extra:
return extra
return None
limit = 200
if hasattr(sys, 'tracebacklimit'):
limit = min(limit, sys.tracebacklimit)
text_formatter = TextExceptionFormatter(limit)
html_formatter = HTMLExceptionFormatter(limit)
def format_exception(t, v, tb, limit=None, as_html=0):
if as_html:
fmt = html_formatter
else:
fmt = text_formatter
return fmt.formatException(t, v, tb, limit=limit)
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""ITracebackSupplement interface definition.
$Id$
"""
from zope.interface import Interface
from zope.interface import Attribute
class ITracebackSupplement(Interface):
"""Provides valuable information to supplement an exception traceback.
The interface is geared toward providing meaningful feedback when
exceptions occur in user code written in mini-languages like
Zope page templates and restricted Python scripts.
"""
source_url = Attribute(
'source_url',
"""Optional. Set to URL of the script where the exception occurred.
Normally this generates a URL in the traceback that the user
can visit to manage the object. Set to None if unknown or
not available.
"""
)
object = Attribute(
'object',
"""Optional. Set to the script or template where the exception
occurred.
Set to None if unknown or not available.
"""
)
line = Attribute(
'line',
"""Optional. Set to the line number (>=1) where the exception
occurred.
Set to 0 or None if the line number is unknown.
"""
)
column = Attribute(
'column',
"""Optional. Set to the column offset (>=0) where the exception
occurred.
Set to None if the column number is unknown.
"""
)
expression = Attribute(
'expression',
"""Optional. Set to the expression that was being evaluated.
Set to None if not available or not applicable.
"""
)
warnings = Attribute(
'warnings',
"""Optional. Set to a sequence of warning messages.
Set to None if not available, not applicable, or if the exception
itself provides enough information.
"""
)
def getInfo(as_html=0):
"""Optional. Returns a string containing any other useful info.
If as_html is set, the implementation must HTML-quote the result
(normally using cgi.escape()). Returns None to provide no
extra info.
"""
# Stock __traceback_supplement__ implementations
class PathTracebackSupplement:
"""Implementation of ITracebackSupplement"""
pp = None
def __init__(self, object):
self.object = object
if hasattr(object, 'getPhysicalPath'):
self.pp = '/'.join(object.getPhysicalPath())
if hasattr(object, 'absolute_url'):
self.source_url = '%s/manage_main' % object.absolute_url()
def getInfo(self, as_html=0):
if self.pp is None:
return
if as_html:
from cgi import escape
return '<b>Physical Path:</b>%s' % (escape(self.pp))
else:
return ' - Physical Path: %s' % self.pp
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""General exceptions that wish they were standard exceptions
These exceptions are so general purpose that they don't belong in Zope
application-specific packages.
$Id$
"""
from types import ClassType
import warnings
from zope.interface import implements
from zope.interface.common.interfaces import IException
from zope.publisher.interfaces import INotFound
from zope.security.interfaces import IForbidden
from zExceptions.unauthorized import Unauthorized
class BadRequest(Exception):
implements(IException)
class InternalError(Exception):
implements(IException)
class NotFound(Exception):
implements(INotFound)
class Forbidden(Exception):
implements(IForbidden)
class MethodNotAllowed(Exception):
pass
class Redirect(Exception):
pass
def convertExceptionType(name):
import zExceptions
etype = None
if __builtins__.has_key(name):
etype = __builtins__[name]
elif hasattr(zExceptions, name):
etype = getattr(zExceptions, name)
if (etype is not None and
isinstance(etype, (type, ClassType)) and
issubclass(etype, Exception)):
return etype
def upgradeException(t, v):
# If a string exception is found, convert it to an equivalent
# exception defined either in builtins or zExceptions. If none of
# that works, tehn convert it to an InternalError and keep the
# original exception name as part of the exception value.
if isinstance(t, basestring):
warnings.warn('String exceptions are deprecated starting '
'with Python 2.5 and will be removed in a '
'future release', DeprecationWarning, stacklevel=2)
etype = convertExceptionType(t)
if etype is not None:
t = etype
else:
v = t, v
t = InternalError
return t, v
"""Package for zExceptions tests"""
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
ExceptionFormatter tests.
Revision information:
$Id$
"""
from __future__ import nested_scopes
from unittest import TestCase, TestSuite, main, makeSuite
try:
from Testing.CleanUp import CleanUp # Base class w registry cleanup
except ImportError:
class CleanUp:
pass
import sys
from zExceptions.ExceptionFormatter import format_exception
def tb(as_html=0):
t, v, b = sys.exc_info()
try:
return ''.join(format_exception(t, v, b, as_html=as_html))
finally:
del b
class ExceptionForTesting (Exception):
pass
class TestingTracebackSupplement:
source_url = '/somepath'
line = 634
column = 57
warnings = ['Repent, for the end is nigh']
def __init__(self, expression):
self.expression = expression
class Test(CleanUp, TestCase):
def testBasicNamesText(self, as_html=0):
try:
raise ExceptionForTesting
except ExceptionForTesting:
s = tb(as_html)
# The traceback should include the name of this function.
self.assert_(s.find('testBasicNamesText') >= 0)
# The traceback should include the name of the exception.
self.assert_(s.find('ExceptionForTesting') >= 0)
else:
self.fail('no exception occurred')
def testBasicNamesHTML(self):
self.testBasicNamesText(1)
def testSupplement(self, as_html=0):
try:
__traceback_supplement__ = (TestingTracebackSupplement,
"You're one in a million")
raise ExceptionForTesting
except ExceptionForTesting:
s = tb(as_html)
# The source URL
self.assert_(s.find('/somepath') >= 0, s)
# The line number
self.assert_(s.find('634') >= 0, s)
# The column number
self.assert_(s.find('57') >= 0, s)
# The expression
self.assert_(s.find("You're one in a million") >= 0, s)
# The warning
self.assert_(s.find("Repent, for the end is nigh") >= 0, s)
else:
self.fail('no exception occurred')
def testSupplementHTML(self):
self.testSupplement(1)
def testTracebackInfo(self, as_html=0):
try:
__traceback_info__ = "Adam & Eve"
raise ExceptionForTesting
except ExceptionForTesting:
s = tb(as_html)
if as_html:
# Be sure quoting is happening.
self.assert_(s.find('Adam &amp; Eve') >= 0, s)
else:
self.assert_(s.find('Adam & Eve') >= 0, s)
else:
self.fail('no exception occurred')
def testTracebackInfoHTML(self):
self.testTracebackInfo(1)
def testMultipleLevels(self):
# Makes sure many levels are shown in a traceback.
def f(n):
"""Produces a (n + 1)-level traceback."""
__traceback_info__ = 'level%d' % n
if n > 0:
f(n - 1)
else:
raise ExceptionForTesting
try:
f(10)
except ExceptionForTesting:
s = tb()
for n in range(11):
self.assert_(s.find('level%d' % n) >= 0, s)
else:
self.fail('no exception occurred')
def testQuoteLastLine(self):
class C: pass
try: raise TypeError, C()
except:
s = tb(1)
else:
self.fail('no exception occurred')
self.assert_(s.find('&lt;') >= 0, s)
self.assert_(s.find('&gt;') >= 0, s)
def test_suite():
return TestSuite((
makeSuite(Test),
))
if __name__=='__main__':
main(defaultTest='test_suite')
import unittest
class Test_convertExceptionType(unittest.TestCase):
def _callFUT(self, name):
from zExceptions import convertExceptionType
return convertExceptionType(name)
def test_name_in___builtins__(self):
self.failUnless(self._callFUT('SyntaxError') is SyntaxError)
def test_name_in___builtins___not_an_exception_returns_None(self):
self.failUnless(self._callFUT('unichr') is None)
def test_name_in_zExceptions(self):
from zExceptions import Redirect
self.failUnless(self._callFUT('Redirect') is Redirect)
def test_name_in_zExceptions_not_an_exception_returns_None(self):
self.failUnless(self._callFUT('convertExceptionType') is None)
class Test_upgradeException(unittest.TestCase):
def _callFUT(self, t, v):
from zExceptions import upgradeException
return upgradeException(t, v)
def test_non_string(self):
t, v = self._callFUT(SyntaxError, 'TEST')
self.assertEqual(t, SyntaxError)
self.assertEqual(v, 'TEST')
def test_string_in___builtins__(self):
t, v = self._callFUT('SyntaxError', 'TEST')
self.assertEqual(t, SyntaxError)
self.assertEqual(v, 'TEST')
def test_string_in_zExceptions(self):
from zExceptions import Redirect
t, v = self._callFUT('Redirect', 'http://example.com/')
self.assertEqual(t, Redirect)
self.assertEqual(v, 'http://example.com/')
def test_string_miss_returns_InternalError(self):
from zExceptions import InternalError
t, v = self._callFUT('Nonesuch', 'TEST')
self.assertEqual(t, InternalError)
self.assertEqual(v, ('Nonesuch', 'TEST'))
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(Test_convertExceptionType),
unittest.makeSuite(Test_upgradeException),
))
##############################################################################
#
# Copyright (c) 2010 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Unit tests for unauthorized module.
$Id$
"""
import unittest
from zope.interface.verify import verifyClass
_MESSAGE = "You are not allowed to access '%s' in this context"
class UnauthorizedTests(unittest.TestCase):
def _getTargetClass(self):
from zExceptions.unauthorized import Unauthorized
return Unauthorized
def _makeOne(self, *args, **kw):
return self._getTargetClass()(*args, **kw)
def test_interfaces(self):
from zope.security.interfaces import IUnauthorized
verifyClass(IUnauthorized, self._getTargetClass())
def test_empty(self):
exc = self._makeOne()
self.assertEqual(exc.name, None)
self.assertEqual(exc.message, None)
self.assertEqual(exc.value, None)
self.assertEqual(exc.needed, None)
self.assertEqual(str(exc), str(repr(exc)))
self.assertEqual(unicode(exc), unicode(repr(exc)))
def test_ascii_message(self):
arg = 'ERROR MESSAGE'
exc = self._makeOne(arg)
self.assertEqual(exc.name, None)
self.assertEqual(exc.message, arg)
self.assertEqual(exc.value, None)
self.assertEqual(exc.needed, None)
self.assertEqual(str(exc), arg)
self.assertEqual(unicode(exc), arg.decode('ascii'))
def test_encoded_message(self):
arg = u'ERROR MESSAGE \u03A9'.encode('utf-8')
exc = self._makeOne(arg)
self.assertEqual(exc.name, None)
self.assertEqual(exc.message, arg)
self.assertEqual(exc.value, None)
self.assertEqual(exc.needed, None)
self.assertEqual(str(exc), arg)
self.assertRaises(UnicodeDecodeError, unicode, exc)
def test_unicode_message(self):
arg = u'ERROR MESSAGE \u03A9'
exc = self._makeOne(arg)
self.assertEqual(exc.name, None)
self.assertEqual(exc.message, arg)
self.assertEqual(exc.value, None)
self.assertEqual(exc.needed, None)
self.assertRaises(UnicodeEncodeError, str, exc)
self.assertEqual(unicode(exc), arg)
def test_ascii_name(self):
arg = 'ERROR_NAME'
exc = self._makeOne(arg)
self.assertEqual(exc.name, arg)
self.assertEqual(exc.message, None)
self.assertEqual(exc.value, None)
self.assertEqual(exc.needed, None)
self.assertEqual(str(exc), _MESSAGE % arg)
self.assertEqual(unicode(exc), _MESSAGE % arg.decode('ascii'))
def test_encoded_name(self):
arg = u'ERROR_NAME_\u03A9'.encode('utf-8')
exc = self._makeOne(arg)
self.assertEqual(exc.name, arg)
self.assertEqual(exc.message, None)
self.assertEqual(exc.value, None)
self.assertEqual(exc.needed, None)
self.assertEqual(str(exc), _MESSAGE % arg)
self.assertRaises(UnicodeDecodeError, unicode, exc)
def test_unicode_name(self):
arg = u'ERROR_NAME_\u03A9'
exc = self._makeOne(arg)
self.assertEqual(exc.name, arg)
self.assertEqual(exc.message, None)
self.assertEqual(exc.value, None)
self.assertEqual(exc.needed, None)
self.assertRaises(UnicodeEncodeError, str, exc)
self.assertEqual(unicode(exc), _MESSAGE % arg)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(UnauthorizedTests))
return suite
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
$Id$
"""
from zope.interface import implements
from zope.security.interfaces import IUnauthorized
class Unauthorized(Exception):
"""Some user wasn't allowed to access a resource
"""
implements(IUnauthorized)
def _get_message(self):
return self._message
message = property(_get_message,)
def __init__(self, message=None, value=None, needed=None, name=None, **kw):
"""Possible signatures:
Unauthorized()
Unauthorized(message) # Note that message includes a space
Unauthorized(name)
Unauthorized(name, value)
Unauthorized(name, value, needed)
Unauthorized(message, value, needed, name)
Where needed is a mapping objects with items representing requirements
(e.g. {'permission': 'add spam'}). Any extra keyword arguments
provides are added to needed.
"""
if name is None and (
not isinstance(message, basestring) or len(message.split()) <= 1):
# First arg is a name, not a message
name=message
message=None
self.name=name
self._message=message
self.value=value
if kw:
if needed: needed.update(kw)
else: needed=kw
self.needed=needed
def __str__(self):
if self.message is not None:
return self.message
if self.name is not None:
return ("You are not allowed to access '%s' in this context"
% self.name)
elif self.value is not None:
return ("You are not allowed to access '%s' in this context"
% self.getValueName())
return repr(self)
def __unicode__(self):
result = self.__str__()
if isinstance(result, unicode):
return result
return unicode(result, 'ascii') # override sys.getdefaultencoding()
def getValueName(self):
v=self.value
vname=getattr(v, '__name__', None)
if vname: return vname
c = getattr(v, '__class__', type(v))
c = getattr(c, '__name__', 'object')
return "a particular %s" % c
......@@ -14,6 +14,7 @@ Persistence = 2.13.1
Record = 2.13.0
RestrictedPython = 3.5.2
tempstorage = 2.11.3
zExceptions = 2.13.0
ZopeUndo = 2.12.0
# Zope2 dependencies
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment