Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-feedback/src/lexigram/ai/feedback/di/provider.py: 53%
70 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 07:19 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 07:19 +0800
1"""Feedback DI provider."""
3from __future__ import annotations
5import inspect
6from typing import TYPE_CHECKING
8from lexigram.ai.feedback.config import FeedbackConfig
9from lexigram.ai.feedback.processors.processor_registry import FeedbackProcessorRegistry
10from lexigram.ai.feedback.services.collector import FeedbackCollector
11from lexigram.ai.feedback.services.feedback_service import FeedbackService
12from lexigram.contracts.ai.feedback import FeedbackProtocol
13from lexigram.contracts.core.health import HealthCheckResult, HealthStatus
14from lexigram.contracts.core.provider import ProviderPriority
15from lexigram.di.provider import Provider
16from lexigram.logging import (
17 get_logger,
18)
20if TYPE_CHECKING:
21 from lexigram.ai.feedback.storage.protocols import FeedbackStoreProtocol
22 from lexigram.contracts.core.di import (
23 ContainerRegistrarProtocol,
24 ContainerResolverProtocol,
25 )
27logger = get_logger(__name__)
30class FeedbackProvider(Provider):
31 """Provider for AI Feedback.
33 Registers :class:`~lexigram.ai.feedback.collector.FeedbackCollector` and
34 :class:`~lexigram.ai.feedback.processor_registry.FeedbackProcessorRegistry`.
35 Wires a durable storage backend (DB-backed, optionally cached) into the
36 collector during :meth:`boot`.
37 """
39 name = "feedback"
40 priority = ProviderPriority.DOMAIN
41 config_key: str | None = "ai_feedback"
42 config_model: type | None = FeedbackConfig
44 def __init__(self, config: FeedbackConfig | dict | None = None) -> None:
45 super().__init__()
46 if isinstance(config, dict):
47 config = FeedbackConfig(**config)
48 self._requested_config = config
49 self._config = config or FeedbackConfig()
51 @classmethod
52 def from_config(cls, config: FeedbackConfig, **context: object) -> FeedbackProvider:
53 """Factory method for DI container setup."""
54 del context
55 return cls(config)
57 async def register(self, container: ContainerRegistrarProtocol) -> None:
58 """Register the feedback services."""
59 self._config = self._requested_config or (
60 self.config
61 if isinstance(getattr(self, "config", None), FeedbackConfig)
62 else self._config
63 )
64 container.singleton(FeedbackConfig, self._config)
66 if not self._config.enabled:
67 logger.info("feedback_disabled", reason="FeedbackConfig.enabled=False")
68 return
70 container.singleton(FeedbackProcessorRegistry)
71 container.singleton(FeedbackCollector)
72 container.singleton(FeedbackProtocol, FeedbackService)
74 logger.info("feedback_registered")
76 async def boot(self, container: ContainerResolverProtocol) -> None:
77 """Wire a durable storage backend into :class:`FeedbackCollector`."""
78 if not self._config.enabled:
79 return
81 from lexigram.ai.feedback.storage.cache import CachedFeedbackStore
82 from lexigram.ai.feedback.storage.database import DatabaseFeedbackStore
83 from lexigram.contracts.data import DatabaseProviderProtocol
84 from lexigram.contracts.infra.cache import CacheBackendProtocol
86 db_provider = await container.resolve_optional(DatabaseProviderProtocol)
87 if db_provider is None:
88 logger.debug(
89 "feedback_store_not_wired",
90 reason="DatabaseProviderProtocol unavailable",
91 )
92 return
94 store: FeedbackStoreProtocol = DatabaseFeedbackStore(db_provider) # type: ignore[assignment]
96 cache_backend = await container.resolve_optional(CacheBackendProtocol)
97 if cache_backend is not None:
98 store = CachedFeedbackStore(store, cache_backend)
99 logger.info("feedback_cached_store_wired")
100 else:
101 logger.info("feedback_database_store_wired")
103 collector_resolved = container.resolve(FeedbackCollector)
104 if inspect.isawaitable(collector_resolved):
105 collector = await collector_resolved
106 else:
107 collector = collector_resolved
108 collector.storage = store
110 service_resolved = container.resolve(FeedbackProtocol)
111 if inspect.isawaitable(service_resolved):
112 service = await service_resolved
113 else:
114 service = service_resolved
115 service._store = store
117 async def shutdown(self) -> None:
118 """Shutdown phase."""
119 logger.debug("feedback_shutdown")
121 async def health_check(self, timeout: float = 5.0) -> HealthCheckResult:
122 """Health check — always healthy (in-process domain provider).
124 No external backend to ping.
126 Args:
127 timeout: Ignored for in-process providers.
129 Returns:
130 Always HEALTHY — no external backend to ping.
131 """
132 return HealthCheckResult(
133 component=self.name,
134 status=HealthStatus.HEALTHY,
135 details={"status": "operational"},
136 )
139__all__ = ["FeedbackProvider"]