示例#1
0
文件: hooks.py 项目: Amua/falcon
def _wrap_with_before(action, responder):
    """Execute the given action function before a responder method.

    Args:
        action: A function with a similar signature to a resource responder
            method, taking the form ``func(req, resp, resource, params)``.
        responder: The responder method to wrap
    """

    # NOTE(swistakm): create shim before checking what will be actually
    # decorated. This allows to avoid excessive nesting
    if 'resource' in get_argnames(action):
        shim = action
    else:
        # TODO(kgriffs): This decorator does not work on callable
        # classes in Python versions prior to 3.4.
        #
        # @wraps(action)
        def shim(req, resp, resource, kwargs):
            # NOTE(kgriffs): Don't have to pass "self" even if has_self,
            # since method is assumed to be bound.
            action(req, resp, kwargs)

    responder_argnames = get_argnames(responder)
    extra_argnames = responder_argnames[2:]  # Skip req, resp

    @wraps(responder)
    def do_before(self, req, resp, *args, **kwargs):
        if args:
            _merge_responder_args(args, kwargs, extra_argnames)

        shim(req, resp, self, kwargs)
        responder(self, req, resp, **kwargs)

    return do_before
示例#2
0
def _wrap_with_after(responder, action, action_args, action_kwargs):
    """Execute the given action function after a responder method.

    Args:
        responder: The responder method to wrap.
        action: A function with a signature similar to a resource responder
            method, taking the form ``func(req, resp, resource)``.
        action_args: Additional positional agruments to pass to *action*.
        action_kwargs: Additional keyword arguments to pass to *action*.
    """

    # NOTE(swistakm): create shim before checking what will be actually
    # decorated. This helps to avoid excessive nesting
    if 'resource' in get_argnames(action):
        shim = action
    else:
        # TODO(kgriffs): This decorator does not work on callable
        # classes in Python vesions prior to 3.4.
        #
        # @wraps(action)
        def shim(req, resp, resource, *args, **kwargs):
            action(req, resp, *args, **kwargs)

    responder_argnames = get_argnames(responder)
    extra_argnames = responder_argnames[2:]  # Skip req, resp

    @wraps(responder)
    def do_after(self, req, resp, *args, **kwargs):
        if args:
            _merge_responder_args(args, kwargs, extra_argnames)

        responder(self, req, resp, **kwargs)
        shim(req, resp, self, *action_args, **action_kwargs)

    return do_after
示例#3
0
文件: hooks.py 项目: jmvrbanac/falcon
def _wrap_with_after(responder, action, action_args, action_kwargs):
    """Execute the given action function after a responder method.

    Args:
        responder: The responder method to wrap.
        action: A function with a signature similar to a resource responder
            method, taking the form ``func(req, resp, resource)``.
        action_args: Additiona positional agruments to pass to *action*.
        action_kwargs: Additional keyword arguments to pass to *action*.
    """

    # NOTE(swistakm): create shim before checking what will be actually
    # decorated. This helps to avoid excessive nesting
    if 'resource' in get_argnames(action):
        shim = action
    else:
        # TODO(kgriffs): This decorator does not work on callable
        # classes in Python vesions prior to 3.4.
        #
        # @wraps(action)
        def shim(req, resp, resource, *args, **kwargs):
            action(req, resp, *args, **kwargs)

    responder_argnames = get_argnames(responder)
    extra_argnames = responder_argnames[2:]  # Skip req, resp

    @wraps(responder)
    def do_after(self, req, resp, *args, **kwargs):
        if args:
            _merge_responder_args(args, kwargs, extra_argnames)

        responder(self, req, resp, **kwargs)
        shim(req, resp, self, *action_args, **action_kwargs)

    return do_after
示例#4
0
def test_get_argnames():
    def foo(a, b, c):
        pass

    class Bar:
        def __call__(self, a, b):
            pass

    assert misc.get_argnames(foo) == ['a', 'b', 'c']
    assert misc.get_argnames(Bar()) == ['a', 'b']
    assert misc.get_argnames(functools.partial(foo, 42)) == ['b', 'c']
