Complete independent projection runtime
This commit is contained in:
parent
1134c2d375
commit
f1fabaf0ca
38 changed files with 4907 additions and 87 deletions
234
src/docforge/projection_policy.py
Normal file
234
src/docforge/projection_policy.py
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
"""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,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue