|
|
|
@ -3,23 +3,24 @@ from __future__ import annotations
|
|
|
|
import hashlib
|
|
|
|
import hashlib
|
|
|
|
from dataclasses import asdict
|
|
|
|
from dataclasses import asdict
|
|
|
|
from datetime import datetime
|
|
|
|
from datetime import datetime
|
|
|
|
from io import BytesIO
|
|
|
|
|
|
|
|
from enum import Enum
|
|
|
|
from enum import Enum
|
|
|
|
|
|
|
|
from io import BytesIO
|
|
|
|
from typing import Any
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
|
|
|
|
from sqlalchemy import select
|
|
|
|
from sqlalchemy import select, update
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
|
|
|
|
|
|
from iti.exceptions import BizError
|
|
|
|
from iti.exceptions import BizError
|
|
|
|
|
|
|
|
|
|
|
|
from .base import (
|
|
|
|
from .base import (
|
|
|
|
ExchangeField,
|
|
|
|
ExchangeOperation,
|
|
|
|
ExchangePlaceholder,
|
|
|
|
ExchangeScope,
|
|
|
|
ExchangeTemplateBinding,
|
|
|
|
ExchangeTaskContext,
|
|
|
|
ExchangeTemplateKind,
|
|
|
|
ExchangeTaskResult,
|
|
|
|
ExchangePlan,
|
|
|
|
ExchangeTemplateLayout,
|
|
|
|
|
|
|
|
ExchangeTemplatePlan,
|
|
|
|
ExchangeTemplateSnapshot,
|
|
|
|
ExchangeTemplateSnapshot,
|
|
|
|
ExchangeTaskKind,
|
|
|
|
ExchangeVariable,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
from .models import (
|
|
|
|
from .models import (
|
|
|
|
ExchangeTaskModel,
|
|
|
|
ExchangeTaskModel,
|
|
|
|
@ -27,6 +28,7 @@ from .models import (
|
|
|
|
ExchangeTemplateModel,
|
|
|
|
ExchangeTemplateModel,
|
|
|
|
ExchangeTemplateVersionModel,
|
|
|
|
ExchangeTemplateVersionModel,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
from .registry import get_exchange_registry
|
|
|
|
from .tasks import get_exchange_storage
|
|
|
|
from .tasks import get_exchange_storage
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -35,22 +37,44 @@ class ExchangeService:
|
|
|
|
self.app = app
|
|
|
|
self.app = app
|
|
|
|
self.db = db
|
|
|
|
self.db = db
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_template_code(
|
|
|
|
|
|
|
|
self,
|
|
|
|
|
|
|
|
*,
|
|
|
|
|
|
|
|
biz_domain: str,
|
|
|
|
|
|
|
|
biz_obj: str,
|
|
|
|
|
|
|
|
operation: ExchangeOperation | str,
|
|
|
|
|
|
|
|
) -> str:
|
|
|
|
|
|
|
|
return ExchangeScope.from_mapping(
|
|
|
|
|
|
|
|
biz_domain=biz_domain,
|
|
|
|
|
|
|
|
biz_obj=biz_obj,
|
|
|
|
|
|
|
|
operation=operation,
|
|
|
|
|
|
|
|
).code()
|
|
|
|
|
|
|
|
|
|
|
|
def create_template(
|
|
|
|
def create_template(
|
|
|
|
self,
|
|
|
|
self,
|
|
|
|
*,
|
|
|
|
*,
|
|
|
|
code: str,
|
|
|
|
|
|
|
|
name: str,
|
|
|
|
name: str,
|
|
|
|
template_kind: ExchangeTemplateKind | str,
|
|
|
|
biz_domain: str,
|
|
|
|
entity: str,
|
|
|
|
biz_obj: str,
|
|
|
|
|
|
|
|
operation: ExchangeOperation | str,
|
|
|
|
|
|
|
|
code: str | None = None,
|
|
|
|
description: str | None = None,
|
|
|
|
description: str | None = None,
|
|
|
|
|
|
|
|
layout: ExchangeTemplateLayout | dict[str, Any] | None = None,
|
|
|
|
meta: dict[str, Any] | None = None,
|
|
|
|
meta: dict[str, Any] | None = None,
|
|
|
|
) -> ExchangeTemplateModel:
|
|
|
|
) -> ExchangeTemplateModel:
|
|
|
|
|
|
|
|
scope = ExchangeScope.from_mapping(
|
|
|
|
|
|
|
|
biz_domain=biz_domain,
|
|
|
|
|
|
|
|
biz_obj=biz_obj,
|
|
|
|
|
|
|
|
operation=operation,
|
|
|
|
|
|
|
|
)
|
|
|
|
template = ExchangeTemplateModel(
|
|
|
|
template = ExchangeTemplateModel(
|
|
|
|
code=code,
|
|
|
|
code=code or scope.code(),
|
|
|
|
name=name,
|
|
|
|
name=name,
|
|
|
|
template_kind=_enum_value(template_kind),
|
|
|
|
biz_domain=scope.biz_domain,
|
|
|
|
entity=entity,
|
|
|
|
biz_obj=scope.biz_obj,
|
|
|
|
|
|
|
|
operation=_operation_value(scope.operation),
|
|
|
|
description=description,
|
|
|
|
description=description,
|
|
|
|
|
|
|
|
layout=asdict(_coerce_layout(layout)),
|
|
|
|
meta=meta or {},
|
|
|
|
meta=meta or {},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
self.db.add(template)
|
|
|
|
self.db.add(template)
|
|
|
|
@ -66,6 +90,7 @@ class ExchangeService:
|
|
|
|
description: str | None = None,
|
|
|
|
description: str | None = None,
|
|
|
|
status: str | None = None,
|
|
|
|
status: str | None = None,
|
|
|
|
current_version: str | None = None,
|
|
|
|
current_version: str | None = None,
|
|
|
|
|
|
|
|
layout: ExchangeTemplateLayout | dict[str, Any] | None = None,
|
|
|
|
meta: dict[str, Any] | None = None,
|
|
|
|
meta: dict[str, Any] | None = None,
|
|
|
|
) -> ExchangeTemplateModel:
|
|
|
|
) -> ExchangeTemplateModel:
|
|
|
|
template = self.get_template_or_404(template_id)
|
|
|
|
template = self.get_template_or_404(template_id)
|
|
|
|
@ -77,26 +102,59 @@ class ExchangeService:
|
|
|
|
template.status = status
|
|
|
|
template.status = status
|
|
|
|
if current_version is not None:
|
|
|
|
if current_version is not None:
|
|
|
|
template.current_version = current_version
|
|
|
|
template.current_version = current_version
|
|
|
|
|
|
|
|
if layout is not None:
|
|
|
|
|
|
|
|
template.layout = asdict(_coerce_layout(layout))
|
|
|
|
if meta is not None:
|
|
|
|
if meta is not None:
|
|
|
|
template.meta = meta
|
|
|
|
template.meta = meta
|
|
|
|
self.db.commit()
|
|
|
|
self.db.commit()
|
|
|
|
self.db.refresh(template)
|
|
|
|
self.db.refresh(template)
|
|
|
|
return template
|
|
|
|
return template
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def delete_template(self, template_id: str) -> None:
|
|
|
|
|
|
|
|
template = self.get_template_or_404(template_id)
|
|
|
|
|
|
|
|
versions = list(template.versions)
|
|
|
|
|
|
|
|
version_ids = [version.id for version in versions]
|
|
|
|
|
|
|
|
storage = get_exchange_storage(self.app)
|
|
|
|
|
|
|
|
for version in versions:
|
|
|
|
|
|
|
|
if version.file_key:
|
|
|
|
|
|
|
|
storage.delete(version.file_key)
|
|
|
|
|
|
|
|
if version_ids:
|
|
|
|
|
|
|
|
self.db.execute(
|
|
|
|
|
|
|
|
update(ExchangeTaskModel)
|
|
|
|
|
|
|
|
.where(ExchangeTaskModel.template_version_id.in_(version_ids))
|
|
|
|
|
|
|
|
.values(template_version_id=None)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
self.db.execute(
|
|
|
|
|
|
|
|
update(ExchangeTaskModel)
|
|
|
|
|
|
|
|
.where(ExchangeTaskModel.template_id == template.id)
|
|
|
|
|
|
|
|
.values(template_id=None, template_version_id=None)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
self.db.delete(template)
|
|
|
|
|
|
|
|
self.db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
def publish_version(
|
|
|
|
def publish_version(
|
|
|
|
self,
|
|
|
|
self,
|
|
|
|
*,
|
|
|
|
*,
|
|
|
|
template_id: str,
|
|
|
|
template_id: str,
|
|
|
|
version: str,
|
|
|
|
version: str,
|
|
|
|
bindings: list[ExchangeTemplateBinding] | None = None,
|
|
|
|
variables: list[ExchangeVariable] | None = None,
|
|
|
|
fields: list[ExchangeField] | None = None,
|
|
|
|
layout: ExchangeTemplateLayout | dict[str, Any] | None = None,
|
|
|
|
placeholders: list[ExchangePlaceholder] | None = None,
|
|
|
|
|
|
|
|
file_content: bytes | None = None,
|
|
|
|
file_content: bytes | None = None,
|
|
|
|
file_name: str | None = None,
|
|
|
|
file_name: str | None = None,
|
|
|
|
meta: dict[str, Any] | None = None,
|
|
|
|
meta: dict[str, Any] | None = None,
|
|
|
|
make_current: bool = True,
|
|
|
|
make_current: bool = True,
|
|
|
|
) -> ExchangeTemplateVersionModel:
|
|
|
|
) -> ExchangeTemplateVersionModel:
|
|
|
|
template = self.get_template_or_404(template_id)
|
|
|
|
template = self.get_template_or_404(template_id)
|
|
|
|
|
|
|
|
resolved_layout = _coerce_layout(layout if layout is not None else template.layout)
|
|
|
|
|
|
|
|
resolved_variables = variables
|
|
|
|
|
|
|
|
if resolved_variables is None:
|
|
|
|
|
|
|
|
spec = get_exchange_registry(self.app).get_spec(
|
|
|
|
|
|
|
|
biz_domain=template.biz_domain,
|
|
|
|
|
|
|
|
biz_obj=template.biz_obj,
|
|
|
|
|
|
|
|
operation=template.operation,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
resolved_variables = list(spec.variables) if spec is not None else []
|
|
|
|
|
|
|
|
|
|
|
|
file_key = None
|
|
|
|
file_key = None
|
|
|
|
checksum = None
|
|
|
|
checksum = None
|
|
|
|
if file_content is not None:
|
|
|
|
if file_content is not None:
|
|
|
|
@ -111,13 +169,14 @@ class ExchangeService:
|
|
|
|
snapshot = ExchangeTemplateVersionModel(
|
|
|
|
snapshot = ExchangeTemplateVersionModel(
|
|
|
|
template_id=template.id,
|
|
|
|
template_id=template.id,
|
|
|
|
version=version,
|
|
|
|
version=version,
|
|
|
|
template_kind=template.template_kind,
|
|
|
|
biz_domain=template.biz_domain,
|
|
|
|
|
|
|
|
biz_obj=template.biz_obj,
|
|
|
|
|
|
|
|
operation=template.operation,
|
|
|
|
published_at=datetime.now(),
|
|
|
|
published_at=datetime.now(),
|
|
|
|
file_key=file_key,
|
|
|
|
file_key=file_key,
|
|
|
|
checksum=checksum,
|
|
|
|
checksum=checksum,
|
|
|
|
bindings=[_jsonable(asdict(item)) for item in bindings or []],
|
|
|
|
layout=asdict(resolved_layout),
|
|
|
|
fields=[_jsonable(asdict(item)) for item in fields or []],
|
|
|
|
variables=[_jsonable(asdict(item)) for item in resolved_variables],
|
|
|
|
placeholders=[_jsonable(asdict(item)) for item in placeholders or []],
|
|
|
|
|
|
|
|
meta=meta or {},
|
|
|
|
meta=meta or {},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
self.db.add(snapshot)
|
|
|
|
self.db.add(snapshot)
|
|
|
|
@ -128,30 +187,74 @@ class ExchangeService:
|
|
|
|
self.db.refresh(snapshot)
|
|
|
|
self.db.refresh(snapshot)
|
|
|
|
return snapshot
|
|
|
|
return snapshot
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def delete_version(self, version_id: str) -> None:
|
|
|
|
|
|
|
|
version = self.get_version_or_404(version_id)
|
|
|
|
|
|
|
|
template = self.get_template_or_404(version.template_id)
|
|
|
|
|
|
|
|
next_current = None
|
|
|
|
|
|
|
|
if template.current_version == version.version:
|
|
|
|
|
|
|
|
with self.db.no_autoflush:
|
|
|
|
|
|
|
|
next_current = self.db.scalar(
|
|
|
|
|
|
|
|
select(ExchangeTemplateVersionModel)
|
|
|
|
|
|
|
|
.where(ExchangeTemplateVersionModel.template_id == template.id)
|
|
|
|
|
|
|
|
.where(ExchangeTemplateVersionModel.id != version.id)
|
|
|
|
|
|
|
|
.order_by(
|
|
|
|
|
|
|
|
ExchangeTemplateVersionModel.created_at.desc(),
|
|
|
|
|
|
|
|
ExchangeTemplateVersionModel.updated_at.desc(),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if version.file_key:
|
|
|
|
|
|
|
|
storage = get_exchange_storage(self.app)
|
|
|
|
|
|
|
|
storage.delete(version.file_key)
|
|
|
|
|
|
|
|
self.db.execute(
|
|
|
|
|
|
|
|
update(ExchangeTaskModel)
|
|
|
|
|
|
|
|
.where(ExchangeTaskModel.template_version_id == version.id)
|
|
|
|
|
|
|
|
.values(template_version_id=None)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
self.db.delete(version)
|
|
|
|
|
|
|
|
if template.current_version == version.version:
|
|
|
|
|
|
|
|
template.current_version = next_current.version if next_current is not None else None
|
|
|
|
|
|
|
|
if next_current is None:
|
|
|
|
|
|
|
|
template.status = "draft"
|
|
|
|
|
|
|
|
self.db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
def build_template_file(self, version_id: str) -> bytes:
|
|
|
|
def build_template_file(self, version_id: str) -> bytes:
|
|
|
|
version = self.get_version_or_404(version_id)
|
|
|
|
version = self.get_version_or_404(version_id)
|
|
|
|
if version.file_key:
|
|
|
|
if version.file_key:
|
|
|
|
storage = get_exchange_storage(self.app)
|
|
|
|
storage = get_exchange_storage(self.app)
|
|
|
|
with storage.download(version.file_key) as file_stream:
|
|
|
|
with storage.download(version.file_key) as file_stream:
|
|
|
|
return file_stream.read()
|
|
|
|
return file_stream.read()
|
|
|
|
snapshot = self.snapshot_from_model(version)
|
|
|
|
return _excel_template_codec().dump(self.snapshot_from_model(version))
|
|
|
|
return _excel_template_codec().dump(snapshot)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_plan_template_file(self, plan: ExchangePlan) -> bytes:
|
|
|
|
def build_plan_template_file(self, plan: ExchangeTemplatePlan) -> bytes:
|
|
|
|
if plan.version_id:
|
|
|
|
if plan.version_id:
|
|
|
|
version = self.get_snapshot_by_version_id(plan.version_id)
|
|
|
|
version = self.db.get(ExchangeTemplateVersionModel, plan.version_id)
|
|
|
|
if version is not None and version.file_key:
|
|
|
|
if version is not None and version.file_key:
|
|
|
|
storage = get_exchange_storage(self.app)
|
|
|
|
storage = get_exchange_storage(self.app)
|
|
|
|
with storage.download(version.file_key) as file_stream:
|
|
|
|
with storage.download(version.file_key) as file_stream:
|
|
|
|
return file_stream.read()
|
|
|
|
return file_stream.read()
|
|
|
|
return _excel_template_codec().dump(plan)
|
|
|
|
return _excel_template_codec().dump(plan)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def save_template_file(
|
|
|
|
|
|
|
|
self,
|
|
|
|
|
|
|
|
*,
|
|
|
|
|
|
|
|
template: ExchangeTemplateModel,
|
|
|
|
|
|
|
|
version: str,
|
|
|
|
|
|
|
|
content: bytes,
|
|
|
|
|
|
|
|
file_name: str | None = None,
|
|
|
|
|
|
|
|
) -> str:
|
|
|
|
|
|
|
|
suffix = _safe_suffix(file_name or "template.xlsx")
|
|
|
|
|
|
|
|
digest = hashlib.sha256(content).hexdigest()
|
|
|
|
|
|
|
|
key = f"exchange/templates/{template.code}/{version}/{digest}.{suffix}"
|
|
|
|
|
|
|
|
storage = get_exchange_storage(self.app)
|
|
|
|
|
|
|
|
storage.upload(BytesIO(content), key, _excel_mime_type())
|
|
|
|
|
|
|
|
return key
|
|
|
|
|
|
|
|
|
|
|
|
def export_rows(
|
|
|
|
def export_rows(
|
|
|
|
self,
|
|
|
|
self,
|
|
|
|
rows: list[dict[str, Any]],
|
|
|
|
rows: list[dict[str, Any]],
|
|
|
|
*,
|
|
|
|
*,
|
|
|
|
plan: ExchangePlan | None = None,
|
|
|
|
plan: ExchangeTemplatePlan | None = None,
|
|
|
|
fields: list[ExchangeField] | None = None,
|
|
|
|
variables: list[ExchangeVariable] | None = None,
|
|
|
|
sheet_name: str | None = None,
|
|
|
|
sheet_name: str | None = None,
|
|
|
|
) -> bytes:
|
|
|
|
) -> bytes:
|
|
|
|
workbook_codec = _excel_workbook_codec()
|
|
|
|
workbook_codec = _excel_workbook_codec()
|
|
|
|
@ -161,9 +264,9 @@ class ExchangeService:
|
|
|
|
rows=rows,
|
|
|
|
rows=rows,
|
|
|
|
sheet_name=sheet_name,
|
|
|
|
sheet_name=sheet_name,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if fields is not None:
|
|
|
|
if variables is not None:
|
|
|
|
return workbook_codec.export_rows_with_template(
|
|
|
|
return workbook_codec.export_rows_with_variables(
|
|
|
|
fields=fields,
|
|
|
|
variables=variables,
|
|
|
|
rows=rows,
|
|
|
|
rows=rows,
|
|
|
|
sheet_name=sheet_name or "Export",
|
|
|
|
sheet_name=sheet_name or "Export",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
@ -176,60 +279,49 @@ class ExchangeService:
|
|
|
|
self,
|
|
|
|
self,
|
|
|
|
content: bytes,
|
|
|
|
content: bytes,
|
|
|
|
*,
|
|
|
|
*,
|
|
|
|
plan: ExchangePlan | None = None,
|
|
|
|
plan: ExchangeTemplatePlan | None = None,
|
|
|
|
fields: list[ExchangeField] | None = None,
|
|
|
|
variables: list[ExchangeVariable] | None = None,
|
|
|
|
) -> list[dict[str, Any]]:
|
|
|
|
) -> list[dict[str, Any]]:
|
|
|
|
workbook_codec = _excel_workbook_codec()
|
|
|
|
workbook_codec = _excel_workbook_codec()
|
|
|
|
if plan is not None and plan.fields:
|
|
|
|
if plan is not None and plan.variables:
|
|
|
|
return workbook_codec.import_rows_with_fields(content, fields=list(plan.fields))
|
|
|
|
return workbook_codec.import_rows_with_variables(
|
|
|
|
if fields is not None:
|
|
|
|
content,
|
|
|
|
return workbook_codec.import_rows_with_fields(content, fields=fields)
|
|
|
|
variables=list(plan.variables),
|
|
|
|
|
|
|
|
header_row=plan.layout.header_row,
|
|
|
|
|
|
|
|
data_start_row=plan.layout.data_start_row,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if variables is not None:
|
|
|
|
|
|
|
|
return workbook_codec.import_rows_with_variables(content, variables=variables)
|
|
|
|
return workbook_codec.import_rows(content)
|
|
|
|
return workbook_codec.import_rows(content)
|
|
|
|
|
|
|
|
|
|
|
|
def save_template_file(
|
|
|
|
|
|
|
|
self,
|
|
|
|
|
|
|
|
*,
|
|
|
|
|
|
|
|
template: ExchangeTemplateModel,
|
|
|
|
|
|
|
|
version: str,
|
|
|
|
|
|
|
|
content: bytes,
|
|
|
|
|
|
|
|
file_name: str | None = None,
|
|
|
|
|
|
|
|
) -> str:
|
|
|
|
|
|
|
|
suffix = _safe_suffix(file_name or "template.xlsx")
|
|
|
|
|
|
|
|
key = f"exchange/templates/{template.code}/{version}/{hashlib.sha256(content).hexdigest()}.{suffix}"
|
|
|
|
|
|
|
|
storage = get_exchange_storage(self.app)
|
|
|
|
|
|
|
|
storage.upload(BytesIO(content), key, _excel_mime_type())
|
|
|
|
|
|
|
|
return key
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_task(
|
|
|
|
def create_task(
|
|
|
|
self,
|
|
|
|
self,
|
|
|
|
*,
|
|
|
|
*,
|
|
|
|
|
|
|
|
biz_domain: str,
|
|
|
|
|
|
|
|
biz_obj: str,
|
|
|
|
|
|
|
|
operation: ExchangeOperation | str,
|
|
|
|
template_id: str | None = None,
|
|
|
|
template_id: str | None = None,
|
|
|
|
version_id: str | None = None,
|
|
|
|
version_id: str | None = None,
|
|
|
|
version: str | None = None,
|
|
|
|
version: str | None = None,
|
|
|
|
task_kind: ExchangeTaskKind | str,
|
|
|
|
|
|
|
|
requested_by: str | None = None,
|
|
|
|
requested_by: str | None = None,
|
|
|
|
storage_key: str | None = None,
|
|
|
|
storage_key: str | None = None,
|
|
|
|
input_payload: dict[str, Any] | None = None,
|
|
|
|
input_payload: dict[str, Any] | None = None,
|
|
|
|
meta: dict[str, Any] | None = None,
|
|
|
|
meta: dict[str, Any] | None = None,
|
|
|
|
) -> ExchangeTaskModel:
|
|
|
|
) -> ExchangeTaskModel:
|
|
|
|
template = self.get_template_or_404(template_id) if template_id else None
|
|
|
|
plan = self.resolve_plan(
|
|
|
|
version_model = self.get_version_or_404(version_id) if version_id else None
|
|
|
|
biz_domain=biz_domain,
|
|
|
|
if template is not None and version_model is not None and version_model.template_id != template.id:
|
|
|
|
biz_obj=biz_obj,
|
|
|
|
raise BizError("模板版本不属于该模板", code=400)
|
|
|
|
operation=operation,
|
|
|
|
if template is None and version_model is not None:
|
|
|
|
template_id=template_id,
|
|
|
|
template = self.get_template_or_404(version_model.template_id)
|
|
|
|
version_id=version_id,
|
|
|
|
if template is not None and version_model is None:
|
|
|
|
version=version,
|
|
|
|
if version:
|
|
|
|
|
|
|
|
version_model = self.get_snapshot(template_id=template.id, version=version)
|
|
|
|
|
|
|
|
elif template.current_version:
|
|
|
|
|
|
|
|
version_model = self.get_snapshot(
|
|
|
|
|
|
|
|
template_id=template.id,
|
|
|
|
|
|
|
|
version=template.current_version,
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
task = ExchangeTaskModel(
|
|
|
|
task = ExchangeTaskModel(
|
|
|
|
template_id=template.id if template is not None else None,
|
|
|
|
template_id=plan.template_id,
|
|
|
|
template_version_id=version_model.id if version_model is not None else None,
|
|
|
|
template_version_id=plan.version_id,
|
|
|
|
task_kind=_enum_value(task_kind),
|
|
|
|
biz_domain=plan.scope.biz_domain,
|
|
|
|
|
|
|
|
biz_obj=plan.scope.biz_obj,
|
|
|
|
|
|
|
|
operation=_operation_value(plan.scope.operation),
|
|
|
|
status="pending",
|
|
|
|
status="pending",
|
|
|
|
requested_by=requested_by,
|
|
|
|
requested_by=requested_by,
|
|
|
|
storage_key=storage_key,
|
|
|
|
storage_key=storage_key,
|
|
|
|
@ -241,6 +333,54 @@ class ExchangeService:
|
|
|
|
self.db.refresh(task)
|
|
|
|
self.db.refresh(task)
|
|
|
|
return task
|
|
|
|
return task
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run_task(self, task_id: str) -> ExchangeTaskModel:
|
|
|
|
|
|
|
|
task = self.mark_task_running(task_id)
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
snapshot = (
|
|
|
|
|
|
|
|
self.get_snapshot_by_version_id(task.template_version_id)
|
|
|
|
|
|
|
|
if task.template_version_id
|
|
|
|
|
|
|
|
else None
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
plan = snapshot or self.resolve_plan(
|
|
|
|
|
|
|
|
biz_domain=task.biz_domain,
|
|
|
|
|
|
|
|
biz_obj=task.biz_obj,
|
|
|
|
|
|
|
|
operation=task.operation,
|
|
|
|
|
|
|
|
template_id=task.template_id,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
handler = get_exchange_registry(self.app).get_scope_handler(
|
|
|
|
|
|
|
|
biz_domain=task.biz_domain,
|
|
|
|
|
|
|
|
biz_obj=task.biz_obj,
|
|
|
|
|
|
|
|
operation=task.operation,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if handler is None:
|
|
|
|
|
|
|
|
raise BizError("导入导出处理器未注册", code=404)
|
|
|
|
|
|
|
|
result = handler(
|
|
|
|
|
|
|
|
ExchangeTaskContext(
|
|
|
|
|
|
|
|
task_id=task.id,
|
|
|
|
|
|
|
|
plan=plan,
|
|
|
|
|
|
|
|
snapshot=snapshot,
|
|
|
|
|
|
|
|
storage_key=task.storage_key,
|
|
|
|
|
|
|
|
payload=task.input_payload,
|
|
|
|
|
|
|
|
requested_by=task.requested_by,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if isinstance(result, dict):
|
|
|
|
|
|
|
|
result = ExchangeTaskResult(**result)
|
|
|
|
|
|
|
|
return self.mark_task_finished(
|
|
|
|
|
|
|
|
task_id,
|
|
|
|
|
|
|
|
status="success",
|
|
|
|
|
|
|
|
message=result.message,
|
|
|
|
|
|
|
|
result_payload=result.result_payload,
|
|
|
|
|
|
|
|
success_count=result.success_count,
|
|
|
|
|
|
|
|
failed_count=result.failed_count,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
|
|
return self.mark_task_finished(
|
|
|
|
|
|
|
|
task_id,
|
|
|
|
|
|
|
|
status="failed",
|
|
|
|
|
|
|
|
message=str(exc),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def get_snapshot(self, *, template_id: str, version: str) -> ExchangeTemplateSnapshot | None:
|
|
|
|
def get_snapshot(self, *, template_id: str, version: str) -> ExchangeTemplateSnapshot | None:
|
|
|
|
version_model = self.db.scalar(
|
|
|
|
version_model = self.db.scalar(
|
|
|
|
select(ExchangeTemplateVersionModel)
|
|
|
|
select(ExchangeTemplateVersionModel)
|
|
|
|
@ -251,7 +391,9 @@ class ExchangeService:
|
|
|
|
return None
|
|
|
|
return None
|
|
|
|
return self.snapshot_from_model(version_model)
|
|
|
|
return self.snapshot_from_model(version_model)
|
|
|
|
|
|
|
|
|
|
|
|
def get_snapshot_by_version_id(self, version_id: str) -> ExchangeTemplateSnapshot | None:
|
|
|
|
def get_snapshot_by_version_id(self, version_id: str | None) -> ExchangeTemplateSnapshot | None:
|
|
|
|
|
|
|
|
if version_id is None:
|
|
|
|
|
|
|
|
return None
|
|
|
|
version_model = self.db.get(ExchangeTemplateVersionModel, version_id)
|
|
|
|
version_model = self.db.get(ExchangeTemplateVersionModel, version_id)
|
|
|
|
if version_model is None:
|
|
|
|
if version_model is None:
|
|
|
|
return None
|
|
|
|
return None
|
|
|
|
@ -266,57 +408,77 @@ class ExchangeService:
|
|
|
|
def resolve_plan(
|
|
|
|
def resolve_plan(
|
|
|
|
self,
|
|
|
|
self,
|
|
|
|
*,
|
|
|
|
*,
|
|
|
|
template_kind: ExchangeTemplateKind | str,
|
|
|
|
biz_domain: str,
|
|
|
|
|
|
|
|
biz_obj: str,
|
|
|
|
|
|
|
|
operation: ExchangeOperation | str,
|
|
|
|
template_id: str | None = None,
|
|
|
|
template_id: str | None = None,
|
|
|
|
version_id: str | None = None,
|
|
|
|
version_id: str | None = None,
|
|
|
|
version: str | None = None,
|
|
|
|
version: str | None = None,
|
|
|
|
bindings: list[ExchangeTemplateBinding] | None = None,
|
|
|
|
code: str | None = None,
|
|
|
|
fields: list[ExchangeField] | None = None,
|
|
|
|
name: str | None = None,
|
|
|
|
placeholders: list[ExchangePlaceholder] | None = None,
|
|
|
|
|
|
|
|
title: str | None = None,
|
|
|
|
|
|
|
|
description: str | None = None,
|
|
|
|
description: str | None = None,
|
|
|
|
sheet_name: str | None = None,
|
|
|
|
layout: ExchangeTemplateLayout | dict[str, Any] | None = None,
|
|
|
|
meta: dict[str, Any] | None = None,
|
|
|
|
variables: list[ExchangeVariable] | None = None,
|
|
|
|
source: Any | None = None,
|
|
|
|
source: Any | None = None,
|
|
|
|
) -> ExchangePlan:
|
|
|
|
) -> ExchangeTemplatePlan:
|
|
|
|
if source is not None:
|
|
|
|
if source is not None:
|
|
|
|
return source.resolve_plan(
|
|
|
|
return source.resolve_plan(
|
|
|
|
template_kind=template_kind,
|
|
|
|
biz_domain=biz_domain,
|
|
|
|
|
|
|
|
biz_obj=biz_obj,
|
|
|
|
|
|
|
|
operation=operation,
|
|
|
|
template_id=template_id,
|
|
|
|
template_id=template_id,
|
|
|
|
version_id=version_id,
|
|
|
|
version_id=version_id,
|
|
|
|
version=version,
|
|
|
|
version=version,
|
|
|
|
bindings=bindings,
|
|
|
|
code=code,
|
|
|
|
fields=fields,
|
|
|
|
name=name,
|
|
|
|
placeholders=placeholders,
|
|
|
|
|
|
|
|
title=title,
|
|
|
|
|
|
|
|
description=description,
|
|
|
|
description=description,
|
|
|
|
sheet_name=sheet_name,
|
|
|
|
layout=layout,
|
|
|
|
meta=meta,
|
|
|
|
variables=variables,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if version_id:
|
|
|
|
if version_id:
|
|
|
|
snapshot = self.get_snapshot_by_version_id(version_id)
|
|
|
|
snapshot = self.get_snapshot_by_version_id(version_id)
|
|
|
|
if snapshot is not None:
|
|
|
|
if snapshot is not None:
|
|
|
|
return snapshot.to_plan()
|
|
|
|
return snapshot
|
|
|
|
if template_id and version:
|
|
|
|
if template_id and version:
|
|
|
|
snapshot = self.get_snapshot(template_id=template_id, version=version)
|
|
|
|
snapshot = self.get_snapshot(template_id=template_id, version=version)
|
|
|
|
if snapshot is not None:
|
|
|
|
if snapshot is not None:
|
|
|
|
return snapshot.to_plan()
|
|
|
|
return snapshot
|
|
|
|
if template_id:
|
|
|
|
if template_id:
|
|
|
|
current = self.get_current_snapshot(template_id)
|
|
|
|
current = self.get_current_snapshot(template_id)
|
|
|
|
if current is not None:
|
|
|
|
if current is not None:
|
|
|
|
return current.to_plan()
|
|
|
|
return current
|
|
|
|
return ExchangePlan.from_mapping(
|
|
|
|
template = self.get_template_or_404(template_id)
|
|
|
|
template_kind=template_kind,
|
|
|
|
return self.plan_from_template_model(template)
|
|
|
|
template_id=template_id,
|
|
|
|
|
|
|
|
version_id=version_id,
|
|
|
|
template = self.get_template_by_scope(
|
|
|
|
version=version,
|
|
|
|
biz_domain=biz_domain,
|
|
|
|
bindings=bindings,
|
|
|
|
biz_obj=biz_obj,
|
|
|
|
fields=fields,
|
|
|
|
operation=operation,
|
|
|
|
placeholders=placeholders,
|
|
|
|
code=code,
|
|
|
|
title=title,
|
|
|
|
)
|
|
|
|
|
|
|
|
if template is not None:
|
|
|
|
|
|
|
|
current = self.get_current_snapshot(template.id)
|
|
|
|
|
|
|
|
if current is not None:
|
|
|
|
|
|
|
|
return current
|
|
|
|
|
|
|
|
return self.plan_from_template_model(template)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
spec = get_exchange_registry(self.app).get_spec(
|
|
|
|
|
|
|
|
biz_domain=biz_domain,
|
|
|
|
|
|
|
|
biz_obj=biz_obj,
|
|
|
|
|
|
|
|
operation=_operation_value(operation),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if spec is not None:
|
|
|
|
|
|
|
|
return spec.to_plan()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return ExchangeTemplatePlan.from_mapping(
|
|
|
|
|
|
|
|
biz_domain=biz_domain,
|
|
|
|
|
|
|
|
biz_obj=biz_obj,
|
|
|
|
|
|
|
|
operation=operation,
|
|
|
|
|
|
|
|
code=code,
|
|
|
|
|
|
|
|
name=name,
|
|
|
|
description=description,
|
|
|
|
description=description,
|
|
|
|
sheet_name=sheet_name,
|
|
|
|
layout=layout,
|
|
|
|
meta=meta,
|
|
|
|
variables=variables,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def mark_task_running(self, task_id: str) -> ExchangeTaskModel:
|
|
|
|
def mark_task_running(self, task_id: str) -> ExchangeTaskModel:
|
|
|
|
@ -381,6 +543,29 @@ class ExchangeService:
|
|
|
|
raise BizError("模板不存在", code=404)
|
|
|
|
raise BizError("模板不存在", code=404)
|
|
|
|
return template
|
|
|
|
return template
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_template_by_scope(
|
|
|
|
|
|
|
|
self,
|
|
|
|
|
|
|
|
*,
|
|
|
|
|
|
|
|
biz_domain: str,
|
|
|
|
|
|
|
|
biz_obj: str,
|
|
|
|
|
|
|
|
operation: ExchangeOperation | str,
|
|
|
|
|
|
|
|
code: str | None = None,
|
|
|
|
|
|
|
|
) -> ExchangeTemplateModel | None:
|
|
|
|
|
|
|
|
statement = (
|
|
|
|
|
|
|
|
select(ExchangeTemplateModel)
|
|
|
|
|
|
|
|
.where(ExchangeTemplateModel.biz_domain == biz_domain)
|
|
|
|
|
|
|
|
.where(ExchangeTemplateModel.biz_obj == biz_obj)
|
|
|
|
|
|
|
|
.where(ExchangeTemplateModel.operation == _operation_value(operation))
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if code is not None:
|
|
|
|
|
|
|
|
statement = statement.where(ExchangeTemplateModel.code == code)
|
|
|
|
|
|
|
|
return self.db.scalar(
|
|
|
|
|
|
|
|
statement.order_by(
|
|
|
|
|
|
|
|
ExchangeTemplateModel.created_at.desc(),
|
|
|
|
|
|
|
|
ExchangeTemplateModel.updated_at.desc(),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def get_version_or_404(self, version_id: str) -> ExchangeTemplateVersionModel:
|
|
|
|
def get_version_or_404(self, version_id: str) -> ExchangeTemplateVersionModel:
|
|
|
|
version = self.db.get(ExchangeTemplateVersionModel, version_id)
|
|
|
|
version = self.db.get(ExchangeTemplateVersionModel, version_id)
|
|
|
|
if version is None:
|
|
|
|
if version is None:
|
|
|
|
@ -393,11 +578,26 @@ class ExchangeService:
|
|
|
|
raise BizError("导入导出任务不存在", code=404)
|
|
|
|
raise BizError("导入导出任务不存在", code=404)
|
|
|
|
return task
|
|
|
|
return task
|
|
|
|
|
|
|
|
|
|
|
|
def list_templates(self) -> list[ExchangeTemplateModel]:
|
|
|
|
def list_templates(
|
|
|
|
|
|
|
|
self,
|
|
|
|
|
|
|
|
*,
|
|
|
|
|
|
|
|
biz_domain: str | None = None,
|
|
|
|
|
|
|
|
biz_obj: str | None = None,
|
|
|
|
|
|
|
|
operation: str | None = None,
|
|
|
|
|
|
|
|
) -> list[ExchangeTemplateModel]:
|
|
|
|
|
|
|
|
statement = select(ExchangeTemplateModel)
|
|
|
|
|
|
|
|
if biz_domain is not None:
|
|
|
|
|
|
|
|
statement = statement.where(ExchangeTemplateModel.biz_domain == biz_domain)
|
|
|
|
|
|
|
|
if biz_obj is not None:
|
|
|
|
|
|
|
|
statement = statement.where(ExchangeTemplateModel.biz_obj == biz_obj)
|
|
|
|
|
|
|
|
if operation is not None:
|
|
|
|
|
|
|
|
statement = statement.where(ExchangeTemplateModel.operation == operation)
|
|
|
|
return list(
|
|
|
|
return list(
|
|
|
|
self.db.scalars(
|
|
|
|
self.db.scalars(
|
|
|
|
select(ExchangeTemplateModel).order_by(
|
|
|
|
statement.order_by(
|
|
|
|
ExchangeTemplateModel.entity,
|
|
|
|
ExchangeTemplateModel.biz_domain,
|
|
|
|
|
|
|
|
ExchangeTemplateModel.biz_obj,
|
|
|
|
|
|
|
|
ExchangeTemplateModel.operation,
|
|
|
|
ExchangeTemplateModel.code,
|
|
|
|
ExchangeTemplateModel.code,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
@ -408,7 +608,10 @@ class ExchangeService:
|
|
|
|
self.db.scalars(
|
|
|
|
self.db.scalars(
|
|
|
|
select(ExchangeTemplateVersionModel)
|
|
|
|
select(ExchangeTemplateVersionModel)
|
|
|
|
.where(ExchangeTemplateVersionModel.template_id == template_id)
|
|
|
|
.where(ExchangeTemplateVersionModel.template_id == template_id)
|
|
|
|
.order_by(ExchangeTemplateVersionModel.version)
|
|
|
|
.order_by(
|
|
|
|
|
|
|
|
ExchangeTemplateVersionModel.created_at.desc(),
|
|
|
|
|
|
|
|
ExchangeTemplateVersionModel.updated_at.desc(),
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@ -427,65 +630,78 @@ class ExchangeService:
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def plan_from_template_model(self, template: ExchangeTemplateModel) -> ExchangeTemplatePlan:
|
|
|
|
|
|
|
|
spec = get_exchange_registry(self.app).get_spec(
|
|
|
|
|
|
|
|
biz_domain=template.biz_domain,
|
|
|
|
|
|
|
|
biz_obj=template.biz_obj,
|
|
|
|
|
|
|
|
operation=template.operation,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
variables = tuple(spec.variables) if spec is not None else ()
|
|
|
|
|
|
|
|
return ExchangeTemplatePlan(
|
|
|
|
|
|
|
|
scope=ExchangeScope.from_mapping(
|
|
|
|
|
|
|
|
biz_domain=template.biz_domain,
|
|
|
|
|
|
|
|
biz_obj=template.biz_obj,
|
|
|
|
|
|
|
|
operation=template.operation,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
code=template.code,
|
|
|
|
|
|
|
|
name=template.name,
|
|
|
|
|
|
|
|
description=template.description,
|
|
|
|
|
|
|
|
template_id=template.id,
|
|
|
|
|
|
|
|
version=template.current_version,
|
|
|
|
|
|
|
|
layout=_coerce_layout(template.layout),
|
|
|
|
|
|
|
|
variables=variables,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def snapshot_from_model(
|
|
|
|
def snapshot_from_model(
|
|
|
|
self, version: ExchangeTemplateVersionModel
|
|
|
|
self, version: ExchangeTemplateVersionModel
|
|
|
|
) -> ExchangeTemplateSnapshot:
|
|
|
|
) -> ExchangeTemplateSnapshot:
|
|
|
|
|
|
|
|
template = self.get_template_or_404(version.template_id)
|
|
|
|
return ExchangeTemplateSnapshot(
|
|
|
|
return ExchangeTemplateSnapshot(
|
|
|
|
id=version.id,
|
|
|
|
scope=ExchangeScope.from_mapping(
|
|
|
|
version=version.version,
|
|
|
|
biz_domain=version.biz_domain,
|
|
|
|
|
|
|
|
biz_obj=version.biz_obj,
|
|
|
|
|
|
|
|
operation=version.operation,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
code=template.code,
|
|
|
|
|
|
|
|
name=template.name,
|
|
|
|
|
|
|
|
description=template.description,
|
|
|
|
template_id=version.template_id,
|
|
|
|
template_id=version.template_id,
|
|
|
|
template_kind=ExchangeTemplateKind(version.template_kind),
|
|
|
|
version_id=version.id,
|
|
|
|
bindings=tuple(_binding_from_dict(item) for item in version.bindings),
|
|
|
|
version=version.version,
|
|
|
|
|
|
|
|
layout=_coerce_layout(version.layout),
|
|
|
|
|
|
|
|
variables=tuple(_variable_from_dict(item) for item in version.variables),
|
|
|
|
published_at=version.published_at.isoformat() if version.published_at else None,
|
|
|
|
published_at=version.published_at.isoformat() if version.published_at else None,
|
|
|
|
file_key=version.file_key,
|
|
|
|
file_key=version.file_key,
|
|
|
|
checksum=version.checksum,
|
|
|
|
checksum=version.checksum,
|
|
|
|
fields=tuple(_field_from_dict(item) for item in version.fields),
|
|
|
|
|
|
|
|
placeholders=tuple(_placeholder_from_dict(item) for item in version.placeholders),
|
|
|
|
|
|
|
|
meta=version.meta,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _field_from_dict(value: dict[str, Any]) -> ExchangeField:
|
|
|
|
|
|
|
|
options = value.get("options") or ()
|
|
|
|
|
|
|
|
return ExchangeField(
|
|
|
|
|
|
|
|
key=value["key"],
|
|
|
|
|
|
|
|
label=value["label"],
|
|
|
|
|
|
|
|
placeholder=value.get("placeholder"),
|
|
|
|
|
|
|
|
required=bool(value.get("required", False)),
|
|
|
|
|
|
|
|
example=value.get("example"),
|
|
|
|
|
|
|
|
width=value.get("width"),
|
|
|
|
|
|
|
|
format=value.get("format"),
|
|
|
|
|
|
|
|
source=value.get("source"),
|
|
|
|
|
|
|
|
target=value.get("target"),
|
|
|
|
|
|
|
|
options=tuple(tuple(item) for item in options),
|
|
|
|
|
|
|
|
meta=value.get("meta") or {},
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _placeholder_from_dict(value: dict[str, Any]) -> ExchangePlaceholder:
|
|
|
|
def _variable_from_dict(value: dict[str, Any]) -> ExchangeVariable:
|
|
|
|
return ExchangePlaceholder(
|
|
|
|
return ExchangeVariable(
|
|
|
|
key=value["key"],
|
|
|
|
key=value["key"],
|
|
|
|
label=value["label"],
|
|
|
|
label=value["label"],
|
|
|
|
|
|
|
|
header=value.get("header"),
|
|
|
|
description=value.get("description"),
|
|
|
|
description=value.get("description"),
|
|
|
|
required=bool(value.get("required", False)),
|
|
|
|
required=bool(value.get("required", False)),
|
|
|
|
example=value.get("example"),
|
|
|
|
example=value.get("example"),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _binding_from_dict(value: dict[str, Any]) -> ExchangeTemplateBinding:
|
|
|
|
def _coerce_layout(value: ExchangeTemplateLayout | dict[str, Any] | None) -> ExchangeTemplateLayout:
|
|
|
|
return ExchangeTemplateBinding(
|
|
|
|
if value is None:
|
|
|
|
entity=value["entity"],
|
|
|
|
return ExchangeTemplateLayout()
|
|
|
|
template_kind=ExchangeTemplateKind(value["template_kind"]),
|
|
|
|
if isinstance(value, ExchangeTemplateLayout):
|
|
|
|
handler=value.get("handler"),
|
|
|
|
return value
|
|
|
|
description=value.get("description"),
|
|
|
|
return ExchangeTemplateLayout(
|
|
|
|
default_sheet_name=value.get("default_sheet_name"),
|
|
|
|
|
|
|
|
default_file_name=value.get("default_file_name"),
|
|
|
|
|
|
|
|
title=value.get("title"),
|
|
|
|
title=value.get("title"),
|
|
|
|
meta=value.get("meta") or {},
|
|
|
|
sheet_name=value.get("sheet_name"),
|
|
|
|
|
|
|
|
title_row=value.get("title_row", 1),
|
|
|
|
|
|
|
|
header_row=int(value.get("header_row") or 2),
|
|
|
|
|
|
|
|
data_start_row=value.get("data_start_row"),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _enum_value(value: Any) -> str:
|
|
|
|
def _operation_value(value: ExchangeOperation | str) -> str:
|
|
|
|
return value.value if hasattr(value, "value") else str(value)
|
|
|
|
return value.value if hasattr(value, "value") else str(value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|