Skip to content

Commit bc094f0

Browse files
authored
Fix #542 Add additional context values for FastAPI apps (#544)
1 parent d5289c9 commit bc094f0

File tree

5 files changed

+181
-14
lines changed

5 files changed

+181
-14
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import logging
2+
3+
logging.basicConfig(level=logging.DEBUG)
4+
5+
from slack_bolt.async_app import AsyncApp
6+
from slack_bolt.adapter.fastapi.async_handler import AsyncSlackRequestHandler
7+
8+
app = AsyncApp()
9+
app_handler = AsyncSlackRequestHandler(app)
10+
11+
12+
@app.event("app_mention")
13+
async def handle_app_mentions(context, say, logger):
14+
logger.info(context)
15+
assert context.get("foo") == "FOO"
16+
await say("What's up?")
17+
18+
19+
@app.event("message")
20+
async def handle_message():
21+
pass
22+
23+
24+
from fastapi import FastAPI, Request, Depends
25+
26+
api = FastAPI()
27+
28+
29+
def get_foo():
30+
yield "FOO"
31+
32+
33+
@api.post("/slack/events")
34+
async def endpoint(req: Request, foo: str = Depends(get_foo)):
35+
return await app_handler.handle(req, {"foo": foo})
36+
37+
38+
# pip install -r requirements.txt
39+
# export SLACK_SIGNING_SECRET=***
40+
# export SLACK_BOT_TOKEN=xoxb-***
41+
# uvicorn async_app_custom_props:api --reload --port 3000 --log-level warning

slack_bolt/adapter/starlette/async_handler.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Dict, Any, Optional
2+
13
from starlette.requests import Request
24
from starlette.responses import Response
35

@@ -6,12 +8,20 @@
68
from slack_bolt.oauth.async_oauth_flow import AsyncOAuthFlow
79

810

