chore: rename project coven → hack-house ⛧

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>
This commit is contained in:
leetcrypt
2026-05-30 13:29:14 -07:00
parent 82a04f3e12
commit bb1d662ee1
2730 changed files with 712933 additions and 46 deletions
@@ -0,0 +1,68 @@
import logging
import os
import sys
from enum import Enum
from typing import TYPE_CHECKING
from sanic.helpers import is_atty
# Python 3.11 changed the way Enum formatting works for mixed-in types.
if sys.version_info < (3, 11, 0):
class StrEnum(str, Enum):
pass
else:
if not TYPE_CHECKING:
from enum import StrEnum
COLORIZE = is_atty() and not os.environ.get("SANIC_NO_COLOR")
class Colors(StrEnum): # no cov
"""
Colors for log messages. If the output is not a TTY, the colors will be
disabled.
Can be used like this:
.. code-block:: python
from sanic.log import logger, Colors
logger.info(f"{Colors.GREEN}This is a green message{Colors.END}")
Attributes:
END: Reset the color
BOLD: Bold text
BLUE: Blue text
GREEN: Green text
PURPLE: Purple text
RED: Red text
SANIC: Sanic pink
YELLOW: Yellow text
GREY: Grey text
"""
END = "\033[0m" if COLORIZE else ""
BOLD = "\033[1m" if COLORIZE else ""
BLUE = "\033[34m" if COLORIZE else ""
GREEN = "\033[32m" if COLORIZE else ""
PURPLE = "\033[35m" if COLORIZE else ""
CYAN = "\033[36m" if COLORIZE else ""
RED = "\033[31m" if COLORIZE else ""
YELLOW = "\033[33m" if COLORIZE else ""
GREY = "\033[38;5;240m" if COLORIZE else ""
SANIC = "\033[38;2;255;13;104m" if COLORIZE else ""
LEVEL_COLORS = {
logging.DEBUG: Colors.BLUE,
logging.WARNING: Colors.YELLOW,
logging.ERROR: Colors.RED,
logging.CRITICAL: Colors.RED + Colors.BOLD,
}
@@ -0,0 +1,60 @@
import sys
from typing import Any
LOGGING_CONFIG_DEFAULTS: dict[str, Any] = dict( # no cov
version=1,
disable_existing_loggers=False,
loggers={
"sanic.root": {"level": "INFO", "handlers": ["console"]},
"sanic.error": {
"level": "INFO",
"handlers": ["error_console"],
"propagate": True,
"qualname": "sanic.error",
},
"sanic.access": {
"level": "INFO",
"handlers": ["access_console"],
"propagate": True,
"qualname": "sanic.access",
},
"sanic.server": {
"level": "INFO",
"handlers": ["console"],
"propagate": True,
"qualname": "sanic.server",
},
"sanic.websockets": {
"level": "INFO",
"handlers": ["console"],
"propagate": True,
"qualname": "sanic.websockets",
},
},
handlers={
"console": {
"class": "logging.StreamHandler",
"formatter": "generic",
"stream": sys.stdout,
},
"error_console": {
"class": "logging.StreamHandler",
"formatter": "generic",
"stream": sys.stderr,
},
"access_console": {
"class": "logging.StreamHandler",
"formatter": "access",
"stream": sys.stdout,
},
},
formatters={
"generic": {"class": "sanic.logging.formatter.AutoFormatter"},
"access": {"class": "sanic.logging.formatter.AutoAccessFormatter"},
},
)
"""
Default logging configuration
"""
@@ -0,0 +1,33 @@
from warnings import warn
from sanic.helpers import is_atty
from sanic.logging.color import Colors
def deprecation(message: str, version: float): # no cov
"""
Add a deprecation notice
Example when a feature is being removed. In this case, version
should be AT LEAST next version + 2
.. code-block:: python
deprecation("Helpful message", 99.9)
Example when a feature is deprecated but not being removed:
.. code-block:: python
deprecation("Helpful message", 0)
Args:
message (str): Deprecation message
version (float): Version when the feature will be removed
"""
version_display = f" v{version}" if version else ""
version_info = f"[DEPRECATION{version_display}] "
if is_atty():
version_info = f"{Colors.RED}{version_info}"
message = f"{Colors.YELLOW}{message}{Colors.END}"
warn(version_info + message, DeprecationWarning)
@@ -0,0 +1,13 @@
import logging
class VerbosityFilter(logging.Filter):
"""
Filter log records based on verbosity level.
"""
verbosity: int = 0
def filter(self, record: logging.LogRecord) -> bool:
verbosity = getattr(record, "verbosity", 0)
return verbosity <= self.verbosity
@@ -0,0 +1,387 @@
from __future__ import annotations
import logging
import os
import re
from sanic.helpers import is_atty, json_dumps
from sanic.logging.color import LEVEL_COLORS
from sanic.logging.color import Colors as c
CONTROL_RE = re.compile(r"\033\[[0-9;]*\w")
CONTROL_LIMIT_IDENT = "\033[1000D\033[{limit}C"
CONTROL_LIMIT_START = "\033[1000D\033[{start}C\033[K"
CONTROL_LIMIT_END = "\033[1000C\033[{right}D\033[K"
EXCEPTION_LINE_RE = re.compile(r"^(?P<exc>.*?): (?P<message>.*)$")
FILE_LINE_RE = re.compile(
r"File \"(?P<path>.*?)\", line (?P<line_num>\d+), in (?P<location>.*)"
)
DEFAULT_FIELDS = set(
logging.LogRecord("", 0, "", 0, "", (), None).__dict__.keys()
) | {
"ident",
"message",
"asctime",
"right",
}
class AutoFormatter(logging.Formatter):
"""
Automatically sets up the formatter based on the environment.
It will switch between the Debug and Production formatters based upon
how the environment is set up. Additionally, it will automatically
detect if the output is a TTY and colorize the output accordingly.
"""
SETUP = False
ATTY = is_atty()
NO_COLOR = os.environ.get("SANIC_NO_COLOR", "false").lower() == "true"
LOG_EXTRA = os.environ.get("SANIC_LOG_EXTRA", "true").lower() == "true"
IDENT = os.environ.get("SANIC_WORKER_IDENTIFIER", "Main ") or "Main "
DATE_FORMAT = "%Y-%m-%d %H:%M:%S %z"
IDENT_LIMIT = 5
MESSAGE_START = 42
PREFIX_FORMAT = (
f"{c.GREY}%(ident)s{{limit}} %(asctime)s {c.END}"
"%(levelname)s: {start}"
)
MESSAGE_FORMAT = "%(message)s"
def __init__(self, *args) -> None:
args_list = list(args)
if not args:
args_list.append(self._make_format())
elif args and not args[0]:
args_list[0] = self._make_format()
if len(args_list) < 2:
args_list.append(self.DATE_FORMAT)
elif not args[1]:
args_list[1] = self.DATE_FORMAT
super().__init__(*args_list)
def format(self, record: logging.LogRecord) -> str:
record.ident = self.IDENT
self._set_levelname(record)
output = super().format(record)
if self.LOG_EXTRA:
output += self._log_extra(record)
return output
def _set_levelname(self, record: logging.LogRecord) -> None:
if (
self.ATTY
and not self.NO_COLOR
and (color := LEVEL_COLORS.get(record.levelno))
):
record.levelname = f"{color}{record.levelname}{c.END}"
def _make_format(self) -> str:
limit = CONTROL_LIMIT_IDENT.format(limit=self.IDENT_LIMIT)
start = CONTROL_LIMIT_START.format(start=self.MESSAGE_START)
base_format = self.PREFIX_FORMAT + self.MESSAGE_FORMAT
fmt = base_format.format(limit=limit, start=start)
if not self.ATTY or self.NO_COLOR:
return CONTROL_RE.sub("", fmt)
return fmt
def _log_extra(self, record: logging.LogRecord, indent: int = 0) -> str:
extra_lines = [""]
for key, value in record.__dict__.items():
if key not in DEFAULT_FIELDS:
extra_lines.append(self._format_key_value(key, value, indent))
return "\n".join(extra_lines)
def _format_key_value(self, key, value, indent):
indentation = " " * indent
template = (
f"{indentation} {{c.YELLOW}}{{key}}{{c.END}}={{value}}"
if self.ATTY and not self.NO_COLOR
else f"{indentation}{{key}}={{value}}"
)
if isinstance(value, dict):
nested_lines = [template.format(c=c, key=key, value="")]
for nested_key, nested_value in value.items():
nested_lines.append(
self._format_key_value(
nested_key, nested_value, indent + 2
)
)
return "\n".join(nested_lines)
else:
return template.format(c=c, key=key, value=value)
class DebugFormatter(AutoFormatter):
"""
The DebugFormatter is used for development and debugging purposes.
It can be used directly, or it will be automatically selected if the
environment is set up for development and is using the AutoFormatter.
"""
IDENT_LIMIT = 5
MESSAGE_START = 23
DATE_FORMAT = "%H:%M:%S"
def _set_levelname(self, record: logging.LogRecord) -> None:
if len(record.levelname) > 5:
record.levelname = record.levelname[:4]
super()._set_levelname(record)
def formatException(self, ei): # no cov
orig = super().formatException(ei)
if not self.ATTY or self.NO_COLOR:
return orig
colored_traceback = []
lines = orig.splitlines()
for idx, line in enumerate(lines):
if line.startswith(" File"):
line = self._color_file_line(line)
elif line.startswith(" "):
line = self._color_code_line(line)
elif (
"Error" in line or "Exception" in line or len(lines) - 1 == idx
):
line = self._color_exception_line(line)
colored_traceback.append(line)
return "\n".join(colored_traceback)
def _color_exception_line(self, line: str) -> str: # no cov
match = EXCEPTION_LINE_RE.match(line)
if not match:
return line
exc = match.group("exc")
message = match.group("message")
return f"{c.SANIC}{c.BOLD}{exc}{c.END}: {c.BOLD}{message}{c.END}"
def _color_file_line(self, line: str) -> str: # no cov
match = FILE_LINE_RE.search(line)
if not match:
return line
path = match.group("path")
line_num = match.group("line_num")
location = match.group("location")
return (
f' File "{path}", line {c.CYAN}{c.BOLD}{line_num}{c.END}, '
f"in {c.BLUE}{c.BOLD}{location}{c.END}"
)
def _color_code_line(self, line: str) -> str: # no cov
return f"{c.YELLOW}{line}{c.END}"
class ProdFormatter(AutoFormatter):
"""
The ProdFormatter is used for production environments.
It can be used directly, or it will be automatically selected if the
environment is set up for production and is using the AutoFormatter.
"""
class LegacyFormatter(AutoFormatter):
"""
The LegacyFormatter is used if you want to use the old style of logging.
You can use it as follows, typically in conjunction with the
LegacyAccessFormatter:
.. code-block:: python
from sanic.log import LOGGING_CONFIG_DEFAULTS
LOGGING_CONFIG_DEFAULTS["formatters"] = {
"generic": {
"class": "sanic.logging.formatter.LegacyFormatter"
},
"access": {
"class": "sanic.logging.formatter.LegacyAccessFormatter"
},
}
"""
PREFIX_FORMAT = "%(asctime)s [%(process)s] [%(levelname)s] "
DATE_FORMAT = "[%Y-%m-%d %H:%M:%S %z]"
class AutoAccessFormatter(AutoFormatter):
MESSAGE_FORMAT = (
f"{c.PURPLE}%(host)s "
f"{c.BLUE + c.BOLD}%(request)s{c.END} "
f"%(right)s%(status)s %(byte)s {c.GREY}%(duration)s{c.END}"
)
def format(self, record: logging.LogRecord) -> str:
status = len(str(getattr(record, "status", "")))
byte = len(str(getattr(record, "byte", "")))
duration = len(str(getattr(record, "duration", "")))
record.right = (
CONTROL_LIMIT_END.format(right=status + byte + duration + 1)
if self.ATTY
else ""
)
return super().format(record)
def _set_levelname(self, record: logging.LogRecord) -> None:
if self.ATTY and record.levelno == logging.INFO:
record.levelname = f"{c.SANIC}ACCESS{c.END}"
class LegacyAccessFormatter(AutoAccessFormatter):
"""
The LegacyFormatter is used if you want to use the old style of logging.
You can use it as follows, typically in conjunction with the
LegacyFormatter:
.. code-block:: python
from sanic.log import LOGGING_CONFIG_DEFAULTS
LOGGING_CONFIG_DEFAULTS["formatters"] = {
"generic": {
"class": "sanic.logging.formatter.LegacyFormatter"
},
"access": {
"class": "sanic.logging.formatter.LegacyAccessFormatter"
},
}
"""
PREFIX_FORMAT = "%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: "
MESSAGE_FORMAT = "%(request)s %(message)s %(status)s %(byte)s"
class DebugAccessFormatter(AutoAccessFormatter):
IDENT_LIMIT = 5
MESSAGE_START = 23
DATE_FORMAT = "%H:%M:%S"
LOG_EXTRA = False
class ProdAccessFormatter(AutoAccessFormatter):
IDENT_LIMIT = 5
MESSAGE_START = 42
PREFIX_FORMAT = (
f"{c.GREY}%(ident)s{{limit}}|%(asctime)s{c.END} "
f"%(levelname)s: {{start}}"
)
MESSAGE_FORMAT = (
f"{c.PURPLE}%(host)s {c.BLUE + c.BOLD}"
f"%(request)s{c.END} "
f"%(right)s%(status)s %(byte)s {c.GREY}%(duration)s{c.END}"
)
LOG_EXTRA = False
class JSONFormatter(AutoFormatter):
"""
The JSONFormatter is used to output logs in JSON format.
This is useful for logging to a file or to a log aggregator that
understands JSON. It will output all the fields from the LogRecord
as well as the extra fields that are passed in.
You can use it as follows:
.. code-block:: python
from sanic.log import LOGGING_CONFIG_DEFAULTS
LOGGING_CONFIG_DEFAULTS["formatters"] = {
"generic": {
"class": "sanic.logging.formatter.JSONFormatter"
},
"access": {
"class": "sanic.logging.formatter.JSONFormatter"
},
}
"""
ATTY = False
NO_COLOR = True
FIELDS = [
"name",
"levelno",
"pathname",
"module",
"filename",
"lineno",
]
dumps = json_dumps
def format(self, record: logging.LogRecord) -> str:
return self.format_dict(self.to_dict(record))
def to_dict(self, record: logging.LogRecord) -> dict:
base = {field: getattr(record, field, None) for field in self.FIELDS}
extra = {
key: value
for key, value in record.__dict__.items()
if key not in DEFAULT_FIELDS
}
info = {}
if record.exc_info:
info["exc_info"] = self.formatException(record.exc_info)
if record.stack_info:
info["stack_info"] = self.formatStack(record.stack_info)
return {
"timestamp": self.formatTime(record, self.datefmt),
"level": record.levelname,
"message": record.getMessage(),
**base,
**info,
**extra,
}
def format_dict(self, record: dict) -> str:
return self.dumps(record)
class JSONAccessFormatter(JSONFormatter):
"""
The JSONAccessFormatter is used to output access logs in JSON format.
This is useful for logging to a file or to a log aggregator that
understands JSON. It will output all the fields from the LogRecord
as well as the extra fields that are passed in.
You can use it as follows:
.. code-block:: python
from sanic.log import LOGGING_CONFIG_DEFAULTS
LOGGING_CONFIG_DEFAULTS["formatters"] = {
"generic": {
"class": "sanic.logging.formatter.JSONFormatter"
},
"access": {
"class": "sanic.logging.formatter.JSONAccessFormatter"
},
}
"""
FIELDS = [
"host",
"request",
"status",
"byte",
"duration",
]
def to_dict(self, record: logging.LogRecord) -> dict:
base = {field: getattr(record, field, None) for field in self.FIELDS}
return {
"timestamp": self.formatTime(record, self.datefmt),
"level": record.levelname,
"message": record.getMessage(),
**base,
}
@@ -0,0 +1,37 @@
import logging
from sanic.logging.filter import VerbosityFilter
_verbosity_filter = VerbosityFilter()
logger = logging.getLogger("sanic.root") # no cov
"""
General Sanic logger
"""
logger.addFilter(_verbosity_filter)
error_logger = logging.getLogger("sanic.error") # no cov
"""
Logger used by Sanic for error logging
"""
error_logger.addFilter(_verbosity_filter)
access_logger = logging.getLogger("sanic.access") # no cov
"""
Logger used by Sanic for access logging
"""
access_logger.addFilter(_verbosity_filter)
server_logger = logging.getLogger("sanic.server") # no cov
"""
Logger used by Sanic for server related messages
"""
server_logger.addFilter(_verbosity_filter)
websockets_logger = logging.getLogger("sanic.websockets") # no cov
"""
Logger used by Sanic for websockets module and protocol related messages
"""
websockets_logger.addFilter(_verbosity_filter)
websockets_logger.setLevel(logging.WARNING) # Too noisy on debug/info
@@ -0,0 +1,61 @@
import logging
import os
from sanic.helpers import Default, _default
from sanic.log import (
access_logger,
error_logger,
logger,
server_logger,
websockets_logger,
)
from sanic.logging.formatter import (
AutoAccessFormatter,
AutoFormatter,
DebugAccessFormatter,
DebugFormatter,
ProdAccessFormatter,
ProdFormatter,
)
def setup_logging(
debug: bool,
no_color: bool = False,
log_extra: bool | Default = _default,
) -> None:
if AutoFormatter.SETUP:
return
if isinstance(log_extra, Default):
log_extra = debug
os.environ["SANIC_LOG_EXTRA"] = str(log_extra)
AutoFormatter.LOG_EXTRA = log_extra
if no_color:
os.environ["SANIC_NO_COLOR"] = str(no_color)
AutoFormatter.NO_COLOR = no_color
AutoFormatter.SETUP = True
for lggr in (logger, server_logger, error_logger, websockets_logger):
_auto_format(
lggr,
AutoFormatter,
DebugFormatter if debug else ProdFormatter,
)
_auto_format(
access_logger,
AutoAccessFormatter,
DebugAccessFormatter if debug else ProdAccessFormatter,
)
def _auto_format(
logger: logging.Logger,
auto_class: type[AutoFormatter],
formatter_class: type[AutoFormatter],
) -> None:
for handler in logger.handlers:
if type(handler.formatter) is auto_class:
handler.setFormatter(formatter_class())