Files
mai-bot/src/common/database/migrations/progress.py
DrSmoothl c2c992ff01 feat(database-migrations): implement database migration manager and related components
- Add DatabaseMigrationManager for orchestrating database migrations, including planning and executing migration steps.
- Introduce models for migration state, execution context, and migration steps.
- Implement MigrationPlanner to generate migration plans based on current and target versions.
- Create MigrationRegistry for registering and managing migration steps.
- Develop SchemaVersionResolver to determine the current database schema version.
- Add SQLiteSchemaInspector for inspecting SQLite database structures.
- Implement progress reporting tools using rich for visualizing migration progress.
- Introduce SQLiteUserVersionStore for managing schema version storage in SQLite.
2026-03-31 09:16:25 +08:00

273 lines
7.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""数据库迁移进度展示工具。"""
from __future__ import annotations
from abc import ABC, abstractmethod
from datetime import timedelta
from typing import Optional
from rich.console import Console
from rich.progress import BarColumn, Progress, ProgressColumn, Task, TaskID
from rich.text import Text
def _format_duration(total_seconds: Optional[float]) -> str:
"""将秒数格式化为适合展示的耗时文本。
Args:
total_seconds: 总秒数;为空时表示暂不可用。
Returns:
str: 格式化后的耗时文本。
"""
if total_seconds is None:
return "--:--:--"
safe_seconds = max(total_seconds, 0.0)
return str(timedelta(seconds=int(safe_seconds)))
class MigrationSummaryColumn(ProgressColumn):
"""渲染数据库迁移总进度摘要列。"""
def render(self, task: Task) -> Text:
"""渲染当前任务的总进度摘要。
Args:
task: 当前进度任务对象。
Returns:
Text: 渲染后的摘要文本。
"""
display_total = task.fields.get("display_total", task.total)
total_text = "?" if display_total is None else str(int(display_total))
completed_text = str(int(task.completed))
return Text(f"总迁移进度({completed_text}/{total_text}")
class MigrationSpeedColumn(ProgressColumn):
"""渲染数据库迁移速度列。"""
def render(self, task: Task) -> Text:
"""渲染当前任务的速度信息。
Args:
task: 当前进度任务对象。
Returns:
Text: 渲染后的速度文本。
"""
unit_name = str(task.fields.get("unit_name", ""))
if task.speed is None or task.speed <= 0:
return Text(f"-- {unit_name}/s")
return Text(f"{task.speed:.2f} {unit_name}/s")
class MigrationElapsedColumn(ProgressColumn):
"""渲染数据库迁移已用时间列。"""
def render(self, task: Task) -> Text:
"""渲染当前任务的已用时间。
Args:
task: 当前进度任务对象。
Returns:
Text: 渲染后的已用时间文本。
"""
return Text(f"已用时间 {_format_duration(task.elapsed)}")
class MigrationRemainingColumn(ProgressColumn):
"""渲染数据库迁移预估剩余时间列。"""
def render(self, task: Task) -> Text:
"""渲染当前任务的预估剩余时间。
Args:
task: 当前进度任务对象。
Returns:
Text: 渲染后的预估剩余时间文本。
"""
return Text(f"预估时间 {_format_duration(task.time_remaining)}")
class BaseMigrationProgressReporter(ABC):
"""数据库迁移进度上报器基类。"""
def __enter__(self) -> "BaseMigrationProgressReporter":
"""进入进度上报上下文。
Returns:
BaseMigrationProgressReporter: 当前上报器实例。
"""
self.open()
return self
def __exit__(self, exc_type, exc_value, traceback) -> None:
"""退出进度上报上下文。
Args:
exc_type: 异常类型。
exc_value: 异常实例。
traceback: 异常追踪对象。
"""
del exc_type, exc_value, traceback
self.close()
@abstractmethod
def open(self) -> None:
"""打开进度上报资源。"""
@abstractmethod
def close(self) -> None:
"""关闭进度上报资源。"""
@abstractmethod
def start(
self,
total: int,
description: str = "总迁移进度",
unit_name: str = "",
) -> None:
"""启动一个新的迁移进度任务。
Args:
total: 任务总数。
description: 任务描述。
unit_name: 进度单位名称。
"""
@abstractmethod
def advance(self, advance: int = 1, item_name: Optional[str] = None) -> None:
"""推进当前迁移进度任务。
Args:
advance: 本次推进的步数。
item_name: 当前完成的项目名称。
"""
class NullMigrationProgressReporter(BaseMigrationProgressReporter):
"""不输出任何内容的空进度上报器。"""
def close(self) -> None:
"""关闭空进度上报器。"""
def start(
self,
total: int,
description: str = "总迁移进度",
unit_name: str = "",
) -> None:
"""启动空进度任务。
Args:
total: 任务总数。
description: 任务描述。
unit_name: 进度单位名称。
"""
del total, description, unit_name
def advance(self, advance: int = 1, item_name: Optional[str] = None) -> None:
"""推进空进度任务。
Args:
advance: 本次推进的步数。
item_name: 当前完成的项目名称。
"""
del advance, item_name
class RichMigrationProgressReporter(BaseMigrationProgressReporter):
"""基于 ``rich`` 的数据库迁移进度上报器。"""
def __init__(
self,
console: Optional[Console] = None,
disable: Optional[bool] = None,
refresh_per_second: int = 10,
) -> None:
"""初始化 ``rich`` 迁移进度上报器。
Args:
console: 输出使用的 ``rich`` 控制台。
disable: 是否禁用进度条;为空时根据终端能力自动判断。
refresh_per_second: 每秒刷新次数。
"""
self.console = console or Console()
self.disable = disable
self.refresh_per_second = refresh_per_second
self._progress: Optional[Progress] = None
self._task_id: Optional[TaskID] = None
def open(self) -> None:
"""打开 ``rich`` 进度条资源。"""
effective_disable = not self.console.is_terminal if self.disable is None else self.disable
self._progress = Progress(
MigrationSummaryColumn(),
BarColumn(),
MigrationSpeedColumn(),
MigrationElapsedColumn(),
MigrationRemainingColumn(),
console=self.console,
transient=False,
disable=effective_disable,
refresh_per_second=self.refresh_per_second,
expand=True,
)
self._progress.start()
def close(self) -> None:
"""关闭 ``rich`` 进度条资源。"""
if self._progress is None:
return
self._progress.stop()
self._progress = None
self._task_id = None
def start(
self,
total: int,
description: str = "总迁移进度",
unit_name: str = "",
) -> None:
"""启动一个新的 ``rich`` 迁移进度任务。
Args:
total: 任务总数。
description: 任务描述。
unit_name: 进度单位名称。
"""
if self._progress is None:
self.open()
assert self._progress is not None
effective_total = max(total, 1)
self._task_id = self._progress.add_task(
description,
total=effective_total,
display_total=total,
unit_name=unit_name,
)
def advance(self, advance: int = 1, item_name: Optional[str] = None) -> None:
"""推进当前 ``rich`` 迁移进度任务。
Args:
advance: 本次推进的步数。
item_name: 当前完成的项目名称。
"""
del item_name
if self._progress is None or self._task_id is None:
return
self._progress.update(self._task_id, advance=advance)
def create_rich_migration_progress_reporter() -> BaseMigrationProgressReporter:
"""创建默认的 ``rich`` 迁移进度上报器。
Returns:
BaseMigrationProgressReporter: 默认迁移进度上报器实例。
"""
return RichMigrationProgressReporter()