Фоновые задачи¶

@bot.every запускает асинхронную функцию без аргументов по расписанию, пока бот готов.
import datetime as dt
import logging
import bostie
from bostie import activity
from bostie.events import TaskError
log = logging.getLogger("my-bot")
bot = bostie.Bot()
TOKYO = dt.timezone(dt.timedelta(hours=9))
@bot.every(seconds=300)
async def show_guild_count() -> None:
count = len(bot.cache.guilds())
await bot.update_presence(activities=[activity.watching(f"{count} servers")])
@bot.every(time=[dt.time(9, tzinfo=TOKYO), dt.time(21, tzinfo=TOKYO)])
async def remind() -> None:
log.info("twice a day, at 9:00 and 21:00 in Tokyo")
@bot.every(time=dt.time(0, 0))
async def midnight_utc() -> None:
log.info("every day at midnight UTC")
@bot.listen
async def task_failed(failure: TaskError) -> None:
log.error("%s failed", failure.task.callback.__name__, exc_info=failure.error)
every(seconds=300) запускает функцию, как только бот готов, а затем каждые 300 секунд. Расписание не уплывает: каждый запуск начинается на следующем кратном seconds от первого, а запуск, длящийся дольше seconds, пропускает такты, на которые пришёлся.
every(time=...) запускает её каждый день в одно время суток или в каждое из нескольких. datetime.time без tzinfo задано в UTC. С фиксированным смещением, как выше, время остаётся с этим смещением; с zoneinfo.ZoneInfo оно следует за переходами на летнее и зимнее время в этой зоне. Передавайте seconds или time, но не оба сразу.
Управление задачей¶
import logging
import bostie
from bostie import Context
from bostie.checks import is_owner
log = logging.getLogger("my-bot")
bot = bostie.Bot()
@bot.every(seconds=60)
async def check_feeds() -> None:
log.info("checking the feeds, run %d", check_feeds.count)
@check_feeds.before
async def open_feeds() -> None:
log.info("feed checks started")
@check_feeds.after
async def close_feeds() -> None:
log.info("feed checks stopped")
@bot.command(checks=[is_owner])
async def feeds(ctx: Context, minutes: int | None = None) -> None:
"""Pause feed checks, or check every so many minutes."""
if minutes is None:
check_feeds.stop()
await ctx.respond("Feed checks paused.", ephemeral=True)
return
check_feeds.change_interval(seconds=minutes * 60)
check_feeds.start()
await ctx.respond(f"Checking feeds every {minutes} minutes.", ephemeral=True)
@bot.every возвращает Task, а ожидание check_feeds() по-прежнему один раз выполняет функцию. Его методы меняют расписание, пока бот работает:
Task |
Что делает |
|---|---|
stop() |
отменяет задачу — во время запуска или между запусками — и не даёт ей стартовать вместе с ботом до start(); если вызвать до запуска бота, задача ждёт start() |
start() |
запускает задачу, пока бот готов, — с этого момента, если он уже готов; уже работающая задача продолжает как прежде |
restart() |
отменяет задачу и запускает её заново, так что задача с интервалом выполняется сразу |
change_interval(seconds=...) или change_interval(time=...) |
переключает на новое расписание; ожидание отсчитывается заново с момента изменения или с конца текущего запуска |
count |
сколько запусков завершилось с последнего старта задачи |
seconds, times |
расписание, которому она следует |
@task.before, @task.after |
регистрируют async def hook(), которого дожидаются при каждом старте задачи и при каждой её остановке — через stop(), restart(), исключённый роутер или close() |
Исключение в хуке отправляется как TaskError, так же как исключение в запуске. Если хук before выбрасывает исключение, задача остаётся остановленной до restart().
Когда выполняются задачи¶
Задачи стартуют, когда бот отправляет Started, а close() отменяет их до закрытия HTTP-клиента. Задача, зарегистрированная на роутере, который включается позже, стартует, как только его включат, а задача, чей роутер исключён, отменяется. Приложение, которое обслуживает только ASGIApp, запускает свои задачи при старте приложения.
Функцию можно зарегистрировать как задачу только один раз. Чтобы выполнять один и тот же код по двум расписаниям, зарегистрируйте две функции.
Когда задача выбрасывает исключение¶
Исключение в запуске отправляется как событие TaskError с task — Task, который выбросил исключение, — и error, и задача снова выполняется в своё следующее время. Если слушателя TaskError нет, bostie пишет исключение в лог с трассировкой.