Source code for litestar_email.backends

from functools import lru_cache
from importlib import import_module
from inspect import signature
from typing import TYPE_CHECKING, Any

from litestar_email.backends.base import BaseEmailBackend

if TYPE_CHECKING:
    from litestar_email.config import BackendConfig, EmailConfig

__all__ = (
    "BaseEmailBackend",
    "ConsoleBackend",
    "InMemoryBackend",
    "MailgunBackend",
    "ResendBackend",
    "SMTPBackend",
    "SendGridBackend",
    "get_backend",
    "get_backend_class",
    "list_backends",
    "register_backend",
)

# Global registry of backend short names to classes
_backend_registry: dict[str, type[BaseEmailBackend]] = {}


[docs] def register_backend(name: str) -> "type[BaseEmailBackend]": """Decorator to register a backend class with a short name. Args: name: The short name for the backend (e.g., "console", "smtp"). Returns: A decorator that registers the backend class. Example: Registering a custom backend:: @register_backend("mybackend") class MyBackend(BaseEmailBackend): async def send_messages(self, messages): ... """ def decorator(cls: type[BaseEmailBackend]) -> type[BaseEmailBackend]: _backend_registry[name] = cls return cls return decorator # type: ignore[return-value]
@lru_cache(maxsize=1) def _register_builtins() -> None: """Register built-in backends. Called lazily to avoid import cycles. All backends can be imported regardless of whether their dependencies are installed. They will raise MissingDependencyError when instantiated if the required packages are not available. """ from litestar_email.backends.console import ConsoleBackend from litestar_email.backends.mailgun import MailgunBackend from litestar_email.backends.memory import InMemoryBackend from litestar_email.backends.resend import ResendBackend from litestar_email.backends.sendgrid import SendGridBackend from litestar_email.backends.smtp import SMTPBackend _backend_registry.setdefault("console", ConsoleBackend) _backend_registry.setdefault("memory", InMemoryBackend) _backend_registry.setdefault("smtp", SMTPBackend) _backend_registry.setdefault("resend", ResendBackend) _backend_registry.setdefault("sendgrid", SendGridBackend) _backend_registry.setdefault("mailgun", MailgunBackend)
[docs] def get_backend_class(backend_path: str) -> type[BaseEmailBackend]: """Get a backend class by short name or full import path. Args: backend_path: Either a registered short name (e.g., "console", "memory") or a full Python import path (e.g., "myapp.backends.CustomBackend"). Returns: The backend class. Raises: ValueError: If the backend cannot be found. Example: Getting a backend class:: # By short name cls = get_backend_class("console") # By full path cls = get_backend_class("litestar_email.backends.console.ConsoleBackend") """ _register_builtins() # Check registry first if backend_path in _backend_registry: return _backend_registry[backend_path] # Try to import as a full path if "." not in backend_path: msg = f"Unknown backend: {backend_path!r}. Available: {list(_backend_registry.keys())}" raise ValueError(msg) module_path, class_name = backend_path.rsplit(".", 1) module = import_module(module_path) return getattr(module, class_name) # type: ignore[no-any-return]
def _get_backend_name_for_config(backend_config: "BackendConfig") -> str: """Map a backend config object to its backend name. Args: backend_config: A backend configuration object. Returns: The backend name string. Raises: ValueError: If the config type is not recognized. """ from litestar_email.config import MailgunConfig, ResendConfig, SendGridConfig, SMTPConfig config_to_backend: dict[type, str] = { SMTPConfig: "smtp", ResendConfig: "resend", SendGridConfig: "sendgrid", MailgunConfig: "mailgun", } for config_type, backend_name in config_to_backend.items(): if isinstance(backend_config, config_type): return backend_name msg = f"Unknown backend config type: {type(backend_config).__name__}" raise ValueError(msg)
[docs] def get_backend( backend: "str | BackendConfig" = "console", fail_silently: bool | None = None, config: "EmailConfig | None" = None, ) -> BaseEmailBackend: """Get an instantiated backend by name or config object. Args: backend: Either a backend short name (e.g., "console", "smtp"), a full import path, or a backend config object (SMTPConfig, ResendConfig, etc.). fail_silently: Whether the backend should suppress exceptions. If None, uses config.fail_silently when config is provided. config: Optional EmailConfig to extract common settings from (from_email, from_name, fail_silently). Returns: An instantiated backend. Note: May raise ``MissingDependencyError`` if the backend requires a package that is not installed. Example: Basic usage:: backend = get_backend("console") async with backend: await backend.send_messages([message]) With config object:: backend = get_backend(SMTPConfig(host="localhost", port=1025)) From EmailConfig:: config = EmailConfig( backend=ResendConfig(api_key="re_xxx..."), fail_silently=True, ) backend = get_backend(config.backend, config=config) """ # Determine backend class and config from the backend argument backend_config: Any = None if isinstance(backend, str): backend_class = get_backend_class(backend) else: # backend is a config object - determine the backend class from type backend_name = _get_backend_name_for_config(backend) backend_class = get_backend_class(backend_name) backend_config = backend # Extract common settings from EmailConfig if provided default_from_email: str | None = None default_from_name: str | None = None resolved_fail_silently = fail_silently if fail_silently is not None else False if config is not None: default_from_email = config.from_email default_from_name = config.from_name if fail_silently is None: resolved_fail_silently = config.fail_silently backend_kwargs: dict[str, Any] = { "fail_silently": resolved_fail_silently, "default_from_email": default_from_email, "default_from_name": default_from_name, } # Pass config to backend if it was found if backend_config is not None: backend_kwargs["config"] = backend_config init_signature = signature(backend_class.__init__) accepts_kwargs = any(param.kind == param.VAR_KEYWORD for param in init_signature.parameters.values()) if not accepts_kwargs: backend_kwargs = {key: value for key, value in backend_kwargs.items() if key in init_signature.parameters} return backend_class(**backend_kwargs)
[docs] def list_backends() -> list[str]: """Return a list of registered backend short names. Returns: A list of backend names that can be used with get_backend(). """ _register_builtins() return list(_backend_registry.keys())
# Re-export backend classes for convenience # Backends can be imported regardless of whether dependencies are installed. # They raise MissingDependencyError on instantiation if dependencies are missing. from litestar_email.backends.console import ConsoleBackend from litestar_email.backends.mailgun import MailgunBackend from litestar_email.backends.memory import InMemoryBackend from litestar_email.backends.resend import ResendBackend from litestar_email.backends.sendgrid import SendGridBackend from litestar_email.backends.smtp import SMTPBackend