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()