如何在使用每个功能连接到数据库时保持干爽?

How to stay dry while connecting to database with every fucntion?

我正在使用 discordpy 机器人,我在每个函数中连接和断开与数据库的连接,我认为这不是一个好方法,但我尝试使用装饰器连接和断开与数据库的连接,并将光标发送为函数的一个参数。
这是我的代码:

def connectToDB(func):
    async def wrapper(*args, **kwargs):
        db = sqlite3.connect(DB_DIR)
        cursor = db.cursor()
        print(*args, **kwargs)

        res = await func(*args, **kwargs, cursor=cursor)

        db.commit()
        cursor.close()
        db.close()
        return res

    return wrapper


class MusicBot(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.command(name="myplaylist", aliases=['mypl'])
    @connectToDB
    async def myPlaylist(self, ctx, name=None, currentPage=0, cursor=None):
        currentPage = int(currentPage)
        if name:
            await self.getPlaylistByName(ctx, name, cursor)
            return

        result = cursor.execute(f"SELECT playlist_name, playlist_items, playlist_length, date"
                                f" FROM PLAYLIST WHERE user = ?", (str(ctx.author),))
        playlists = result.fetchall()

        embed = getPlaylistsEmbed(ctx, playlists, currentPage)
        await ctx.send(embed=embed)

def setup(bot):
    bot.add_cog(MusicBot(bot))

但这并没有优化代码,因为 discordpy 必须知道你想要命令的参数是什么,所以在 wrapper 中我已经说过 def wrapper(*args, **kwargs): 没有指定任何参数所以 discordpy 假设我只需要默认参数。

为了解决这个问题,我尝试过:

  1. 使用 exec 更改每个 运行 所需的参数,并检查以获取参数,如下所示:
def connectToDB(f):
    global func
    func = f
    params = inspect.signature(func)
    exec(
       f"async def wrapper{str(params)}:"
        "    db = sqlite3.connect(DB_DIR)"
        "    cursor = db.cursor()"
        "    print(*args, **kwargs)"
       f"    res = await func{str(params)}"
        "    db.commit()"
        "    cursor.close()"
        "    db.close()"
        "    return res", globals()
    )

    return wrapper

这是行不通的,因为在每个命令的 运行 结束后它将保留其最后的更改。
2. 像这样分配装饰器

@connectToDB
@commands.command(name="myplaylist", aliases=['mypl'])

3. 这个很蠢。制作装饰器工厂以获取参数

def connectToDB(*args, **kwargs):
    print(args, kwargs)

    def decorator(func):
        async def wrapper(*_args, **_kwargs):
            db = sqlite3.connect(DB_DIR)
            cursor = db.cursor()
            print(*_args, **_kwargs)

            res = await func(*_args, **_kwargs, cursor=cursor)

            db.commit()
            cursor.close()
            db.close()
            return res

        return wrapper

    return decorator


@commands.command(name="myplaylist", aliases=['mypl'])
@connectToDB(self=None, ctx=None, name=None, page=0, msg=None, cursor=None)
async def myPlaylist(self, ctx, name=None, page=0, msg=None, cursor=None):
   ...

当尝试将装饰器与依赖于内省函数的库一起使用时,这个问题经常发生。解决方案是 functools.wraps().

def connectToDB(func):
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        # setup database...
        res = await func(*args, **kwargs, cursor=cursor)
        # teardown database...
        return res
    return wrapper

functools.wraps 调用 functools.update_wrapper,这将更新“包装函数使其看起来像包装函数”,复制元数据以便内省工具报告原始函数的属性,而不是包装器。