Add template and generated artifact contracts

This commit is contained in:
2026-08-02 12:37:58 +02:00
parent fe62fd4644
commit e4d2d10c7e
9 changed files with 411 additions and 2 deletions
+39
View File
@@ -1,13 +1,52 @@
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass, field
from typing import Protocol, runtime_checkable
from govoplan_core.core.access import ResourceAccessExplanationProvider
CAPABILITY_FILES_ACCESS = "files.access"
CAPABILITY_FILES_ARTIFACT_STORE = "files.artifact_store"
@dataclass(frozen=True, slots=True)
class ManagedArtifactWriteRequest:
filename: str
payload: bytes
content_type: str
folder: str = "Generated"
description: str | None = None
idempotency_key: str | None = None
metadata: Mapping[str, object] = field(default_factory=dict)
@dataclass(frozen=True, slots=True)
class ManagedArtifactRef:
file_asset_id: str
file_version_id: str
filename: str
display_path: str
content_type: str
size_bytes: int
sha256: str
provenance: Mapping[str, object] = field(default_factory=dict)
@runtime_checkable
class FileAccessProvider(ResourceAccessExplanationProvider, Protocol):
"""Resource-level access explanation provider for Files-owned resources."""
@runtime_checkable
class ManagedArtifactStore(Protocol):
"""Store generated module artifacts without exposing Files internals."""
def store_artifact(
self,
session: object,
principal: object,
*,
request: ManagedArtifactWriteRequest,
) -> ManagedArtifactRef: ...
+236
View File
@@ -0,0 +1,236 @@
from __future__ import annotations
from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field
from datetime import datetime
from typing import Literal, Protocol, runtime_checkable
CAPABILITY_TEMPLATE_CATALOG = "templates.catalog"
CAPABILITY_TEMPLATE_RENDERER = "templates.renderer"
TemplateType = Literal[
"label",
"label_sheet",
"envelope",
"serial_letter",
"form_letter",
"list_layout",
"email",
"generic",
]
TemplateOutputFormat = Literal["html", "text"]
TemplateRenderMode = Literal["preview", "final"]
class TemplateContractError(ValueError):
"""Stable base error for provider-neutral template operations."""
class TemplateNotFoundError(TemplateContractError):
pass
class TemplateCompatibilityError(TemplateContractError):
pass
class TemplateRenderError(TemplateContractError):
pass
@dataclass(frozen=True, slots=True)
class TemplateFieldRequirement:
path: str
value_type: Literal[
"string",
"integer",
"number",
"boolean",
"date",
"datetime",
"object",
"array",
] = "string"
label: str | None = None
required: bool = True
description: str | None = None
@dataclass(frozen=True, slots=True)
class TemplateOutputProfile:
id: str
label: str
output_format: TemplateOutputFormat
media_type: str
channel: str = "print"
capabilities: tuple[str, ...] = ()
page: Mapping[str, object] = field(default_factory=dict)
@dataclass(frozen=True, slots=True)
class TemplateRevisionRef:
id: str
template_id: str
revision: int
definition_hash: str
template_type: TemplateType
usages: tuple[str, ...]
locale: str
required_fields: tuple[TemplateFieldRequirement, ...]
output_profiles: tuple[TemplateOutputProfile, ...]
published_at: datetime | None = None
provenance: Mapping[str, object] = field(default_factory=dict)
@dataclass(frozen=True, slots=True)
class TemplateRef:
id: str
tenant_id: str
name: str
slug: str
template_type: TemplateType
status: str
current_revision: int
current_revision_id: str
published_revision_id: str | None
description: str | None = None
scope_type: str = "tenant"
scope_id: str | None = None
read_only: bool = False
updated_at: datetime | None = None
revision: TemplateRevisionRef | None = None
metadata: Mapping[str, object] = field(default_factory=dict)
@dataclass(frozen=True, slots=True)
class TemplateCompatibility:
compatible: bool
template_id: str
revision_id: str
usage: str | None
output_format: str | None
missing_fields: tuple[str, ...] = ()
incompatible_fields: tuple[str, ...] = ()
diagnostics: tuple[Mapping[str, object], ...] = ()
@dataclass(frozen=True, slots=True)
class TemplateRenderRequest:
template_id: str
revision: int | None = None
usage: str | None = None
locale: str | None = None
output_format: TemplateOutputFormat = "html"
profile_id: str | None = None
parameters: Mapping[str, object] = field(default_factory=dict)
items: tuple[Mapping[str, object], ...] = ()
input_snapshot: Mapping[str, object] = field(default_factory=dict)
mode: TemplateRenderMode = "preview"
idempotency_key: str | None = None
persist_to_files: bool = False
@dataclass(frozen=True, slots=True)
class TemplateArtifactRef:
kind: Literal["managed_file", "bounded_download"]
filename: str
content_type: str
size_bytes: int
sha256: str
file_asset_id: str | None = None
file_version_id: str | None = None
download_path: str | None = None
provenance: Mapping[str, object] = field(default_factory=dict)
@dataclass(frozen=True, slots=True)
class TemplateRenderResult:
render_id: str
template_id: str
revision_id: str
revision: int
template_hash: str
input_hash: str
renderer_version: str
output_format: TemplateOutputFormat
content_type: str
filename: str
item_count: int
page_count: int
output_sha256: str
output_size_bytes: int
diagnostics: tuple[Mapping[str, object], ...] = ()
artifact: TemplateArtifactRef | None = None
generated_at: datetime | None = None
payload: bytes | None = None
@runtime_checkable
class TemplateCatalogProvider(Protocol):
def list_templates(
self,
session: object,
principal: object,
*,
query: str = "",
usage: str | None = None,
template_type: str | None = None,
locale: str | None = None,
limit: int = 100,
) -> Sequence[TemplateRef]: ...
def get_template(
self,
session: object,
principal: object,
*,
template_id: str,
revision: int | None = None,
) -> TemplateRef | None: ...
def check_compatibility(
self,
session: object,
principal: object,
*,
template_id: str,
revision: int | None = None,
usage: str | None = None,
output_format: str | None = None,
available_fields: Mapping[str, str] | Sequence[str] = (),
) -> TemplateCompatibility: ...
@runtime_checkable
class TemplateRendererProvider(Protocol):
def render(
self,
session: object,
principal: object,
*,
request: TemplateRenderRequest,
) -> TemplateRenderResult: ...
__all__ = [
"CAPABILITY_TEMPLATE_CATALOG",
"CAPABILITY_TEMPLATE_RENDERER",
"TemplateArtifactRef",
"TemplateCatalogProvider",
"TemplateCompatibility",
"TemplateCompatibilityError",
"TemplateContractError",
"TemplateFieldRequirement",
"TemplateNotFoundError",
"TemplateOutputFormat",
"TemplateOutputProfile",
"TemplateRef",
"TemplateRenderError",
"TemplateRenderMode",
"TemplateRenderRequest",
"TemplateRenderResult",
"TemplateRendererProvider",
"TemplateRevisionRef",
"TemplateType",
]