from __future__ import annotations from dataclasses import dataclass, field from enum import Enum from typing import Any, Protocol, Sequence class ExchangeTemplateKind(str, Enum): IMPORT = "import" EXPORT = "export" class ExchangeTaskKind(str, Enum): IMPORT = "import" EXPORT = "export" @dataclass(frozen=True) class ExchangePlaceholder: key: str label: str description: str | None = None required: bool = False example: str | None = None @dataclass(frozen=True) class ExchangeField: key: str label: str placeholder: str | None = None required: bool = False example: str | None = None width: int | None = None format: str | None = None source: str | None = None target: str | None = None options: tuple[tuple[str, str], ...] = () meta: dict[str, Any] = field(default_factory=dict) def workbook_header(self) -> str: return self.placeholder or self.label or self.key def export_source_key(self) -> str: return self.source or self.key def import_target_key(self) -> str: return self.target or self.key @dataclass(frozen=True) class ExchangeTemplateBinding: entity: str template_kind: ExchangeTemplateKind handler: str | None = None description: str | None = None default_sheet_name: str | None = None default_file_name: str | None = None title: str | None = None meta: dict[str, Any] = field(default_factory=dict) class ExchangeTemplateSourceKind(str, Enum): LOCAL = "local" REMOTE = "remote" MAPPING = "mapping" CUSTOM = "custom" @dataclass(frozen=True) class ExchangeTemplateSnapshot: id: str version: str template_id: str template_kind: ExchangeTemplateKind bindings: tuple[ExchangeTemplateBinding, ...] = () published_at: str | None = None file_key: str | None = None checksum: str | None = None fields: tuple[ExchangeField, ...] = () placeholders: tuple[ExchangePlaceholder, ...] = () meta: dict[str, Any] = field(default_factory=dict) def to_plan(self) -> "ExchangePlan": meta = dict(self.meta) return ExchangePlan( template_kind=self.template_kind, template_id=self.template_id, version_id=self.id, version=self.version, bindings=self.bindings, fields=self.fields, placeholders=self.placeholders, title=meta.get("title"), description=meta.get("description"), sheet_name=meta.get("sheet_name"), meta=meta, ) @dataclass(frozen=True) class ExchangePlan: template_kind: ExchangeTemplateKind template_id: str | None = None version_id: str | None = None version: str | None = None bindings: tuple[ExchangeTemplateBinding, ...] = () fields: tuple[ExchangeField, ...] = () placeholders: tuple[ExchangePlaceholder, ...] = () title: str | None = None description: str | None = None sheet_name: str | None = None meta: dict[str, Any] = field(default_factory=dict) @classmethod def from_mapping( cls, *, template_kind: ExchangeTemplateKind | str, template_id: str | None = None, version_id: str | None = None, version: str | None = None, bindings: Sequence[ExchangeTemplateBinding] | None = None, fields: Sequence[ExchangeField] | None = None, placeholders: Sequence[ExchangePlaceholder] | None = None, title: str | None = None, description: str | None = None, sheet_name: str | None = None, meta: dict[str, Any] | None = None, ) -> "ExchangePlan": return cls( template_kind=ExchangeTemplateKind(template_kind), template_id=template_id, version_id=version_id, version=version, bindings=tuple(bindings or ()), fields=tuple(fields or ()), placeholders=tuple(placeholders or ()), title=title, description=description, sheet_name=sheet_name, meta=meta or {}, ) def resolved_meta(self) -> dict[str, Any]: meta = dict(self.meta) if self.template_id is not None: meta.setdefault("template_id", self.template_id) if self.version_id is not None: meta.setdefault("version_id", self.version_id) if self.version is not None: meta.setdefault("version", self.version) if self.title is not None: meta.setdefault("title", self.title) if self.description is not None: meta.setdefault("description", self.description) if self.sheet_name is not None: meta.setdefault("sheet_name", self.sheet_name) return meta @dataclass(frozen=True) class ExchangeTemplateSource: kind: ExchangeTemplateSourceKind template_kind: ExchangeTemplateKind template_id: str | None = None version_id: str | None = None version: str | None = None service: str | None = None bindings: tuple[ExchangeTemplateBinding, ...] = () fields: tuple[ExchangeField, ...] = () placeholders: tuple[ExchangePlaceholder, ...] = () title: str | None = None description: str | None = None sheet_name: str | None = None meta: dict[str, Any] = field(default_factory=dict) def to_plan(self) -> ExchangePlan: meta = dict(self.meta) return ExchangePlan( template_kind=self.template_kind, template_id=self.template_id, version_id=self.version_id, version=self.version, bindings=self.bindings, fields=self.fields, placeholders=self.placeholders, title=self.title, description=self.description, sheet_name=self.sheet_name, meta=meta, ) @dataclass(frozen=True) class ExchangeTemplate: id: str code: str name: str template_kind: ExchangeTemplateKind entity: str status: str = "draft" description: str | None = None current_version: str | None = None bindings: tuple[ExchangeTemplateBinding, ...] = () fields: tuple[ExchangeField, ...] = () placeholders: tuple[ExchangePlaceholder, ...] = () meta: dict[str, Any] = field(default_factory=dict) def to_plan(self) -> ExchangePlan: meta = dict(self.meta) return ExchangePlan( template_kind=self.template_kind, template_id=self.id, version_id=self.current_version, bindings=self.bindings, fields=self.fields, placeholders=self.placeholders, title=self.name, description=self.description, sheet_name=meta.get("sheet_name"), meta={ "code": self.code, "status": self.status, "current_version": self.current_version, **meta, }, ) class DataExchangeModule(Protocol): name: str def init_app(self, app) -> None: ... def register_routes(self, app) -> None: ... def register_permissions(self, app) -> None: ... def register_menu_seed(self, app) -> None: ... def register_tasks(self, app) -> None: ...