235 lines
6.8 KiB
Python
235 lines
6.8 KiB
Python
|
|
"""Independent version-2 policy for manual, portable graph, and live projections."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import hashlib
|
||
|
|
import json
|
||
|
|
from dataclasses import dataclass
|
||
|
|
from typing import Literal, cast
|
||
|
|
|
||
|
|
from .errors import DocForgeError
|
||
|
|
|
||
|
|
ManualProjectionMode = Literal["auto", "explicit", "disabled"]
|
||
|
|
PortableGraphProjectionMode = Literal["explicit", "disabled"]
|
||
|
|
LiveViewerProjectionMode = Literal["on-demand", "disabled"]
|
||
|
|
|
||
|
|
MANUAL_PROJECTION_MODES: tuple[ManualProjectionMode, ...] = (
|
||
|
|
"auto",
|
||
|
|
"explicit",
|
||
|
|
"disabled",
|
||
|
|
)
|
||
|
|
PORTABLE_GRAPH_PROJECTION_MODES: tuple[PortableGraphProjectionMode, ...] = (
|
||
|
|
"explicit",
|
||
|
|
"disabled",
|
||
|
|
)
|
||
|
|
LIVE_VIEWER_PROJECTION_MODES: tuple[LiveViewerProjectionMode, ...] = (
|
||
|
|
"on-demand",
|
||
|
|
"disabled",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _invalid_mode(field: str, value: object, allowed: tuple[str, ...]) -> DocForgeError:
|
||
|
|
return DocForgeError(
|
||
|
|
"invalid_projection_policy",
|
||
|
|
"Projection policy mode is unsupported",
|
||
|
|
projection=field,
|
||
|
|
mode=value,
|
||
|
|
allowed=list(allowed),
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _select_mode(
|
||
|
|
value: object | None,
|
||
|
|
*,
|
||
|
|
field: str,
|
||
|
|
default: str,
|
||
|
|
allowed: tuple[str, ...],
|
||
|
|
) -> str:
|
||
|
|
selected: object = default if value is None else value
|
||
|
|
if not isinstance(selected, str) or selected not in allowed:
|
||
|
|
raise _invalid_mode(field, selected, allowed)
|
||
|
|
return selected
|
||
|
|
|
||
|
|
|
||
|
|
def _require_boolean(field: str, value: object) -> bool:
|
||
|
|
if type(value) is not bool:
|
||
|
|
raise DocForgeError(
|
||
|
|
"invalid_projection_policy",
|
||
|
|
"Projection policy availability must be Boolean",
|
||
|
|
field=field,
|
||
|
|
)
|
||
|
|
return value
|
||
|
|
|
||
|
|
|
||
|
|
def _unavailable(field: str, mode: str, required: str) -> DocForgeError:
|
||
|
|
return DocForgeError(
|
||
|
|
"projection_policy_unavailable",
|
||
|
|
"Projection policy mode is unavailable",
|
||
|
|
projection=field,
|
||
|
|
mode=mode,
|
||
|
|
required=required,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def validate_manual_projection_mode(value: object) -> ManualProjectionMode:
|
||
|
|
"""Validate one direct manual-service policy selection."""
|
||
|
|
|
||
|
|
return cast(
|
||
|
|
ManualProjectionMode,
|
||
|
|
_select_mode(
|
||
|
|
value,
|
||
|
|
field="manual",
|
||
|
|
default="explicit",
|
||
|
|
allowed=MANUAL_PROJECTION_MODES,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def validate_portable_graph_projection_mode(
|
||
|
|
value: object,
|
||
|
|
) -> PortableGraphProjectionMode:
|
||
|
|
"""Validate one direct portable-graph service policy selection."""
|
||
|
|
|
||
|
|
return cast(
|
||
|
|
PortableGraphProjectionMode,
|
||
|
|
_select_mode(
|
||
|
|
value,
|
||
|
|
field="portable_graph",
|
||
|
|
default="explicit",
|
||
|
|
allowed=PORTABLE_GRAPH_PROJECTION_MODES,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def validate_live_viewer_projection_mode(value: object) -> LiveViewerProjectionMode:
|
||
|
|
"""Validate one direct live-viewer service policy selection."""
|
||
|
|
|
||
|
|
return cast(
|
||
|
|
LiveViewerProjectionMode,
|
||
|
|
_select_mode(
|
||
|
|
value,
|
||
|
|
field="live_viewer",
|
||
|
|
default="on-demand",
|
||
|
|
allowed=LIVE_VIEWER_PROJECTION_MODES,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(frozen=True)
|
||
|
|
class ProjectionPolicyV2:
|
||
|
|
"""One immutable policy for three independent projection consumers."""
|
||
|
|
|
||
|
|
manual: ManualProjectionMode
|
||
|
|
portable_graph: PortableGraphProjectionMode
|
||
|
|
live_viewer: LiveViewerProjectionMode
|
||
|
|
|
||
|
|
def __post_init__(self) -> None:
|
||
|
|
if self.manual not in MANUAL_PROJECTION_MODES:
|
||
|
|
raise _invalid_mode("manual", self.manual, MANUAL_PROJECTION_MODES)
|
||
|
|
if self.portable_graph not in PORTABLE_GRAPH_PROJECTION_MODES:
|
||
|
|
raise _invalid_mode(
|
||
|
|
"portable_graph",
|
||
|
|
self.portable_graph,
|
||
|
|
PORTABLE_GRAPH_PROJECTION_MODES,
|
||
|
|
)
|
||
|
|
if self.live_viewer not in LIVE_VIEWER_PROJECTION_MODES:
|
||
|
|
raise _invalid_mode(
|
||
|
|
"live_viewer",
|
||
|
|
self.live_viewer,
|
||
|
|
LIVE_VIEWER_PROJECTION_MODES,
|
||
|
|
)
|
||
|
|
|
||
|
|
def as_dict(self) -> dict[str, object]:
|
||
|
|
return {
|
||
|
|
"schema_version": 2,
|
||
|
|
"manual": self.manual,
|
||
|
|
"portable_graph": self.portable_graph,
|
||
|
|
"live_viewer": self.live_viewer,
|
||
|
|
}
|
||
|
|
|
||
|
|
@property
|
||
|
|
def policy_hash(self) -> str:
|
||
|
|
raw = json.dumps(
|
||
|
|
self.as_dict(),
|
||
|
|
sort_keys=True,
|
||
|
|
separators=(",", ":"),
|
||
|
|
ensure_ascii=False,
|
||
|
|
).encode("utf-8")
|
||
|
|
return hashlib.sha256(raw).hexdigest()
|
||
|
|
|
||
|
|
|
||
|
|
def compose_projection_policy(
|
||
|
|
*,
|
||
|
|
manual: str | None = None,
|
||
|
|
portable_graph: str | None = None,
|
||
|
|
live_viewer: str | None = None,
|
||
|
|
manual_configured: bool,
|
||
|
|
portable_graph_configured: bool,
|
||
|
|
application_enabled: bool,
|
||
|
|
live_viewer_available: bool = True,
|
||
|
|
) -> ProjectionPolicyV2:
|
||
|
|
"""Compose compatible defaults with explicit availability-checked selections."""
|
||
|
|
|
||
|
|
manual_available = _require_boolean("manual_configured", manual_configured)
|
||
|
|
graph_available = _require_boolean(
|
||
|
|
"portable_graph_configured",
|
||
|
|
portable_graph_configured,
|
||
|
|
)
|
||
|
|
application_available = _require_boolean("application_enabled", application_enabled)
|
||
|
|
viewer_available = _require_boolean("live_viewer_available", live_viewer_available)
|
||
|
|
|
||
|
|
default_manual = (
|
||
|
|
"auto"
|
||
|
|
if manual_available and application_available
|
||
|
|
else ("explicit" if manual_available else "disabled")
|
||
|
|
)
|
||
|
|
default_graph = "explicit" if graph_available else "disabled"
|
||
|
|
default_viewer = "on-demand" if viewer_available else "disabled"
|
||
|
|
|
||
|
|
manual_mode = cast(
|
||
|
|
ManualProjectionMode,
|
||
|
|
_select_mode(
|
||
|
|
manual,
|
||
|
|
field="manual",
|
||
|
|
default=default_manual,
|
||
|
|
allowed=MANUAL_PROJECTION_MODES,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
graph_mode = cast(
|
||
|
|
PortableGraphProjectionMode,
|
||
|
|
_select_mode(
|
||
|
|
portable_graph,
|
||
|
|
field="portable_graph",
|
||
|
|
default=default_graph,
|
||
|
|
allowed=PORTABLE_GRAPH_PROJECTION_MODES,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
viewer_mode = cast(
|
||
|
|
LiveViewerProjectionMode,
|
||
|
|
_select_mode(
|
||
|
|
live_viewer,
|
||
|
|
field="live_viewer",
|
||
|
|
default=default_viewer,
|
||
|
|
allowed=LIVE_VIEWER_PROJECTION_MODES,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
if manual_mode != "disabled" and not manual_available:
|
||
|
|
raise _unavailable("manual", manual_mode, "manual_render_config")
|
||
|
|
if manual_mode == "auto" and not application_available:
|
||
|
|
raise _unavailable("manual", manual_mode, "canonical_application")
|
||
|
|
if graph_mode == "explicit" and not graph_available:
|
||
|
|
raise _unavailable(
|
||
|
|
"portable_graph",
|
||
|
|
graph_mode,
|
||
|
|
"portable_graph_render_config",
|
||
|
|
)
|
||
|
|
if viewer_mode == "on-demand" and not viewer_available:
|
||
|
|
raise _unavailable("live_viewer", viewer_mode, "live_viewer_runtime")
|
||
|
|
|
||
|
|
return ProjectionPolicyV2(
|
||
|
|
manual=manual_mode,
|
||
|
|
portable_graph=graph_mode,
|
||
|
|
live_viewer=viewer_mode,
|
||
|
|
)
|