forked from orbit-oss/flask
Fixed and intuitivized exception handling
This commit is contained in:
parent
dac45f6c5d
commit
eae48d97b0
1 changed files with 98 additions and 26 deletions
124
flask/app.py
124
flask/app.py
|
|
@ -8,6 +8,7 @@
|
|||
:copyright: (c) 2015 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from collections import ChainMap
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
|
@ -19,7 +20,7 @@ from functools import update_wrapper
|
|||
from werkzeug.datastructures import ImmutableDict
|
||||
from werkzeug.routing import Map, Rule, RequestRedirect, BuildError
|
||||
from werkzeug.exceptions import HTTPException, InternalServerError, \
|
||||
MethodNotAllowed, BadRequest
|
||||
MethodNotAllowed, BadRequest, default_exceptions
|
||||
|
||||
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
|
||||
locked_cached_property, _endpoint_from_view_func, find_package
|
||||
|
|
@ -65,6 +66,64 @@ def setupmethod(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):
|
||||
"""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
|
||||
|
|
@ -365,7 +424,7 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
# support for the now deprecated `error_handlers` attribute. The
|
||||
# :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``
|
||||
#: for error handlers active on the application, otherwise the key is
|
||||
|
|
@ -1136,16 +1195,30 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
@setupmethod
|
||||
def _register_error_handler(self, key, code_or_exception, f):
|
||||
if isinstance(code_or_exception, HTTPException):
|
||||
code_or_exception = code_or_exception.code
|
||||
if isinstance(code_or_exception, integer_types):
|
||||
assert code_or_exception != 500 or key is None, \
|
||||
"""
|
||||
:type key: None|str
|
||||
:type code_or_exception: int|T<=Exception
|
||||
: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 ' \
|
||||
'server error on a per-blueprint level.'
|
||||
self.error_handler_spec.setdefault(key, {})[code_or_exception] = f
|
||||
else:
|
||||
self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \
|
||||
.append((code_or_exception, f))
|
||||
|
||||
handlers[code_or_exception] = f
|
||||
|
||||
@setupmethod
|
||||
def template_filter(self, name=None):
|
||||
|
|
@ -1386,6 +1459,13 @@ class Flask(_PackageBoundObject):
|
|||
self.url_default_functions.setdefault(None, []).append(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):
|
||||
"""Handles an HTTP exception. By default this will invoke the
|
||||
registered error handlers and fall back to returning the
|
||||
|
|
@ -1393,15 +1473,12 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
.. versionadded:: 0.3
|
||||
"""
|
||||
handlers = self.error_handler_spec.get(request.blueprint)
|
||||
# Proxy exceptions don't have error codes. We want to always return
|
||||
# those unchanged as errors
|
||||
if e.code is None:
|
||||
return e
|
||||
if handlers and e.code in handlers:
|
||||
handler = handlers[e.code]
|
||||
else:
|
||||
handler = self.error_handler_spec[None].get(e.code)
|
||||
|
||||
handler = self._find_error_handler(e)
|
||||
if handler is None:
|
||||
return e
|
||||
return handler(e)
|
||||
|
|
@ -1443,20 +1520,15 @@ class Flask(_PackageBoundObject):
|
|||
# wants the traceback preserved in handle_http_exception. Of course
|
||||
# we cannot prevent users from trashing it themselves in a custom
|
||||
# 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):
|
||||
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):
|
||||
"""Default exception handling that kicks in when an exception
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue