バックグラウンドタスク¶

@bot.every は、Bot の準備ができている間、引数のない非同期関数をスケジュールに従って実行します。
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) は、Bot の準備ができた時点で関数を実行し、その後 300 秒ごとに実行します。スケジュールがずれることはありません。各実行は最初の実行から数えて seconds の次の倍数の時刻に始まり、seconds より長くかかった実行は、重なった回をスキップします。
every(time=...) は、毎日決まった時刻に、または複数の時刻それぞれに関数を実行します。tzinfo のない datetime.time は 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() を await すると、これまでどおり関数が一度実行されます。そのメソッドで、Bot の実行中にスケジュールを変更できます。
Task |
動作 |
|---|---|
stop() |
実行中でも実行の合間でもタスクをキャンセルし、start() が呼ばれるまで Bot と一緒に開始されないようにします。Bot の実行前に呼び出すと、タスクは start() を待ちます |
start() |
Bot の準備ができている間タスクを実行します。準備ができていれば今すぐ開始します。実行中のタスクはそのまま続きます |
restart() |
タスクをキャンセルして新たに開始します。そのため、間隔指定のタスクはすぐに実行されます |
change_interval(seconds=...) または change_interval(time=...) |
新しいスケジュールに切り替えます。待機は変更の時点から、または実行中の回が終わった時点から改めて始まります |
count |
タスクが最後に開始されてから終了した実行の回数 |
seconds、times |
タスクが従うスケジュール |
@task.before、@task.after |
async def hook() を登録します。タスクが開始するたび、および停止するたびに await されます。停止の理由が stop()、restart()、ルーターの除外、close() のどれであっても同じです |
フック内の例外は、実行中の例外と同じく TaskError として送られます。before フックが例外を送出した場合、タスクは restart() まで停止したままになります。
タスクが実行されるとき¶
タスクは Bot が Started を送ったときに開始され、close() は HTTP クライアントを閉じる前にタスクをキャンセルします。後から組み込まれるルーターに登録されたタスクは組み込まれた時点で開始し、ルーターが除外されたタスクはキャンセルされます。ASGIApp だけで提供されるアプリは、アプリの起動時にタスクを開始します。
1 つの関数をタスクとして登録できるのは一度だけです。同じコードを 2 つのスケジュールで実行するには、2 つの関数を登録してください。
タスクが例外を送出したとき¶
実行中の例外は、task(例外を送出した Task)と error を持つ TaskError イベントとして送られ、タスクは次の時刻に再び実行されます。TaskError のリスナーがなければ、bostie はその例外をトレースバック付きでログに出力します。