# Signals

シグナルは、アプリケーションのある部分が別の部分に何かが起こったことを伝える方法を提供します。

@app.signal("user.registration.created")
async def send_registration_email(**context):
    await send_email(context["email"], template="registration")
@app.post("/register")
async def handle_registration(request):
    await do_registration(request)
    await request.app.dispatch(
        "user.registration.created",
        context={"email": request.json.email}
    })

# signalを追加する

信号を追加するためのAPIは、ルートの追加と非常によく似ています。

async def my_signal_handler():
    print("something happened")
app.add_signal(my_signal_handler, "something.happened.ohmy")

ただし、おそらくもう少し便利な方法は、組み込みのデコレータを使用することです。

@app.signal("something.happened.ohmy")
async def my_signal_handler():
    print("something happened")

信号は設計図で宣言することもできます

bp = Blueprint("foo")
@bp.signal("something.happened.ohmy")
async def my_signal_handler():
    print("something happened")

# signalsを設計する

新しい信号を作成することに加えて、Sanic自体からディスパッチされる組み込み信号がいくつかあります。これらの信号は、開発者に要求とサーバーのライフサイクルに機能を追加する機会を増やすために存在します。

新しいシグナルを作成することに加えて、Sanic自体からディスパッチされる多くの組み込みシグナルがあります。これらのシグナルは、開発者がリクエストとサーバーのライフサイクルに機能を追加する機会を増やすために存在します。

他のシグナルと同じように、アプリケーションまたはブループリントインスタンスにアタッチできます。

@app.signal("http.lifecycle.complete")
async def my_signal_handler(conn_info):
    print("Connection has been closed")

これらの信号は、ハンドラーが取る引数、およびアタッチする条件(存在する場合)とともに、利用可能な信号です。

イベント名 変数 条件
http.routing.before request
http.routing.after request, route, kwargs, handler
http.lifecycle.begin conn_info
http.lifecycle.read_head head
http.lifecycle.request request
http.lifecycle.handle request
http.lifecycle.read_body body
http.lifecycle.exception request, exception
http.lifecycle.response request, response
http.lifecycle.send data
http.lifecycle.complete conn_info
http.middleware.before request, response {"attach_to": "request"} or {"attach_to": "response"}
http.middleware.after request, response {"attach_to": "request"} or {"attach_to": "response"}
server.init.before app, loop
server.init.after app, loop
server.shutdown.before app, loop
server.shutdown.after app, loop

NEW in v21.12

ビルトインシグナルを使いやすくするために、許可されたビルトインをすべて含む Enum オブジェクトが用意されています。最近の IDE では、イベント名の完全なリストを文字列として覚えておく必要がないので、これは便利です。

from sanic.signals import Event
@app.signal(Event.HTTP_LIFECYCLE_COMPLETE)
async def my_signal_handler(conn_info):
    print("Connection has been closed")

# イベント

信号は_event_に基づいています。イベントは、単に次のパターンの文字列です。

namespace.reference.action

::: 次に イベントには3つの部分が必要です。何を使うべきかわからない場合は、次のパターンを試してください。

  • my_app.something.happened
  • sanic.notice.hello

# イベントパラメータ

イベントは「動的」であり、pathパラメータと同じ構文を使用して宣言できます。これにより、任意の値に基づいてマッチングできます。

@app.signal("foo.bar.<thing>")
async def signal_handler(thing):
    print(f"[signal_handler] {thing=}")
@app.get("/")
async def trigger(request):
    await app.dispatch("foo.bar.baz")
    return response.text("Done.")

許可された型定義の詳細については、pathパラメータをチェックしてください。

::: 注意 イベントの3番目の部分(「アクション」)のみが動的です。

  • foo.bar.<thing> 🆗
  • foo.<bar>.baz ❌ :::

# 待つ

シグナルハンドラの実行に加えて、アプリケーションはイベントがトリガーされるのを待つことができます。

await app.event("foo.bar.baz")

大事: 待つことはブロッキング機能です。したがって、これをバックグラウンドタスクで実行する必要があります。

async def wait_for_event(app):
    while True:
        print("> waiting")
        await app.event("foo.bar.baz")
        print("> event found\n")
@app.after_server_start
async def after_server_start(app, loop):
    app.add_task(wait_for_event(app))

イベントが動的パスで定義されている場合は、*を使用して任意のアクションをキャッチできます。

@app.signal("foo.bar.<thing>")
...
await app.event("foo.bar.*")

# Dispatching

将来的には、Sanicは開発者がライフサイクルイベントに参加するのを支援するために、いくつかのイベントを自動的にディスパッチします。

イベントをDispatchすると、2つのことを行います。

  1. イベントで定義されたシグナルハンドラを実行し、
  2. イベントが完了するまで「待っている」ことをすべて解決します。
@app.signal("foo.bar.<thing>")
async def foo_bar(thing):
    print(f"{thing=}")
await app.dispatch("foo.bar.baz")
thing=baz

# Context

場合によっては、信号ハンドラに余分な情報を渡す必要があるかもしれません。上記の最初の例では、電子メール登録プロセスにユーザーの電子メールアドレスを指定してほしかった。

@app.signal("user.registration.created")
async def send_registration_email(**context):
    print(context)
await app.dispatch(
    "user.registration.created",
    context={"hello": "world"}
)
{'hello': 'world'}

FYI

信号はバックグラウンドタスクでディスパッチされます。

# Blueprints

青写真信号のdispatchは、middlewareと同様に機能します。アプリレベルから行われるものは、青写真まで流れ落ちます。ただし、青写真でディスパッチすると、その青写真で定義されている信号のみが実行されます。

おそらく、例は説明しやすいです:

bp = Blueprint("bp")
app_counter = 0
bp_counter = 0
@app.signal("foo.bar.baz")
def app_signal():
    nonlocal app_counter
    app_counter += 1
@bp.signal("foo.bar.baz")
def bp_signal():
    nonlocal bp_counter
    bp_counter += 1

app.dispatch("foo.bar.baz")を実行すると、両方の信号が実行されます。

await app.dispatch("foo.bar.baz")
assert app_counter == 1
assert bp_counter == 1

bp.dispatch("foo.bar.baz")を実行すると、ブループリント信号のみが実行されます。

await bp.dispatch("foo.bar.baz")
assert app_counter == 1
assert bp_counter == 2
MIT Licensed
Copyright © 2018-present Sanic Community Organization

~ Made with ❤️ and ☕️ ~