Skip to content

Commit 899ce76

Browse files
seratchliuyangc3
andcommitted
Fix #174 by enabling to use instance/class methods for listeners/middleware
Co-authored-by: Yang Liu <[email protected]>
1 parent e29e061 commit 899ce76

File tree

5 files changed

+382
-0
lines changed

5 files changed

+382
-0
lines changed

slack_bolt/kwargs_injection/async_utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
to_message,
1616
to_step,
1717
)
18+
from ..logger.messages import warning_skip_uncommon_arg_name
1819

1920

2021
def build_async_required_kwargs(
@@ -65,6 +66,16 @@ def build_async_required_kwargs(
6566
if k not in all_available_args:
6667
all_available_args[k] = v
6768

69+
if len(required_arg_names) > 0:
70+
# To support instance/class methods in a class for listeners/middleware,
71+
# check if the first argument is either self or cls
72+
first_arg_name = required_arg_names[0]
73+
if first_arg_name in {"self", "cls"}:
74+
required_arg_names.pop(0)
75+
elif first_arg_name not in all_available_args.keys():
76+
logger.warning(warning_skip_uncommon_arg_name(first_arg_name))
77+
required_arg_names.pop(0)
78+
6879
kwargs: Dict[str, Any] = {
6980
k: v for k, v in all_available_args.items() if k in required_arg_names
7081
}

slack_bolt/kwargs_injection/utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
to_message,
1616
to_step,
1717
)
18+
from ..logger.messages import warning_skip_uncommon_arg_name
1819

1920

2021
def build_required_kwargs(
@@ -65,6 +66,16 @@ def build_required_kwargs(
6566
if k not in all_available_args:
6667
all_available_args[k] = v
6768

69+
if len(required_arg_names) > 0:
70+
# To support instance/class methods in a class for listeners/middleware,
71+
# check if the first argument is either self or cls
72+
first_arg_name = required_arg_names[0]
73+
if first_arg_name in {"self", "cls"}:
74+
required_arg_names.pop(0)
75+
elif first_arg_name not in all_available_args.keys():
76+
logger.warning(warning_skip_uncommon_arg_name(first_arg_name))
77+
required_arg_names.pop(0)
78+
6879
kwargs: Dict[str, Any] = {
6980
k: v for k, v in all_available_args.items() if k in required_arg_names
7081
}

slack_bolt/logger/messages.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ def warning_bot_only_conflicts() -> str:
8484
)
8585

8686

87+
def warning_skip_uncommon_arg_name(arg_name: str) -> str:
88+
return (
89+
f"Bolt skips injecting a value to the first keyword argument ({arg_name}). "
90+
"If it is self/cls of a method, we recommend using the common names."
91+
)
92+
93+
8794
# -------------------------------
8895
# Info
8996
# -------------------------------
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import json
2+
from time import time, sleep
3+
from typing import Callable
4+
5+
from slack_sdk.signature import SignatureVerifier
6+
from slack_sdk.web import WebClient
7+
8+
from slack_bolt import App, BoltRequest, Say, Ack, BoltContext
9+
from tests.mock_web_api_server import (
10+
setup_mock_web_api_server,
11+
cleanup_mock_web_api_server,
12+
)
13+
from tests.utils import remove_os_env_temporarily, restore_os_env
14+
15+
16+
class TestAppUsingMethodsInClass:
17+
signing_secret = "secret"
18+
valid_token = "xoxb-valid"
19+
mock_api_server_base_url = "http://localhost:8888"
20+
signature_verifier = SignatureVerifier(signing_secret)
21+
web_client = WebClient(
22+
token=valid_token,
23+
base_url=mock_api_server_base_url,
24+
)
25+
26+
def setup_method(self):
27+
self.old_os_env = remove_os_env_temporarily()
28+
setup_mock_web_api_server(self)
29+
30+
def teardown_method(self):
31+
cleanup_mock_web_api_server(self)
32+
restore_os_env(self.old_os_env)
33+
34+
def run_app_and_verify(self, app: App):
35+
payload = {
36+
"type": "message_action",
37+
"token": "verification_token",
38+
"action_ts": "1583637157.207593",
39+
"team": {
40+
"id": "T111",
41+
"domain": "test-test",
42+
"enterprise_id": "E111",
43+
"enterprise_name": "Org Name",
44+
},
45+
"user": {"id": "W111", "name": "test-test"},
46+
"channel": {"id": "C111", "name": "dev"},
47+
"callback_id": "test-shortcut",
48+
"trigger_id": "111.222.xxx",
49+
"message_ts": "1583636382.000300",
50+
"message": {
51+
"client_msg_id": "zzzz-111-222-xxx-yyy",
52+
"type": "message",
53+
"text": "<@W222> test",
54+
"user": "W111",
55+
"ts": "1583636382.000300",
56+
"team": "T111",
57+
"blocks": [
58+
{
59+
"type": "rich_text",
60+
"block_id": "d7eJ",
61+
"elements": [
62+
{
63+
"type": "rich_text_section",
64+
"elements": [
65+
{"type": "user", "user_id": "U222"},
66+
{"type": "text", "text": " test"},
67+
],
68+
}
69+
],
70+
}
71+
],
72+
},
73+
"response_url": "https://hooks.slack.com/app/T111/111/xxx",
74+
}
75+
76+
timestamp, body = str(int(time())), f"payload={json.dumps(payload)}"
77+
request: BoltRequest = BoltRequest(
78+
body=body,
79+
headers={
80+
"content-type": ["application/x-www-form-urlencoded"],
81+
"x-slack-signature": [
82+
self.signature_verifier.generate_signature(
83+
body=body,
84+
timestamp=timestamp,
85+
)
86+
],
87+
"x-slack-request-timestamp": [timestamp],
88+
},
89+
)
90+
response = app.dispatch(request)
91+
assert response.status == 200
92+
assert self.mock_received_requests["/auth.test"] == 1
93+
sleep(0.5) # wait a bit after auto ack()
94+
assert self.mock_received_requests["/chat.postMessage"] == 1
95+
96+
def test_class_methods(self):
97+
app = App(client=self.web_client, signing_secret=self.signing_secret)
98+
app.use(AwesomeClass.class_middleware)
99+
app.shortcut("test-shortcut")(AwesomeClass.class_method)
100+
self.run_app_and_verify(app)
101+
102+
def test_class_methods_uncommon_name(self):
103+
app = App(client=self.web_client, signing_secret=self.signing_secret)
104+
app.use(AwesomeClass.class_middleware)
105+
app.shortcut("test-shortcut")(AwesomeClass.class_method2)
106+
self.run_app_and_verify(app)
107+
108+
def test_instance_methods(self):
109+
app = App(client=self.web_client, signing_secret=self.signing_secret)
110+
awesome = AwesomeClass("Slackbot")
111+
app.use(awesome.instance_middleware)
112+
app.shortcut("test-shortcut")(awesome.instance_method)
113+
self.run_app_and_verify(app)
114+
115+
def test_instance_methods_uncommon_name(self):
116+
app = App(client=self.web_client, signing_secret=self.signing_secret)
117+
awesome = AwesomeClass("Slackbot")
118+
app.use(awesome.instance_middleware)
119+
app.shortcut("test-shortcut")(awesome.instance_method2)
120+
self.run_app_and_verify(app)
121+
122+
def test_static_methods(self):
123+
app = App(client=self.web_client, signing_secret=self.signing_secret)
124+
app.use(AwesomeClass.static_middleware)
125+
app.shortcut("test-shortcut")(AwesomeClass.static_method)
126+
self.run_app_and_verify(app)
127+
128+
129+
class AwesomeClass:
130+
def __init__(self, name: str):
131+
self.name = name
132+
133+
@classmethod
134+
def class_middleware(cls, next: Callable):
135+
next()
136+
137+
def instance_middleware(self, next: Callable):
138+
next()
139+
140+
@staticmethod
141+
def static_middleware(next):
142+
next()
143+
144+
@classmethod
145+
def class_method(cls, context: BoltContext, say: Say, ack: Ack):
146+
ack()
147+
say(f"Hello <@{context.user_id}>!")
148+
149+
@classmethod
150+
def class_method2(xyz, context: BoltContext, say: Say, ack: Ack):
151+
ack()
152+
say(f"Hello <@{context.user_id}>!")
153+
154+
def instance_method(self, context: BoltContext, say: Say, ack: Ack):
155+
ack()
156+
say(f"Hello <@{context.user_id}>! My name is {self.name}")
157+
158+
def instance_method2(whatever, context: BoltContext, say: Say, ack: Ack):
159+
ack()
160+
say(f"Hello <@{context.user_id}>! My name is {whatever.name}")
161+
162+
@staticmethod
163+
def static_method(context: BoltContext, say: Say, ack: Ack):
164+
ack()
165+
say(f"Hello <@{context.user_id}>!")

0 commit comments

Comments
 (0)