Взаимодействовать с фоновой задачей с помощью команд в подмодуле [discord.py] - PullRequest
0 голосов
/ 14 сентября 2018

У меня есть бот Discord, написанный с не переписанной версией discord.py, которая отправляет сообщение, похожее на сердцебиение (среди прочего). Я не знаю, правильно ли я понял, но из тестов я обнаружил, что мне нужно иметь функцию async def heartbeat() в файле main.py.

Выдержка из main.py (сердцебиение работает как положено):

[...]
import asyncio
import datetime
from configparser import ConfigParser

startup_time = datetime.datetime.utcnow()
[...]

async def heartbeat():
    await bot.wait_until_ready()

    heartbeat_config = ConfigParser()
    heartbeat_config.read('./config/config.ini')
    hb_freq = int(heartbeat_config.get('Heartbeat', 'hb_freq'))  # frequency of heartbeat message
    hb_channel = heartbeat_config.get('Heartbeat', 'hb_channel')  # target channel of heartbeat message
    hb_channel = bot.get_channel(hb_channel)  # get channel from bot's channels

    await bot.send_message(hb_channel, "Starting up at: `" + str(startup_time) + "`")
    await asyncio.sleep(hb_freq)  # sleep for hb_freq seconds before entering loop
    while not bot.is_closed:
        now = datetime.datetime.utcnow()  # time right now
        tdelta = now - startup_time  # time since startup
        tdelta = tdelta - datetime.timedelta(microseconds=tdelta.microseconds)  # remove microseconds from tdelta
        beat = await bot.send_message(hb_channel, "Still running\nSince: `" + str(startup_time) + "`.\nCurrent uptime: `" + str(tdelta))
        await asyncio.sleep(hb_freq)  # sleep for hb_freq seconds before initialising next beat
        await bot.delete_message(beat)  # delete old beat so it can be replaced

[...]
if __name__ == "__main__":
    global heartbeat_task
    heartbeat_task = bot.loop.create_task(heartbeat())  # creates heartbeat task in the background
    bot.run(token)  # run bot

У меня есть несколько команд, которые должны взаимодействовать с созданным heartbeat_task, но они находятся в другом модуле, который называется dev.py (находится в том же каталоге, что и main.py).

Выдержка из dev.py:

[...]
from main import heartbeat_task, heartbeat
[...]
@commands.group(pass_context=True)
async def heart(self, ctx):
    if ctx.invoked_subcommand is None:
        return

@heart.command(pass_context=True)
async def stop(self, ctx):
    # should cancel the task from main.py
    heartbeat_task.cancel()
    await self.bot.say('Heartbeat stopped by user {}'.format(ctx.message.author.name))

@heart.command(pass_context=True)
async def start(self, ctx):
    # start the heartbeat if it is not running
    global heartbeat_task
    if heartbeat_task.cancelled():
        heartbeat_task = self.bot.loop.create_task(heartbeat())
        await self.bot.say('Heartbeat started by user {}'.format(ctx.message.author.name))
    else:
        return
[...]

Эти команды прекрасно работают, когда они являются частью main.py (с необходимыми настройками, конечно, такими как удаление self, импорт и т. Д.), Но так как я хочу, чтобы все связанные с разработчиком команды входили в модуль их, поэтому я попытался переместить их.

При попытке загрузить модуль появляется следующая ошибка:

ImportError: cannot import name 'heartbeat_task'.

Удаление этого импорта из dev.py приводит к успешной загрузке модуля, но при использовании любой из команд консоль выдает ошибку:

NameError: name 'heartbeat_task' is not defined

Который восходит к линии heartbeat_task.cancel() (в случае heart stop // if heartbeat_task.cancelled(): (в случае heart start).

Теперь мой вопрос. Как я могу использовать async heartbeat() в main.py, но влиять на задачу с помощью команд в модуле dev.py?

И если я не могу, каковы возможные альтернативы, которые держат команды в dev.py (самой функции не нужно оставаться в main.py, но предпочтительнее оставаться там)?

(Я искал довольно долгое время и не смог найти проблему, подобную моей, или решение, которое также помогло мне)

1 Ответ

0 голосов
/ 14 сентября 2018

Самый простой способ создать фоновую задачу в Cog - это добавить сопрограмму on_ready в Cog, которая запускает фоновую задачу, вместо того, чтобы запускать ее вручную:

class MyCog:
    def __init__(self, bot):
        self.bot = bot

    async def heartbeat(self):
        ...

    async def on_ready(self):
        self.heartbeat_task = self.bot.loop.create_task(heartbeat())

    @commands.command(pass_context=True)
    async def stop(self, ctx):
        self.heartbeat_task.cancel()
        await self.bot.say('Heartbeat stopped by user {}'.format(ctx.message.author.name))


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

Обратите внимание, что вам не нужно украшать on_ready чем-либо в винтике, механизм add_cog подберет его по названию.

...