Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions slack_bolt/adapter/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
"""Adapter modules for running Bolt apps along with Web frameworks or Socket Mode.
"""
"""Adapter modules for running Bolt apps along with Web frameworks or Socket Mode."""
9 changes: 8 additions & 1 deletion slack_bolt/adapter/socket_mode/async_internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import json
import logging
from time import time
from typing import Dict, Union, Sequence

from slack_sdk.socket_mode.async_client import AsyncBaseSocketModeClient
from slack_sdk.socket_mode.request import SocketModeRequest
Expand All @@ -14,7 +15,13 @@


async def run_async_bolt_app(app: AsyncApp, req: SocketModeRequest):
bolt_req: AsyncBoltRequest = AsyncBoltRequest(mode="socket_mode", body=req.payload)
headers: Dict[str, Union[str, Sequence[str]]] = {}
if req.retry_attempt is not None:
headers["X-Slack-Retry-Num"] = str(req.retry_attempt)
if req.retry_reason is not None:
headers["X-Slack-Retry-Reason"] = req.retry_reason

bolt_req: AsyncBoltRequest = AsyncBoltRequest(mode="socket_mode", body=req.payload, headers=headers)
bolt_resp: BoltResponse = await app.async_dispatch(bolt_req)
return bolt_resp

Expand Down
9 changes: 8 additions & 1 deletion slack_bolt/adapter/socket_mode/internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import json
import logging
from time import time
from typing import Dict, Union, Sequence

from slack_sdk.socket_mode.client import BaseSocketModeClient
from slack_sdk.socket_mode.request import SocketModeRequest
Expand All @@ -14,7 +15,13 @@


def run_bolt_app(app: App, req: SocketModeRequest):
bolt_req: BoltRequest = BoltRequest(mode="socket_mode", body=req.payload)
headers: Dict[str, Union[str, Sequence[str]]] = {}
if req.retry_attempt is not None:
headers["X-Slack-Retry-Num"] = str(req.retry_attempt)
if req.retry_reason is not None:
headers["X-Slack-Retry-Reason"] = req.retry_reason

bolt_req: BoltRequest = BoltRequest(mode="socket_mode", body=req.payload, headers=headers)
bolt_resp: BoltResponse = app.dispatch(bolt_req)
return bolt_resp

Expand Down
12 changes: 12 additions & 0 deletions slack_bolt/context/base_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class BaseContext(dict):
"set_status",
"set_title",
"set_suggested_prompts",
"retry_num",
"retry_reason",
]
# Note that these items are not copyable, so when you add new items to this list,
# you must modify ThreadListenerRunner/AsyncioListenerRunner's _build_lazy_request method to pass the values.
Expand Down Expand Up @@ -173,6 +175,16 @@ def user_token(self) -> Optional[str]:
"""The user token resolved for this request."""
return self.get("user_token")

@property
def retry_num(self) -> Optional[int]:
"""The retry number for this request (X-Slack-Retry-Num header in HTTP mode, retry_attempt in Socket Mode)."""
return self.get("retry_num")

@property
def retry_reason(self) -> Optional[str]:
"""The retry reason for this request (X-Slack-Retry-Reason header in HTTP mode, retry_reason in Socket Mode)."""
return self.get("retry_reason")

def set_authorize_result(self, authorize_result: AuthorizeResult):
self["authorize_result"] = authorize_result
if authorize_result.bot_id is not None:
Expand Down
16 changes: 15 additions & 1 deletion slack_bolt/request/async_internals.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, Any
from typing import Dict, Any, Optional, Sequence