示例#5
0
def test_get_argnames():
    def foo(a, b, c):
        pass

    class Bar:
        def __call__(self, a, b):
            pass

    assert misc.get_argnames(foo) == ['a', 'b', 'c']
    assert misc.get_argnames(Bar()) == ['a', 'b']
    assert misc.get_argnames(functools.partial(foo, 42)) == ['b', 'c']
示例#6
0
def test_get_argnames():
    def foo(a, b, c):
        pass

    class Bar(object):
        def __call__(self, a, b):
            pass

    assert misc.get_argnames(foo) == ['a', 'b', 'c']
    assert misc.get_argnames(Bar()) == ['a', 'b']

    # NOTE(kgriffs): This difference will go away once we drop Python 2.7
    # support, so we just use this regression test to ensure the status quo.
    expected = ['b', 'c'] if compat.PY3 else ['a', 'b', 'c']
    assert misc.get_argnames(functools.partial(foo, 42)) == expected
示例#7
0
文件: hooks.py 项目: bryson/falcon
def _wrap_with_after(action, responder):
    """Execute the given action function after a responder method.

    Args:
        action: A function with a signature similar to a resource responder
            method, taking the form ``func(req, resp, resource)``.
        responder: The responder method to wrap.
    """

    # NOTE(swistakm): create shim before checking what will be actually
    # decorated. This helps to avoid excessive nesting
    if "resource" in get_argnames(action):
        shim = action
    else:
        # TODO(kgriffs): This decorator does not work on callable
        # classes in Python vesions prior to 3.4.
        #
        # @wraps(action)
        def shim(req, resp, resource):
            action(req, resp)

    @wraps(responder)
    def do_after(self, req, resp, **kwargs):
        responder(self, req, resp, **kwargs)
        shim(req, resp, self)

    return do_after
示例#8
0
def _wrap_with_after(action, responder):
    """Execute the given action function after a responder method.

    Args:
        action: A function with a signature similar to a resource responder
            method, taking the form ``func(req, resp, resource)``.
        responder: The responder method to wrap.
    """

    # NOTE(swistakm): create shim before checking what will be actually
    # decorated. This helps to avoid excessive nesting
    if 'resource' in get_argnames(action):
        shim = action
    else:
        # TODO(kgriffs): This decorator does not work on callable
        # classes in Python vesions prior to 3.4.
        #
        # @wraps(action)
        def shim(req, resp, resource):
            action(req, resp)

    @wraps(responder)
    def do_after(self, req, resp, **kwargs):
        responder(self, req, resp, **kwargs)
        shim(req, resp, self)

    return do_after
示例#9
0
文件: hooks.py 项目: nZac/falcon
def _wrap_with_after(responder, action, action_args, action_kwargs):
    """Execute the given action function after a responder method.

    Args:
        responder: The responder method to wrap.
        action: A function with a signature similar to a resource responder
            method, taking the form ``func(req, resp, resource)``.
        action_args: Additional positional agruments to pass to *action*.
        action_kwargs: Additional keyword arguments to pass to *action*.
    """

    responder_argnames = get_argnames(responder)
    extra_argnames = responder_argnames[2:]  # Skip req, resp

    if iscoroutinefunction(responder):
        action = _wrap_non_coroutine_unsafe(action)

        @wraps(responder)
        async def do_after(self, req, resp, *args, **kwargs):
            if args:
                _merge_responder_args(args, kwargs, extra_argnames)

            await responder(self, req, resp, **kwargs)
            await action(req, resp, self, *action_args, **action_kwargs)
    else:

        @wraps(responder)
        def do_after(self, req, resp, *args, **kwargs):
            if args:
                _merge_responder_args(args, kwargs, extra_argnames)

            responder(self, req, resp, **kwargs)
            action(req, resp, self, *action_args, **action_kwargs)

    return do_after
