Coverage for src/lexigram/notification/mailer/module.py: 100%
19 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 02:32 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 02:32 +0800
1"""MailerModule — IoC module for email delivery."""
3from __future__ import annotations
5from typing import TYPE_CHECKING, Any
7from lexigram.contracts.mailer.protocols import MailerProtocol
8from lexigram.di.module import DynamicModule, Module, module
10if TYPE_CHECKING:
11 from lexigram.notification.config import MailerConfig
14@module(is_global=True)
15class MailerModule(Module):
16 """Email delivery integration with Named DI multi-backend support.
18 Registers :class:`~lexigram.contracts.mailer.protocols.MailerProtocol` for
19 constructor injection, supporting SMTP and SendGrid backends.
21 Usage::
23 from lexigram.notification.config import MailerConfig, NamedMailerConfig
24 from lexigram.notification.mailer.module import MailerModule
26 @module(
27 imports=[
28 MailerModule.configure(
29 MailerConfig(
30 backends=[
31 NamedMailerConfig(
32 name="transactional",
33 primary=True,
34 driver="sendgrid",
35 )
36 ]
37 )
38 )
39 ]
40 )
41 class AppModule(Module):
42 pass
44 Named injection::
46 class MyService:
47 def __init__(
48 self,
49 mailer: MailerProtocol, # primary
50 bulk: Annotated[MailerProtocol, Named("bulk")], # named
51 ) -> None: ...
52 """
54 @classmethod
55 def configure(cls, config: MailerConfig | Any | None = None) -> DynamicModule:
56 """Create a MailerModule with explicit configuration.
58 Args:
59 config: :class:`~lexigram.notification.config.MailerConfig` or ``None``
60 to use defaults (no backends).
62 Returns:
63 A :class:`~lexigram.di.module.DynamicModule` descriptor.
64 """
65 from lexigram.notification.di.mailer_provider import MailerProvider
67 return DynamicModule(
68 module=cls,
69 providers=[MailerProvider(config=config)],
70 exports=[MailerProtocol],
71 )
73 @classmethod
74 def stub(cls, config: MailerConfig | Any | None = None) -> DynamicModule:
75 """Return a MailerModule for unit tests — no emails sent.
77 Uses an empty config (no backends configured) so all mail operations
78 are dropped and no external services are contacted.
80 Args:
81 config: Optional :class:`~lexigram.notification.config.MailerConfig`
82 for test scenarios that need specific stubs.
84 Returns:
85 A :class:`~lexigram.di.module.DynamicModule` descriptor.
86 """
87 from lexigram.notification.config import MailerConfig as _MailerConfig
88 from lexigram.notification.di.mailer_provider import MailerProvider
90 stub_config = config or _MailerConfig()
91 if not stub_config.backends:
92 stub_config.console_fallback = False
93 return DynamicModule(
94 module=cls,
95 providers=[MailerProvider(config=stub_config)],
96 exports=[MailerProtocol],
97 )
100__all__ = ["MailerModule"]