Add template and generated artifact contracts
This commit is contained in:
@@ -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: ...
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
Reference in New Issue
Block a user