def add_sink(self, sink, prefix=r'/'): if not iscoroutinefunction(sink) and is_python_func(sink): if _should_wrap_non_coroutines(): sink = wrap_sync_to_async(sink) else: raise CompatibilityError( 'The sink method must be an awaitable coroutine function ' 'in order to be used safely with an ASGI app.') super().add_sink(sink, prefix=prefix)
def schedule(self, callback): """Schedule an async callback to run soon after sending the HTTP response. This method can be used to execute a background job after the response has been returned to the client. The callback is assumed to be an async coroutine function. It will be scheduled to run on the event loop as soon as possible. The callback will be invoked without arguments. Use :any:`functools.partial` to pass arguments to the callback as needed. Note: If an unhandled exception is raised while processing the request, the callback will not be scheduled to run. Note: When an SSE emitter has been set on the response, the callback will be scheduled before the first call to the emitter. Warning: Because coroutines run on the main request thread, care should be taken to ensure they are non-blocking. Long-running operations must use async libraries or delegate to an :class:`~concurrent.futures.Executor` pool to avoid blocking the processing of subsequent requests. Args: callback(object): An async coroutine function. The callback will be invoked without arguments. """ # NOTE(kgriffs): We also have to do the CoroWrapper check because # iscoroutine is less reliable under Python 3.6. if not iscoroutinefunction(callback): if iscoroutine(callback) or isinstance(callback, CoroWrapper): raise TypeError( 'The callback object appears to ' 'be a coroutine, rather than a coroutine function. Please ' 'pass the function itself, rather than the result obtained ' 'by calling the function. ' ) elif is_python_func(callback): # pragma: nocover raise TypeError('The callback must be a coroutine function.') # NOTE(kgriffs): The implicit "else" branch is actually covered # by tests running in a Cython environment, but we can't # detect it with the coverage tool. rc = (callback, True) if not self._registered_callbacks: self._registered_callbacks = [rc] else: self._registered_callbacks.append(rc)
def _require_coroutine_responders(self, method_map): for method, responder in method_map.items(): # NOTE(kgriffs): We don't simply wrap non-async functions # since they likely peform relatively long blocking # operations that need to be explicitly made non-blocking # by the developer; raising an error helps highlight this # issue. if not iscoroutinefunction(responder) and is_python_func(responder): if _should_wrap_non_coroutines(): def let(responder=responder): method_map[method] = wrap_sync_to_async(responder) let() else: msg = ( 'The {} responder must be a non-blocking ' 'async coroutine (i.e., defined using async def) to ' 'avoid blocking the main request thread.' ) msg = msg.format(responder) raise TypeError(msg)
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 coroutine function taking the form ``async 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 async 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. """ 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.') handler = _wrap_non_coroutine_unsafe(handler) # NOTE(kgriffs): iscoroutinefunction() always returns False # for cythonized functions. # # https://github.com/cython/cython/issues/2273 # https://bugs.python.org/issue38225 # if not iscoroutinefunction(handler) and is_python_func(handler): raise CompatibilityError( 'The handler must be an awaitable coroutine function in order ' 'to be used safely with an ASGI app.') 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. 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. When a generic unhandled exception is raised while handling a :ref:`WebSocket <ws>` connection, the default handler will close the connection with the standard close code ``1011`` (Internal Error). If your ASGI server does not support this code, the framework will use code ``3011`` instead; or you can customize it via the :attr:`~falcon.asgi.WebSocketOptions.error_close_code` property of :attr:`~.ws_options`. On the other hand, if an ``on_websocket()`` responder raises an instance of :class:`~falcon.HTTPError`, the default error handler will close the :ref:`WebSocket <ws>` connection with a framework close code derived by adding ``3000`` to the HTTP status code (e.g., ``3404``) 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. Keyword Args: handler (callable): A coroutine function taking the form:: async def func(req, resp, ex, params, ws=None): pass In the case of a WebSocket connection, the `resp` argument will be ``None``, while the `ws` keyword argument will receive the :class:`~falcon.asgi.WebSocket` object representing the connection. If the `handler` keyword argument is not provided to :meth:`~.add_error_handler`, 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 params just described. For example:: class CustomException(CustomBaseException): @staticmethod async def handle(req, resp, ex, params): # TODO: Log the error # Convert to an instance of falcon.HTTPError raise falcon.HTTPError(falcon.HTTP_792) Note, however, that if an iterable of exception types is specified instead of a single type, the handler must be explicitly specified using the `handler` keyword argument. """ 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.') # NOTE(vytas): Do not shoot ourselves in the foot in case error # handlers are our own cythonized code. if handler not in ( self._http_status_handler, self._http_error_handler, self._python_error_handler, ): handler = _wrap_non_coroutine_unsafe(handler) # NOTE(kgriffs): iscoroutinefunction() always returns False # for cythonized functions. # # https://github.com/cython/cython/issues/2273 # https://bugs.python.org/issue38225 # if not iscoroutinefunction(handler) and is_python_func(handler): raise CompatibilityError( 'The handler must be an awaitable coroutine function in order ' 'to be used safely with an ASGI app.') 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