manager.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. """数据库管理器 —— 连接封装 + 生命周期 + 后端路由
  2. 不包含具体数据操作逻辑(操作在各子包 backend.py 实现类内部)。
  3. 子门面 MysqlManager / PgManager / RedisManager 在各子包 manager.py 中。
  4. """
  5. import logging
  6. from typing import Any, Dict, List, Optional
  7. from .ports import (
  8. BackendType,
  9. BaseBackend,
  10. SqlBackend,
  11. )
  12. logger = logging.getLogger(__name__)
  13. # ==================== 后端工厂函数 ====================
  14. def _detect_backend_type(config: Any) -> BackendType:
  15. """从配置读取后端类型。要求显式声明 backend 字段。"""
  16. backend = getattr(config, "backend", None)
  17. if not backend:
  18. raise ValueError(
  19. f"配置 {type(config).__name__} 缺少 backend 字段,"
  20. "请显式设置为 mysql/postgresql/milvus/redis"
  21. )
  22. try:
  23. return BackendType(str(backend).lower())
  24. except ValueError as exc:
  25. allowed = ", ".join(bt.value for bt in BackendType)
  26. raise ValueError(
  27. f"配置 {type(config).__name__} 的 backend={backend!r} 非法,可选: {allowed}"
  28. ) from exc
  29. def create_backend(name: str, config: Any) -> BaseBackend:
  30. """工厂函数:根据配置创建对应后端实例"""
  31. from .mysql.backend import MySQLBackend
  32. from .postgresql.backend import PgBackend
  33. from .milvus.backend import MilvusBackend
  34. from .redis.backend import RedisBackend
  35. bt = _detect_backend_type(config)
  36. if bt == BackendType.MYSQL:
  37. return MySQLBackend(name, config)
  38. elif bt == BackendType.POSTGRESQL:
  39. return PgBackend(name, config)
  40. elif bt == BackendType.MILVUS:
  41. return MilvusBackend(name, config)
  42. elif bt == BackendType.REDIS:
  43. return RedisBackend(name, config)
  44. raise ValueError(f"未知的存储后端类型: {bt}")
  45. # ==================== 管理器 ====================
  46. class DatabaseManager:
  47. """数据库连接管理器 —— 封装多后端实例的生命周期、路由、子门面工厂
  48. 职责: 连接池初始化/关闭、后端直取、子门面创建
  49. 不负责: 具体 SQL/KV 操作(操作在各子门面及后端实现类内部)
  50. 使用方式:
  51. backends = {"aigc": MySQLBackend("aigc", config)}
  52. db = DatabaseManager(backends)
  53. await db.init_pools()
  54. # 通过子门面操作
  55. mysql = db.mysql_manager("long_articles")
  56. rows = await mysql.fetch("SELECT * FROM t")
  57. """
  58. def __init__(
  59. self,
  60. backends: Dict[str, BaseBackend],
  61. *,
  62. default_db: Optional[str] = None,
  63. ):
  64. if not backends:
  65. raise ValueError("DatabaseManager 至少需要一个后端")
  66. self._backends = backends
  67. if default_db is None:
  68. self._default_db = next(iter(backends))
  69. else:
  70. if default_db not in backends:
  71. raise ValueError(
  72. f"default_db={default_db!r} 未注册,可用: {list(backends.keys())}"
  73. )
  74. self._default_db = default_db
  75. # ==================== 子门面工厂 ====================
  76. def mysql_manager(self, db_name: str = "long_articles"):
  77. """获取 MySQL 专用门面"""
  78. from .mysql.manager import MysqlManager
  79. return MysqlManager(self._get_sql(db_name))
  80. def pg_manager(self, db_name: str = "default"):
  81. """获取 PostgreSQL 专用门面"""
  82. from .postgresql.manager import PgManager
  83. return PgManager(self._get_sql(db_name))
  84. def redis_manager(self, db_name: str = "default"):
  85. """获取 Redis 专用门面"""
  86. from .redis.backend import RedisBackend
  87. from .redis.manager import RedisManager
  88. return RedisManager(self._get_typed(db_name, RedisBackend))
  89. # ==================== 生命周期 ====================
  90. async def init_pools(self) -> None:
  91. initialized: list[tuple[str, BaseBackend]] = []
  92. try:
  93. for name, backend in self._backends.items():
  94. await backend.init()
  95. initialized.append((name, backend))
  96. logger.info("存储后端 [%s] 初始化完成 → %s", name, type(backend).__name__)
  97. except Exception as exc:
  98. logger.exception("存储后端初始化失败,开始回滚已初始化连接: %s", exc)
  99. for name, backend in reversed(initialized):
  100. try:
  101. await backend.close()
  102. logger.info("存储后端 [%s] 回滚关闭完成", name)
  103. except Exception:
  104. logger.exception("存储后端 [%s] 回滚关闭失败", name)
  105. raise
  106. async def close_pools(self) -> None:
  107. for name, backend in self._backends.items():
  108. await backend.close()
  109. logger.info("存储后端 [%s] 已关闭", name)
  110. # ==================== 后端直取 ====================
  111. def mysql(self, name: Optional[str] = None):
  112. from .mysql.backend import MySQLBackend
  113. return self._get_typed(name or self._default_db, MySQLBackend)
  114. def pg(self, name: str):
  115. from .postgresql.backend import PgBackend
  116. return self._get_typed(name, PgBackend)
  117. def milvus(self, name: str):
  118. from .milvus.backend import MilvusBackend
  119. return self._get_typed(name, MilvusBackend)
  120. def redis(self, name: str):
  121. from .redis.backend import RedisBackend
  122. return self._get_typed(name, RedisBackend)
  123. # ==================== 辅助 ====================
  124. def get_pool(self, db_name: str):
  125. """向后兼容:返回 MySQL 连接池对象(不建议新增调用)"""
  126. return self.mysql(db_name)._pool
  127. def list_databases(self) -> List[str]:
  128. return list(self._backends.keys())
  129. def list_backends(self) -> Dict[str, str]:
  130. return {n: type(b).__name__ for n, b in self._backends.items()}
  131. # ==================== 内部 ====================
  132. def _get_backend(self, db_name: Optional[str] = None) -> BaseBackend:
  133. name = db_name or self._default_db
  134. backend = self._backends.get(name)
  135. if backend is None:
  136. raise KeyError(f"存储后端 '{name}' 未注册,可用: {self.list_databases()}")
  137. return backend
  138. def _get_sql(self, db_name: Optional[str] = None) -> SqlBackend:
  139. backend = self._get_backend(db_name)
  140. if not isinstance(backend, SqlBackend):
  141. raise TypeError(
  142. f"后端 '{db_name}' 类型为 {type(backend).__name__},不支持 SQL 操作"
  143. )
  144. return backend
  145. def _get_typed(self, name: str, expected_type: type) -> BaseBackend:
  146. backend = self._get_backend(name)
  147. if not isinstance(backend, expected_type):
  148. raise TypeError(
  149. f"后端 '{name}' 实际类型为 {type(backend).__name__},"
  150. f"期望 {expected_type.__name__}"
  151. )
  152. return backend