Skip to content

Commit e95bf15

Browse files
Future function interactivity (#1)
moving changes to future branch
1 parent c826fc3 commit e95bf15

7 files changed

Lines changed: 122 additions & 5 deletions

File tree

slack_bolt/app/app.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
CallableAuthorize,
2020
)
2121
from slack_bolt.error import BoltError, BoltUnhandledRequestError
22+
from slack_bolt.function import Function
2223
from slack_bolt.lazy_listener.thread_runner import ThreadLazyListenerRunner
2324
from slack_bolt.listener.builtins import TokenRevocationListeners
2425
from slack_bolt.listener.custom_listener import CustomListener
@@ -63,6 +64,7 @@
6364
IgnoringSelfEvents,
6465
CustomMiddleware,
6566
)
67+
from slack_bolt.middleware.function_listener_matches import FunctionListenerToken
6668
from slack_bolt.middleware.message_listener_matches import MessageListenerMatches
6769
from slack_bolt.middleware.middleware_error_handler import (
6870
DefaultMiddlewareErrorHandler,
@@ -78,6 +80,7 @@
7880
create_web_client,
7981
get_boot_message,
8082
get_name_for_callable,
83+
create_copy
8184
)
8285
from slack_bolt.workflows.step import WorkflowStep, WorkflowStepMiddleware
8386
from slack_bolt.workflows.step.step import WorkflowStepBuilder
@@ -794,7 +797,7 @@ def __call__(*args, **kwargs):
794797

