Release v0.1.2
This commit is contained in:
@@ -35,6 +35,8 @@ def discover_module_manifests(
|
||||
enabled = set(enabled_modules) if enabled_modules is not None else None
|
||||
manifests: list[ModuleManifest] = []
|
||||
for entry_point in iter_module_entry_points(group):
|
||||
if enabled is not None and entry_point.name not in enabled:
|
||||
continue
|
||||
manifest = _load_manifest(entry_point)
|
||||
if enabled is not None and manifest.id not in enabled:
|
||||
continue
|
||||
|
||||
@@ -104,6 +104,7 @@ class ResourceAclProvider(Protocol):
|
||||
TenantSummaryProvider = Callable[[object, str], Mapping[str, int]]
|
||||
DeleteVetoProvider = Callable[[object, str, str], None]
|
||||
RouteFactory = Callable[[ModuleContext], "APIRouter"]
|
||||
CapabilityFactory = Callable[[ModuleContext], object]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
@@ -122,4 +123,5 @@ class ModuleManifest:
|
||||
resource_acl_providers: tuple[ResourceAclProvider, ...] = ()
|
||||
tenant_summary_providers: tuple[TenantSummaryProvider, ...] = ()
|
||||
delete_veto_providers: Mapping[str, Sequence[DeleteVetoProvider]] = field(default_factory=dict)
|
||||
capability_factories: Mapping[str, CapabilityFactory] = field(default_factory=dict)
|
||||
|
||||
|
||||
13
src/govoplan_core/core/optional.py
Normal file
13
src/govoplan_core/core/optional.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def module_not_found_for(exc: ModuleNotFoundError, package: str) -> bool:
|
||||
"""Return true when a ModuleNotFoundError belongs to an optional package."""
|
||||
|
||||
missing = exc.name or ""
|
||||
return missing == package or missing.startswith(f"{package}.")
|
||||
|
||||
|
||||
def reraise_unless_missing_package(exc: ModuleNotFoundError, package: str) -> None:
|
||||
if not module_not_found_for(exc, package):
|
||||
raise exc
|
||||
@@ -6,7 +6,9 @@ from collections.abc import Iterable, Mapping
|
||||
from dataclasses import dataclass
|
||||
|
||||
from govoplan_core.core.modules import (
|
||||
CapabilityFactory,
|
||||
DeleteVetoProvider,
|
||||
ModuleContext,
|
||||
ModuleManifest,
|
||||
NavItem,
|
||||
PermissionDefinition,
|
||||
@@ -36,6 +38,9 @@ class PlatformRegistry:
|
||||
self._manifests: dict[str, ModuleManifest] = {}
|
||||
self._tenant_summary_providers: dict[str, TenantSummaryProvider] = {}
|
||||
self._delete_veto_providers: dict[str, list[DeleteVetoProvider]] = defaultdict(list)
|
||||
self._capability_factories: dict[str, CapabilityFactory] = {}
|
||||
self._capabilities: dict[str, object] = {}
|
||||
self._capability_context: ModuleContext | None = None
|
||||
|
||||
def register(self, manifest: ModuleManifest) -> ModuleManifest:
|
||||
if manifest.id in self._manifests:
|
||||
@@ -46,6 +51,8 @@ class PlatformRegistry:
|
||||
for resource_type, providers in manifest.delete_veto_providers.items():
|
||||
for provider in providers:
|
||||
self.register_delete_veto(resource_type, provider)
|
||||
for name, factory in manifest.capability_factories.items():
|
||||
self.register_capability_factory(manifest.id, name, factory)
|
||||
return manifest
|
||||
|
||||
def get(self, module_id: str) -> ModuleManifest | None:
|
||||
@@ -79,6 +86,36 @@ class PlatformRegistry:
|
||||
def resource_acl_providers(self) -> tuple[ResourceAclProvider, ...]:
|
||||
return tuple(provider for manifest in self.manifests() for provider in manifest.resource_acl_providers)
|
||||
|
||||
def configure_capability_context(self, context: ModuleContext) -> None:
|
||||
self._capability_context = context
|
||||
self._capabilities.clear()
|
||||
|
||||
def register_capability_factory(self, module_id: str, name: str, factory: CapabilityFactory) -> None:
|
||||
if name in self._capability_factories:
|
||||
raise RegistryError(f"Duplicate capability: {name}")
|
||||
self._capability_factories[name] = factory
|
||||
|
||||
def has_capability(self, name: str) -> bool:
|
||||
return name in self._capability_factories
|
||||
|
||||
def capability(self, name: str) -> object | None:
|
||||
if name in self._capabilities:
|
||||
return self._capabilities[name]
|
||||
factory = self._capability_factories.get(name)
|
||||
if factory is None:
|
||||
return None
|
||||
if self._capability_context is None:
|
||||
raise RegistryError(f"Capability context is not configured: {name}")
|
||||
capability = factory(self._capability_context)
|
||||
self._capabilities[name] = capability
|
||||
return capability
|
||||
|
||||
def require_capability(self, name: str) -> object:
|
||||
capability = self.capability(name)
|
||||
if capability is None:
|
||||
raise RegistryError(f"Required capability is not available: {name}")
|
||||
return capability
|
||||
|
||||
def register_tenant_summary_provider(self, module_id: str, provider: TenantSummaryProvider) -> None:
|
||||
self._tenant_summary_providers[module_id] = provider
|
||||
|
||||
|
||||
Reference in New Issue
Block a user