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
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
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
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']
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
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
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
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
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
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
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
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
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
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
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.')
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.')