Browse Source

Fixed and intuitivized exception handling

pull/1291/head
Phil Schaf 10 years ago
parent
commit
eae48d97b0
  1. 124
      flask/app.py

124
flask/app.py

@ -8,6 +8,7 @@
:copyright: (c) 2015 by Armin Ronacher. :copyright: (c) 2015 by Armin Ronacher.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
from collections import ChainMap
import os import os
import sys import sys
@ -19,7 +20,7 @@ from functools import update_wrapper
from werkzeug.datastructures import ImmutableDict from werkzeug.datastructures import ImmutableDict
from werkzeug.routing import Map, Rule, RequestRedirect, BuildError from werkzeug.routing import Map, Rule, RequestRedirect, BuildError
from werkzeug.exceptions import HTTPException, InternalServerError, \ from werkzeug.exceptions import HTTPException, InternalServerError, \
MethodNotAllowed, BadRequest MethodNotAllowed, BadRequest, default_exceptions
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
locked_cached_property, _endpoint_from_view_func, find_package locked_cached_property, _endpoint_from_view_func, find_package
@ -65,6 +66,64 @@ def setupmethod(f):
return update_wrapper(wrapper_func, f) return update_wrapper(wrapper_func, f)
def get_http_code(error_class_or_instance):
if (
isinstance(error_class_or_instance, HTTPException) or
isinstance(error_class_or_instance, type) and
issubclass(error_class_or_instance, HTTPException)
):
return error_class_or_instance.code
return None
class ExceptionHandlerDict(ChainMap):
"""A dict storing exception handlers or falling back to the default ones
Designed to be app.error_handler_spec[blueprint_or_none]
And hold a Exception handler function mapping.
Converts error codes to default HTTPException subclasses.
Returns None if no handler is defined for blueprint or app
"""
def __init__(self, app, blueprint):
self.app = app
init = super(ExceptionHandlerDict, self).__init__
if blueprint: # fall back to app mapping
init({}, app.error_handler_spec[None])
else:
init({})
def get_class(self, exc_class_or_code):
if isinstance(exc_class_or_code, integer_types):
# ensure that we register only exceptions as keys
exc_class = default_exceptions[exc_class_or_code]
else:
assert issubclass(exc_class_or_code, Exception)
exc_class = exc_class_or_code
return exc_class
def __contains__(self, e_or_c):
return super(ExceptionHandlerDict, self).__contains__(self.get_class(e_or_c))
def __getitem__(self, e_or_c):
return super(ExceptionHandlerDict, self).__getitem__(self.get_class(e_or_c))
def __setitem__(self, e_or_c, handler):
assert callable(handler)
return super(ExceptionHandlerDict, self).__setitem__(self.get_class(e_or_c), handler)
def find_handler(self, ex_instance):
assert isinstance(ex_instance, Exception)
for superclass in type(ex_instance).mro():
if superclass is BaseException:
return None
handler = self.get(superclass)
if handler is not None:
return handler
return None
class Flask(_PackageBoundObject): class Flask(_PackageBoundObject):
"""The flask object implements a WSGI application and acts as the central """The flask object implements a WSGI application and acts as the central
object. It is passed the name of the module or package of the object. It is passed the name of the module or package of the
@ -365,7 +424,7 @@ class Flask(_PackageBoundObject):
# support for the now deprecated `error_handlers` attribute. The # support for the now deprecated `error_handlers` attribute. The
# :attr:`error_handler_spec` shall be used now. # :attr:`error_handler_spec` shall be used now.
self._error_handlers = {} self._error_handlers = ExceptionHandlerDict(self, None)
#: A dictionary of all registered error handlers. The key is ``None`` #: A dictionary of all registered error handlers. The key is ``None``
#: for error handlers active on the application, otherwise the key is #: for error handlers active on the application, otherwise the key is
@ -1136,16 +1195,30 @@ class Flask(_PackageBoundObject):
@setupmethod @setupmethod
def _register_error_handler(self, key, code_or_exception, f): def _register_error_handler(self, key, code_or_exception, f):
if isinstance(code_or_exception, HTTPException): """
code_or_exception = code_or_exception.code :type key: None|str
if isinstance(code_or_exception, integer_types): :type code_or_exception: int|T<=Exception
assert code_or_exception != 500 or key is None, \ :type f: callable
"""
assert not isinstance(code_or_exception, HTTPException) # old broken behavior
code = code_or_exception
is_code = isinstance(code_or_exception, integer_types)
if not is_code:
if issubclass(code_or_exception, HTTPException):
code = code_or_exception.code
else:
code = None
handlers = self.error_handler_spec.setdefault(key, ExceptionHandlerDict(self, key))
if is_code:
# TODO: why is this?
assert code != 500 or key is None, \
'It is currently not possible to register a 500 internal ' \ 'It is currently not possible to register a 500 internal ' \
'server error on a per-blueprint level.' 'server error on a per-blueprint level.'
self.error_handler_spec.setdefault(key, {})[code_or_exception] = f
else: handlers[code_or_exception] = f
self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \
.append((code_or_exception, f))
@setupmethod @setupmethod
def template_filter(self, name=None): def template_filter(self, name=None):
@ -1386,6 +1459,13 @@ class Flask(_PackageBoundObject):
self.url_default_functions.setdefault(None, []).append(f) self.url_default_functions.setdefault(None, []).append(f)
return f return f
def _find_error_handler(self, e):
"""Finds a registered error handler for the request’s blueprint.
If nether blueprint nor App has a suitable handler registered, returns None
"""
handlers = self.error_handler_spec.get(request.blueprint, self.error_handler_spec[None])
return handlers.find_handler(e)
def handle_http_exception(self, e): def handle_http_exception(self, e):
"""Handles an HTTP exception. By default this will invoke the """Handles an HTTP exception. By default this will invoke the
registered error handlers and fall back to returning the registered error handlers and fall back to returning the
@ -1393,15 +1473,12 @@ class Flask(_PackageBoundObject):
.. versionadded:: 0.3 .. versionadded:: 0.3
""" """
handlers = self.error_handler_spec.get(request.blueprint)
# Proxy exceptions don't have error codes. We want to always return # Proxy exceptions don't have error codes. We want to always return
# those unchanged as errors # those unchanged as errors
if e.code is None: if e.code is None:
return e return e
if handlers and e.code in handlers:
handler = handlers[e.code] handler = self._find_error_handler(e)
else:
handler = self.error_handler_spec[None].get(e.code)
if handler is None: if handler is None:
return e return e
return handler(e) return handler(e)
@ -1443,20 +1520,15 @@ class Flask(_PackageBoundObject):
# wants the traceback preserved in handle_http_exception. Of course # wants the traceback preserved in handle_http_exception. Of course
# we cannot prevent users from trashing it themselves in a custom # we cannot prevent users from trashing it themselves in a custom
# trap_http_exception method so that's their fault then. # trap_http_exception method so that's their fault then.
blueprint_handlers = ()
handlers = self.error_handler_spec.get(request.blueprint)
if handlers is not None:
blueprint_handlers = handlers.get(None, ())
app_handlers = self.error_handler_spec[None].get(None, ())
for typecheck, handler in chain(blueprint_handlers, app_handlers):
if isinstance(e, typecheck):
return handler(e)
if isinstance(e, HTTPException) and not self.trap_http_exception(e): if isinstance(e, HTTPException) and not self.trap_http_exception(e):
return self.handle_http_exception(e) return self.handle_http_exception(e)
reraise(exc_type, exc_value, tb) handler = self._find_error_handler(e)
if handler is None:
reraise(exc_type, exc_value, tb)
return handler(e)
def handle_exception(self, e): def handle_exception(self, e):
"""Default exception handling that kicks in when an exception """Default exception handling that kicks in when an exception

Loading…
Cancel
Save