Coverage for src/lexigram/notification/module.py: 69%

16 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-26 07:17 +0800

1"""NotificationModule — IoC module for lexigram-notification.""" 

2 

3from __future__ import annotations 

4 

5from typing import TYPE_CHECKING, Any 

6 

7from lexigram.contracts.notification.protocols import ( 

8 PushChannelProtocol, 

9 SMSChannelProtocol, 

10) 

11from lexigram.di.module import DynamicModule, Module, module 

12 

13if TYPE_CHECKING: 

14 from lexigram.notification.config import NotificationConfig 

15 

16 

17@module() 

18class NotificationModule(Module): 

19 """SMS and push notification integration with Named DI multi-backend support. 

20 

21 Registers :class:`~lexigram.contracts.notification.protocols.SMSChannelProtocol` 

22 and :class:`~lexigram.contracts.notification.protocols.PushChannelProtocol` for 

23 constructor injection. 

24 

25 Usage:: 

26 

27 from lexigram.notification.config import NotificationConfig 

28 from lexigram.notification.module import NotificationModule 

29 

30 @module( 

31 imports=[NotificationModule.configure(NotificationConfig(backends=[...]))] 

32 ) 

33 class AppModule(Module): 

34 pass 

35 

36 Named injection:: 

37 

38 class MyService: 

39 def __init__( 

40 self, 

41 sms: SMSChannelProtocol, # primary 

42 alerts_sms: Annotated[SMSChannelProtocol, Named("alerts")], # named 

43 push: PushChannelProtocol, # primary 

44 mobile_push: Annotated[PushChannelProtocol, Named("mobile")], # named 

45 ) -> None: ... 

46 """ 

47 

48 @classmethod 

49 def configure(cls, config: NotificationConfig | Any | None = None) -> DynamicModule: 

50 """Create a NotificationModule with explicit configuration. 

51 

52 Args: 

53 config: :class:`~lexigram.notification.config.NotificationConfig` or ``None`` 

54 to use defaults (reads from environment variables). 

55 

56 Returns: 

57 A :class:`~lexigram.di.module.DynamicModule` descriptor. 

58 """ 

59 from lexigram.notification.di.provider import NotificationProvider 

60 

61 return DynamicModule( 

62 module=cls, 

63 providers=[NotificationProvider(config=config)], 

64 exports=[SMSChannelProtocol, PushChannelProtocol], 

65 ) 

66 

67 @classmethod 

68 def stub(cls, config: Any = None) -> DynamicModule: 

69 """Return a NotificationModule for unit tests — no messages sent. 

70 

71 Uses an empty config (no backends configured) so all notifications 

72 are dropped and no external services are contacted. 

73 

74 Returns: 

75 A :class:`~lexigram.di.module.DynamicModule` descriptor. 

76 """ 

77 from lexigram.notification.config import NotificationConfig 

78 from lexigram.notification.di.provider import NotificationProvider 

79 

80 return DynamicModule( 

81 module=cls, 

82 providers=[NotificationProvider(config=NotificationConfig())], 

83 exports=[SMSChannelProtocol, PushChannelProtocol], 

84 ) 

85 

86 

87__all__ = ["NotificationModule"]