ShiErFeng/shierfeng-fastapi-backend/cli/runtime/db/support.py
2026-07-24 09:43:10 +08:00

161 lines
5.6 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.

import os
import subprocess
from typing import Any
from cli.exit_codes import DATABASE_ERROR
from cli.runtime.base import RuntimeEnvironmentService
from .gateway import DatabaseInfrastructureGateway
class DatabaseRevisionSupport:
"""
数据库迁移版本支持对象。
该对象负责 Alembic script directory 构建、修订版本规整和
版本对象序列化,避免主运行时服务继续承载迁移元数据细节。
:param infrastructure_gateway: 数据库基础设施网关
:param runtime_environment: 运行时环境服务
"""
def __init__(
self,
infrastructure_gateway: DatabaseInfrastructureGateway,
runtime_environment: RuntimeEnvironmentService,
) -> None:
"""
初始化数据库迁移版本支持对象。
:param infrastructure_gateway: 数据库基础设施网关
:param runtime_environment: 运行时环境服务
:return: None
"""
self.infrastructure_gateway = infrastructure_gateway
self.runtime_environment = runtime_environment
def build_alembic_script_directory(self) -> Any:
"""
基于当前后端目录构建 Alembic 脚本目录对象。
:return: Alembic 脚本目录对象
"""
config_class = self.infrastructure_gateway.get_alembic_config_class()
script_directory_class = self.infrastructure_gateway.get_alembic_script_directory_class()
config = config_class(os.path.join(self.runtime_environment.get_backend_dir(), 'alembic.ini'))
return script_directory_class.from_config(config)
@staticmethod
def normalize_revision_value(value: Any) -> list[str]:
"""
规范化 Alembic 修订版本引用值。
:param value: 原始修订版本引用
:return: 规范化后的修订版本列表
"""
if value is None:
return []
if isinstance(value, str):
return [value]
return [str(item) for item in value]
def serialize_revision(self, script_revision: Any) -> dict[str, Any]:
"""
序列化 Alembic 修订版本对象。
:param script_revision: Alembic 修订版本对象
:return: 序列化后的修订版本字典
"""
return {
'revision': str(script_revision.revision),
'downRevisions': self.normalize_revision_value(script_revision.down_revision),
'branchLabels': sorted(str(item) for item in script_revision.branch_labels or []),
'dependsOn': self.normalize_revision_value(script_revision.dependencies),
'doc': (script_revision.doc or '').strip(),
'path': str(script_revision.path),
}
class DatabaseAlembicCommandSupport:
"""
数据库 Alembic 命令支持对象。
该对象负责 Alembic 命令组装与子进程执行,
避免主运行时服务继续承载命令行桥接细节。
:param runtime_environment: 运行时环境服务
"""
def __init__(self, runtime_environment: RuntimeEnvironmentService) -> None:
"""
初始化数据库 Alembic 命令支持对象。
:param runtime_environment: 运行时环境服务
:return: None
"""
self.runtime_environment = runtime_environment
def build_alembic_command(self, command: str, *arguments: str) -> list[str]:
"""
构建 Alembic 命令。
:param command: Alembic 子命令名称
:param arguments: Alembic 子命令参数列表
:return: Alembic 命令参数列表
"""
alembic_ini_path = os.path.join(self.runtime_environment.get_backend_dir(), 'alembic.ini')
return ['alembic', '-c', alembic_ini_path, command, *arguments]
def run_alembic_command(
self,
command: str,
*arguments: str,
success_message: str,
failure_message: str,
dry_run: bool = False,
) -> dict[str, Any]:
"""
执行 Alembic 命令并返回统一结果。
:param command: Alembic 子命令名称
:param arguments: Alembic 子命令参数列表
:param success_message: 成功提示信息
:param failure_message: 失败提示信息
:param dry_run: 是否仅演练执行
:return: Alembic 执行结果
"""
command_arguments = self.build_alembic_command(command, *arguments)
if dry_run:
return {
'ok': True,
'message': f'{success_message}dry-run',
'dryRun': True,
'command': command_arguments,
'cwd': self.runtime_environment.get_backend_dir(),
}
try:
completed = subprocess.run(
command_arguments,
cwd=self.runtime_environment.get_backend_dir(),
env={**os.environ, 'APP_ENV': os.environ.get('APP_ENV', 'dev')},
capture_output=True,
text=True,
check=False,
)
if completed.returncode != 0:
return {
'ok': False,
'message': failure_message,
'error': completed.stderr.strip() or completed.stdout.strip(),
'exit_code': DATABASE_ERROR,
}
payload: dict[str, Any] = {'ok': True, 'message': success_message}
stdout = completed.stdout.strip()
if stdout:
payload['stdout'] = stdout
return payload
except Exception as exc:
return {'ok': False, 'message': failure_message, 'error': str(exc), 'exit_code': DATABASE_ERROR}