需求

以我的bot来说:

Bot:
├─config
├─core
├─modules
│  ├─required
│  │  ├─BotManager
│  │  ├─BotStatus
│  │  ├─ExceptionCatch
│  │  ├─...
│  └─self
│      ├─Modules1
│      ├─Modules2
│      ├─...
├─resources
├─service
└─utils

模块统一放在modules下,required是bot运行必须的模块,self是可卸载安装的模块。

当有模块涉及到需要数据库的操作时,我设想的是,每个模块的数据源不放在一起,如果是sqlite的话单独以db.sqlite的形式存放在本地,远程DB的话就不需要考虑存放。

好处是,解耦(大概?),不同数据源可以带来性能提升,迁移维护时可以先关闭某个模块而不是整个程序。

单说实现的话也挺简单,维护一个dict数据,使用模块名作为key,实际DB对象(存放数据源信息)作为value,之后就是建个类生成和管理。问题是模块需要有实际的DB对象,才能进行操作,能不能让模块和数据源建立联系,这样模块不需要自己持有DB,只构建实体类和调用公共CURD方法。

模块统一放在modules下。其中,required是bot运行必须的模块,而self则是可卸载安装的模块。

当某个模块涉及到需要数据库操作时,我设想将该模块的数据源单独存放。如果使用sqlite,则将其存储在本地,并命名为db.sqlite;如果使用远程DB,则无需考虑存储位置。

这样做有以下好处:

  1. 解耦(大概?)
  2. 不同数据源可以带来性能提升
  3. 迁移维护时可以先关闭某个模块而不是整个程序。

具体实现方法如下:

  1. 维护一个字典数据结构;
  2. 使用模块名作为键值;
  3. 使用实际数据库对象作为值;
  4. 建立一个类来生成和管理字典。

但问题在于:每个模块都需要有实际数据库对象才能进行操作。因此我们需要让每个模块与其对应的数据库建立联系,并且只构建实体类和调用公共CURD方法即可。

思路

模块名与模块所在的文件夹是一一对应的。这也可以让模块的文件夹名与数据源一一对应。当模块内的方法调用公共CURD方法时,它能够从模块文件夹名中找到数据源。核心就是inspect模块:

import inspect
# 从当前调用帧中寻找上一个调用帧的代码所在的文件名
inspect.currentframe().f_back.f_code.co_filename

然后可以写个方法:

import inspect
from pathlib import Path

__current_dir = Path(__file__).parent


def print_caller_info():
    last_frame = None
    back_frame = None
    try:
        for _ in range(3):
            if not last_frame:
                last_frame = inspect.currentframe()
            assert last_frame
            back_frame = last_frame.f_back
            assert back_frame
            back_dir = Path(back_frame.f_code.co_filename).parent
            if back_dir == __current_dir:
                last_frame = back_frame
                continue
            else:
                print(
                    f"Caller: {back_frame.f_code.co_name} in {back_frame.f_code.co_filename}"
                )
        print("loop end")
    finally:
        del last_frame
        del back_frame

由于CURD方法的重复包装,有可能上上帧才是真正的模块方法。辨别方法就是对比该方法所在的文件夹和上个帧所在的文件夹。如果相同,则继续对比上一个;不一样则说明该帧为模块的调用帧。

目前仅记录这一用法,不准备实际更新为这种方法,所以可能存在未知的错误。