Coverage for src/lexigram/admin/di/mount/core.py: 89%
46 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 14:56 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 14:56 +0800
1"""Core mount phases: resource resolution and the admin settings service."""
3from __future__ import annotations
5from typing import TYPE_CHECKING, Any
7from lexigram.logging import get_logger
9if TYPE_CHECKING:
10 from lexigram.admin.di.bundle_provider import AdminProvider
11 from lexigram.admin.di.mount.context import MountContext
13_log = get_logger(__name__)
16class AdminMountCoreMixin:
17 """Mount phases that resolve admin resources and settings plumbing."""
19 async def _mount_resources(
20 self: AdminProvider, resolver: Any, ctx: MountContext
21 ) -> None:
22 """Resolve registered resource classes into named instances.
24 Failures are recorded in ``_mount_failures`` and re-raised only when
25 strict resource resolution is configured.
27 Args:
28 resolver: The DI resolver for resource resolution.
29 ctx: Mount pipeline state (``resources`` populated in place).
30 """
31 from lexigram.admin.rbac.inventory import PermissionInventoryService
33 for resource_cls in self._resources:
34 name = (
35 getattr(resource_cls, "name", None)
36 or resource_cls.__name__.replace("Resource", "").lower()
37 )
38 try:
39 ctx.resources[name] = await resolver.resolve(
40 resource_cls,
41 bypass_visibility=True,
42 )
43 except Exception as exc:
44 _log.error(
45 "admin.resource_resolution_failed",
46 resource=resource_cls.__name__,
47 error=str(exc),
48 strict=self._config.strict_resource_resolution,
49 )
50 self._mount_failures[f"resource:{resource_cls.__name__}"] = str(exc)
51 if self._config.strict_resource_resolution:
52 raise
54 # Populate the RBAC permission inventory from registered resources
55 try:
56 inventory = await resolver.resolve(
57 PermissionInventoryService,
58 bypass_visibility=True,
59 )
60 inventory.register_resources(ctx.resources.keys())
61 except Exception as exc: # noqa: BLE001 — discovery is best-effort
62 _log.warning(
63 "admin.rbac_inventory_discovery_failed",
64 error=str(exc),
65 )
67 async def _mount_settings_service(
68 self: AdminProvider, resolver: Any, ctx: MountContext
69 ) -> None:
70 """Create the shared settings service for runtime theme overrides.
72 Best-effort: without a database provider the service falls back to an
73 in-memory instance so the panel still mounts.
75 Args:
76 resolver: The DI resolver for settings dependencies.
77 ctx: Mount pipeline state (``settings_service`` populated).
78 """
79 admin_settings_service: Any = None
80 try:
81 from lexigram.admin.services.settings_service import (
82 AdminSettingsDbProvider,
83 AdminSettingsService,
84 )
85 from lexigram.contracts.data import DatabaseProviderProtocol
87 db_provider = await resolver.resolve(
88 DatabaseProviderProtocol,
89 bypass_visibility=True,
90 )
91 config_provider = AdminSettingsDbProvider(db=db_provider)
92 # Ensure the tenant_configs table is created eagerly at startup
93 try:
94 await config_provider._ensure_table()
95 except Exception:
96 _log.warning("admin.tenant_config_table_creation_failed")
97 admin_settings_service = AdminSettingsService(
98 config_provider=config_provider,
99 )
100 from lexigram.admin.settings.panel.registry import ConfigRegistry
101 from lexigram.admin.settings.store import TenantConfigStore
103 try:
104 registry = await resolver.resolve(
105 ConfigRegistry,
106 bypass_visibility=True,
107 )
108 registry.register_store("db", TenantConfigStore(admin_settings_service))
109 except Exception:
110 _log.warning("admin.settings_store_registration_failed")
111 except Exception:
112 try:
113 admin_settings_service = AdminSettingsService()
114 except Exception as exc: # noqa: BLE001 — fallback is best-effort
115 _log.warning("admin.settings_service_fallback_failed", reason=str(exc))
116 ctx.settings_service = admin_settings_service