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>
225 lines
7.5 KiB
Python
225 lines
7.5 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
from collections.abc import Mapping
|
|
from types import SimpleNamespace
|
|
from typing import Any, Callable, Optional, Union
|
|
from warnings import warn
|
|
|
|
from sanic import Sanic, __version__
|
|
from sanic.config import DEFAULT_CONFIG
|
|
from sanic.exceptions import SanicException
|
|
from sanic.helpers import Default, _default
|
|
from sanic.log import logger
|
|
|
|
from sanic_ext.config import Config, add_fallback_config
|
|
from sanic_ext.extensions.base import Extension
|
|
from sanic_ext.extensions.health.extension import HealthExtension
|
|
from sanic_ext.extensions.http.extension import HTTPExtension
|
|
from sanic_ext.extensions.injection.extension import InjectionExtension
|
|
from sanic_ext.extensions.injection.registry import (
|
|
ConstantRegistry,
|
|
InjectionRegistry,
|
|
)
|
|
from sanic_ext.extensions.logging.extension import LoggingExtension
|
|
from sanic_ext.extensions.openapi.builders import SpecificationBuilder
|
|
from sanic_ext.extensions.openapi.extension import OpenAPIExtension
|
|
from sanic_ext.utils.string import camel_to_snake
|
|
from sanic_ext.utils.version import get_version
|
|
|
|
|
|
try:
|
|
from jinja2 import Environment
|
|
|
|
from sanic_ext.extensions.templating.engine import Templating
|
|
from sanic_ext.extensions.templating.extension import TemplatingExtension
|
|
|
|
TEMPLATING_ENABLED = True
|
|
except (ImportError, ModuleNotFoundError):
|
|
TEMPLATING_ENABLED = False
|
|
|
|
MIN_SUPPORT = (21, 3, 2)
|
|
|
|
|
|
class Extend:
|
|
_pre_registry: list[Union[type[Extension], Extension]] = []
|
|
|
|
if TEMPLATING_ENABLED:
|
|
environment: Environment
|
|
templating: Templating
|
|
|
|
def __init__(
|
|
self,
|
|
app: Sanic,
|
|
*,
|
|
extensions: Optional[list[Union[type[Extension], Extension]]] = None,
|
|
built_in_extensions: bool = True,
|
|
config: Optional[Union[Config, dict[str, Any]]] = None,
|
|
**kwargs,
|
|
) -> None:
|
|
"""
|
|
Ingress for instantiating sanic-ext
|
|
|
|
:param app: Sanic application
|
|
:type app: Sanic
|
|
"""
|
|
if not isinstance(app, Sanic):
|
|
raise SanicException(
|
|
f"Cannot apply SanicExt to {app.__class__.__name__}"
|
|
)
|
|
|
|
sanic_version = get_version(__version__)
|
|
|
|
if MIN_SUPPORT > sanic_version:
|
|
min_version = ".".join(map(str, MIN_SUPPORT))
|
|
raise SanicException(
|
|
f"Sanic Extensions only works with Sanic v{min_version} "
|
|
f"and above. It looks like you are running {__version__}."
|
|
)
|
|
|
|
self._injection_registry: Optional[InjectionRegistry] = None
|
|
self._constant_registry: Optional[ConstantRegistry] = None
|
|
self._openapi: Optional[SpecificationBuilder] = None
|
|
self.app = app
|
|
self.extensions: list[Extension] = []
|
|
self.sanic_version = sanic_version
|
|
app._ext = self
|
|
app.ctx._dependencies = SimpleNamespace()
|
|
|
|
if not isinstance(config, Config):
|
|
config = Config.from_dict(config or {})
|
|
self.config = add_fallback_config(app, config, **kwargs)
|
|
|
|
extensions = extensions or []
|
|
if built_in_extensions:
|
|
extensions.extend(
|
|
[
|
|
InjectionExtension,
|
|
OpenAPIExtension,
|
|
HTTPExtension,
|
|
HealthExtension,
|
|
LoggingExtension,
|
|
]
|
|
)
|
|
|
|
if TEMPLATING_ENABLED:
|
|
extensions.append(TemplatingExtension)
|
|
extensions.extend(Extend._pre_registry)
|
|
|
|
started = set()
|
|
for ext in extensions:
|
|
if ext in started:
|
|
continue
|
|
extension = Extension.create(ext, app, self.config)
|
|
extension._startup(self)
|
|
self.extensions.append(extension)
|
|
started.add(ext)
|
|
|
|
def _display(self):
|
|
if "SANIC_WORKER_IDENTIFIER" in os.environ:
|
|
return
|
|
init_logs = ["Sanic Extensions:"]
|
|
for extension in self.extensions:
|
|
label = extension.render_label()
|
|
if extension.included():
|
|
init_logs.append(f" > {extension.name} {label}")
|
|
|
|
list(map(logger.info, init_logs))
|
|
|
|
def injection(
|
|
self,
|
|
type: type,
|
|
constructor: Optional[Callable[..., Any]] = None,
|
|
) -> None:
|
|
warn(
|
|
"The 'ext.injection' method has been deprecated and will be "
|
|
"removed in v22.6. Please use 'ext.add_dependency' instead.",
|
|
DeprecationWarning,
|
|
)
|
|
self.add_dependency(type=type, constructor=constructor)
|
|
|
|
def add_dependency(
|
|
self,
|
|
type: type,
|
|
constructor: Optional[Callable[..., Any]] = None,
|
|
request_arg: Optional[str] = None,
|
|
) -> None:
|
|
"""
|
|
Add a dependency for injection
|
|
|
|
:param type: The type of the dependency
|
|
:type type: Type
|
|
:param constructor: A callable that will return an instance to be
|
|
injected, when ``False`` it will call the type, defaults to None
|
|
:type constructor: Optional[Callable[..., Any]], optional
|
|
:param request_arg: Explicitly state which argument in the
|
|
constructor (if any) should be a ``Request`` object, when set to
|
|
``None`` the constructor will be introspected to check the
|
|
type annotations looking for a request object. You should really
|
|
only use this if you **MUST** use a lambda explression, it is
|
|
otherwise better to use a properly type annotated constructor,
|
|
defaults to None
|
|
:type request_arg: Optional[str], optional
|
|
:raises SanicException: _description_
|
|
"""
|
|
if not self._injection_registry:
|
|
raise SanicException("Injection extension not enabled")
|
|
self._injection_registry.register(
|
|
type, constructor, request_arg=request_arg
|
|
)
|
|
|
|
def add_constant(self, name: str, value: Any, overwrite: bool = False):
|
|
if not self._constant_registry:
|
|
raise ValueError("Cannot add constant. No registry created.")
|
|
self._constant_registry.register(name, value, overwrite)
|
|
|
|
def load_constants(
|
|
self,
|
|
constants: Optional[Mapping[str, Any]] = None,
|
|
overwrite: Union[Default, bool] = _default,
|
|
):
|
|
if not constants:
|
|
constants = {
|
|
k: v
|
|
for k, v in self.app.config.items()
|
|
if k.isupper()
|
|
and k not in DEFAULT_CONFIG
|
|
and k not in self.config
|
|
and not k.startswith("_")
|
|
}
|
|
if isinstance(overwrite, Default):
|
|
overwrite = True
|
|
if isinstance(overwrite, Default):
|
|
overwrite = False
|
|
for name, value in constants.items():
|
|
self.add_constant(name, value, overwrite)
|
|
|
|
def dependency(self, obj: Any, name: Optional[str] = None) -> None:
|
|
if not name:
|
|
name = camel_to_snake(obj.__class__.__name__)
|
|
setattr(self.app.ctx._dependencies, name, obj)
|
|
|
|
def getter(*_):
|
|
return obj
|
|
|
|
self.add_dependency(obj.__class__, getter)
|
|
|
|
@property
|
|
def openapi(self) -> SpecificationBuilder:
|
|
if not self._openapi:
|
|
self._openapi = SpecificationBuilder()
|
|
|
|
return self._openapi
|
|
|
|
def template(self, template_name: str, **kwargs):
|
|
return self.templating.template(template_name, **kwargs)
|
|
|
|
@classmethod
|
|
def register(cls, extension: Union[type[Extension], Extension]) -> None:
|
|
cls._pre_registry.append(extension)
|
|
|
|
@classmethod
|
|
def reset(cls) -> None:
|
|
cls._pre_registry.clear()
|