Saltar a contenido

Tareas en segundo plano

Bostie duerme mientras suena un despertador

@bot.every ejecuta una función asíncrona sin argumentos según un horario, mientras el bot está listo.

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) ejecuta la función en cuanto el bot está listo y luego cada 300 segundos. El horario no se desfasa: cada ejecución empieza en el siguiente múltiplo de seconds contado desde la primera, y una ejecución que tarda más que seconds se salta los ticks con los que se superpuso.

every(time=...) la ejecuta todos los días a una hora del día, o a cada una de varias. Un datetime.time sin tzinfo está en UTC. Con un desfase fijo, como arriba, se mantiene en ese desfase; con un zoneinfo.ZoneInfo, sigue los cambios de horario de verano de la zona. Pasa seconds o time, no los dos.

Controlar una tarea

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 devuelve un Task, y esperar check_feeds() con await sigue ejecutando la función una vez. Sus métodos cambian el horario mientras el bot se ejecuta:

Task Qué hace
stop() cancela la tarea, durante una ejecución o entre ejecuciones, y evita que arranque con el bot hasta start(); si se llama antes de que el bot se ejecute, la tarea espera a start()
start() ejecuta la tarea mientras el bot está listo, desde ahora si lo está; una tarea en marcha sigue como estaba
restart() cancela la tarea y la vuelve a iniciar, así que una tarea con intervalo se ejecuta de inmediato
change_interval(seconds=...) o change_interval(time=...) pasa al nuevo horario; la espera vuelve a empezar desde el cambio, o desde el final de una ejecución en curso
count cuántas ejecuciones terminaron desde que la tarea arrancó por última vez
seconds, times el horario que sigue
@task.before, @task.after registran un async def hook(), que se espera cada vez que la tarea arranca y cada vez que se detiene, ya sea por stop(), restart(), un router excluido o close()

Una excepción en un hook se envía como un TaskError, igual que una en una ejecución. Cuando el hook before lanza una excepción, la tarea queda detenida hasta restart().

Cuándo se ejecutan las tareas

Las tareas arrancan cuando el bot envía Started, y close() las cancela antes de cerrar el cliente HTTP. Una tarea registrada en un router que se incluye más tarde arranca en cuanto se incluye, y una cuyo router se excluye se cancela. Una aplicación servida solo por ASGIApp arranca sus tareas cuando arranca la aplicación.

Una función se puede registrar como tarea una sola vez. Para ejecutar el mismo código con dos horarios, registra dos funciones.

Cuando una tarea lanza una excepción

Una excepción en una ejecución se envía como un evento TaskError con el task, el Task que la lanzó, y el error, y la tarea se vuelve a ejecutar en su siguiente horario. Sin un listener de TaskError, bostie registra la excepción en el log con su traceback.