示例#10
0
文件: hooks.py 项目: bryson/falcon
def _wrap_with_before(action, responder):
    """Execute the given action function before a responder method.

    Args:
        action: A function with a similar signature to a resource responder
            method, taking the form ``func(req, resp, resource, params)``.
        responder: The responder method to wrap
    """

    # NOTE(swistakm): create shim before checking what will be actually
    # decorated. This allows to avoid excessive nesting
    if "resource" in get_argnames(action):
        shim = action
    else:
        # TODO(kgriffs): This decorator does not work on callable
        # classes in Python vesions prior to 3.4.
        #
        # @wraps(action)
        def shim(req, resp, resource, kwargs):
            # NOTE(kgriffs): Don't have to pass "self" even if has_self,
            # since method is assumed to be bound.
            action(req, resp, kwargs)

    @wraps(responder)
    def do_before(self, req, resp, **kwargs):
        shim(req, resp, self, kwargs)
        responder(self, req, resp, **kwargs)

    return do_before
示例#11
0
    def set_error_serializer(self, serializer):
        """Override the default serializer for instances of :class:`~.HTTPError`.

        When a responder raises an instance of :class:`~.HTTPError`,
        Falcon converts it to an HTTP response automatically. The
        default serializer supports JSON and XML, but may be overridden
        by this method to use a custom serializer in order to support
        other media types.

        Note:
            If a custom media type is used and the type includes a
            "+json" or "+xml" suffix, the default serializer will
            convert the error to JSON or XML, respectively.

        Note:
            The default serializer will not render any response body for
            :class:`~.HTTPError` instances where the `has_representation`
            property evaluates to ``False`` (such as in the case of types
            that subclass :class:`falcon.http_error.NoRepresentation`).
            However a custom serializer will be called regardless of the
            property value, and it may choose to override the
            representation logic.

        The :class:`~.HTTPError` class contains helper methods,
        such as `to_json()` and `to_dict()`, that can be used from
        within custom serializers. For example::

            def my_serializer(req, resp, exception):
                representation = None

                preferred = req.client_prefers(('application/x-yaml',
                                                'application/json'))

                if exception.has_representation and preferred is not None:
                    if preferred == 'application/json':
                        representation = exception.to_json()
                    else:
                        representation = yaml.dump(exception.to_dict(),
                                                   encoding=None)
                    resp.body = representation
                    resp.content_type = preferred

                resp.append_header('Vary', 'Accept')

        Args:
            serializer (callable): A function taking the form
                ``func(req, resp, exception)``, where `req` is the request
                object that was passed to the responder method, `resp` is
                the response object, and `exception` is an instance of
                ``falcon.HTTPError``.

        """

        if len(get_argnames(serializer)) == 2:
            serializer = helpers.wrap_old_error_serializer(serializer)

        self._serialize_error = serializer
示例#12
0
文件: api.py 项目: hozn/falcon
    def set_error_serializer(self, serializer):
        """Override the default serializer for instances of HTTPError.

        When a responder raises an instance of HTTPError, Falcon converts
        it to an HTTP response automatically. The default serializer
        supports JSON and XML, but may be overridden by this method to
        use a custom serializer in order to support other media types.

        The ``falcon.HTTPError`` class contains helper methods, such as
        `to_json()` and `to_dict()`, that can be used from within
        custom serializers. For example::

            def my_serializer(req, resp, exception):
                representation = None

                preferred = req.client_prefers(('application/x-yaml',
                                                'application/json'))

                if preferred is not None:
                    if preferred == 'application/json':
                        representation = exception.to_json()
                    else:
                        representation = yaml.dump(exception.to_dict(),
                                                   encoding=None)
                    resp.body = representation
                    resp.content_type = preferred

                resp.append_header('Vary', 'Accept')

        Note:
            If a custom media type is used and the type includes a
            "+json" or "+xml" suffix, the default serializer will
            convert the error to JSON or XML, respectively. If this
            is not desirable, a custom error serializer may be used
            to override this behavior.

        Args:
            serializer (callable): A function taking the form
                ``func(req, resp, exception)``, where `req` is the request
                object that was passed to the responder method, `resp` is
                the response object, and `exception` is an instance of
                ``falcon.HTTPError``.

        """

        if len(get_argnames(serializer)) == 2:
            serializer = helpers.wrap_old_error_serializer(serializer)

        self._serialize_error = serializer