from slack_bolt.context.async_context import AsyncBoltContext
from slack_bolt.request.internals import (
Expand All @@ -21,6 +21,7 @@
def build_async_context(
context: AsyncBoltContext,
body: Dict[str, Any],
headers: Optional[Dict[str, Sequence[str]]] = None,
) -> AsyncBoltContext:
context["is_enterprise_install"] = extract_is_enterprise_install(body)
enterprise_id = extract_enterprise_id(body)
Expand Down Expand Up @@ -67,4 +68,17 @@ def build_async_context(
context.logger.debug(debug_multiple_response_urls_detected())
response_url = response_urls[0].get("response_url")
context["response_url"] = response_url

if headers is not None:
retry_num_header = headers.get("x-slack-retry-num")
if retry_num_header is not None and len(retry_num_header) > 0:
try:
context["retry_num"] = int(retry_num_header[0])
except (ValueError, TypeError):
pass

retry_reason_header = headers.get("x-slack-retry-reason")
if retry_reason_header is not None and len(retry_reason_header) > 0:
context["retry_reason"] = retry_reason_header[0]

return context
2 changes: 1 addition & 1 deletion slack_bolt/request/async_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def __init__(
else:
self.body = {}

self.context = build_async_context(AsyncBoltContext(context if context else {}), self.body)
self.context = build_async_context(AsyncBoltContext(context if context else {}), self.body, self.headers)
self.lazy_only = bool(self.headers.get("x-slack-bolt-lazy-only", [False])[0])
self.lazy_function_name = self.headers.get("x-slack-bolt-lazy-function-name", [None])[0]
self.mode = mode
Expand Down
17 changes: 16 additions & 1 deletion slack_bolt/request/internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,9 @@ def extract_function_inputs(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]
return None


def build_context(context: BoltContext, body: Dict[str, Any]) -> BoltContext:
def build_context(
context: BoltContext, body: Dict[str, Any], headers: Optional[Dict[str, Sequence[str]]] = None
) -> BoltContext:
context["is_enterprise_install"] = extract_is_enterprise_install(body)
enterprise_id = extract_enterprise_id(body)
if enterprise_id:
Expand Down Expand Up @@ -314,6 +316,19 @@ def build_context(context: BoltContext, body: Dict[str, Any]) -> BoltContext:
context.logger.debug(debug_multiple_response_urls_detected())
response_url = response_urls[0].get("response_url")
context["response_url"] = response_url

if headers is not None:
retry_num_header = headers.get("x-slack-retry-num")
if retry_num_header is not None and len(retry_num_header) > 0:
try:
context["retry_num"] = int(retry_num_header[0])
except (ValueError, TypeError):
pass

retry_reason_header = headers.get("x-slack-retry-reason")
if retry_reason_header is not None and len(retry_reason_header) > 0:
context["retry_reason"] = retry_reason_header[0]

return context


Expand Down
2 changes: 1 addition & 1 deletion slack_bolt/request/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def __init__(
else:
self.body = {}

self.context = build_context(BoltContext(context if context else {}), self.body)
self.context = build_context(BoltContext(context if context else {}), self.body, self.headers)
self.lazy_only = bool(self.headers.get("x-slack-bolt-lazy-only", [False])[0])
self.lazy_function_name = self.headers.get("x-slack-bolt-lazy-function-name", [None])[0]
self.mode = mode
Expand Down
22 changes: 22 additions & 0 deletions tests/scenario_tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,28 @@ def handle_app_mention(body, say, payload, event):
assert_auth_test_count(self, 1)
assert_received_request_count(self, path="/chat.postMessage", min_count=1)

def test_retry_headers_http_mode(self):
app = App(client=self.web_client, signing_secret=self.signing_secret)

retry_num_received = []
retry_reason_received = []

@app.event("app_mention")
def handle_app_mention(context: BoltContext):
retry_num_received.append(context.retry_num)
retry_reason_received.append(context.retry_reason)

timestamp, body = str(int(time())), json.dumps(self.valid_event_body)
headers = self.build_headers(timestamp, body)
headers["x-slack-retry-num"] = ["2"]
headers["x-slack-retry-reason"] = ["http_error"]

request: BoltRequest = BoltRequest(body=body, headers=headers)
response = app.dispatch(request)
assert response.status == 200
assert retry_num_received[0] == 2
assert retry_reason_received[0] == "http_error"


def my_decorator(f):
@wraps(f)
Expand Down
24 changes: 24 additions & 0 deletions tests/scenario_tests/test_events_socket_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,3 +342,27 @@ def handler2(say: Say):

assert_auth_test_count(self, 1)
assert_received_request_count(self, path="/chat.postMessage", min_count=2)

def test_retry_headers_socket_mode(self):
from slack_bolt import BoltContext

app = App(client=self.web_client)

retry_num_received = []
retry_reason_received = []

@app.event("app_mention")
def handle_app_mention(context: BoltContext):
retry_num_received.append(context.retry_num)
retry_reason_received.append(context.retry_reason)

headers = {
"X-Slack-Retry-Num": ["3"],
"X-Slack-Retry-Reason": ["timeout"],
}

request: BoltRequest = BoltRequest(body=self.valid_event_body, mode="socket_mode", headers=headers)
response = app.dispatch(request)
assert response.status == 200
assert retry_num_received[0] == 3
assert retry_reason_received[0] == "timeout"