9-
def to_async_bolt_request(req: Request, body: bytes) -> AsyncBoltRequest:
10-
return AsyncBoltRequest(
11+
def to_async_bolt_request(
12+
req: Request,
13+
body: bytes,
14+
addition_context_properties: Optional[Dict[str, Any]] = None,
15+
) -> AsyncBoltRequest:
16+
request = AsyncBoltRequest(
1117
body=body.decode("utf-8"),
1218
query=req.query_params,
1319
headers=req.headers,
1420
)
21+
if addition_context_properties is not None:
22+
for k, v in addition_context_properties.items():
23+
request.context[k] = v
24+
return request
1525

1626

1727
def to_starlette_response(bolt_resp: BoltResponse) -> Response:
@@ -39,23 +49,27 @@ class AsyncSlackRequestHandler:
3949
def __init__(self, app: AsyncApp): # type: ignore
4050
self.app = app
4151

42-
async def handle(self, req: Request) -> Response:
52+
async def handle(
53+
self, req: Request, addition_context_properties: Optional[Dict[str, Any]] = None
54+
) -> Response:
4355
body = await req.body()
4456
if req.method == "GET":
4557
if self.app.oauth_flow is not None:
4658
oauth_flow: AsyncOAuthFlow = self.app.oauth_flow
4759
if req.url.path == oauth_flow.install_path:
4860
bolt_resp = await oauth_flow.handle_installation(
49-
to_async_bolt_request(req, body)
61+
to_async_bolt_request(req, body, addition_context_properties)
5062
)
5163
return to_starlette_response(bolt_resp)
5264
elif req.url.path == oauth_flow.redirect_uri_path:
5365
bolt_resp = await oauth_flow.handle_callback(
54-
to_async_bolt_request(req, body)
66+
to_async_bolt_request(req, body, addition_context_properties)
5567
)
5668
return to_starlette_response(bolt_resp)
5769
elif req.method == "POST":
58-
bolt_resp = await self.app.async_dispatch(to_async_bolt_request(req, body))
70+
bolt_resp = await self.app.async_dispatch(
71+
to_async_bolt_request(req, body, addition_context_properties)
72+
)
5973
return to_starlette_response(bolt_resp)
6074

6175
return Response(

slack_bolt/adapter/starlette/handler.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1+
from typing import Dict, Any, Optional
2+
13
from starlette.requests import Request
24
from starlette.responses import Response
35

46
from slack_bolt import BoltRequest, App, BoltResponse
57
from slack_bolt.oauth import OAuthFlow
68

79

8-
def to_bolt_request(req: Request, body: bytes) -> BoltRequest:
9-
return BoltRequest(
10+
def to_bolt_request(
11+
req: Request,
12+
body: bytes,
13+
addition_context_properties: Optional[Dict[str, Any]] = None,
14+
) -> BoltRequest:
15+
request = BoltRequest(
1016
body=body.decode("utf-8"),
1117
query=req.query_params,
1218
headers=req.headers,
1319
)
20+
if addition_context_properties is not None:
21+
for k, v in addition_context_properties.items():
22+
request.context[k] = v
23+
return request
1424

1525

1626
def to_starlette_response(bolt_resp: BoltResponse) -> Response:
@@ -38,21 +48,27 @@ class SlackRequestHandler:
3848
def __init__(self, app: App): # type: ignore
3949
self.app = app
4050

41-
async def handle(self, req: Request) -> Response:
51+
async def handle(
52+
self, req: Request, addition_context_properties: Optional[Dict[str, Any]] = None
53+
) -> Response:
4254
body = await req.body()
4355
if req.method == "GET":
4456
if self.app.oauth_flow is not None:
4557
oauth_flow: OAuthFlow = self.app.oauth_flow
4658
if req.url.path == oauth_flow.install_path:
4759
bolt_resp = oauth_flow.handle_installation(
48-
to_bolt_request(req, body)
60+
to_bolt_request(req, body, addition_context_properties)
4961
)
5062
return to_starlette_response(bolt_resp)
5163
elif req.url.path == oauth_flow.redirect_uri_path:
52-
bolt_resp = oauth_flow.handle_callback(to_bolt_request(req, body))
64+
bolt_resp = oauth_flow.handle_callback(
65+
to_bolt_request(req, body, addition_context_properties)
66+
)
5367
return to_starlette_response(bolt_resp)
5468
elif req.method == "POST":
55-
bolt_resp = self.app.dispatch(to_bolt_request(req, body))
69+
bolt_resp = self.app.dispatch(
70+
to_bolt_request(req, body, addition_context_properties)
71+
)
5672
return to_starlette_response(bolt_resp)
5773

5874
return Response(

tests/adapter_tests/starlette/test_fastapi.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from time import time
33
from urllib.parse import quote
44

5-
from fastapi import FastAPI
5+
from fastapi import FastAPI, Depends
66
from slack_sdk.signature import SignatureVerifier
77
from slack_sdk.web import WebClient
88
from starlette.requests import Request
@@ -214,3 +214,51 @@ async def endpoint(req: Request):
214214
assert response.status_code == 200
215215
assert response.headers.get("content-type") == "text/html; charset=utf-8"
216216
assert "https://slack.com/oauth/v2/authorize?state=" in response.text
217+
218+
def test_custom_props(self):
219+
app = App(
220+
client=self.web_client,
221+
signing_secret=self.signing_secret,
222+
)
223+
224+
def shortcut_handler(ack, context):
225+
assert context.get("foo") == "FOO"
226+
ack()
227+
228+
app.shortcut("test-shortcut")(shortcut_handler)
229+
230+
input = {
231+
"type": "shortcut",
232+
"token": "verification_token",
233+
"action_ts": "111.111",
234+
"team": {
235+
"id": "T111",
236+
"domain": "workspace-domain",
237+
"enterprise_id": "E111",
238+
"enterprise_name": "Org Name",
239+
},
240+
"user": {"id": "W111", "username": "primary-owner", "team_id": "T111"},
241+
"callback_id": "test-shortcut",
242+
"trigger_id": "111.111.xxxxxx",
243+
}
244+
245+
timestamp, body = str(int(time())), f"payload={quote(json.dumps(input))}"
246+
247+
api = FastAPI()
248+
app_handler = SlackRequestHandler(app)
249+
250+
def get_foo():
251+
yield "FOO"
252+
253+
@api.post("/slack/events")
254+
async def endpoint(req: Request, foo: str = Depends(get_foo)):
255+
return await app_handler.handle(req, {"foo": foo})
256+
257+
client = TestClient(api)
258+
response = client.post(
259+
"/slack/events",
260+
data=body,
261+
headers=self.build_headers(timestamp, body),
262+
)
263+
assert response.status_code == 200
264+
assert_auth_test_count(self, 1)

tests/adapter_tests_async/test_async_fastapi.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from time import time
33
from urllib.parse import quote
44

5-
from fastapi import FastAPI
5+
from fastapi import FastAPI, Depends
66
from slack_sdk.signature import SignatureVerifier
77
from slack_sdk.web.async_client import AsyncWebClient
88
from starlette.requests import Request
@@ -215,3 +215,51 @@ async def endpoint(req: Request):
215215
assert response.headers.get("content-type") == "text/html; charset=utf-8"
216216
assert response.headers.get("content-length") == "597"
217217
assert "https://slack.com/oauth/v2/authorize?state=" in response.text
218+
219+
def test_custom_props(self):
220+
app = AsyncApp(
221+
client=self.web_client,
222+
signing_secret=self.signing_secret,
223+
)
224+
225+
async def shortcut_handler(ack, context):
226+
assert context.get("foo") == "FOO"
227+
await ack()
228+
229+
app.shortcut("test-shortcut")(shortcut_handler)
230+
231+
input = {
232+
"type": "shortcut",
233+
"token": "verification_token",
234+
"action_ts": "111.111",
235+
"team": {
236+
"id": "T111",
237+
"domain": "workspace-domain",
238+
"enterprise_id": "E111",
239+
"enterprise_name": "Org Name",
240+
},
241+
"user": {"id": "W111", "username": "primary-owner", "team_id": "T111"},
242+
"callback_id": "test-shortcut",
243+
"trigger_id": "111.111.xxxxxx",
244+
}
245+
246+
timestamp, body = str(int(time())), f"payload={quote(json.dumps(input))}"
247+
248+
api = FastAPI()
249+
app_handler = AsyncSlackRequestHandler(app)
250+
251+
def get_foo():
252+
yield "FOO"
253+
254+
@api.post("/slack/events")
255+
async def endpoint(req: Request, foo: str = Depends(get_foo)):
256+
return await app_handler.handle(req, {"foo": foo})
257+
258+
client = TestClient(api)
259+
response = client.post(
260+
"/slack/events",
261+
data=body,
262+
headers=self.build_headers(timestamp, body),
263+
)
264+
assert response.status_code == 200
265+
assert_auth_test_count(self, 1)

0 commit comments

Comments
 (0)