202 lines
6.9 KiB
Python
202 lines
6.9 KiB
Python
|
|
"""领域模型(dataclass) ↔ Output DTO(Pydantic) 转换。
|
|||
|
|
|
|||
|
|
规范 §6.1/§7.3(对齐 ``external_systems/use_cases/mappers.py``):
|
|||
|
|
- persistence 返回领域模型 dataclass,service 返回 Output DTO
|
|||
|
|
- scheduler 域无敏感字段,mapper 仅做字段映射与 DateTime → ISO 字符串格式化
|
|||
|
|
- use_cases 层不调用 ORM 的 ``to_dict`` 方法
|
|||
|
|
|
|||
|
|
DateTime 字段(``run_at`` / ``last_run_at`` / ``next_run_at`` / ``started_at`` /
|
|||
|
|
``finished_at`` / ``created_at`` / ``updated_at`` / ``deleted_at``)在 dataclass
|
|||
|
|
中以 naive UTC ``datetime`` 表达,mapper 通过 ``format_utc_datetime`` 统一格式化
|
|||
|
|
为 ISO 8601 字符串写入 Output DTO。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
from typing import Any
|
|||
|
|
|
|||
|
|
from yuxi.scheduler.core.models import (
|
|||
|
|
DailyStat,
|
|||
|
|
HandlerSummary,
|
|||
|
|
ScheduledTask,
|
|||
|
|
ScheduledTaskRunLog,
|
|||
|
|
)
|
|||
|
|
from yuxi.scheduler.use_cases.dto.scheduler import (
|
|||
|
|
CountByStatusOutput,
|
|||
|
|
DailyStatOutput,
|
|||
|
|
GetHealthOutput,
|
|||
|
|
HandlerSummaryOutput,
|
|||
|
|
ListDailyStatsOutput,
|
|||
|
|
ListHandlerSummaryOutput,
|
|||
|
|
ListUpcomingOutput,
|
|||
|
|
RunLogOutput,
|
|||
|
|
TaskOutput,
|
|||
|
|
)
|
|||
|
|
from yuxi.utils.datetime_utils import format_utc_datetime
|
|||
|
|
|
|||
|
|
__all__ = [
|
|||
|
|
"daily_stat_to_dict",
|
|||
|
|
"to_count_by_status_output",
|
|||
|
|
"to_daily_stat_output",
|
|||
|
|
"to_get_health_output",
|
|||
|
|
"to_handler_summary_output",
|
|||
|
|
"to_list_daily_stats_output",
|
|||
|
|
"to_run_log_output",
|
|||
|
|
"to_task_output",
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
|
|||
|
|
def to_task_output(task: ScheduledTask) -> TaskOutput:
|
|||
|
|
"""``ScheduledTask`` dataclass → ``TaskOutput`` DTO。
|
|||
|
|
|
|||
|
|
DateTime 字段通过 ``format_utc_datetime`` 格式化为 ISO 8601 字符串。
|
|||
|
|
"""
|
|||
|
|
return TaskOutput(
|
|||
|
|
task_id=task.task_id,
|
|||
|
|
handler_name=task.handler_name,
|
|||
|
|
owner_scope=task.owner_scope,
|
|||
|
|
owner_id=task.owner_id,
|
|||
|
|
schedule_kind=task.schedule_kind,
|
|||
|
|
cron_expression=task.cron_expression,
|
|||
|
|
run_at=format_utc_datetime(task.run_at),
|
|||
|
|
tz=task.tz,
|
|||
|
|
payload=task.payload or {},
|
|||
|
|
enabled=task.enabled,
|
|||
|
|
delete_after_run=task.delete_after_run,
|
|||
|
|
block_strategy=task.block_strategy,
|
|||
|
|
stagger_seconds=task.stagger_seconds,
|
|||
|
|
consecutive_errors=task.consecutive_errors,
|
|||
|
|
status=task.status,
|
|||
|
|
last_run_at=format_utc_datetime(task.last_run_at),
|
|||
|
|
next_run_at=format_utc_datetime(task.next_run_at),
|
|||
|
|
last_error=task.last_error,
|
|||
|
|
created_by=task.created_by,
|
|||
|
|
updated_by=task.updated_by,
|
|||
|
|
id=task.id,
|
|||
|
|
created_at=format_utc_datetime(task.created_at),
|
|||
|
|
updated_at=format_utc_datetime(task.updated_at),
|
|||
|
|
is_deleted=task.is_deleted,
|
|||
|
|
deleted_at=format_utc_datetime(task.deleted_at),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def to_run_log_output(log: ScheduledTaskRunLog) -> RunLogOutput:
|
|||
|
|
"""``ScheduledTaskRunLog`` dataclass → ``RunLogOutput`` DTO。"""
|
|||
|
|
return RunLogOutput(
|
|||
|
|
task_id=log.task_id,
|
|||
|
|
run_id=log.run_id,
|
|||
|
|
triggered_by=log.triggered_by,
|
|||
|
|
status=log.status,
|
|||
|
|
error_message=log.error_message,
|
|||
|
|
output=log.output,
|
|||
|
|
started_at=format_utc_datetime(log.started_at),
|
|||
|
|
finished_at=format_utc_datetime(log.finished_at),
|
|||
|
|
created_by=log.created_by,
|
|||
|
|
updated_by=log.updated_by,
|
|||
|
|
id=log.id,
|
|||
|
|
created_at=format_utc_datetime(log.created_at),
|
|||
|
|
updated_at=format_utc_datetime(log.updated_at),
|
|||
|
|
is_deleted=log.is_deleted,
|
|||
|
|
deleted_at=format_utc_datetime(log.deleted_at),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def to_handler_summary_output(summary: HandlerSummary) -> HandlerSummaryOutput:
|
|||
|
|
"""``HandlerSummary`` dataclass → ``HandlerSummaryOutput`` DTO。
|
|||
|
|
|
|||
|
|
``description`` 字段仓储层不聚合(DB 无此列),由调用方按需补充;
|
|||
|
|
``active_task_count`` 同理,默认 0,由调用方按需补充。
|
|||
|
|
"""
|
|||
|
|
return HandlerSummaryOutput(
|
|||
|
|
handler_name=summary.name,
|
|||
|
|
task_count=summary.task_count,
|
|||
|
|
last_active_at=format_utc_datetime(summary.last_active_at),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def to_count_by_status_output(dist: dict[str, int]) -> CountByStatusOutput:
|
|||
|
|
"""状态分布字典 → ``CountByStatusOutput`` DTO。
|
|||
|
|
|
|||
|
|
仓储层 ``count_by_status`` 返回 ``{status: count}``,未出现的状态不在字典中,
|
|||
|
|
这里统一补 0。
|
|||
|
|
"""
|
|||
|
|
return CountByStatusOutput(
|
|||
|
|
active=dist.get("active", 0),
|
|||
|
|
paused=dist.get("paused", 0),
|
|||
|
|
dead_letter=dist.get("dead_letter", 0),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def to_get_health_output(
|
|||
|
|
*,
|
|||
|
|
last_tick_at: Any,
|
|||
|
|
active_task_count: int,
|
|||
|
|
dead_letter_count: int,
|
|||
|
|
running_count: int,
|
|||
|
|
healthy: bool,
|
|||
|
|
) -> GetHealthOutput:
|
|||
|
|
"""构造 ``GetHealthOutput`` DTO。
|
|||
|
|
|
|||
|
|
``healthy`` 由调用方按"最近 tick 是否在 5 分钟内"判断后传入。
|
|||
|
|
"""
|
|||
|
|
return GetHealthOutput(
|
|||
|
|
status="healthy" if healthy else "unhealthy",
|
|||
|
|
last_tick_at=format_utc_datetime(last_tick_at),
|
|||
|
|
active_task_count=active_task_count,
|
|||
|
|
dead_letter_count=dead_letter_count,
|
|||
|
|
running_count=running_count,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def daily_stat_to_dict(stat: DailyStat) -> dict[str, Any]:
|
|||
|
|
"""``DailyStat`` dataclass → dict(供指标导出使用)。
|
|||
|
|
|
|||
|
|
指标服务(``SchedulerMetricService``)从 ``DailyStat`` 提取字段构造
|
|||
|
|
Prometheus 文本格式,此处提供统一的 dict 转换避免重复字段读取。
|
|||
|
|
"""
|
|||
|
|
return {
|
|||
|
|
"stat_date": stat.stat_date,
|
|||
|
|
"handler_name": stat.handler_name,
|
|||
|
|
"success_count": stat.success_count,
|
|||
|
|
"failure_count": stat.failure_count,
|
|||
|
|
"timeout_count": stat.timeout_count,
|
|||
|
|
"dead_letter_count": stat.dead_letter_count,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def to_daily_stat_output(stat: DailyStat) -> DailyStatOutput:
|
|||
|
|
"""``DailyStat`` dataclass → ``DailyStatOutput`` DTO。
|
|||
|
|
|
|||
|
|
计算派生字段 ``total_count`` / ``success_rate``,``stat_date`` 格式化为
|
|||
|
|
ISO 日期字符串(``YYYY-MM-DD``)。
|
|||
|
|
"""
|
|||
|
|
total_count = stat.success_count + stat.failure_count + stat.timeout_count
|
|||
|
|
success_rate = stat.success_count / total_count if total_count > 0 else 0.0
|
|||
|
|
return DailyStatOutput(
|
|||
|
|
stat_date=stat.stat_date.isoformat(),
|
|||
|
|
handler_name=stat.handler_name,
|
|||
|
|
success_count=stat.success_count,
|
|||
|
|
failure_count=stat.failure_count,
|
|||
|
|
timeout_count=stat.timeout_count,
|
|||
|
|
dead_letter_count=stat.dead_letter_count,
|
|||
|
|
total_count=total_count,
|
|||
|
|
success_rate=success_rate,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def to_list_daily_stats_output(stats: list[DailyStat]) -> ListDailyStatsOutput:
|
|||
|
|
"""``DailyStat`` 列表 → ``ListDailyStatsOutput`` DTO。"""
|
|||
|
|
return ListDailyStatsOutput(items=[to_daily_stat_output(s) for s in stats])
|
|||
|
|
|
|||
|
|
|
|||
|
|
def to_list_upcoming_output(tasks: list[ScheduledTask]) -> ListUpcomingOutput:
|
|||
|
|
"""``ScheduledTask`` 列表 → ``ListUpcomingOutput`` DTO。"""
|
|||
|
|
return ListUpcomingOutput(items=[to_task_output(t) for t in tasks])
|
|||
|
|
|
|||
|
|
|
|||
|
|
def to_list_handler_summary_output(
|
|||
|
|
summaries: list[HandlerSummary],
|
|||
|
|
) -> ListHandlerSummaryOutput:
|
|||
|
|
"""``HandlerSummary`` 列表 → ``ListHandlerSummaryOutput`` DTO。"""
|
|||
|
|
return ListHandlerSummaryOutput(items=[to_handler_summary_output(s) for s in summaries])
|