示例#13
0
文件: api.py 项目: bryson/falcon
    def set_error_serializer(self, serializer):
        """Override the default serializer for instances of HTTPError.

        When a responder raises an instance of HTTPError, Falcon converts
        it to an HTTP response automatically. The default serializer
        supports JSON and XML, but may be overridden by this method to
        use a custom serializer in order to support other media types.

        The ``falcon.HTTPError`` class contains helper methods, such as
        `to_json()` and `to_dict()`, that can be used from within
        custom serializers. For example::

            def my_serializer(req, resp, exception):
                representation = None

                preferred = req.client_prefers(('application/x-yaml',
                                                'application/json'))

                if preferred is not None:
                    if preferred == 'application/json':
                        representation = exception.to_json()
                    else:
                        representation = yaml.dump(exception.to_dict(),
                                                   encoding=None)
                    resp.body = representation
                    resp.content_type = preferred

                resp.append_header('Vary', 'Accept')

        Note:
            If a custom media type is used and the type includes a
            "+json" or "+xml" suffix, the default serializer will
            convert the error to JSON or XML, respectively. If this
            is not desirable, a custom error serializer may be used
            to override this behavior.

        Args:
            serializer (callable): A function taking the form
                ``func(req, resp, exception)``, where `req` is the request
                object that was passed to the responder method, `resp` is
                the response object, and `exception` is an instance of
                ``falcon.HTTPError``.

        """

        if len(get_argnames(serializer)) == 2:
            serializer = helpers.wrap_old_error_serializer(serializer)

        self._serialize_error = serializer
示例#14
0
文件: hooks.py 项目: wsz/falcon
def _wrap_with_before(responder, action, action_args, action_kwargs, is_async):
    """Execute the given action function before a responder method.

    Args:
        responder: The responder method to wrap.
        action: A function with a similar signature to a resource responder
            method, taking the form ``func(req, resp, resource, params)``.
        action_args: Additional positional agruments to pass to *action*.
        action_kwargs: Additional keyword arguments to pass to *action*.
        is_async: Set to ``True`` for cythonized responders that are
            actually coroutine functions, since such responders can not
            be auto-detected. A hint is also required for regular functions
            that happen to return an awaitable coroutine object.
    """

    responder_argnames = get_argnames(responder)
    extra_argnames = responder_argnames[2:]  # Skip req, resp

    if is_async or iscoroutinefunction(responder):
        # NOTE(kgriffs): I manually verified that the implicit "else" branch
        #   is actually covered, but coverage isn't tracking it for
        #   some reason.
        if not is_async:  # pragma: nocover
            action = _wrap_non_coroutine_unsafe(action)

        @wraps(responder)
        async def do_before(self, req, resp, *args, **kwargs):
            if args:
                _merge_responder_args(args, kwargs, extra_argnames)

            await action(req, resp, self, kwargs, *action_args,
                         **action_kwargs)
            await responder(self, req, resp, **kwargs)

    else:

        @wraps(responder)
        def do_before(self, req, resp, *args, **kwargs):
            if args:
                _merge_responder_args(args, kwargs, extra_argnames)

            action(req, resp, self, kwargs, *action_args, **action_kwargs)
            responder(self, req, resp, **kwargs)

    return do_before
示例#15
0
文件: hooks.py 项目: falconry/falcon
def _wrap_with_before(responder, action, action_args, action_kwargs):
    """Execute the given action function before a responder method.

    Args:
        responder: The responder method to wrap.
        action: A function with a similar signature to a resource responder
            method, taking the form ``func(req, resp, resource, params)``.
        action_args: Additional positional agruments to pass to *action*.
        action_kwargs: Additional keyword arguments to pass to *action*.
    """

    responder_argnames = get_argnames(responder)
    extra_argnames = responder_argnames[2:]  # Skip req, resp

    @wraps(responder)
    def do_before(self, req, resp, *args, **kwargs):
        if args:
            _merge_responder_args(args, kwargs, extra_argnames)

        action(req, resp, self, kwargs, *action_args, **action_kwargs)
        responder(self, req, resp, **kwargs)

    return do_before
