Implement typed template library and rendering
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping, Sequence
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_core.auth import ApiPrincipal
|
||||
from govoplan_core.core.modules import ModuleContext
|
||||
from govoplan_core.core.templates import (
|
||||
TemplateCatalogProvider,
|
||||
TemplateCompatibility,
|
||||
TemplateRef,
|
||||
)
|
||||
from govoplan_templates.backend.rendering import SqlTemplateRenderer
|
||||
from govoplan_templates.backend.service import (
|
||||
READ_SCOPE,
|
||||
compatibility,
|
||||
get_template,
|
||||
get_template_revision,
|
||||
list_templates,
|
||||
template_ref,
|
||||
)
|
||||
|
||||
|
||||
class SqlTemplateCatalog(TemplateCatalogProvider):
|
||||
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]:
|
||||
sql_session, api_principal = _context(session, principal)
|
||||
_require_read(api_principal)
|
||||
rows = list_templates(
|
||||
sql_session,
|
||||
api_principal,
|
||||
query=query,
|
||||
usage=usage,
|
||||
template_type=template_type,
|
||||
locale=locale,
|
||||
limit=limit,
|
||||
)
|
||||
return tuple(
|
||||
template_ref(
|
||||
row,
|
||||
get_template_revision(sql_session, row, published_preferred=True),
|
||||
read_only=_read_only(api_principal, row.scope_type, row.scope_id),
|
||||
)
|
||||
for row in rows
|
||||
)
|
||||
|
||||
def get_template(
|
||||
self,
|
||||
session: object,
|
||||
principal: object,
|
||||
*,
|
||||
template_id: str,
|
||||
revision: int | None = None,
|
||||
) -> TemplateRef | None:
|
||||
sql_session, api_principal = _context(session, principal)
|
||||
_require_read(api_principal)
|
||||
try:
|
||||
row = get_template(sql_session, api_principal, template_id)
|
||||
item_revision = get_template_revision(
|
||||
sql_session,
|
||||
row,
|
||||
revision=revision,
|
||||
published_preferred=revision is None,
|
||||
)
|
||||
except ValueError:
|
||||
return None
|
||||
return template_ref(
|
||||
row,
|
||||
item_revision,
|
||||
read_only=_read_only(api_principal, row.scope_type, row.scope_id),
|
||||
)
|
||||
|
||||
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:
|
||||
sql_session, api_principal = _context(session, principal)
|
||||
_require_read(api_principal)
|
||||
row = get_template(sql_session, api_principal, template_id)
|
||||
item_revision = get_template_revision(
|
||||
sql_session,
|
||||
row,
|
||||
revision=revision,
|
||||
published_preferred=revision is None,
|
||||
)
|
||||
return compatibility(
|
||||
item_revision,
|
||||
usage=usage,
|
||||
output_format=output_format,
|
||||
available_fields=available_fields,
|
||||
)
|
||||
|
||||
|
||||
def catalog_capability(_context: ModuleContext) -> SqlTemplateCatalog:
|
||||
return SqlTemplateCatalog()
|
||||
|
||||
|
||||
def renderer_capability(context: ModuleContext) -> SqlTemplateRenderer:
|
||||
return SqlTemplateRenderer(context.registry)
|
||||
|
||||
|
||||
def _context(session: object, principal: object) -> tuple[Session, ApiPrincipal]:
|
||||
if not isinstance(session, Session):
|
||||
raise TypeError("Template catalogue access requires a SQLAlchemy session.")
|
||||
if not isinstance(principal, ApiPrincipal):
|
||||
raise TypeError("Template catalogue access requires an API principal.")
|
||||
return session, principal
|
||||
|
||||
|
||||
def _require_read(principal: ApiPrincipal) -> None:
|
||||
if not any(
|
||||
principal.has(scope)
|
||||
for scope in (
|
||||
READ_SCOPE,
|
||||
"templates:template:write",
|
||||
"templates:template:publish",
|
||||
"templates:template:admin",
|
||||
)
|
||||
):
|
||||
raise PermissionError(f"Template catalogue access requires {READ_SCOPE}.")
|
||||
|
||||
|
||||
def _read_only(principal: ApiPrincipal, scope_type: str, scope_id: str | None) -> bool:
|
||||
if principal.has("templates:template:admin") or scope_type == "tenant":
|
||||
return False
|
||||
if scope_type == "user":
|
||||
return scope_id != principal.account_id
|
||||
return scope_id not in principal.group_ids
|
||||
|
||||
|
||||
__all__ = [
|
||||
"SqlTemplateCatalog",
|
||||
"catalog_capability",
|
||||
"renderer_capability",
|
||||
]
|
||||
Reference in New Issue
Block a user