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>
83 lines
2.1 KiB
Python
83 lines
2.1 KiB
Python
from __future__ import annotations
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import Any, Union
|
|
|
|
from sanic.app import Sanic
|
|
from sanic.exceptions import SanicException
|
|
|
|
from sanic_ext.config import Config
|
|
from sanic_ext.exceptions import InitError
|
|
|
|
|
|
class NoDuplicateDict(dict): # type: ignore
|
|
def __setitem__(self, key: Any, value: Any) -> None:
|
|
if key in self:
|
|
raise KeyError(f"Duplicate key: {key}")
|
|
return super().__setitem__(key, value)
|
|
|
|
|
|
class Extension(ABC):
|
|
_name_registry: dict[str, type[Extension]] = NoDuplicateDict()
|
|
_started: bool
|
|
name: str
|
|
app: Sanic
|
|
config: Config
|
|
|
|
def __init_subclass__(cls):
|
|
if not getattr(cls, "name", None) or not cls.name.isalpha():
|
|
raise InitError(
|
|
"Extensions must be named, and may only contain "
|
|
"alphabetic characters"
|
|
)
|
|
|
|
if cls.name in cls._name_registry:
|
|
raise InitError(f'Extension "{cls.name}" already exists')
|
|
|
|
cls._name_registry[cls.name] = cls
|
|
|
|
def _startup(self, bootstrap):
|
|
if self._started:
|
|
raise SanicException(
|
|
"Extension already started. Cannot start "
|
|
f"Extension:{self.name} multiple times."
|
|
)
|
|
self.startup(bootstrap)
|
|
self._started = True
|
|
|
|
@abstractmethod
|
|
def startup(self, bootstrap) -> None: ...
|
|
|
|
def label(self):
|
|
return ""
|
|
|
|
def render_label(self):
|
|
if not self.included:
|
|
return "~~disabled~~"
|
|
label = self.label()
|
|
if not label:
|
|
return ""
|
|
return f"[{label}]"
|
|
|
|
def included(self):
|
|
return True
|
|
|
|
@classmethod
|
|
def create(
|
|
cls,
|
|
extension: Union[type[Extension], Extension],
|
|
app: Sanic,
|
|
config: Config,
|
|
) -> Extension:
|
|
instance = (
|
|
extension if isinstance(extension, Extension) else extension()
|
|
)
|
|
instance.app = app
|
|
instance.config = config
|
|
instance._started = False
|
|
return instance
|
|
|
|
@classmethod
|
|
def reset(cls) -> None:
|
|
cls._name_registry.clear()
|