Coverage for src/lexigram/notification/di/inbox_provider.py: 100%
42 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"""InboxProvider — DI provider for the user inbox subsystem."""
3from __future__ import annotations
5from typing import TYPE_CHECKING
7from lexigram.contracts.core import HealthCheckResult, HealthStatus, ProviderPriority
8from lexigram.contracts.notification.inbox import InboxStoreProtocol
9from lexigram.di.provider import Provider
10from lexigram.logging import get_logger
11from lexigram.notification.config import InboxConfig
13if TYPE_CHECKING:
14 from lexigram.contracts.core.di import (
15 ContainerRegistrarProtocol,
16 ContainerResolverProtocol,
17 )
19logger = get_logger(__name__)
22class InboxProvider(Provider):
23 """Register the inbox store and service into the DI container.
25 Honors :class:`~lexigram.notification.config.InboxConfig.store_backend`:
27 - ``database`` — :class:`~lexigram.notification.inbox.database.DatabaseInboxStore`
28 backed by :class:`~lexigram.contracts.data.sql.database.DatabaseProviderProtocol`
29 (resolved lazily at first use).
30 - ``memory`` — :class:`~lexigram.notification.inbox.memory.InMemoryInboxStore`.
32 Also registers :class:`~lexigram.notification.inbox.service.InboxService`
33 for constructor injection.
35 Args:
36 config: Inbox configuration. When ``None`` the default
37 :class:`InboxConfig` (``store_backend="database"``) is used.
38 """
40 name = "inbox"
41 priority = ProviderPriority.INFRASTRUCTURE
43 def __init__(self, config: InboxConfig | None = None) -> None:
44 super().__init__()
45 self._config = config or InboxConfig()
46 self._store: InboxStoreProtocol | None = None
48 async def register(self, container: ContainerRegistrarProtocol) -> None:
49 """Bind inbox store and service into the container.
51 Args:
52 container: DI registrar received from the framework.
53 """
54 from lexigram.notification.inbox.service import InboxService
56 backend = self._config.store_backend
57 if backend == "database":
58 from lexigram.contracts.data.sql.database import (
59 DatabaseProviderProtocol,
60 )
61 from lexigram.notification.inbox.database import DatabaseInboxStore
63 async def _database_store_factory(
64 resolver: ContainerResolverProtocol,
65 ) -> DatabaseInboxStore:
66 db = await resolver.resolve(DatabaseProviderProtocol)
67 return DatabaseInboxStore(db=db)
69 container.singleton(
70 InboxStoreProtocol,
71 factory=_database_store_factory,
72 )
73 else:
74 from lexigram.notification.inbox.memory import InMemoryInboxStore
76 container.singleton(InboxStoreProtocol, InMemoryInboxStore)
78 container.singleton(InboxService, InboxService)
79 logger.info("inbox_registered", backend=backend)
81 async def boot(self, container: ContainerResolverProtocol) -> None:
82 """Resolve the inbox store for health checking.
84 Args:
85 container: DI resolver used to obtain the registered store.
86 """
87 from lexigram.notification.inbox.service import InboxService
89 self._store = await container.resolve(InboxStoreProtocol)
90 await container.resolve(InboxService)
91 logger.info("inbox_booted", backend=self._config.store_backend)
93 async def shutdown(self) -> None:
94 """Release store reference on shutdown."""
95 self._store = None
97 async def health_check(self, timeout: float = 5.0) -> HealthCheckResult:
98 """Delegate health check to the registered inbox store.
100 Returns degraded status before :meth:`boot` has been called.
102 Args:
103 timeout: Max seconds to wait for the store health probe.
105 Returns:
106 :class:`~lexigram.contracts.core.HealthCheckResult`.
107 """
108 if self._store is None:
109 return HealthCheckResult(
110 component="inbox",
111 status=HealthStatus.DEGRADED,
112 message="inbox store not initialized",
113 )
115 store_result = await self._store.health_check(timeout=timeout)
116 return HealthCheckResult(
117 component="inbox",
118 status=store_result.status,
119 message=store_result.message,
120 details=store_result.details,
121 error=store_result.error,
122 duration_ms=store_result.duration_ms,
123 )
126__all__ = ["InboxProvider"]