summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-05-28 19:22:13 -0400
committerChris McDonough <chrism@plope.com>2011-05-28 19:22:13 -0400
commit4184d956514ada7dccf2f99ced09cbf07a721cc3 (patch)
tree2bacc1824954ff24c76e5d51d8afc9e34066b013
parentf77703d25056236d027a1a61bf63fab3a7c1b2c2 (diff)
downloadpyramid-4184d956514ada7dccf2f99ced09cbf07a721cc3.tar.gz
pyramid-4184d956514ada7dccf2f99ced09cbf07a721cc3.tar.bz2
pyramid-4184d956514ada7dccf2f99ced09cbf07a721cc3.zip
bite the bullet and replace all webob.exc classes with ones of our own
-rw-r--r--pyramid/exceptions.py1144
-rw-r--r--pyramid/tests/test_exceptions.py299
2 files changed, 1308 insertions, 135 deletions
diff --git a/pyramid/exceptions.py b/pyramid/exceptions.py
index e9718c6ab..53cb0e5a8 100644
--- a/pyramid/exceptions.py
+++ b/pyramid/exceptions.py
@@ -1,139 +1,1033 @@
+"""
+HTTP Exceptions
+---------------
+
+This module contains Python exceptions that relate to HTTP status codes by
+defining a set of classes, all subclasses of HTTPException. Each
+exception, in addition to being a Python exception that can be raised and
+caught, is also a ``Response`` object.
+
+This module defines exceptions according to RFC 2068 [1]_ : codes with
+100-300 are not really errors; 400's are client errors, and 500's are
+server errors.
+
+Exception
+ HTTPException
+ HTTPOk
+ * 200 - HTTPOk
+ * 201 - HTTPCreated
+ * 202 - HTTPAccepted
+ * 203 - HTTPNonAuthoritativeInformation
+ * 204 - HTTPNoContent
+ * 205 - HTTPResetContent
+ * 206 - HTTPPartialContent
+ HTTPRedirection
+ * 300 - HTTPMultipleChoices
+ * 301 - HTTPMovedPermanently
+ * 302 - HTTPFound
+ * 303 - HTTPSeeOther
+ * 304 - HTTPNotModified
+ * 305 - HTTPUseProxy
+ * 306 - Unused (not implemented, obviously)
+ * 307 - HTTPTemporaryRedirect
+ HTTPError
+ HTTPClientError
+ * 400 - HTTPBadRequest
+ * 401 - HTTPUnauthorized
+ * 402 - HTTPPaymentRequired
+ * 403 - HTTPForbidden
+ * 404 - HTTPNotFound
+ * 405 - HTTPMethodNotAllowed
+ * 406 - HTTPNotAcceptable
+ * 407 - HTTPProxyAuthenticationRequired
+ * 408 - HTTPRequestTimeout
+ * 409 - HTTPConflict
+ * 410 - HTTPGone
+ * 411 - HTTPLengthRequired
+ * 412 - HTTPPreconditionFailed
+ * 413 - HTTPRequestEntityTooLarge
+ * 414 - HTTPRequestURITooLong
+ * 415 - HTTPUnsupportedMediaType
+ * 416 - HTTPRequestRangeNotSatisfiable
+ * 417 - HTTPExpectationFailed
+ HTTPServerError
+ * 500 - HTTPInternalServerError
+ * 501 - HTTPNotImplemented
+ * 502 - HTTPBadGateway
+ * 503 - HTTPServiceUnavailable
+ * 504 - HTTPGatewayTimeout
+ * 505 - HTTPVersionNotSupported
+
+Each HTTP exception has the following attributes:
+
+ ``code``
+ the HTTP status code for the exception
+
+ ``title``
+ remainder of the status line (stuff after the code)
+
+ ``explanation``
+ a plain-text explanation of the error message that is
+ not subject to environment or header substitutions;
+ it is accessible in the template via %(explanation)s
+
+ ``detail``
+ a plain-text message customization that is not subject
+ to environment or header substitutions; accessible in
+ the template via %(detail)s
+
+ ``body_template``
+ a content fragment (in HTML) used for environment and
+ header substitution; the default template includes both
+ the explanation and further detail provided in the
+ message
+
+Each HTTP exception accepts the following parameters:
+
+ ``detail``
+ a plain-text override of the default ``detail``
+
+ ``headers``
+ a list of (k,v) header pairs
+
+ ``comment``
+ a plain-text additional information which is
+ usually stripped/hidden for end-users
+
+ ``body_template``
+ a string.Template object containing a content fragment in HTML
+ that frames the explanation and further detail
+
+Substitution of environment variables and headers into template values is
+performed if a ``request`` is passed to the exception constructor.
+
+The subclasses of :class:`~_HTTPMove`
+(:class:`~HTTPMultipleChoices`, :class:`~HTTPMovedPermanently`,
+:class:`~HTTPFound`, :class:`~HTTPSeeOther`, :class:`~HTTPUseProxy` and
+:class:`~HTTPTemporaryRedirect`) are redirections that require a ``Location``
+field. Reflecting this, these subclasses have one additional keyword argument:
+``location``, which indicates the location to which to redirect.
+
+References:
+
+.. [1] http://www.python.org/peps/pep-0333.html#error-handling
+"""
+
+import types
+from string import Template
+from webob import Response
+from webob import html_escape
+
from zope.configuration.exceptions import ConfigurationError as ZCE
-from zope.interface import classImplements
+from zope.interface import implements
from pyramid.interfaces import IExceptionResponse
-from webob.response import Response
-
-# Documentation proxy import
-from webob.exc import __doc__
-
-# API: status_map
-from webob.exc import status_map
-status_map = status_map.copy() # we mutate it
-
-# API: parent classes
-from webob.exc import HTTPException
-from webob.exc import WSGIHTTPException
-from webob.exc import HTTPOk
-from webob.exc import HTTPRedirection
-from webob.exc import HTTPError
-from webob.exc import HTTPClientError
-from webob.exc import HTTPServerError
-
-# slightly nasty import-time side effect to provide WSGIHTTPException
-# with IExceptionResponse interface (used during config.py exception view
-# registration)
-classImplements(WSGIHTTPException, IExceptionResponse)
-
-# API: Child classes
-from webob.exc import HTTPCreated
-from webob.exc import HTTPAccepted
-from webob.exc import HTTPNonAuthoritativeInformation
-from webob.exc import HTTPNoContent
-from webob.exc import HTTPResetContent
-from webob.exc import HTTPPartialContent
-from webob.exc import HTTPMultipleChoices
-from webob.exc import HTTPMovedPermanently
-from webob.exc import HTTPFound
-from webob.exc import HTTPSeeOther
-from webob.exc import HTTPNotModified
-from webob.exc import HTTPUseProxy
-from webob.exc import HTTPTemporaryRedirect
-from webob.exc import HTTPBadRequest
-from webob.exc import HTTPUnauthorized
-from webob.exc import HTTPPaymentRequired
-from webob.exc import HTTPMethodNotAllowed
-from webob.exc import HTTPNotAcceptable
-from webob.exc import HTTPProxyAuthenticationRequired
-from webob.exc import HTTPRequestTimeout
-from webob.exc import HTTPConflict
-from webob.exc import HTTPGone
-from webob.exc import HTTPLengthRequired
-from webob.exc import HTTPPreconditionFailed
-from webob.exc import HTTPRequestEntityTooLarge
-from webob.exc import HTTPRequestURITooLong
-from webob.exc import HTTPUnsupportedMediaType
-from webob.exc import HTTPRequestRangeNotSatisfiable
-from webob.exc import HTTPExpectationFailed
-from webob.exc import HTTPInternalServerError
-from webob.exc import HTTPNotImplemented
-from webob.exc import HTTPBadGateway
-from webob.exc import HTTPServiceUnavailable
-from webob.exc import HTTPGatewayTimeout
-from webob.exc import HTTPVersionNotSupported
-
-# API: HTTPNotFound and HTTPForbidden (redefined for bw compat)
-
-from webob.exc import HTTPForbidden as _HTTPForbidden
-from webob.exc import HTTPNotFound as _HTTPNotFound
-
-class HTTPNotFound(_HTTPNotFound):
- """
- Raise this exception within :term:`view` code to immediately
- return the :term:`Not Found view` to the invoking user. Usually
- this is a basic ``404`` page, but the Not Found view can be
- customized as necessary. See :ref:`changing_the_notfound_view`.
- This exception's constructor accepts a single positional argument, which
- should be a string. The value of this string will be available as the
- ``message`` attribute of this exception, for availability to the
- :term:`Not Found View`.
+newstyle_exceptions = issubclass(Exception, object)
+
+def no_escape(value):
+ if value is None:
+ return ''
+ if not isinstance(value, basestring):
+ if hasattr(value, '__unicode__'):
+ value = unicode(value)
+ else:
+ value = str(value)
+ return value
+
+class HTTPException(Exception):
+ implements(IExceptionResponse)
"""
+ Exception used on pre-Python-2.5, where new-style classes cannot be used as
+ an exception.
+ """
+
+ def __init__(self, message, wsgi_response):
+ self.message = message
+ Exception.__init__(self, message)
+ self.__dict__['wsgi_response'] = wsgi_response
+
+ def exception(self):
+ return self
+
+ exception = property(exception)
+
+ # for old style exceptions
+ if not newstyle_exceptions: #pragma NO COVERAGE
+ def __getattr__(self, attr):
+ if not attr.startswith('_'):
+ return getattr(self.wsgi_response, attr)
+ else:
+ raise AttributeError(attr)
+
+ def __setattr__(self, attr, value):
+ if attr.startswith('_') or attr in ('args',):
+ self.__dict__[attr] = value
+ else:
+ setattr(self.wsgi_response, attr, value)
+
+class WSGIHTTPException(Response, HTTPException):
+
+ ## You should set in subclasses:
+ # code = 200
+ # title = 'OK'
+ # explanation = 'why this happens'
+ # body_template_obj = Template('response template')
+
+ # differences from webob.exc.WSGIHTTPException:
+ # - not a WSGI application (just a response)
+ #
+ # as a result:
+ #
+ # - bases plaintext vs. html result on self.content_type rather than
+ # on request environ
+ #
+ # - doesn't add request.environ keys to template substitutions unless
+ # 'request' is passed as a keyword argument.
+ #
+ # - doesn't use "strip_tags" (${br} placeholder for <br/>, no other html
+ # in default body template)
+ #
+ # - sets a default app_iter if no body, app_iter, or unicode_body is
+ # passed
+ #
+ # - explicitly sets self.message = detail to prevent whining by Python
+ # 2.6.5+ Exception.message
+ #
+ code = None
+ title = None
+ explanation = ''
+ body_template_obj = Template('''\
+${explanation}${br}${br}
+${detail}
+${html_comment}
+''')
+
+ plain_template_obj = Template('''\
+${status}
+
+${body}''')
+
+ html_template_obj = Template('''\
+<html>
+ <head>
+ <title>${status}</title>
+ </head>
+ <body>
+ <h1>${status}</h1>
+ ${body}
+ </body>
+</html>''')
+
+ ## Set this to True for responses that should have no request body
+ empty_body = False
+ _default_called = False
+
def __init__(self, detail=None, headers=None, comment=None,
body_template=None, **kw):
- self.message = detail # prevent 2.6.X whining
- _HTTPNotFound.__init__(self, detail=detail, headers=headers,
- comment=comment, body_template=body_template,
- **kw)
- if not ('body' in kw or 'app_iter' in kw):
- if not self.empty_body:
- body = self.html_body(self.environ)
- if isinstance(body, unicode):
- body = body.encode(self.charset)
- self.body = body
-
-class HTTPForbidden(_HTTPForbidden):
+ status = '%s %s' % (self.code, self.title)
+ Response.__init__(self, status=status, **kw)
+ Exception.__init__(self, detail)
+ self.detail = self.message = detail
+ if headers:
+ self.headers.extend(headers)
+ self.comment = comment
+ if body_template is not None:
+ self.body_template = body_template
+ self.body_template_obj = Template(body_template)
+
+ if self.empty_body:
+ del self.content_type
+ del self.content_length
+ elif not ('unicode_body' in kw or 'body' in kw or 'app_iter' in kw):
+ self.app_iter = iter(self._default_app_iter, None)
+
+ def __str__(self):
+ return self.detail or self.explanation
+
+ def _default_app_iter(self):
+ if self._default_called:
+ return None
+ html_comment = ''
+ comment = self.comment or ''
+ if 'html' in self.content_type or '':
+ escape = html_escape
+ page_template = self.html_template_obj
+ br = '<br/>'
+ if comment:
+ html_comment = '<!-- %s -->' % escape(comment)
+ else:
+ escape = no_escape
+ page_template = self.plain_template_obj
+ br = '\n'
+ if comment:
+ html_comment = escape(comment)
+ args = {
+ 'br':br,
+ 'explanation': escape(self.explanation),
+ 'detail': escape(self.detail or ''),
+ 'comment': escape(comment),
+ 'html_comment':html_comment,
+ }
+ body_tmpl = self.body_template_obj
+ if WSGIHTTPException.body_template_obj is not body_tmpl:
+ # Custom template; add headers to args
+ environ = self.environ
+ if environ is not None:
+ for k, v in environ.items():
+ args[k] = escape(v)
+ for k, v in self.headers.items():
+ args[k.lower()] = escape(v)
+ body = body_tmpl.substitute(args)
+ page = page_template.substitute(status=self.status, body=body)
+ if isinstance(page, unicode):
+ page = page.encode(self.charset)
+ self._default_called = True
+ return page
+
+ def wsgi_response(self):
+ return self
+
+ wsgi_response = property(wsgi_response)
+
+ def exception(self):
+ if newstyle_exceptions:
+ return self
+ else:
+ return HTTPException(self.detail, self)
+
+ exception = property(exception)
+
+class HTTPError(WSGIHTTPException):
+ """
+ base class for status codes in the 400's and 500's
+
+ This is an exception which indicates that an error has occurred,
+ and that any work in progress should not be committed. These are
+ typically results in the 400's and 500's.
+ """
+
+class HTTPRedirection(WSGIHTTPException):
+ """
+ base class for 300's status code (redirections)
+
+ This is an abstract base class for 3xx redirection. It indicates
+ that further action needs to be taken by the user agent in order
+ to fulfill the request. It does not necessarly signal an error
+ condition.
+ """
+
+class HTTPOk(WSGIHTTPException):
+ """
+ Base class for the 200's status code (successful responses)
+
+ code: 200, title: OK
+ """
+ code = 200
+ title = 'OK'
+
+############################################################
+## 2xx success
+############################################################
+
+class HTTPCreated(HTTPOk):
+ """
+ subclass of :class:`~HTTPOk`
+
+ This indicates that request has been fulfilled and resulted in a new
+ resource being created.
+
+ code: 201, title: Created
+ """
+ code = 201
+ title = 'Created'
+
+class HTTPAccepted(HTTPOk):
+ """
+ subclass of :class:`~HTTPOk`
+
+ This indicates that the request has been accepted for processing, but the
+ processing has not been completed.
+
+ code: 202, title: Accepted
+ """
+ code = 202
+ title = 'Accepted'
+ explanation = 'The request is accepted for processing.'
+
+class HTTPNonAuthoritativeInformation(HTTPOk):
+ """
+ subclass of :class:`~HTTPOk`
+
+ This indicates that the returned metainformation in the entity-header is
+ not the definitive set as available from the origin server, but is
+ gathered from a local or a third-party copy.
+
+ code: 203, title: Non-Authoritative Information
+ """
+ code = 203
+ title = 'Non-Authoritative Information'
+
+class HTTPNoContent(HTTPOk):
+ """
+ subclass of :class:`~HTTPOk`
+
+ This indicates that the server has fulfilled the request but does
+ not need to return an entity-body, and might want to return updated
+ metainformation.
+
+ code: 204, title: No Content
+ """
+ code = 204
+ title = 'No Content'
+ empty_body = True
+
+class HTTPResetContent(HTTPOk):
+ """
+ subclass of :class:`~HTTPOk`
+
+ This indicates that the the server has fulfilled the request and
+ the user agent SHOULD reset the document view which caused the
+ request to be sent.
+
+ code: 205, title: Reset Content
+ """
+ code = 205
+ title = 'Reset Content'
+ empty_body = True
+
+class HTTPPartialContent(HTTPOk):
+ """
+ subclass of :class:`~HTTPOk`
+
+ This indicates that the server has fulfilled the partial GET
+ request for the resource.
+
+ code: 206, title: Partial Content
+ """
+ code = 206
+ title = 'Partial Content'
+
+## FIXME: add 207 Multi-Status (but it's complicated)
+
+############################################################
+## 3xx redirection
+############################################################
+
+class _HTTPMove(HTTPRedirection):
+ """
+ redirections which require a Location field
+
+ Since a 'Location' header is a required attribute of 301, 302, 303,
+ 305 and 307 (but not 304), this base class provides the mechanics to
+ make this easy.
+
+ You must provide a ``location`` keyword argument.
+ """
+ # differences from webob.exc._HTTPMove:
+ #
+ # - not a wsgi app
+ #
+ # - ${location} isn't wrapped in an <a> tag in body
+ #
+ # - location keyword arg defaults to ''
+ #
+ # - ``add_slash`` argument is no longer accepted: code that passes
+ # add_slash argument will receive an exception.
+ explanation = 'The resource has been moved to'
+ body_template_obj = Template('''\
+${explanation} ${location};
+you should be redirected automatically.
+${detail}
+${html_comment}''')
+
+ def __init__(self, detail=None, headers=None, comment=None,
+ body_template=None, location='', **kw):
+ super(_HTTPMove, self).__init__(
+ detail=detail, headers=headers, comment=comment,
+ body_template=body_template, location=location, **kw)
+
+class HTTPMultipleChoices(_HTTPMove):
+ """
+ subclass of :class:`~_HTTPMove`
+
+ This indicates that the requested resource corresponds to any one
+ of a set of representations, each with its own specific location,
+ and agent-driven negotiation information is being provided so that
+ the user can select a preferred representation and redirect its
+ request to that location.
+
+ code: 300, title: Multiple Choices
+ """
+ code = 300
+ title = 'Multiple Choices'
+
+class HTTPMovedPermanently(_HTTPMove):
+ """
+ subclass of :class:`~_HTTPMove`
+
+ This indicates that the requested resource has been assigned a new
+ permanent URI and any future references to this resource SHOULD use
+ one of the returned URIs.
+
+ code: 301, title: Moved Permanently
+ """
+ code = 301
+ title = 'Moved Permanently'
+
+class HTTPFound(_HTTPMove):
+ """
+ subclass of :class:`~_HTTPMove`
+
+ This indicates that the requested resource resides temporarily under
+ a different URI.
+
+ code: 302, title: Found
+ """
+ code = 302
+ title = 'Found'
+ explanation = 'The resource was found at'
+
+# This one is safe after a POST (the redirected location will be
+# retrieved with GET):
+class HTTPSeeOther(_HTTPMove):
+ """
+ subclass of :class:`~_HTTPMove`
+
+ This indicates that the response to the request can be found under
+ a different URI and SHOULD be retrieved using a GET method on that
+ resource.
+
+ code: 303, title: See Other
+ """
+ code = 303
+ title = 'See Other'
+
+class HTTPNotModified(HTTPRedirection):
+ """
+ subclass of :class:`~HTTPRedirection`
+
+ This indicates that if the client has performed a conditional GET
+ request and access is allowed, but the document has not been
+ modified, the server SHOULD respond with this status code.
+
+ code: 304, title: Not Modified
+ """
+ # FIXME: this should include a date or etag header
+ code = 304
+ title = 'Not Modified'
+ empty_body = True
+
+class HTTPUseProxy(_HTTPMove):
+ """
+ subclass of :class:`~_HTTPMove`
+
+ This indicates that the requested resource MUST be accessed through
+ the proxy given by the Location field.
+
+ code: 305, title: Use Proxy
+ """
+ # Not a move, but looks a little like one
+ code = 305
+ title = 'Use Proxy'
+ explanation = (
+ 'The resource must be accessed through a proxy located at')
+
+class HTTPTemporaryRedirect(_HTTPMove):
+ """
+ subclass of :class:`~_HTTPMove`
+
+ This indicates that the requested resource resides temporarily
+ under a different URI.
+
+ code: 307, title: Temporary Redirect
+ """
+ code = 307
+ title = 'Temporary Redirect'
+
+############################################################
+## 4xx client error
+############################################################
+
+class HTTPClientError(HTTPError):
+ """
+ base class for the 400's, where the client is in error
+
+ This is an error condition in which the client is presumed to be
+ in-error. This is an expected problem, and thus is not considered
+ a bug. A server-side traceback is not warranted. Unless specialized,
+ this is a '400 Bad Request'
+ """
+ code = 400
+ title = 'Bad Request'
+ explanation = ('The server could not comply with the request since\r\n'
+ 'it is either malformed or otherwise incorrect.\r\n')
+
+class HTTPBadRequest(HTTPClientError):
+ pass
+
+class HTTPUnauthorized(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the request requires user authentication.
+
+ code: 401, title: Unauthorized
"""
+ code = 401
+ title = 'Unauthorized'
+ explanation = (
+ 'This server could not verify that you are authorized to\r\n'
+ 'access the document you requested. Either you supplied the\r\n'
+ 'wrong credentials (e.g., bad password), or your browser\r\n'
+ 'does not understand how to supply the credentials required.\r\n')
+
+class HTTPPaymentRequired(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ code: 402, title: Payment Required
+ """
+ code = 402
+ title = 'Payment Required'
+ explanation = ('Access was denied for financial reasons.')
+
+class HTTPForbidden(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server understood the request, but is
+ refusing to fulfill it.
+
+ code: 403, title: Forbidden
+
Raise this exception within :term:`view` code to immediately return the
:term:`forbidden view` to the invoking user. Usually this is a basic
``403`` page, but the forbidden view can be customized as necessary. See
:ref:`changing_the_forbidden_view`. A ``Forbidden`` exception will be
the ``context`` of a :term:`Forbidden View`.
- This exception's constructor accepts two arguments. The first argument,
- ``message``, should be a string. The value of this string will be used
- as the ``message`` attribute of the exception object. The second
- argument, ``result`` is usually an instance of
+ This exception's constructor treats two arguments specially. The first
+ argument, ``detail``, should be a string. The value of this string will
+ be used as the ``message`` attribute of the exception object. The second
+ special keyword argument, ``result`` is usually an instance of
:class:`pyramid.security.Denied` or :class:`pyramid.security.ACLDenied`
each of which indicates a reason for the forbidden error. However,
``result`` is also permitted to be just a plain boolean ``False`` object.
The ``result`` value will be used as the ``result`` attribute of the
- exception object.
+ exception object. It defaults to ``None``.
The :term:`Forbidden View` can use the attributes of a Forbidden
exception as necessary to provide extended information in an error
report shown to a user.
"""
+ # differences from webob.exc.HTTPForbidden:
+ #
+ # - accepts a ``result`` keyword argument
+ #
+ # - overrides constructor to set ``self.result``
+ #
+ # differences from older pyramid.exceptions.Forbidden:
+ #
+ # - ``result`` must be passed as a keyword argument.
+ #
+ code = 403
+ title = 'Forbidden'
+ explanation = ('Access was denied to this resource.')
def __init__(self, detail=None, headers=None, comment=None,
body_template=None, result=None, **kw):
- self.message = detail # prevent 2.6.X whining
- self.result = result # bw compat
- _HTTPForbidden.__init__(self, detail=detail, headers=headers,
- comment=comment, body_template=body_template,
- **kw)
- if not ('body' in kw or 'app_iter' in kw):
- if not self.empty_body:
- body = self.html_body(self.environ)
- if isinstance(body, unicode):
- body = body.encode(self.charset)
- self.body = body
+ HTTPClientError.__init__(self, detail=detail, headers=headers,
+ comment=comment, body_template=body_template,
+ **kw)
+ self.result = result
+
+class HTTPNotFound(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server did not find anything matching the
+ Request-URI.
+
+ code: 404, title: Not Found
+
+ Raise this exception within :term:`view` code to immediately
+ return the :term:`Not Found view` to the invoking user. Usually
+ this is a basic ``404`` page, but the Not Found view can be
+ customized as necessary. See :ref:`changing_the_notfound_view`.
+
+ This exception's constructor accepts a ``detail`` argument
+ (the first argument), which should be a string. The value of this
+ string will be available as the ``message`` attribute of this exception,
+ for availability to the :term:`Not Found View`.
+ """
+ code = 404
+ title = 'Not Found'
+ explanation = ('The resource could not be found.')
+
+class HTTPMethodNotAllowed(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the method specified in the Request-Line is
+ not allowed for the resource identified by the Request-URI.
+
+ code: 405, title: Method Not Allowed
+ """
+ # differences from webob.exc.HTTPMethodNotAllowed:
+ #
+ # - body_template_obj not overridden (it tried to use request environ's
+ # REQUEST_METHOD)
+ code = 405
+ title = 'Method Not Allowed'
+
+class HTTPNotAcceptable(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates the resource identified by the request is only
+ capable of generating response entities which have content
+ characteristics not acceptable according to the accept headers
+ sent in the request.
+
+ code: 406, title: Not Acceptable
+ """
+ # differences from webob.exc.HTTPNotAcceptable:
+ #
+ # - body_template_obj not overridden (it tried to use request environ's
+ # HTTP_ACCEPT)
+ code = 406
+ title = 'Not Acceptable'
+
+class HTTPProxyAuthenticationRequired(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This is similar to 401, but indicates that the client must first
+ authenticate itself with the proxy.
+
+ code: 407, title: Proxy Authentication Required
+ """
+ code = 407
+ title = 'Proxy Authentication Required'
+ explanation = ('Authentication with a local proxy is needed.')
+
+class HTTPRequestTimeout(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the client did not produce a request within
+ the time that the server was prepared to wait.
+
+ code: 408, title: Request Timeout
+ """
+ code = 408
+ title = 'Request Timeout'
+ explanation = ('The server has waited too long for the request to '
+ 'be sent by the client.')
+
+class HTTPConflict(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the request could not be completed due to a
+ conflict with the current state of the resource.
+
+ code: 409, title: Conflict
+ """
+ code = 409
+ title = 'Conflict'
+ explanation = ('There was a conflict when trying to complete '
+ 'your request.')
+
+class HTTPGone(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the requested resource is no longer available
+ at the server and no forwarding address is known.
+
+ code: 410, title: Gone
+ """
+ code = 410
+ title = 'Gone'
+ explanation = ('This resource is no longer available. No forwarding '
+ 'address is given.')
+
+class HTTPLengthRequired(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the the server refuses to accept the request
+ without a defined Content-Length.
+
+ code: 411, title: Length Required
+ """
+ code = 411
+ title = 'Length Required'
+ explanation = ('Content-Length header required.')
+
+class HTTPPreconditionFailed(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the precondition given in one or more of the
+ request-header fields evaluated to false when it was tested on the
+ server.
+
+ code: 412, title: Precondition Failed
+ """
+ code = 412
+ title = 'Precondition Failed'
+ explanation = ('Request precondition failed.')
+
+class HTTPRequestEntityTooLarge(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server is refusing to process a request
+ because the request entity is larger than the server is willing or
+ able to process.
+
+ code: 413, title: Request Entity Too Large
+ """
+ code = 413
+ title = 'Request Entity Too Large'
+ explanation = ('The body of your request was too large for this server.')
+
+class HTTPRequestURITooLong(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server is refusing to service the request
+ because the Request-URI is longer than the server is willing to
+ interpret.
+
+ code: 414, title: Request-URI Too Long
+ """
+ code = 414
+ title = 'Request-URI Too Long'
+ explanation = ('The request URI was too long for this server.')
+
+class HTTPUnsupportedMediaType(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server is refusing to service the request
+ because the entity of the request is in a format not supported by
+ the requested resource for the requested method.
+
+ code: 415, title: Unsupported Media Type
+ """
+ # differences from webob.exc.HTTPUnsupportedMediaType:
+ #
+ # - body_template_obj not overridden (it tried to use request environ's
+ # CONTENT_TYPE)
+ code = 415
+ title = 'Unsupported Media Type'
+
+class HTTPRequestRangeNotSatisfiable(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ The server SHOULD return a response with this status code if a
+ request included a Range request-header field, and none of the
+ range-specifier values in this field overlap the current extent
+ of the selected resource, and the request did not include an
+ If-Range request-header field.
+
+ code: 416, title: Request Range Not Satisfiable
+ """
+ code = 416
+ title = 'Request Range Not Satisfiable'
+ explanation = ('The Range requested is not available.')
+
+class HTTPExpectationFailed(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indidcates that the expectation given in an Expect
+ request-header field could not be met by this server.
+
+ code: 417, title: Expectation Failed
+ """
+ code = 417
+ title = 'Expectation Failed'
+ explanation = ('Expectation failed.')
+
+class HTTPUnprocessableEntity(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server is unable to process the contained
+ instructions. Only for WebDAV.
+
+ code: 422, title: Unprocessable Entity
+ """
+ ## Note: from WebDAV
+ code = 422
+ title = 'Unprocessable Entity'
+ explanation = 'Unable to process the contained instructions'
+
+class HTTPLocked(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the resource is locked. Only for WebDAV
+
+ code: 423, title: Locked
+ """
+ ## Note: from WebDAV
+ code = 423
+ title = 'Locked'
+ explanation = ('The resource is locked')
+
+class HTTPFailedDependency(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the method could not be performed because the
+ requested action depended on another action and that action failed.
+ Only for WebDAV.
+
+ code: 424, title: Failed Dependency
+ """
+ ## Note: from WebDAV
+ code = 424
+ title = 'Failed Dependency'
+ explanation = (
+ 'The method could not be performed because the requested '
+ 'action dependended on another action and that action failed')
+
+############################################################
+## 5xx Server Error
+############################################################
+# Response status codes beginning with the digit "5" indicate cases in
+# which the server is aware that it has erred or is incapable of
+# performing the request. Except when responding to a HEAD request, the
+# server SHOULD include an entity containing an explanation of the error
+# situation, and whether it is a temporary or permanent condition. User
+# agents SHOULD display any included entity to the user. These response
+# codes are applicable to any request method.
+
+class HTTPServerError(HTTPError):
+ """
+ base class for the 500's, where the server is in-error
+
+ This is an error condition in which the server is presumed to be
+ in-error. This is usually unexpected, and thus requires a traceback;
+ ideally, opening a support ticket for the customer. Unless specialized,
+ this is a '500 Internal Server Error'
+ """
+ code = 500
+ title = 'Internal Server Error'
+ explanation = (
+ 'The server has either erred or is incapable of performing\r\n'
+ 'the requested operation.\r\n')
+
+class HTTPInternalServerError(HTTPServerError):
+ pass
+
+class HTTPNotImplemented(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server does not support the functionality
+ required to fulfill the request.
+
+ code: 501, title: Not Implemented
+ """
+ # differences from webob.exc.HTTPNotAcceptable:
+ #
+ # - body_template_obj not overridden (it tried to use request environ's
+ # REQUEST_METHOD)
+ code = 501
+ title = 'Not Implemented'
+
+class HTTPBadGateway(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server, while acting as a gateway or proxy,
+ received an invalid response from the upstream server it accessed
+ in attempting to fulfill the request.
+
+ code: 502, title: Bad Gateway
+ """
+ code = 502
+ title = 'Bad Gateway'
+ explanation = ('Bad gateway.')
+
+class HTTPServiceUnavailable(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server is currently unable to handle the
+ request due to a temporary overloading or maintenance of the server.
+
+ code: 503, title: Service Unavailable
+ """
+ code = 503
+ title = 'Service Unavailable'
+ explanation = ('The server is currently unavailable. '
+ 'Please try again at a later time.')
+
+class HTTPGatewayTimeout(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server, while acting as a gateway or proxy,
+ did not receive a timely response from the upstream server specified
+ by the URI (e.g. HTTP, FTP, LDAP) or some other auxiliary server
+ (e.g. DNS) it needed to access in attempting to complete the request.
+
+ code: 504, title: Gateway Timeout
+ """
+ code = 504
+ title = 'Gateway Timeout'
+ explanation = ('The gateway has timed out.')
+
+class HTTPVersionNotSupported(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server does not support, or refuses to
+ support, the HTTP protocol version that was used in the request
+ message.
+
+ code: 505, title: HTTP Version Not Supported
+ """
+ code = 505
+ title = 'HTTP Version Not Supported'
+ explanation = ('The HTTP version is not supported.')
+
+class HTTPInsufficientStorage(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server does not have enough space to save
+ the resource.
+
+ code: 507, title: Insufficient Storage
+ """
+ code = 507
+ title = 'Insufficient Storage'
+ explanation = ('There was not enough space to save the resource')
+
+__all__ = ['status_map']
+status_map={}
+for name, value in globals().items():
+ if (isinstance(value, (type, types.ClassType)) and
+ issubclass(value, HTTPException)
+ and not name.startswith('_')):
+ __all__.append(name)
+ if getattr(value, 'code', None):
+ status_map[value.code]=value
+ if hasattr(value, 'explanation'):
+ value.explanation = ' '.join(value.explanation.strip().split())
+del name, value
NotFound = HTTPNotFound # bw compat
Forbidden = HTTPForbidden # bw compat
-# patch our status map with subclasses
-status_map[403] = HTTPForbidden
-status_map[404] = HTTPNotFound
-
class PredicateMismatch(NotFound):
"""
Internal exception (not an API) raised by multiviews when no
@@ -197,27 +1091,15 @@ def is_response(ob):
return True
return False
-newstyle_exceptions = issubclass(Exception, object)
+def default_exceptionresponse_view(context, request):
+ if not isinstance(context, Exception):
+ # backwards compat for an exception response view registered via
+ # config.set_notfound_view or config.set_forbidden_view
+ # instead of as a proper exception view
+ context = request.exception or context
+ # WSGIHTTPException, a Response (2.5+)
+ return context
-if newstyle_exceptions:
- # webob exceptions will be Response objects (Py 2.5+)
- def default_exceptionresponse_view(context, request):
- if not isinstance(context, Exception):
- # backwards compat for an exception response view registered via
- # config.set_notfound_view or config.set_forbidden_view
- # instead of as a proper exception view
- context = request.exception or context
- # WSGIHTTPException, a Response (2.5+)
- return context
-
-else:
- # webob exceptions will not be Response objects (Py 2.4)
- def default_exceptionresponse_view(context, request):
- if not isinstance(context, Exception):
- # backwards compat for an exception response view registered via
- # config.set_notfound_view or config.set_forbidden_view
- # instead of as a proper exception view
- context = request.exception or context
- # HTTPException, not a Response (2.4)
- get_response = getattr(request, 'get_response', lambda c: c) # testing
- return get_response(context)
+__all__.extend(['NotFound', 'Forbidden', 'PredicateMismatch', 'URLDecodeError',
+ 'ConfigurationError', 'abort', 'redirect', 'is_response',
+ 'default_exceptionresponse_view'])
diff --git a/pyramid/tests/test_exceptions.py b/pyramid/tests/test_exceptions.py
index 2e9279f66..3f303e3df 100644
--- a/pyramid/tests/test_exceptions.py
+++ b/pyramid/tests/test_exceptions.py
@@ -96,10 +96,301 @@ class Test_default_exceptionresponse_view(unittest.TestCase):
result = self._callFUT(None, request)
self.assertEqual(result, context)
+class Test_no_escape(unittest.TestCase):
+ def _callFUT(self, val):
+ from pyramid.exceptions import no_escape
+ return no_escape(val)
+
+ def test_null(self):
+ self.assertEqual(self._callFUT(None), '')
+
+ def test_not_basestring(self):
+ self.assertEqual(self._callFUT(42), '42')
+
+ def test_unicode(self):
+ class DummyUnicodeObject(object):
+ def __unicode__(self):
+ return u'42'
+ duo = DummyUnicodeObject()
+ self.assertEqual(self._callFUT(duo), u'42')
+
class DummyRequest(object):
exception = None
- def get_response(self, context):
- return 'response'
+
+# from webob.request import Request
+# from webob.exc import HTTPException
+# from webob.exc import WSGIHTTPException
+# from webob.exc import HTTPMethodNotAllowed
+# from webob.exc import _HTTPMove
+# from webob.exc import HTTPExceptionMiddleware
+
+# from nose.tools import eq_, ok_, assert_equal, assert_raises
+
+# def test_HTTPException(self):
+# _called = []
+# _result = object()
+# def _response(environ, start_response):
+# _called.append((environ, start_response))
+# return _result
+# environ = {}
+# start_response = object()
+# exc = HTTPException('testing', _response)
+# ok_(exc.wsgi_response is _response)
+# ok_(exc.exception is exc)
+# result = exc(environ, start_response)
+# ok_(result is result)
+# assert_equal(_called, [(environ, start_response)])
+
+# from webob.dec import wsgify
+# @wsgify
+# def method_not_allowed_app(req):
+# if req.method != 'GET':
+# raise HTTPMethodNotAllowed().exception
+# return 'hello!'
+
+
+# def test_exception_with_unicode_data():
+# req = Request.blank('/', method=u'POST')
+# res = req.get_response(method_not_allowed_app)
+# assert res.status_int == 405
+
+# def test_WSGIHTTPException_headers():
+# exc = WSGIHTTPException(headers=[('Set-Cookie', 'a=1'),
+# ('Set-Cookie', 'a=2')])
+# mixed = exc.headers.mixed()
+# assert mixed['set-cookie'] == ['a=1', 'a=2']
+
+# def test_WSGIHTTPException_w_body_template():
+# from string import Template
+# TEMPLATE = '$foo: $bar'
+# exc = WSGIHTTPException(body_template = TEMPLATE)
+# assert_equal(exc.body_template, TEMPLATE)
+# ok_(isinstance(exc.body_template_obj, Template))
+# eq_(exc.body_template_obj.substitute({'foo': 'FOO', 'bar': 'BAR'}),
+# 'FOO: BAR')
+
+# def test_WSGIHTTPException_w_empty_body():
+# class EmptyOnly(WSGIHTTPException):
+# empty_body = True
+# exc = EmptyOnly(content_type='text/plain', content_length=234)
+# ok_('content_type' not in exc.__dict__)
+# ok_('content_length' not in exc.__dict__)
+
+# def test_WSGIHTTPException___str__():
+# exc1 = WSGIHTTPException(detail='Detail')
+# eq_(str(exc1), 'Detail')
+# class Explain(WSGIHTTPException):
+# explanation = 'Explanation'
+# eq_(str(Explain()), 'Explanation')
+
+# def test_WSGIHTTPException_plain_body_no_comment():
+# class Explain(WSGIHTTPException):
+# code = '999'
+# title = 'Testing'
+# explanation = 'Explanation'
+# exc = Explain(detail='Detail')
+# eq_(exc.plain_body({}),
+# '999 Testing\n\nExplanation\n\n Detail ')
+
+# def test_WSGIHTTPException_html_body_w_comment():
+# class Explain(WSGIHTTPException):
+# code = '999'
+# title = 'Testing'
+# explanation = 'Explanation'
+# exc = Explain(detail='Detail', comment='Comment')
+# eq_(exc.html_body({}),
+# '<html>\n'
+# ' <head>\n'
+# ' <title>999 Testing</title>\n'
+# ' </head>\n'
+# ' <body>\n'
+# ' <h1>999 Testing</h1>\n'
+# ' Explanation<br /><br />\n'
+# 'Detail\n'
+# '<!-- Comment -->\n\n'
+# ' </body>\n'
+# '</html>'
+# )
+
+# def test_WSGIHTTPException_generate_response():
+# def start_response(status, headers, exc_info=None):
+# pass
+# environ = {
+# 'wsgi.url_scheme': 'HTTP',
+# 'SERVER_NAME': 'localhost',
+# 'SERVER_PORT': '80',
+# 'REQUEST_METHOD': 'PUT',
+# 'HTTP_ACCEPT': 'text/html'
+# }
+# excep = WSGIHTTPException()
+# assert_equal( excep(environ,start_response), [
+# '<html>\n'
+# ' <head>\n'
+# ' <title>None None</title>\n'
+# ' </head>\n'
+# ' <body>\n'
+# ' <h1>None None</h1>\n'
+# ' <br /><br />\n'
+# '\n'
+# '\n\n'
+# ' </body>\n'
+# '</html>' ]
+# )
+
+# def test_WSGIHTTPException_call_w_body():
+# def start_response(status, headers, exc_info=None):
+# pass
+# environ = {
+# 'wsgi.url_scheme': 'HTTP',
+# 'SERVER_NAME': 'localhost',
+# 'SERVER_PORT': '80',
+# 'REQUEST_METHOD': 'PUT'
+# }
+# excep = WSGIHTTPException()
+# excep.body = 'test'
+# assert_equal( excep(environ,start_response), ['test'] )
+
+
+# def test_WSGIHTTPException_wsgi_response():
+# def start_response(status, headers, exc_info=None):
+# pass
+# environ = {
+# 'wsgi.url_scheme': 'HTTP',
+# 'SERVER_NAME': 'localhost',
+# 'SERVER_PORT': '80',
+# 'REQUEST_METHOD': 'HEAD'
+# }
+# excep = WSGIHTTPException()
+# assert_equal( excep.wsgi_response(environ,start_response), [] )
+
+# def test_WSGIHTTPException_exception_newstyle():
+# def start_response(status, headers, exc_info=None):
+# pass
+# environ = {
+# 'wsgi.url_scheme': 'HTTP',
+# 'SERVER_NAME': 'localhost',
+# 'SERVER_PORT': '80',
+# 'REQUEST_METHOD': 'HEAD'
+# }
+# excep = WSGIHTTPException()
+# exc.newstyle_exceptions = True
+# assert_equal( excep.exception(environ,start_response), [] )
+
+# def test_WSGIHTTPException_exception_no_newstyle():
+# def start_response(status, headers, exc_info=None):
+# pass
+# environ = {
+# 'wsgi.url_scheme': 'HTTP',
+# 'SERVER_NAME': 'localhost',
+# 'SERVER_PORT': '80',
+# 'REQUEST_METHOD': 'HEAD'
+# }
+# excep = WSGIHTTPException()
+# exc.newstyle_exceptions = False
+# assert_equal( excep.exception(environ,start_response), [] )
+
+# def test_HTTPMove():
+# def start_response(status, headers, exc_info=None):
+# pass
+# environ = {
+# 'wsgi.url_scheme': 'HTTP',
+# 'SERVER_NAME': 'localhost',
+# 'SERVER_PORT': '80',
+# 'REQUEST_METHOD': 'HEAD'
+# }
+# m = _HTTPMove()
+# assert_equal( m( environ, start_response ), [] )
+
+# def test_HTTPMove_location_not_none():
+# def start_response(status, headers, exc_info=None):
+# pass
+# environ = {
+# 'wsgi.url_scheme': 'HTTP',
+# 'SERVER_NAME': 'localhost',
+# 'SERVER_PORT': '80',
+# 'REQUEST_METHOD': 'HEAD'
+# }
+# m = _HTTPMove(location='http://example.com')
+# assert_equal( m( environ, start_response ), [] )
+
+# def test_HTTPMove_add_slash_and_location():
+# def start_response(status, headers, exc_info=None):
+# pass
+# environ = {
+# 'wsgi.url_scheme': 'HTTP',
+# 'SERVER_NAME': 'localhost',
+# 'SERVER_PORT': '80',
+# 'REQUEST_METHOD': 'HEAD'
+# }
+# assert_raises( TypeError, _HTTPMove, location='http://example.com', add_slash=True )
+
+# def test_HTTPMove_call_add_slash():
+# def start_response(status, headers, exc_info=None):
+# pass
+# environ = {
+# 'wsgi.url_scheme': 'HTTP',
+# 'SERVER_NAME': 'localhost',
+# 'SERVER_PORT': '80',
+# 'REQUEST_METHOD': 'HEAD'
+# }
+# m = _HTTPMove()
+# m.add_slash = True
+# assert_equal( m( environ, start_response ), [] )
+
+# def test_HTTPMove_call_query_string():
+# def start_response(status, headers, exc_info=None):
+# pass
+# environ = {
+# 'wsgi.url_scheme': 'HTTP',
+# 'SERVER_NAME': 'localhost',
+# 'SERVER_PORT': '80',
+# 'REQUEST_METHOD': 'HEAD'
+# }
+# m = _HTTPMove()
+# m.add_slash = True
+# environ[ 'QUERY_STRING' ] = 'querystring'
+# assert_equal( m( environ, start_response ), [] )
+
+# def test_HTTPExceptionMiddleware_ok():
+# def app( environ, start_response ):
+# return '123'
+# application = app
+# m = HTTPExceptionMiddleware(application)
+# environ = {}
+# start_response = None
+# res = m( environ, start_response )
+# assert_equal( res, '123' )
-
-
+# def test_HTTPExceptionMiddleware_exception():
+# def wsgi_response( environ, start_response):
+# return '123'
+# def app( environ, start_response ):
+# raise HTTPException( None, wsgi_response )
+# application = app
+# m = HTTPExceptionMiddleware(application)
+# environ = {}
+# start_response = None
+# res = m( environ, start_response )
+# assert_equal( res, '123' )
+
+# def test_HTTPExceptionMiddleware_exception_exc_info_none():
+# class DummySys:
+# def exc_info(self):
+# return None
+# def wsgi_response( environ, start_response):
+# return start_response('200 OK', [], exc_info=None)
+# def app( environ, start_response ):
+# raise HTTPException( None, wsgi_response )
+# application = app
+# m = HTTPExceptionMiddleware(application)
+# environ = {}
+# def start_response(status, headers, exc_info):
+# pass
+# try:
+# from webob import exc
+# old_sys = exc.sys
+# sys = DummySys()
+# res = m( environ, start_response )
+# assert_equal( res, None )
+# finally:
+# exc.sys = old_sys