示例#16
0
def _wrap_with_before(responder, action, action_args, action_kwargs):
    """Execute the given action function before a responder method.

    Args:
        responder: The responder method to wrap.
        action: A function with a similar signature to a resource responder
            method, taking the form ``func(req, resp, resource, params)``.
        action_args: Additional positional agruments to pass to *action*.
        action_kwargs: Additional keyword arguments to pass to *action*.
    """

    responder_argnames = get_argnames(responder)
    extra_argnames = responder_argnames[2:]  # Skip req, resp

    @wraps(responder)
    def do_before(self, req, resp, *args, **kwargs):
        if args:
            _merge_responder_args(args, kwargs, extra_argnames)

        action(req, resp, self, kwargs, *action_args, **action_kwargs)
        responder(self, req, resp, **kwargs)

    return do_before
示例#17
0
    def add_error_handler(self, exception, handler=None):
        """Register a handler for one or more exception types.

        Error handlers may be registered for any exception type, including
        :class:`~.HTTPError` or :class:`~.HTTPStatus`. This feature
        provides a central location for logging and otherwise handling
        exceptions raised by responders, hooks, and middleware components.

        A handler can raise an instance of :class:`~.HTTPError` or
        :class:`~.HTTPStatus` to communicate information about the issue to
        the client.  Alternatively, a handler may modify `resp`
        directly.

        An error handler "matches" a raised exception if the exception is an
        instance of the corresponding exception type. If more than one error
        handler matches the raised exception, the framework will choose the
        most specific one, as determined by the method resolution order of the
        raised exception type. If multiple error handlers are registered for the
        *same* exception class, then the most recently-registered handler is
        used.

        For example, suppose we register error handlers as follows::

            app = App()
            app.add_error_handler(falcon.HTTPNotFound, custom_handle_not_found)
            app.add_error_handler(falcon.HTTPError, custom_handle_http_error)
            app.add_error_handler(Exception, custom_handle_uncaught_exception)
            app.add_error_handler(falcon.HTTPNotFound, custom_handle_404)

        If an instance of ``falcon.HTTPForbidden`` is raised, it will be
        handled by ``custom_handle_http_error()``. ``falcon.HTTPError`` is a
        superclass of ``falcon.HTTPForbidden`` and a subclass of ``Exception``,
        so it is the most specific exception type with a registered handler.

        If an instance of ``falcon.HTTPNotFound`` is raised, it will be handled
        by ``custom_handle_404()``, not by ``custom_handle_not_found()``, because
        ``custom_handle_404()`` was registered more recently.

        .. Note::

            By default, the framework installs three handlers, one for
            :class:`~.HTTPError`, one for :class:`~.HTTPStatus`, and one for
            the standard ``Exception`` type, which prevents passing uncaught
            exceptions to the WSGI server. These can be overridden by adding a
            custom error handler method for the exception type in question.

        Args:
            exception (type or iterable of types): When handling a request,
                whenever an error occurs that is an instance of the specified
                type(s), the associated handler will be called. Either a single
                type or an iterable of types may be specified.
            handler (callable): A function or callable object taking the form
                ``func(req, resp, ex, params)``.

                If not specified explicitly, the handler will default to
                ``exception.handle``, where ``exception`` is the error
                type specified above, and ``handle`` is a static method
                (i.e., decorated with ``@staticmethod``) that accepts
                the same params just described. For example::

                    class CustomException(CustomBaseException):

                        @staticmethod
                        def handle(req, resp, ex, params):
                            # TODO: Log the error
                            # Convert to an instance of falcon.HTTPError
                            raise falcon.HTTPError(falcon.HTTP_792)

                If an iterable of exception types is specified instead of
                a single type, the handler must be explicitly specified.

        .. versionchanged:: 3.0

            The error handler is now selected by the most-specific matching
            error class, rather than the most-recently registered matching error
            class.

        """
        def wrap_old_handler(old_handler):
            # NOTE(kgriffs): This branch *is* actually tested by
            #   test_error_handlers.test_handler_signature_shim_asgi() (as
            #   verified manually via pdb), but for some reason coverage
            #   tracking isn't picking it up.
            if iscoroutinefunction(old_handler):  # pragma: no cover

                @wraps(old_handler)
                async def handler_async(req, resp, ex, params):
                    await old_handler(ex, req, resp, params)

                return handler_async

            @wraps(old_handler)
            def handler(req, resp, ex, params):
                old_handler(ex, req, resp, params)

            return handler

        if handler is None:
            try:
                handler = exception.handle
            except AttributeError:
                raise AttributeError('handler must either be specified '
                                     'explicitly or defined as a static'
                                     'method named "handle" that is a '
                                     'member of the given exception class.')

        # TODO(vytas): Remove this shimming in a future Falcon version.
        arg_names = tuple(misc.get_argnames(handler))
        if (arg_names[0:1] in (('e', ), ('err', ), ('error', ), ('ex', ),
                               ('exception', ))
                or arg_names[1:3] in (('req', 'resp'),
                                      ('request', 'response'))):
            handler = wrap_old_handler(handler)

        try:
            exception_tuple = tuple(exception)
        except TypeError:
            exception_tuple = (exception, )

        for exc in exception_tuple:
            if not issubclass(exc, BaseException):
                raise TypeError('"exception" must be an exception type.')

            self._error_handlers[exc] = handler
