Slackアプリを作っています。
今回は、TimeLineというアプリで、過去のpostをすべて取得するような機能を追加していたところ、APIの叩きすぎでratelimitに達してしまい、エラーが出ました。
{'ok': False, 'error': 'ratelimited'}
ああ、なるほど、となる訳です。
このページにある通り、Slack APIにはTierが設定されており、そのTierによって回数制限が課されています。
一時的なバーストは問題ないのですが、定常的に超えてくると上記のようなエラーを返すようになります。
詳細はこちらに書いてあります
ここにコード例が書いてあるのですが、残念ながらjsです。Pythonで取得するにはどうすればよいのか…とヘルプミーしてみたところ、いつもお世話になっておりますSlackの @seratch さんからレスポンスを頂きました。
私----
bolt for Pythonで APIを叩いたときに ratelimited が返ってきてしまいました。
Retry-Afterを取りたいのですが、取得方法がいまいちわからず。
どんな形で取得すればよいのでしょうか。
@seratch ----
https://slack.dev/python-slack-sdk/web/index.html#rate-limits
で取れますが、デフォルトでは有効になっていない retry handler を有効にする方が楽かな。
https://slack.dev/python-slack-sdk/web/index.html#retryhandler
ratelimited error が発生しそうな処理の前に handler を append するのがいいと思います。
色々検索してたのに全然出てこなかった情報が一瞬で解決。上述の2ページにはコード例も書かれているのでそれを使っていきます。
retry handlerは使わないパターン
while True:
try:
response = send_slack_message(channel, message)
except SlackApiError as e:
if e.response.status_code == 429:
# The `Retry-After` header will tell you how long to wait before retrying
delay = int(e.response.headers['Retry-After'])
print(f"Rate limited. Retrying in {delay} seconds")
time.sleep(delay)
response = send_slack_message(channel, message)
else:
# other errors
raise e
Retry-Afterの値を見たかったので、こっちで実装してしまいましたが、おすすめパターンはこちら
# This handler does retries when HTTP status 429 is returned
from slack_sdk.http_retry.builtin_handlers import RateLimitErrorRetryHandler
rate_limit_handler = RateLimitErrorRetryHandler(max_retry_count=1)
# Enable rate limited error retries as well
client.retry_handlers.append(rate_limit_handler)
こうやってclientにappendすることで処理してくれる模様。便利すぎるな。
加えて、rate_limit_handler をセットした client を App 初期化時に渡すという方法でもできるように @seratch さんが実装をスタートしてくれたようなので、それが実現すれば最初に使っておくことで全部気にしなくて良いという状態になりそうですね。
↑の件
1時間ほどでリリースされました。すごい
https://github.com/slackapi/bolt-python/releases/tag/v1.15.5
初期化時にclient=WebClient(retry_handlers = retry_handlers),
とすれば良い模様。めちゃ便利。
from slack_bolt.app import App
from slack_sdk import WebClient
from slack_sdk.http_retry import all_builtin_retry_handlers
# Customized retry handlers
retry_handlers = all_builtin_retry_handlers()
app = App(
# this client will be used when generating a client per request later
client=WebClient(retry_handlers = retry_handlers),
signing_secret="xxxxx",
)
@app.action("a")
def listener(ack, client):
# This client arg should copy the list of retry handlers
# but it does not work in the way as of v1.15.4
assert len(client.retry_handlers) == len(retry_handlers) # this fails
ack()
ということで、初期にはぶつからなかったエラーにいよいよぶつかったので、その対応をしましたよという話でした。
ありがとうございました @seratch