You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
287 lines
10 KiB
Python
287 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any, Protocol
|
|
|
|
from iti.service_client import ServiceClient, service_client
|
|
|
|
from .base import (
|
|
ExchangeField,
|
|
ExchangePlaceholder,
|
|
ExchangePlan,
|
|
ExchangeTemplateBinding,
|
|
ExchangeTemplateKind,
|
|
ExchangeTemplateSource,
|
|
ExchangeTemplateSourceKind,
|
|
)
|
|
from .excel import ExcelTemplateCodec
|
|
|
|
|
|
class ExchangeSource(Protocol):
|
|
def resolve_plan(
|
|
self,
|
|
*,
|
|
template_kind: ExchangeTemplateKind | str,
|
|
template_id: str | None = None,
|
|
version_id: str | None = None,
|
|
version: str | None = None,
|
|
bindings: list[ExchangeTemplateBinding] | None = None,
|
|
fields: list[ExchangeField] | None = None,
|
|
placeholders: list[ExchangePlaceholder] | None = None,
|
|
title: str | None = None,
|
|
description: str | None = None,
|
|
sheet_name: str | None = None,
|
|
meta: dict[str, Any] | None = None,
|
|
source: ExchangeTemplateSource | None = None,
|
|
) -> ExchangePlan:
|
|
...
|
|
|
|
def load_template_file(self, plan: ExchangePlan) -> bytes | None:
|
|
...
|
|
|
|
|
|
@dataclass
|
|
class MappingExchangeSource:
|
|
def resolve_plan(
|
|
self,
|
|
*,
|
|
template_kind: ExchangeTemplateKind | str,
|
|
template_id: str | None = None,
|
|
version_id: str | None = None,
|
|
version: str | None = None,
|
|
bindings: list[ExchangeTemplateBinding] | None = None,
|
|
fields: list[ExchangeField] | None = None,
|
|
placeholders: list[ExchangePlaceholder] | None = None,
|
|
title: str | None = None,
|
|
description: str | None = None,
|
|
sheet_name: str | None = None,
|
|
meta: dict[str, Any] | None = None,
|
|
source: ExchangeTemplateSource | None = None,
|
|
) -> ExchangePlan:
|
|
if source is not None:
|
|
return source.to_plan()
|
|
return ExchangePlan.from_mapping(
|
|
template_kind=template_kind,
|
|
template_id=template_id,
|
|
version_id=version_id,
|
|
version=version,
|
|
bindings=bindings,
|
|
fields=fields,
|
|
placeholders=placeholders,
|
|
title=title,
|
|
description=description,
|
|
sheet_name=sheet_name,
|
|
meta=meta,
|
|
)
|
|
|
|
def load_template_file(self, plan: ExchangePlan) -> bytes | None:
|
|
return ExcelTemplateCodec().dump(plan)
|
|
|
|
|
|
@dataclass
|
|
class LocalExchangeSource:
|
|
app: Any
|
|
db: Any
|
|
|
|
def resolve_plan(
|
|
self,
|
|
*,
|
|
template_kind: ExchangeTemplateKind | str,
|
|
template_id: str | None = None,
|
|
version_id: str | None = None,
|
|
version: str | None = None,
|
|
bindings: list[ExchangeTemplateBinding] | None = None,
|
|
fields: list[ExchangeField] | None = None,
|
|
placeholders: list[ExchangePlaceholder] | None = None,
|
|
title: str | None = None,
|
|
description: str | None = None,
|
|
sheet_name: str | None = None,
|
|
meta: dict[str, Any] | None = None,
|
|
source: ExchangeTemplateSource | None = None,
|
|
) -> ExchangePlan:
|
|
from .service import ExchangeService
|
|
|
|
service = ExchangeService(self.app, self.db)
|
|
if source is not None:
|
|
return source.to_plan()
|
|
if version_id:
|
|
snapshot = service.get_snapshot_by_version_id(version_id)
|
|
if snapshot is not None:
|
|
return snapshot.to_plan()
|
|
if template_id:
|
|
snapshot = service.get_current_snapshot(template_id)
|
|
if snapshot is not None:
|
|
return snapshot.to_plan()
|
|
return ExchangePlan.from_mapping(
|
|
template_kind=template_kind,
|
|
template_id=template_id,
|
|
version_id=version_id,
|
|
version=version,
|
|
bindings=bindings,
|
|
fields=fields,
|
|
placeholders=placeholders,
|
|
title=title,
|
|
description=description,
|
|
sheet_name=sheet_name,
|
|
meta=meta,
|
|
)
|
|
|
|
def load_template_file(self, plan: ExchangePlan) -> bytes | None:
|
|
if plan.version_id:
|
|
from .service import ExchangeService
|
|
|
|
return ExchangeService(self.app, self.db).build_template_file(plan.version_id)
|
|
return ExcelTemplateCodec().dump(plan)
|
|
|
|
|
|
@dataclass
|
|
class RemoteExchangeSource:
|
|
app: Any
|
|
service_name: str = "exchange"
|
|
|
|
def resolve_plan(
|
|
self,
|
|
*,
|
|
template_kind: ExchangeTemplateKind | str,
|
|
template_id: str | None = None,
|
|
version_id: str | None = None,
|
|
version: str | None = None,
|
|
bindings: list[ExchangeTemplateBinding] | None = None,
|
|
fields: list[ExchangeField] | None = None,
|
|
placeholders: list[ExchangePlaceholder] | None = None,
|
|
title: str | None = None,
|
|
description: str | None = None,
|
|
sheet_name: str | None = None,
|
|
meta: dict[str, Any] | None = None,
|
|
source: ExchangeTemplateSource | None = None,
|
|
) -> ExchangePlan:
|
|
if source is not None:
|
|
return source.to_plan()
|
|
client = service_client(self.app, self.service_name)
|
|
payload = self._fetch_plan(client, template_id=template_id, version=version, version_id=version_id)
|
|
if payload is not None:
|
|
meta = payload.get("meta") or {}
|
|
return ExchangePlan.from_mapping(
|
|
template_kind=payload.get("template_kind") or template_kind,
|
|
template_id=payload.get("template_id") or template_id,
|
|
version_id=payload.get("id") or version_id,
|
|
version=payload.get("version") or version,
|
|
bindings=[_binding_from_mapping(item) for item in payload.get("bindings", [])],
|
|
fields=[_field_from_mapping(item) for item in payload.get("fields", [])],
|
|
placeholders=[
|
|
_placeholder_from_mapping(item) for item in payload.get("placeholders", [])
|
|
],
|
|
title=payload.get("title") or meta.get("title") or title,
|
|
description=payload.get("description") or meta.get("description") or description,
|
|
sheet_name=payload.get("sheet_name") or meta.get("sheet_name") or sheet_name,
|
|
meta=meta or payload.get("meta") or {},
|
|
)
|
|
return ExchangePlan.from_mapping(
|
|
template_kind=template_kind,
|
|
template_id=template_id,
|
|
version_id=version_id,
|
|
version=version,
|
|
bindings=bindings,
|
|
fields=fields,
|
|
placeholders=placeholders,
|
|
title=title,
|
|
description=description,
|
|
sheet_name=sheet_name,
|
|
meta=meta,
|
|
)
|
|
|
|
def load_template_file(self, plan: ExchangePlan) -> bytes | None:
|
|
if not plan.version_id:
|
|
return ExcelTemplateCodec().dump(plan)
|
|
client = service_client(self.app, self.service_name)
|
|
response = client.get(
|
|
f"/exchange/template-versions/{plan.version_id}/download",
|
|
expect_json=False,
|
|
)
|
|
return response.content
|
|
|
|
def _fetch_plan(
|
|
self,
|
|
client: ServiceClient,
|
|
*,
|
|
template_id: str | None,
|
|
version: str | None,
|
|
version_id: str | None,
|
|
) -> dict[str, Any] | None:
|
|
if version_id:
|
|
return client.get(f"/exchange/template-versions/{version_id}")
|
|
if template_id and version:
|
|
return client.get(f"/exchange/templates/{template_id}/versions/{version}")
|
|
if template_id:
|
|
template = client.get(f"/exchange/templates/{template_id}")
|
|
current_version = (template or {}).get("current_version")
|
|
if current_version:
|
|
return client.get(f"/exchange/templates/{template_id}/versions/{current_version}")
|
|
return None
|
|
|
|
|
|
def _binding_from_mapping(item: dict[str, Any]) -> ExchangeTemplateBinding:
|
|
return ExchangeTemplateBinding(
|
|
entity=item.get("entity"),
|
|
template_kind=item.get("template_kind") or item.get("templateKind"),
|
|
handler=item.get("handler"),
|
|
description=item.get("description"),
|
|
default_sheet_name=item.get("default_sheet_name") or item.get("defaultSheetName"),
|
|
default_file_name=item.get("default_file_name") or item.get("defaultFileName"),
|
|
title=item.get("title"),
|
|
meta=item.get("meta") or {},
|
|
)
|
|
|
|
|
|
def _field_from_mapping(item: dict[str, Any]) -> ExchangeField:
|
|
return ExchangeField(
|
|
key=item.get("key"),
|
|
label=item.get("label"),
|
|
placeholder=item.get("placeholder"),
|
|
required=bool(item.get("required", False)),
|
|
example=item.get("example"),
|
|
width=item.get("width"),
|
|
format=item.get("format"),
|
|
source=item.get("source"),
|
|
target=item.get("target"),
|
|
options=tuple(tuple(option) for option in item.get("options") or []),
|
|
meta=item.get("meta") or {},
|
|
)
|
|
|
|
|
|
def _placeholder_from_mapping(item: dict[str, Any]) -> ExchangePlaceholder:
|
|
return ExchangePlaceholder(
|
|
key=item.get("key"),
|
|
label=item.get("label"),
|
|
description=item.get("description"),
|
|
required=bool(item.get("required", False)),
|
|
example=item.get("example"),
|
|
)
|
|
|
|
|
|
def get_exchange_source(
|
|
app: Any,
|
|
*,
|
|
source_kind: ExchangeTemplateSourceKind | str = ExchangeTemplateSourceKind.MAPPING,
|
|
source_name: str | None = None,
|
|
db: Any | None = None,
|
|
service_name: str = "exchange",
|
|
) -> ExchangeSource:
|
|
if source_name:
|
|
from .registry import get_exchange_registry
|
|
|
|
source = get_exchange_registry(app).get_source(source_name)
|
|
if source is None:
|
|
raise ValueError(f"exchange source not registered: {source_name}")
|
|
return source # type: ignore[return-value]
|
|
kind = ExchangeTemplateSourceKind(source_kind)
|
|
if kind == ExchangeTemplateSourceKind.LOCAL:
|
|
if db is None:
|
|
raise ValueError("local exchange source requires db")
|
|
return LocalExchangeSource(app=app, db=db)
|
|
if kind == ExchangeTemplateSourceKind.REMOTE:
|
|
return RemoteExchangeSource(app=app, service_name=service_name)
|
|
if kind == ExchangeTemplateSourceKind.CUSTOM:
|
|
raise ValueError("custom exchange source requires source_name")
|
|
return MappingExchangeSource()
|