Aller au contenu

Tâches de fond

Bostie endormi pendant qu'un réveil sonne

@bot.every exécute une fonction asynchrone sans arguments selon un calendrier, tant que le bot est prêt.

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) exécute la fonction dès que le bot est prêt, puis toutes les 300 secondes. Le calendrier ne dérive pas : chaque exécution démarre au multiple suivant de seconds à partir de la première, et une exécution qui dure plus de seconds saute les échéances qu'elle a chevauchées.

every(time=...) l'exécute chaque jour à une heure donnée, ou à chacune de plusieurs heures. Un datetime.time sans tzinfo est en UTC. Avec un décalage fixe, comme ci-dessus, il reste à ce décalage ; avec un zoneinfo.ZoneInfo, il suit les changements d'heure d'été de la zone. Passez seconds ou time, pas les deux.

Contrôler une tâche

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 renvoie une Task, et attendre check_feeds() exécute toujours la fonction une fois. Ses méthodes modifient le calendrier pendant que le bot tourne :

Task Effet
stop() annule la tâche, pendant une exécution ou entre deux, et l'empêche de démarrer avec le bot jusqu'à start() ; appelée avant que le bot tourne, la tâche attend start()
start() exécute la tâche tant que le bot est prêt, dès maintenant s'il l'est ; une tâche en cours continue comme avant
restart() annule la tâche et la redémarre de zéro : une tâche à intervalle s'exécute donc immédiatement
change_interval(seconds=...) ou change_interval(time=...) passe au nouveau calendrier ; l'attente repart de zéro à partir du changement, ou de la fin d'une exécution en cours
count le nombre d'exécutions terminées depuis le dernier démarrage de la tâche
seconds, times le calendrier qu'elle suit
@task.before, @task.after enregistrent async def hook(), attendu à chaque démarrage de la tâche et à chaque arrêt, que ce soit par stop(), restart(), un routeur exclu ou close()

Une exception dans un hook est envoyée sous forme de TaskError, comme une exception dans une exécution. Quand le hook before lève une exception, la tâche reste arrêtée jusqu'à restart().

Quand les tâches s'exécutent

Les tâches démarrent quand le bot envoie Started, et close() les annule avant de fermer le client HTTP. Une tâche enregistrée sur un routeur inclus plus tard démarre dès son inclusion, et une tâche dont le routeur est exclu est annulée. Une application servie uniquement par ASGIApp démarre ses tâches au démarrage de l'application.

Une fonction ne peut être enregistrée comme tâche qu'une seule fois. Pour exécuter le même code selon deux calendriers, enregistrez deux fonctions.

Quand une tâche lève une exception

Une exception dans une exécution est envoyée sous forme d'événement TaskError avec la task, la Task qui a levé l'exception, et l'error, et la tâche s'exécute de nouveau à sa prochaine échéance. Sans écouteur TaskError, bostie journalise l'exception avec sa trace d'appels.