certflow.controllers.base_controller 源代码

"""控制器基类

提供所有业务控制器的基类实现,封装数据库会话管理、事务处理等通用功能.
"""

from __future__ import annotations

import types
from typing import Any

from loguru import logger
from sqlalchemy.orm import Session

from certflow.bootstrap import get_default_context
from certflow.utils.database import DatabaseManager


[文档] class BaseController: """控制器基类 所有业务控制器的基类,提供统一的数据库会话管理和事务处理方法. 支持上下文管理器协议,可自动管理会话生命周期. 支持共享数据库管理器,实现全局WebDAV配置统一管理. Attributes: db_manager: 数据库管理器实例,负责创建和管理数据库连接 session: SQLAlchemy数据库会话对象,用于执行数据库操作 _owns_session: 是否拥有会话的所有权,决定close()时是否关闭会话 Examples: >>> # 方式1: 使用上下文管理器(推荐) >>> with BaseController() as controller: ... result = controller.session.query(User).all() ... controller.commit() >>> >>> # 方式2: 手动管理会话 >>> controller = BaseController() >>> try: ... result = controller.session.query(User).all() ... controller.commit() ... finally: ... controller.close() >>> >>> # 方式3: 共享数据库管理器(支持WebDAV) >>> db_manager = DatabaseManager(webdav_config=config) >>> controller = BaseController(db_manager=db_manager) >>> >>> # 方式4: 使用全局共享管理器 >>> BaseController.set_shared_db_manager(db_manager) >>> controller = BaseController() # 自动使用共享管理器 """ # 类级别的共享数据库管理器(所有子类共享) _shared_db_manager: DatabaseManager | None = None
[文档] @classmethod def set_shared_db_manager(cls, db_manager: DatabaseManager) -> None: """设置共享的数据库管理器(用于全局WebDAV配置) 设置后,所有新创建的控制器如果没有显式传入db_manager, 都会使用这个共享实例。 Args: db_manager: 共享的数据库管理器实例 Returns: None Examples: >>> # 在应用启动时设置 >>> db_manager = DatabaseManager(webdav_config=config) >>> BaseController.set_shared_db_manager(db_manager) """ cls._shared_db_manager = db_manager logger.info("已设置全局共享数据库管理器")
[文档] @classmethod def get_shared_db_manager(cls) -> DatabaseManager | None: """获取共享的数据库管理器 Returns: 共享的数据库管理器实例,如果未设置则返回None """ return cls._shared_db_manager
[文档] @classmethod def clear_shared_db_manager(cls) -> None: """清除共享的数据库管理器 将类级别的共享管理器置空,后续控制器将回退到独立管理器。 Returns: None """ cls._shared_db_manager = None logger.debug("已清除全局共享数据库管理器")
def __init__( self, db_session: Session | None = None, db_manager: DatabaseManager | None = None, use_shared: bool = True, ): """初始化控制器 支持多种使用方式: 1. 自动创建会话:不传入任何参数,自动创建数据库会话 2. 共享会话:传入外部会话,多个控制器共享同一个会话 3. 共享管理器:传入db_manager或使用全局共享管理器 4. 独立实例:不使用共享,创建独立数据库管理器 Args: db_session: 外部传入的数据库会话对象,如果为None则自动创建新会话 传入外部会话时,控制器不会拥有会话所有权, close()方法不会关闭该会话. db_manager: 外部传入的数据库管理器实例,如果为None则创建默认实例 传入db_manager可以共享WebDAV配置等资源 注意:如果同时传入db_session和db_manager, db_session优先使用,db_manager仅用于引用 use_shared: 是否使用类级别的共享管理器(默认True) 当db_manager为None且use_shared为True时, 会尝试使用共享管理器 Examples: >>> # 自动创建(使用共享管理器) >>> controller = BaseController() >>> >>> # 使用独立管理器(不使用共享) >>> controller = BaseController(use_shared=False) >>> >>> # 传入外部管理器 >>> controller = BaseController(db_manager=my_manager) >>> >>> # 传入外部会话 >>> controller = BaseController(db_session=my_session) """ # 1. 处理数据库管理器(优先级:参数 > 共享 > 默认上下文) if db_manager is not None: self.db_manager = db_manager logger.debug("使用显式传入的数据库管理器") elif use_shared and self._shared_db_manager is not None: self.db_manager = self._shared_db_manager logger.debug("使用全局共享数据库管理器") else: # 经阶段 B 的装配入口统一取默认上下文的 db_manager, # 消除原先散落的 DatabaseManager() 兜底(无 WebDAV 配置)。 self.db_manager = get_default_context().db_manager logger.debug("使用默认应用上下文的数据库管理器") # 确保数据库已初始化(仅在引擎尚未创建时触发建表/迁移, # 避免每次构造都重复执行;应用引导阶段通常已完成)。 self.db_manager.ensure_initialized_if_needed() # 2. 处理数据库会话(优先级:参数 > 自动创建) if db_session is not None: # 使用外部传入的会话 self.session = db_session self._owns_session = False logger.debug("使用外部传入的数据库会话") else: # 自动创建会话 self.session = self.db_manager.get_session() self._owns_session = True logger.debug("自动创建数据库会话")
[文档] def __enter__(self) -> BaseController: """上下文管理器入口 支持with语句,返回控制器实例自身. Returns: BaseController: 控制器实例自身 Examples: >>> with BaseController() as controller: ... # 使用controller进行操作 ... pass """ return self
[文档] def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: types.TracebackType | None, ) -> None: """上下文管理器出口 退出with代码块时自动关闭数据库会话. Args: exc_type: 异常类型,如果发生异常则为异常类 exc_val: 异常实例,如果发生异常则为异常对象 exc_tb: 异常追踪信息,如果发生异常则为traceback对象 Returns: None """ self.close()
[文档] def close(self) -> None: """关闭数据库会话 仅当控制器拥有会话所有权(即通过无参构造函数自动创建会话)时, 才会关闭会话.外部传入的会话不会被关闭. Returns: None """ if self._owns_session and self.session: self.session.close() logger.debug("数据库会话已关闭")
[文档] def commit(self) -> None: """提交数据库事务 提交当前会话中的所有更改.如果提交过程中发生异常, 会自动回滚事务并重新抛出异常. Returns: None Raises: Exception: 事务提交失败时抛出原始异常 """ try: self.session.commit() logger.debug("事务提交成功") except Exception as e: self.session.rollback() logger.error(f"事务提交失败: {e}") raise
[文档] def rollback(self) -> None: """回滚数据库事务 撤销当前会话中所有未提交的更改. Returns: None """ self.session.rollback() logger.debug("事务回滚")
[文档] def get_session(self) -> Session: """获取当前会话 Returns: Session: 当前使用的数据库会话 """ return self.session
[文档] def get_service(self, service_cls: type, *args: object, **kwargs: object) -> object: """以当前控制器会话构造一个 Service 实例(收口 Service 构造来源) 统一替代 ``XxxService(self.session)`` 的散落写法:session 来源经 ``BaseController`` 收口,调用方不直接触碰 ``self.session``。 仅适用于构造签名首个位置参数为 ``Session`` 的 Service;首个参数非 会话的 Service(如 ``PrinterService(printer_name)``)不应走此工厂。 Args: service_cls: Service 类(如 ``CertificateService``) *args: 透传给 Service 构造的额外位置参数(排在 session 之后) **kwargs: 透传给 Service 构造的关键字参数 Returns: object: 构造好的 Service 实例 """ return service_cls(self.session, *args, **kwargs)
# ============================================================ # 静态门面:收口视图层对 services 工具的散落直连 # (dict CSV 路径解析为各查表视图通用能力,下沉到基类控制器) # ============================================================
[文档] @staticmethod def resolve_dict_csv_path(dict_key: str) -> Any: """解析字典 CSV 导出/导入路径(委托 ``dict_csv_sync.resolve_dict_csv_path``)。 供 ``LookupTableManagerView`` 等查表视图替代对 ``certflow.services. dict_csv_sync`` 的直接依赖,统一经控制器门面取路径。 """ from certflow.services.dict_csv_sync import resolve_dict_csv_path return resolve_dict_csv_path(dict_key)
[文档] def refresh_session(self) -> None: """刷新会话(如果会话已关闭,重新创建) 用于在长时间运行的应用中恢复会话。 Returns: None """ if self.session is None or not hasattr(self.session, "is_active"): # 会话不存在或无效,重新创建 self.session = self.db_manager.get_session() self._owns_session = True logger.debug("重新创建数据库会话") elif not self.session.is_active: # 会话不活跃,关闭旧会话并创建新会话 self.close() self.session = self.db_manager.get_session() self._owns_session = True logger.debug("刷新数据库会话")
[文档] def is_session_active(self) -> bool: """检查会话是否活跃 Returns: bool: 会话是否存在且活跃 """ return ( self.session is not None and hasattr(self.session, "is_active") and self.session.is_active )