示例#18
0
文件: api.py 项目: falconry/falcon
    def add_error_handler(self, exception, handler=None):
        """Register a handler for one or more exception types.

        Error handlers may be registered for any exception type, including
        :class:`~.HTTPError` or :class:`~.HTTPStatus`. This feature
        provides a central location for logging and otherwise handling
        exceptions raised by responders, hooks, and middleware components.

        A handler can raise an instance of :class:`~.HTTPError` or
        :class:`~.HTTPStatus` to communicate information about the issue to
        the client.  Alternatively, a handler may modify `resp`
        directly.

        Error handlers are matched in LIFO order. In other words, when
        searching for an error handler to match a raised exception, and
        more than one handler matches the exception type, the framework
        will choose the one that was most recently registered.
        Therefore, more general error handlers (e.g., for the
        standard ``Exception`` type) should be added first, to avoid
        masking more specific handlers for subclassed types.

        .. Note::

            By default, the framework installs two handlers, one for
            :class:`~.HTTPError` and one for :class:`~.HTTPStatus`. These can
            be overridden by adding a custom error handler method for the
            exception type in question.

        Args:
            exception (type or iterable of types): When handling a request,
                whenever an error occurs that is an instance of the specified
                type(s), the associated handler will be called. Either a single
                type or an iterable of types may be specified.
            handler (callable): A function or callable object taking the form
                ``func(req, resp, ex, params)``.

                If not specified explicitly, the handler will default to
                ``exception.handle``, where ``exception`` is the error
                type specified above, and ``handle`` is a static method
                (i.e., decorated with @staticmethod) that accepts
                the same params just described. For example::

                    class CustomException(CustomBaseException):

                        @staticmethod
                        def handle(req, resp, ex, params):
                            # TODO: Log the error
                            # Convert to an instance of falcon.HTTPError
                            raise falcon.HTTPError(falcon.HTTP_792)

                If an iterable of exception types is specified instead of
                a single type, the handler must be explicitly specified.

        """
        def wrap_old_handler(old_handler):
            @wraps(old_handler)
            def handler(req, resp, ex, params):
                old_handler(ex, req, resp, params)
            return handler

        if handler is None:
            try:
                handler = exception.handle
            except AttributeError:
                raise AttributeError('handler must either be specified '
                                     'explicitly or defined as a static'
                                     'method named "handle" that is a '
                                     'member of the given exception class.')

        # TODO(vytas): Remove this shimming in a future Falcon version.
        arg_names = tuple(misc.get_argnames(handler))
        if (arg_names[0:1] in (('e',), ('err',), ('error',), ('ex',), ('exception',)) or
                arg_names[1:3] in (('req', 'resp'), ('request', 'response'))):
            handler = wrap_old_handler(handler)

        try:
            exception_tuple = tuple(exception)
        except TypeError:
            exception_tuple = (exception, )

        if all(issubclass(exc, BaseException) for exc in exception_tuple):
            # Insert at the head of the list in case we get duplicate
            # adds (will cause the most recently added one to win).
            if len(exception_tuple) == 1:
                # In this case, insert only the single exception type
                # (not a tuple), to avoid unnnecessary overhead in the
                # exception handling path.
                self._error_handlers.insert(0, (exception_tuple[0], handler))
            else:
                self._error_handlers.insert(0, (exception_tuple, handler))
        else:
            raise TypeError('"exception" must be an exception type.')
