Rebrand the Rust client crate (coven/ → hh/, package+binary "hack-house"), README, CLI strings, and branch (coven → hack-house). Gitea repo renamed cmd-chat → hack-house to match. Crypto/server logic unchanged; selftest + golden-vector test still green, binary is now `hack-house`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
204 lines
7.1 KiB
Python
204 lines
7.1 KiB
Python
from __future__ import annotations
|
|
|
|
from sanic.errorpages import BaseRenderer, TextRenderer, exception_response
|
|
from sanic.exceptions import ServerError
|
|
from sanic.log import error_logger
|
|
from sanic.models.handler_types import RouteHandler
|
|
from sanic.request.types import Request
|
|
from sanic.response import text
|
|
from sanic.response.types import HTTPResponse
|
|
|
|
|
|
class ErrorHandler:
|
|
"""Process and handle all uncaught exceptions.
|
|
|
|
This error handling framework is built into the core that can be extended
|
|
by the developers to perform a wide range of tasks from recording the error
|
|
stats to reporting them to an external service that can be used for
|
|
realtime alerting system.
|
|
|
|
Args:
|
|
base (BaseRenderer): The renderer to use for the error pages.
|
|
""" # noqa: E501
|
|
|
|
def __init__(
|
|
self,
|
|
base: type[BaseRenderer] = TextRenderer,
|
|
):
|
|
self.cached_handlers: dict[
|
|
tuple[type[BaseException], str | None], RouteHandler | None
|
|
] = {}
|
|
self.debug = False
|
|
self.base = base
|
|
|
|
def _full_lookup(self, exception, route_name: str | None = None):
|
|
return self.lookup(exception, route_name)
|
|
|
|
def _add(
|
|
self,
|
|
key: tuple[type[BaseException], str | None],
|
|
handler: RouteHandler,
|
|
) -> None:
|
|
if key in self.cached_handlers:
|
|
exc, name = key
|
|
if name is None:
|
|
name = "__ALL_ROUTES__"
|
|
|
|
message = (
|
|
f"Duplicate exception handler definition on: route={name} "
|
|
f"and exception={exc}"
|
|
)
|
|
raise ServerError(message)
|
|
self.cached_handlers[key] = handler
|
|
|
|
def add(self, exception, handler, route_names: list[str] | None = None):
|
|
"""Add a new exception handler to an already existing handler object.
|
|
|
|
Args:
|
|
exception (sanic.exceptions.SanicException or Exception): Type
|
|
of exception that needs to be handled.
|
|
handler (function): Reference to the function that will
|
|
handle the exception.
|
|
|
|
Returns:
|
|
None
|
|
|
|
""" # noqa: E501
|
|
if route_names:
|
|
for route in route_names:
|
|
self._add((exception, route), handler)
|
|
else:
|
|
self._add((exception, None), handler)
|
|
|
|
def lookup(self, exception, route_name: str | None = None):
|
|
"""Lookup the existing instance of `ErrorHandler` and fetch the registered handler for a specific type of exception.
|
|
|
|
This method leverages a dict lookup to speedup the retrieval process.
|
|
|
|
Args:
|
|
exception (sanic.exceptions.SanicException or Exception): Type
|
|
of exception.
|
|
|
|
Returns:
|
|
Registered function if found, ``None`` otherwise.
|
|
|
|
""" # noqa: E501
|
|
exception_class = type(exception)
|
|
|
|
for name in (route_name, None):
|
|
exception_key = (exception_class, name)
|
|
handler = self.cached_handlers.get(exception_key)
|
|
if handler:
|
|
return handler
|
|
|
|
for name in (route_name, None):
|
|
for ancestor in type.mro(exception_class):
|
|
exception_key = (ancestor, name)
|
|
if exception_key in self.cached_handlers:
|
|
handler = self.cached_handlers[exception_key]
|
|
self.cached_handlers[(exception_class, route_name)] = (
|
|
handler
|
|
)
|
|
return handler
|
|
|
|
if ancestor is BaseException:
|
|
break
|
|
self.cached_handlers[(exception_class, route_name)] = None
|
|
handler = None
|
|
return handler
|
|
|
|
_lookup = _full_lookup
|
|
|
|
def response(self, request, exception):
|
|
"""Fetch and executes an exception handler and returns a response object.
|
|
|
|
Args:
|
|
request (sanic.request.Request): Instance of the request.
|
|
exception (sanic.exceptions.SanicException or Exception): Exception to handle.
|
|
|
|
Returns:
|
|
Wrap the return value obtained from the `default` function or the registered handler for that type of exception.
|
|
|
|
""" # noqa: E501
|
|
route_name = request.name if request else None
|
|
handler = self._lookup(exception, route_name)
|
|
response = None
|
|
try:
|
|
if handler:
|
|
response = handler(request, exception)
|
|
if response is None:
|
|
response = self.default(request, exception)
|
|
except Exception:
|
|
try:
|
|
url = repr(request.url)
|
|
except AttributeError: # no cov
|
|
url = "unknown"
|
|
response_message = (
|
|
'Exception raised in exception handler "%s" for uri: %s'
|
|
)
|
|
error_logger.exception(response_message, handler.__name__, url)
|
|
|
|
if self.debug:
|
|
return text(response_message % (handler.__name__, url), 500)
|
|
else:
|
|
return text("An error occurred while handling an error", 500)
|
|
return response
|
|
|
|
def default(self, request: Request, exception: Exception) -> HTTPResponse:
|
|
"""Provide a default behavior for the objects of ErrorHandler.
|
|
|
|
If a developer chooses to extend the ErrorHandler, they can
|
|
provide a custom implementation for this method to behave in a way
|
|
they see fit.
|
|
|
|
Args:
|
|
request (sanic.request.Request): Incoming request.
|
|
exception (sanic.exceptions.SanicException or Exception): Exception object.
|
|
|
|
Returns:
|
|
HTTPResponse: The response object.
|
|
|
|
Examples:
|
|
```python
|
|
class CustomErrorHandler(ErrorHandler):
|
|
def default(self, request: Request, exception: Exception) -> HTTPResponse:
|
|
# Custom logic for handling the exception and creating a response
|
|
custom_response = my_custom_logic(request, exception)
|
|
return custom_response
|
|
|
|
app = Sanic("MyApp", error_handler=CustomErrorHandler())
|
|
```
|
|
""" # noqa: E501
|
|
self.log(request, exception)
|
|
fallback = request.app.config.FALLBACK_ERROR_FORMAT
|
|
return exception_response(
|
|
request,
|
|
exception,
|
|
debug=self.debug,
|
|
base=self.base,
|
|
fallback=fallback,
|
|
)
|
|
|
|
@staticmethod
|
|
def log(request: Request, exception: Exception) -> None:
|
|
"""Logs information about an incoming request and the associated exception.
|
|
|
|
Args:
|
|
request (Request): The incoming request to be logged.
|
|
exception (Exception): The exception that occurred during the handling of the request.
|
|
|
|
Returns:
|
|
None
|
|
""" # noqa: E501
|
|
quiet = getattr(exception, "quiet", False)
|
|
noisy = getattr(request.app.config, "NOISY_EXCEPTIONS", False)
|
|
if quiet is False or noisy is True:
|
|
try:
|
|
url = repr(request.url)
|
|
except AttributeError: # no cov
|
|
url = "unknown"
|
|
|
|
error_logger.exception(
|
|
"Exception occurred while handling uri: %s", url
|
|
)
|