795798
def function(
796799
self,
797-
callback_id: Union[str, Pattern],
800+
callback_id: Union[str, Function],
798801
matchers: Optional[Sequence[Callable[..., bool]]] = None,
799802
middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
800803
) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]:
@@ -825,11 +828,11 @@ def reverse_string(event, complete_success: CompleteSuccess, complete_error: Com
825828
middleware: A list of lister middleware functions.
826829
Only when all the middleware call `next()` method, the listener function can be invoked.
827830
"""
828-
831+
middleware = list(middleware) if middleware else []
832+
middleware.insert(0, FunctionListenerToken())
829833
def __call__(*args, **kwargs):
830834
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
831-
primary_matcher = builtin_matchers.function_event(callback_id=callback_id, base_logger=self._base_logger)
832-
return self._register_listener(list(functions), primary_matcher, matchers, middleware, True)
835+
return Function(self._register_listener, self._base_logger, list(functions), callback_id, matchers, middleware)
833836

834837
return __call__
835838

@@ -1249,7 +1252,7 @@ def _init_context(self, req: BoltRequest):
12491252
req.context["token"] = self._token
12501253
if self._token is not None:
12511254
# This WebClient instance can be safely singleton
1252-
req.context["client"] = self._client
1255+
req.context["client"] = create_copy(self._client)
12531256
else:
12541257
# Set a new dedicated instance for this request
12551258
client_per_request: WebClient = WebClient(

slack_bolt/context/base_context.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class BaseContext(dict):
2121
"response_url",
2222
"matches",
2323
"authorize_result",
24+
"bot_access_token",
2425
"bot_token",
2526
"bot_id",
2627
"bot_user_id",
@@ -91,6 +92,11 @@ def authorize_result(self) -> Optional[AuthorizeResult]:
9192
"""The authorize result resolved for this request."""
9293
return self.get("authorize_result")
9394

95+
@property
96+
def bot_access_token(self) -> Optional[str]:
97+
"""The bot token resolved for this function request."""
98+
return self.get("bot_access_token")
99+
94100
@property
95101
def bot_token(self) -> Optional[str]:
96102
"""The bot token resolved for this request."""

slack_bolt/function/Function.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from typing import List, Union, Pattern, Callable, Dict, Optional, Sequence, Any
2+
from logging import Logger
3+
4+
from slack_bolt.listener_matcher import builtins as builtin_matchers
5+
6+
from slack_bolt.response import BoltResponse
7+
from slack_bolt.middleware import Middleware
8+
9+
10+
# TDOD this is a duplicate function in App
11+
def _to_listener_functions(
12+
kwargs: dict,
13+
) -> Optional[Sequence[Callable[..., Optional[BoltResponse]]]]:
14+
if kwargs:
15+
functions = [kwargs["ack"]]
16+
for sub in kwargs["lazy"]:
17+
functions.append(sub)
18+
return functions
19+
return None
20+
21+
22+
class Function:
23+
24+
def __init__(
25+
self,
26+
_register_listener: Callable[..., Optional[Callable[..., Optional[BoltResponse]]]],
27+
_base_logger: Logger,
28+
functions: List[Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]],
29+
callback_id: Union[str, Pattern],
30+
matchers: Optional[Sequence[Callable[..., bool]]] = None,
31+
middleware: Optional[Sequence[Union[Callable, Middleware]]] = None
32+
):
33+
self._register_listener = _register_listener
34+
self._base_logger = _base_logger
35+
self.callback_id = callback_id
36+
37+
primary_matcher = builtin_matchers.function_event(callback_id=self.callback_id, base_logger=_base_logger)
38+
self.function = self._register_listener(functions, primary_matcher, matchers, middleware, True)
39+
40+
def __call__(self, *args, **kwargs) -> Optional[Callable[..., Optional[BoltResponse]]]:
41+
return self.function(*args, **kwargs)
42+
43+
def action(
44+
self,
45+
constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
46+
matchers: Optional[Sequence[Callable[..., bool]]] = None,
47+
middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
48+
) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]:
49+
"""Registers a new action listener. This method can be used as either a decorator or a method.
50+
51+
"""
52+
53+
def __call__(*args, **kwargs):
54+
print("action reistered")
55+
56+
return __call__
57+
58+
@property
59+
def __isabstractmethod__(self):
60+
return getattr(self.function, '__isabstractmethod__', False)

slack_bolt/function/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from .function import Function
2+
3+
4+
__all__ = [
5+
"Function",
6+
]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .function_listener_token import FunctionListenerToken
2+
3+
__all__ = [
4+
"FunctionListenerToken",
5+
]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import re
2+
from typing import Callable, Pattern, Union
3+
4+
from slack_bolt.request import BoltRequest
5+
from slack_bolt.response import BoltResponse
6+
from slack_bolt.middleware.middleware import Middleware
7+
8+
9+
class FunctionListenerToken(Middleware): # type: ignore
10+
11+
def process(
12+
self,
13+
*,
14+
req: BoltRequest,
15+
resp: BoltResponse,
16+
# As this method is not supposed to be invoked by bolt-python users,
17+
# the naming conflict with the built-in one affects
18+
# only the internals of this method
19+
next: Callable[[], BoltResponse],
20+
) -> BoltResponse:
21+
if req.context.bot_access_token:
22+
req.context.client.token = req.context.bot_access_token
23+
return next()
24+
25+
# As the text doesn't match, skip running the listener
26+
return resp

slack_bolt/request/internals.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ def extract_function_execution_id(payload: Dict[str, Any]) -> Optional[str]:
135135
return None
136136

137137

138+
def extract_bot_access_token(payload: Dict[str, Any]) -> Optional[str]:
139+
if payload.get("bot_access_token") is not None:
140+
return payload.get("bot_access_token")
141+
if payload.get("event") is not None:
142+
return extract_bot_access_token(payload["event"])
143+
return None
144+
145+
138146
def build_context(context: BoltContext, body: Dict[str, Any]) -> BoltContext:
139147
context["is_enterprise_install"] = extract_is_enterprise_install(body)
140148
enterprise_id = extract_enterprise_id(body)
@@ -152,6 +160,9 @@ def build_context(context: BoltContext, body: Dict[str, Any]) -> BoltContext:
152160
function_execution_id = extract_function_execution_id(body)
153161
if function_execution_id:
154162
context["function_execution_id"] = function_execution_id
163+
bot_access_token = extract_bot_access_token(body)
164+
if bot_access_token:
165+
context["bot_access_token"] = bot_access_token
155166
if "response_url" in body:
156167
context["response_url"] = body["response_url"]
157168
elif "response_urls" in body:

0 commit comments

Comments
 (0)