示例#19
0
文件: api.py 项目: sair770/falcon
    def add_error_handler(self, exception, handler=None):
        """Register a handler for one or more exception types.

        Error handlers may be registered for any exception type, including
        :class:`~.HTTPError` or :class:`~.HTTPStatus`. This feature
        provides a central location for logging and otherwise handling
        exceptions raised by responders, hooks, and middleware components.

        A handler can raise an instance of :class:`~.HTTPError` or
        :class:`~.HTTPStatus` to communicate information about the issue to
        the client.  Alternatively, a handler may modify `resp`
        directly.

        Error handlers are matched in LIFO order. In other words, when
        searching for an error handler to match a raised exception, and
        more than one handler matches the exception type, the framework
        will choose the one that was most recently registered.
        Therefore, more general error handlers (e.g., for the
        standard ``Exception`` type) should be added first, to avoid
        masking more specific handlers for subclassed types. For example::

            app = falcon.API()
            app.add_error_handler(Exception, custom_handle_uncaught_exception)
            app.add_error_handler(falcon.HTTPError, custom_handle_http_error)
            app.add_error_handler(CustomException)

        .. Note::

            By default, the framework installs three handlers, one for
            :class:`~.HTTPError`, one for :class:`~.HTTPStatus`, and one for
            the standard ``Exception`` type, which prevents passing uncaught
            exceptions to the WSGI server. These can be overridden by adding a
            custom error handler method for the exception type in question.

            Be aware that both :class:`~.HTTPError` and :class:`~.HTTPStatus`
            inherit from the standard ``Exception`` type, so overriding the
            default ``Exception`` handler will override all three default
            handlers, due to the LIFO ordering of handler-matching.

        Args:
            exception (type or iterable of types): When handling a request,
                whenever an error occurs that is an instance of the specified
                type(s), the associated handler will be called. Either a single
                type or an iterable of types may be specified.
            handler (callable): A function or callable object taking the form
                ``func(req, resp, ex, params)``.

                If not specified explicitly, the handler will default to
                ``exception.handle``, where ``exception`` is the error
                type specified above, and ``handle`` is a static method
                (i.e., decorated with ``@staticmethod``) that accepts
                the same params just described. For example::

                    class CustomException(CustomBaseException):

                        @staticmethod
                        def handle(req, resp, ex, params):
                            # TODO: Log the error
                            # Convert to an instance of falcon.HTTPError
                            raise falcon.HTTPError(falcon.HTTP_792)

                If an iterable of exception types is specified instead of
                a single type, the handler must be explicitly specified.

        """
        def wrap_old_handler(old_handler):
            @wraps(old_handler)
            def handler(req, resp, ex, params):
                old_handler(ex, req, resp, params)

            return handler

        if handler is None:
            try:
                handler = exception.handle
            except AttributeError:
                raise AttributeError('handler must either be specified '
                                     'explicitly or defined as a static'
                                     'method named "handle" that is a '
                                     'member of the given exception class.')

        # TODO(vytas): Remove this shimming in a future Falcon version.
        arg_names = tuple(misc.get_argnames(handler))
        if (arg_names[0:1] in (('e', ), ('err', ), ('error', ), ('ex', ),
                               ('exception', ))
                or arg_names[1:3] in (('req', 'resp'),
                                      ('request', 'response'))):
            handler = wrap_old_handler(handler)

        try:
            exception_tuple = tuple(exception)
        except TypeError:
            exception_tuple = (exception, )

        if all(issubclass(exc, BaseException) for exc in exception_tuple):
            # Insert at the head of the list in case we get duplicate
            # adds (will cause the most recently added one to win).
            if len(exception_tuple) == 1:
                # In this case, insert only the single exception type
                # (not a tuple), to avoid unnnecessary overhead in the
                # exception handling path.
                self._error_handlers.insert(0, (exception_tuple[0], handler))
            else:
                self._error_handlers.insert(0, (exception_tuple, handler))
        else:
            raise TypeError('"exception" must be an exception type.')