From 9f6283f542ae8ff0337cfdbffb754259fdeb498d Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Tue, 19 Jan 2021 18:13:42 -0700 Subject: [PATCH 1/8] Fixed fetching of names for callable objects (#215) Added new function name_for_callable() in util.utils. Fixed name properties for CustomMiddleware and AsyncCustomMiddleware to use the new function. Modified listener/thread_runner.py and listener/asyncio_runner.py to use the new function to name lazy functions. Added test cases for middleware and lazy function cases. --- slack_bolt/listener/asyncio_runner.py | 8 +++--- slack_bolt/listener/thread_runner.py | 8 +++--- .../middleware/async_custom_middleware.py | 3 +- slack_bolt/middleware/custom_middleware.py | 4 +-- slack_bolt/util/utils.py | 7 +++++ tests/scenario_tests/test_lazy.py | 28 +++++++++++++++++++ .../test_listener_middleware.py | 17 +++++++++++ tests/scenario_tests/test_middleware.py | 17 +++++++++++ 8 files changed, 81 insertions(+), 11 deletions(-) diff --git a/slack_bolt/listener/asyncio_runner.py b/slack_bolt/listener/asyncio_runner.py index 1ea9de2a2..b35f05e23 100644 --- a/slack_bolt/listener/asyncio_runner.py +++ b/slack_bolt/listener/asyncio_runner.py @@ -15,7 +15,7 @@ ) from slack_bolt.request.async_request import AsyncBoltRequest from slack_bolt.response import BoltResponse -from slack_bolt.util.utils import create_copy +from slack_bolt.util.utils import create_copy, name_for_callable class AsyncioListenerRunner: @@ -71,7 +71,7 @@ async def run( for lazy_func in listener.lazy_functions: if request.lazy_function_name: - func_name = lazy_func.__name__ + func_name = name_for_callable(lazy_func) if func_name == request.lazy_function_name: await self.lazy_listener_runner.run( function=lazy_func, request=request @@ -128,7 +128,7 @@ async def run_ack_function_asynchronously( for lazy_func in listener.lazy_functions: if request.lazy_function_name: - func_name = lazy_func.__name__ + func_name = name_for_callable(lazy_func) if func_name == request.lazy_function_name: await self.lazy_listener_runner.run( function=lazy_func, request=request @@ -163,7 +163,7 @@ def _start_lazy_function( self, lazy_func: Callable[..., Awaitable[None]], request: AsyncBoltRequest ) -> None: # Start a lazy function asynchronously - func_name: str = lazy_func.__name__ + func_name: str = name_for_callable(lazy_func) self.logger.debug(debug_running_lazy_listener(func_name)) copied_request = self._build_lazy_request(request, func_name) self.lazy_listener_runner.start(function=lazy_func, request=copied_request) diff --git a/slack_bolt/listener/thread_runner.py b/slack_bolt/listener/thread_runner.py index 2e60e1c41..05601a511 100644 --- a/slack_bolt/listener/thread_runner.py +++ b/slack_bolt/listener/thread_runner.py @@ -13,7 +13,7 @@ ) from slack_bolt.request import BoltRequest from slack_bolt.response import BoltResponse -from slack_bolt.util.utils import create_copy +from slack_bolt.util.utils import create_copy, name_for_callable class ThreadListenerRunner: @@ -72,7 +72,7 @@ def run( # type: ignore for lazy_func in listener.lazy_functions: if request.lazy_function_name: - func_name = lazy_func.__name__ + func_name = name_for_callable(lazy_func) if func_name == request.lazy_function_name: self.lazy_listener_runner.run( function=lazy_func, request=request @@ -127,7 +127,7 @@ def run_ack_function_asynchronously(): for lazy_func in listener.lazy_functions: if request.lazy_function_name: - func_name = lazy_func.__name__ + func_name = name_for_callable(lazy_func) if func_name == request.lazy_function_name: self.lazy_listener_runner.run( function=lazy_func, request=request @@ -162,7 +162,7 @@ def _start_lazy_function( self, lazy_func: Callable[..., None], request: BoltRequest ) -> None: # Start a lazy function asynchronously - func_name: str = lazy_func.__name__ + func_name: str = name_for_callable(lazy_func) self.logger.debug(debug_running_lazy_listener(func_name)) copied_request = self._build_lazy_request(request, func_name) self.lazy_listener_runner.start(function=lazy_func, request=copied_request) diff --git a/slack_bolt/middleware/async_custom_middleware.py b/slack_bolt/middleware/async_custom_middleware.py index be44cf2db..5d6b6837c 100644 --- a/slack_bolt/middleware/async_custom_middleware.py +++ b/slack_bolt/middleware/async_custom_middleware.py @@ -7,6 +7,7 @@ from slack_bolt.request.async_request import AsyncBoltRequest from slack_bolt.response import BoltResponse from .async_middleware import AsyncMiddleware +from slack_bolt.util.utils import name_for_callable class AsyncCustomMiddleware(AsyncMiddleware): @@ -45,4 +46,4 @@ async def async_process( @property def name(self) -> str: - return f"AsyncCustomMiddleware(func={self.func.__name__})" + return f"AsyncCustomMiddleware(func={name_for_callable(self.func)})" diff --git a/slack_bolt/middleware/custom_middleware.py b/slack_bolt/middleware/custom_middleware.py index f3e4afff8..17c550417 100644 --- a/slack_bolt/middleware/custom_middleware.py +++ b/slack_bolt/middleware/custom_middleware.py @@ -7,7 +7,7 @@ from slack_bolt.request import BoltRequest from slack_bolt.response import BoltResponse from .middleware import Middleware - +from slack_bolt.util.utils import name_for_callable class CustomMiddleware(Middleware): app_name: str @@ -41,4 +41,4 @@ def process( @property def name(self) -> str: - return f"CustomMiddleware(func={self.func.__name__})" + return f"CustomMiddleware(func={name_for_callable(self.func)})" diff --git a/slack_bolt/util/utils.py b/slack_bolt/util/utils.py index e52a53674..cac7f0d0a 100644 --- a/slack_bolt/util/utils.py +++ b/slack_bolt/util/utils.py @@ -57,3 +57,10 @@ def get_boot_message(development_server: bool = False) -> str: return "⚡️ Bolt app is running! (development server)" else: return "⚡️ Bolt app is running!" + + +def name_for_callable(func): + if hasattr(func, "__name__"): + func_name = func.__name__ + else: + func_name = f"{func.__class__.__module__}.{func.__class__.__name__}" diff --git a/tests/scenario_tests/test_lazy.py b/tests/scenario_tests/test_lazy.py index 8a633f8cc..6a2c89607 100644 --- a/tests/scenario_tests/test_lazy.py +++ b/tests/scenario_tests/test_lazy.py @@ -110,3 +110,31 @@ def async2(say): assert response.status == 200 time.sleep(1) # wait a bit assert self.mock_received_requests["/chat.postMessage"] == 2 + + def test_lazy_class(self): + def just_ack(ack): + ack() + + class LazyClass: + def __call__(self, say): + time.sleep(0.3) + say(text="lazy function 1") + + def async2(say): + time.sleep(0.5) + say(text="lazy function 2") + + app = App( + client=self.web_client, + signing_secret=self.signing_secret, + ) + app.action("a")( + ack=just_ack, + lazy=[LazyClass(), async2], + ) + + request = self.build_valid_request() + response = app.dispatch(request) + assert response.status == 200 + time.sleep(1) # wait a bit + assert self.mock_received_requests["/chat.postMessage"] == 2 diff --git a/tests/scenario_tests/test_listener_middleware.py b/tests/scenario_tests/test_listener_middleware.py index 4906375d8..11ca31115 100644 --- a/tests/scenario_tests/test_listener_middleware.py +++ b/tests/scenario_tests/test_listener_middleware.py @@ -93,6 +93,23 @@ def handle(ack): response = app.dispatch(self.build_request()) assert response.status == 200 + def test_class_next(self): + class NextClass: + def __call__(self, next): + next() + + app = App( + client=self.web_client, + signing_secret=self.signing_secret, + ) + + @app.shortcut(constraints="test-shortcut", middleware=[NextClass()]) + def handle(ack): + ack() + + response = app.dispatch(self.build_request()) + assert response.status == 200 + def listener_middleware_returning_response(): return BoltResponse(status=200, body="listener middleware") diff --git a/tests/scenario_tests/test_middleware.py b/tests/scenario_tests/test_middleware.py index 569dc6261..deb677d1d 100644 --- a/tests/scenario_tests/test_middleware.py +++ b/tests/scenario_tests/test_middleware.py @@ -86,6 +86,23 @@ def test_next_call(self): assert response.body == "acknowledged!" assert self.mock_received_requests["/auth.test"] == 1 + def test_class_call(self): + class NextClass: + def __call__(self, next): + next() + + app = App( + client=self.web_client, + signing_secret=self.signing_secret, + ) + app.use(NextClass()) + app.shortcut("test-shortcut")(just_ack) + + response = app.dispatch(self.build_request()) + assert response.status == 200 + assert response.body == "acknowledged!" + assert self.mock_received_requests["/auth.test"] == 1 + def just_ack(ack): ack("acknowledged!") From f798c478c713c77ad7f93cbed04ed97089a3867a Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Tue, 19 Jan 2021 18:13:42 -0700 Subject: [PATCH 2/8] Fixed fetching of names for callable objects (#215) Added new function name_for_callable() in util.utils. Fixed name properties for CustomMiddleware and AsyncCustomMiddleware to use the new function. Modified listener/thread_runner.py and listener/asyncio_runner.py to use the new function to name lazy functions. Added test cases for middleware and lazy function cases. --- slack_bolt/listener/asyncio_runner.py | 8 +++--- slack_bolt/listener/thread_runner.py | 8 +++--- .../middleware/async_custom_middleware.py | 3 +- slack_bolt/middleware/custom_middleware.py | 4 +-- slack_bolt/util/utils.py | 7 +++++ tests/scenario_tests/test_lazy.py | 28 +++++++++++++++++++ .../test_listener_middleware.py | 17 +++++++++++ tests/scenario_tests/test_middleware.py | 17 +++++++++++ 8 files changed, 81 insertions(+), 11 deletions(-) diff --git a/slack_bolt/listener/asyncio_runner.py b/slack_bolt/listener/asyncio_runner.py index 1ea9de2a2..b35f05e23 100644 --- a/slack_bolt/listener/asyncio_runner.py +++ b/slack_bolt/listener/asyncio_runner.py @@ -15,7 +15,7 @@ ) from slack_bolt.request.async_request import AsyncBoltRequest from slack_bolt.response import BoltResponse -from slack_bolt.util.utils import create_copy +from slack_bolt.util.utils import create_copy, name_for_callable class AsyncioListenerRunner: @@ -71,7 +71,7 @@ async def run( for lazy_func in listener.lazy_functions: if request.lazy_function_name: - func_name = lazy_func.__name__ + func_name = name_for_callable(lazy_func) if func_name == request.lazy_function_name: await self.lazy_listener_runner.run( function=lazy_func, request=request @@ -128,7 +128,7 @@ async def run_ack_function_asynchronously( for lazy_func in listener.lazy_functions: if request.lazy_function_name: - func_name = lazy_func.__name__ + func_name = name_for_callable(lazy_func) if func_name == request.lazy_function_name: await self.lazy_listener_runner.run( function=lazy_func, request=request @@ -163,7 +163,7 @@ def _start_lazy_function( self, lazy_func: Callable[..., Awaitable[None]], request: AsyncBoltRequest ) -> None: # Start a lazy function asynchronously - func_name: str = lazy_func.__name__ + func_name: str = name_for_callable(lazy_func) self.logger.debug(debug_running_lazy_listener(func_name)) copied_request = self._build_lazy_request(request, func_name) self.lazy_listener_runner.start(function=lazy_func, request=copied_request) diff --git a/slack_bolt/listener/thread_runner.py b/slack_bolt/listener/thread_runner.py index 2e60e1c41..05601a511 100644 --- a/slack_bolt/listener/thread_runner.py +++ b/slack_bolt/listener/thread_runner.py @@ -13,7 +13,7 @@ ) from slack_bolt.request import BoltRequest from slack_bolt.response import BoltResponse -from slack_bolt.util.utils import create_copy +from slack_bolt.util.utils import create_copy, name_for_callable class ThreadListenerRunner: @@ -72,7 +72,7 @@ def run( # type: ignore for lazy_func in listener.lazy_functions: if request.lazy_function_name: - func_name = lazy_func.__name__ + func_name = name_for_callable(lazy_func) if func_name == request.lazy_function_name: self.lazy_listener_runner.run( function=lazy_func, request=request @@ -127,7 +127,7 @@ def run_ack_function_asynchronously(): for lazy_func in listener.lazy_functions: if request.lazy_function_name: - func_name = lazy_func.__name__ + func_name = name_for_callable(lazy_func) if func_name == request.lazy_function_name: self.lazy_listener_runner.run( function=lazy_func, request=request @@ -162,7 +162,7 @@ def _start_lazy_function( self, lazy_func: Callable[..., None], request: BoltRequest ) -> None: # Start a lazy function asynchronously - func_name: str = lazy_func.__name__ + func_name: str = name_for_callable(lazy_func) self.logger.debug(debug_running_lazy_listener(func_name)) copied_request = self._build_lazy_request(request, func_name) self.lazy_listener_runner.start(function=lazy_func, request=copied_request) diff --git a/slack_bolt/middleware/async_custom_middleware.py b/slack_bolt/middleware/async_custom_middleware.py index be44cf2db..5d6b6837c 100644 --- a/slack_bolt/middleware/async_custom_middleware.py +++ b/slack_bolt/middleware/async_custom_middleware.py @@ -7,6 +7,7 @@ from slack_bolt.request.async_request import AsyncBoltRequest from slack_bolt.response import BoltResponse from .async_middleware import AsyncMiddleware +from slack_bolt.util.utils import name_for_callable class AsyncCustomMiddleware(AsyncMiddleware): @@ -45,4 +46,4 @@ async def async_process( @property def name(self) -> str: - return f"AsyncCustomMiddleware(func={self.func.__name__})" + return f"AsyncCustomMiddleware(func={name_for_callable(self.func)})" diff --git a/slack_bolt/middleware/custom_middleware.py b/slack_bolt/middleware/custom_middleware.py index f3e4afff8..17c550417 100644 --- a/slack_bolt/middleware/custom_middleware.py +++ b/slack_bolt/middleware/custom_middleware.py @@ -7,7 +7,7 @@ from slack_bolt.request import BoltRequest from slack_bolt.response import BoltResponse from .middleware import Middleware - +from slack_bolt.util.utils import name_for_callable class CustomMiddleware(Middleware): app_name: str @@ -41,4 +41,4 @@ def process( @property def name(self) -> str: - return f"CustomMiddleware(func={self.func.__name__})" + return f"CustomMiddleware(func={name_for_callable(self.func)})" diff --git a/slack_bolt/util/utils.py b/slack_bolt/util/utils.py index e52a53674..cac7f0d0a 100644 --- a/slack_bolt/util/utils.py +++ b/slack_bolt/util/utils.py @@ -57,3 +57,10 @@ def get_boot_message(development_server: bool = False) -> str: return "⚡️ Bolt app is running! (development server)" else: return "⚡️ Bolt app is running!" + + +def name_for_callable(func): + if hasattr(func, "__name__"): + func_name = func.__name__ + else: + func_name = f"{func.__class__.__module__}.{func.__class__.__name__}" diff --git a/tests/scenario_tests/test_lazy.py b/tests/scenario_tests/test_lazy.py index 8a633f8cc..6a2c89607 100644 --- a/tests/scenario_tests/test_lazy.py +++ b/tests/scenario_tests/test_lazy.py @@ -110,3 +110,31 @@ def async2(say): assert response.status == 200 time.sleep(1) # wait a bit assert self.mock_received_requests["/chat.postMessage"] == 2 + + def test_lazy_class(self): + def just_ack(ack): + ack() + + class LazyClass: + def __call__(self, say): + time.sleep(0.3) + say(text="lazy function 1") + + def async2(say): + time.sleep(0.5) + say(text="lazy function 2") + + app = App( + client=self.web_client, + signing_secret=self.signing_secret, + ) + app.action("a")( + ack=just_ack, + lazy=[LazyClass(), async2], + ) + + request = self.build_valid_request() + response = app.dispatch(request) + assert response.status == 200 + time.sleep(1) # wait a bit + assert self.mock_received_requests["/chat.postMessage"] == 2 diff --git a/tests/scenario_tests/test_listener_middleware.py b/tests/scenario_tests/test_listener_middleware.py index 4906375d8..11ca31115 100644 --- a/tests/scenario_tests/test_listener_middleware.py +++ b/tests/scenario_tests/test_listener_middleware.py @@ -93,6 +93,23 @@ def handle(ack): response = app.dispatch(self.build_request()) assert response.status == 200 + def test_class_next(self): + class NextClass: + def __call__(self, next): + next() + + app = App( + client=self.web_client, + signing_secret=self.signing_secret, + ) + + @app.shortcut(constraints="test-shortcut", middleware=[NextClass()]) + def handle(ack): + ack() + + response = app.dispatch(self.build_request()) + assert response.status == 200 + def listener_middleware_returning_response(): return BoltResponse(status=200, body="listener middleware") diff --git a/tests/scenario_tests/test_middleware.py b/tests/scenario_tests/test_middleware.py index 569dc6261..deb677d1d 100644 --- a/tests/scenario_tests/test_middleware.py +++ b/tests/scenario_tests/test_middleware.py @@ -86,6 +86,23 @@ def test_next_call(self): assert response.body == "acknowledged!" assert self.mock_received_requests["/auth.test"] == 1 + def test_class_call(self): + class NextClass: + def __call__(self, next): + next() + + app = App( + client=self.web_client, + signing_secret=self.signing_secret, + ) + app.use(NextClass()) + app.shortcut("test-shortcut")(just_ack) + + response = app.dispatch(self.build_request()) + assert response.status == 200 + assert response.body == "acknowledged!" + assert self.mock_received_requests["/auth.test"] == 1 + def just_ack(ack): ack("acknowledged!") From e09a879aa2dc4d4d4b59acab35b52e7173f11a5c Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Mon, 18 Jan 2021 15:27:46 +0900 Subject: [PATCH 3/8] Fix #208 by updating AsyncOAuthFlow --- slack_bolt/oauth/async_oauth_flow.py | 14 +++++++++++++- slack_bolt/oauth/oauth_flow.py | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/slack_bolt/oauth/async_oauth_flow.py b/slack_bolt/oauth/async_oauth_flow.py index dc2d0f094..42a98c9e5 100644 --- a/slack_bolt/oauth/async_oauth_flow.py +++ b/slack_bolt/oauth/async_oauth_flow.py @@ -308,6 +308,9 @@ async def run_installation(self, code: str) -> Optional[Installation]: installed_enterprise: Dict[str, str] = ( oauth_response.get("enterprise") or {} ) + is_enterprise_install: bool = ( + oauth_response.get("is_enterprise_install") or False + ) installed_team: Dict[str, str] = oauth_response.get("team") or {} installer: Dict[str, str] = oauth_response.get("authed_user") or {} incoming_webhook: Dict[str, str] = ( @@ -317,14 +320,20 @@ async def run_installation(self, code: str) -> Optional[Installation]: bot_token: Optional[str] = oauth_response.get("access_token") # NOTE: oauth.v2.access doesn't include bot_id in response bot_id: Optional[str] = None + enterprise_url: Optional[str] = None if bot_token is not None: auth_test = await self.client.auth_test(token=bot_token) bot_id = auth_test["bot_id"] + if is_enterprise_install is True: + enterprise_url = auth_test.get("url") return Installation( app_id=oauth_response.get("app_id"), enterprise_id=installed_enterprise.get("id"), + enterprise_name=installed_enterprise.get("name"), + enterprise_url=enterprise_url, team_id=installed_team.get("id"), + team_name=installed_team.get("name"), bot_token=bot_token, bot_id=bot_id, bot_user_id=oauth_response.get("bot_user_id"), @@ -333,10 +342,13 @@ async def run_installation(self, code: str) -> Optional[Installation]: user_token=installer.get("access_token"), user_scopes=installer.get("scope"), # comma-separated string incoming_webhook_url=incoming_webhook.get("url"), + incoming_webhook_channel=incoming_webhook.get("channel"), incoming_webhook_channel_id=incoming_webhook.get("channel_id"), incoming_webhook_configuration_url=incoming_webhook.get( - "configuration_url", None + "configuration_url" ), + is_enterprise_install=is_enterprise_install, + token_type=oauth_response.get("token_type"), ) except SlackApiError as e: diff --git a/slack_bolt/oauth/oauth_flow.py b/slack_bolt/oauth/oauth_flow.py index 867c90761..c03f406d2 100644 --- a/slack_bolt/oauth/oauth_flow.py +++ b/slack_bolt/oauth/oauth_flow.py @@ -340,7 +340,7 @@ def run_installation(self, code: str) -> Optional[Installation]: incoming_webhook_channel=incoming_webhook.get("channel"), incoming_webhook_channel_id=incoming_webhook.get("channel_id"), incoming_webhook_configuration_url=incoming_webhook.get( - "configuration_url", None + "configuration_url" ), is_enterprise_install=is_enterprise_install, token_type=oauth_response.get("token_type"), From 8a819676e8975d626f02bf64fbdfa4455e86f4af Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Mon, 18 Jan 2021 15:34:10 +0900 Subject: [PATCH 4/8] Fix #210 by correcting a type hint in async OAuth classes --- slack_bolt/oauth/async_oauth_settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/slack_bolt/oauth/async_oauth_settings.py b/slack_bolt/oauth/async_oauth_settings.py index 948bf7547..59aff5fae 100644 --- a/slack_bolt/oauth/async_oauth_settings.py +++ b/slack_bolt/oauth/async_oauth_settings.py @@ -19,8 +19,8 @@ AsyncAuthorize, ) from slack_bolt.error import BoltError +from slack_bolt.oauth.async_callback_options import AsyncCallbackOptions from slack_bolt.oauth.async_internals import get_or_create_default_installation_store -from slack_bolt.oauth.callback_options import CallbackOptions class AsyncOAuthSettings: @@ -34,7 +34,7 @@ class AsyncOAuthSettings: install_path: str install_page_rendering_enabled: bool redirect_uri_path: str - callback_options: Optional[CallbackOptions] = None + callback_options: Optional[AsyncCallbackOptions] = None success_url: Optional[str] failure_url: Optional[str] authorization_url: str # default: https://slack.com/oauth/v2/authorize @@ -66,7 +66,7 @@ def __init__( install_path: str = "/slack/install", install_page_rendering_enabled: bool = True, redirect_uri_path: str = "/slack/oauth_redirect", - callback_options: Optional[CallbackOptions] = None, + callback_options: Optional[AsyncCallbackOptions] = None, success_url: Optional[str] = None, failure_url: Optional[str] = None, authorization_url: Optional[str] = None, From c27d4b52c1191da4703856431edf6fbce36d3bf4 Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Tue, 12 Jan 2021 07:51:30 +0900 Subject: [PATCH 5/8] Improvement related to #199: add event type name validation --- slack_bolt/listener_matcher/builtins.py | 10 ++++++++ slack_bolt/logger/messages.py | 8 ++++++ tests/scenario_tests/test_events.py | 28 ++++++++++++++++++++ tests/scenario_tests_async/test_events.py | 31 +++++++++++++++++++++++ 4 files changed, 77 insertions(+) diff --git a/slack_bolt/listener_matcher/builtins.py b/slack_bolt/listener_matcher/builtins.py index ae1d52dc2..cffcf95c1 100644 --- a/slack_bolt/listener_matcher/builtins.py +++ b/slack_bolt/listener_matcher/builtins.py @@ -21,6 +21,7 @@ to_action, is_workflow_step_save, ) +from ..logger.messages import error_message_event_type if sys.version_info.major == 3 and sys.version_info.minor <= 6: from re import _pattern_type as Pattern @@ -82,6 +83,7 @@ def event( ) -> Union[ListenerMatcher, "AsyncListenerMatcher"]: if isinstance(constraints, (str, Pattern)): event_type: Union[str, Pattern] = constraints + _verify_message_event_type(event_type) def func(body: Dict[str, Any]) -> bool: return is_event(body) and _matches(event_type, body["event"]["type"]) @@ -89,6 +91,7 @@ def func(body: Dict[str, Any]) -> bool: return build_listener_matcher(func, asyncio) elif "type" in constraints: + _verify_message_event_type(constraints["type"]) def func(body: Dict[str, Any]) -> bool: if is_event(body): @@ -132,6 +135,13 @@ def func(body: Dict[str, Any]) -> bool: ) +def _verify_message_event_type(event_type: str) -> None: + if isinstance(event_type, str) and event_type.startswith("message."): + raise ValueError(error_message_event_type(event_type)) + if isinstance(event_type, Pattern) and "message\\." in event_type.pattern: + raise ValueError(error_message_event_type(event_type)) + + def workflow_step_execute( callback_id: Union[str, Pattern], asyncio: bool = False, diff --git a/slack_bolt/logger/messages.py b/slack_bolt/logger/messages.py index 9be35c486..f7bc8af7e 100644 --- a/slack_bolt/logger/messages.py +++ b/slack_bolt/logger/messages.py @@ -50,6 +50,14 @@ def error_authorize_conflicts() -> str: return "`authorize` in the top-level arguments is not allowed when you pass either `oauth_settings` or `oauth_flow`" +def error_message_event_type(event_type: str) -> str: + return ( + f'Although the document mentions "{event_type}", ' + 'it is not a valid event type. Use "message" instead. ' + "If you want to filter message events, you can use `event.channel_type` for it." + ) + + # ------------------------------- # Warning # ------------------------------- diff --git a/tests/scenario_tests/test_events.py b/tests/scenario_tests/test_events.py index 0ce15d255..c80a23bff 100644 --- a/tests/scenario_tests/test_events.py +++ b/tests/scenario_tests/test_events.py @@ -2,6 +2,7 @@ import re from time import time, sleep +import pytest from slack_sdk.signature import SignatureVerifier from slack_sdk.web import WebClient @@ -553,3 +554,30 @@ def handler1(event): ) response = app.dispatch(request) assert response.status == 200 + + # https://github.com/slackapi/bolt-python/issues/199 + def test_invalid_message_events(self): + app = App(client=self.web_client, signing_secret=self.signing_secret) + + def handle(): + pass + + # valid + app.event("message")(handle) + + with pytest.raises(ValueError): + app.event("message.channels")(handle) + with pytest.raises(ValueError): + app.event("message.groups")(handle) + with pytest.raises(ValueError): + app.event("message.im")(handle) + with pytest.raises(ValueError): + app.event("message.mpim")(handle) + + with pytest.raises(ValueError): + app.event(re.compile("message\\..*"))(handle) + + with pytest.raises(ValueError): + app.event({"type": "message.channels"})(handle) + with pytest.raises(ValueError): + app.event({"type": re.compile("message\\..*")})(handle) diff --git a/tests/scenario_tests_async/test_events.py b/tests/scenario_tests_async/test_events.py index 9dda0b052..16fb92ef3 100644 --- a/tests/scenario_tests_async/test_events.py +++ b/tests/scenario_tests_async/test_events.py @@ -561,6 +561,37 @@ async def handler1(event): response = await app.async_dispatch(request) assert response.status == 200 + # https://github.com/slackapi/bolt-python/issues/199 + @pytest.mark.asyncio + async def test_invalid_message_events(self): + app = AsyncApp( + client=self.web_client, + signing_secret=self.signing_secret, + ) + + async def handle(): + pass + + # valid + app.event("message")(handle) + + with pytest.raises(ValueError): + app.event("message.channels")(handle) + with pytest.raises(ValueError): + app.event("message.groups")(handle) + with pytest.raises(ValueError): + app.event("message.im")(handle) + with pytest.raises(ValueError): + app.event("message.mpim")(handle) + + with pytest.raises(ValueError): + app.event(re.compile("message\\..*"))(handle) + + with pytest.raises(ValueError): + app.event({"type": "message.channels"})(handle) + with pytest.raises(ValueError): + app.event({"type": re.compile("message\\..*")})(handle) + app_mention_body = { "token": "verification_token", From 0f2e9849fb5941773f2980905469a6d957c8892b Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Tue, 19 Jan 2021 13:18:10 +0900 Subject: [PATCH 6/8] version 1.2.2 --- slack_bolt/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slack_bolt/version.py b/slack_bolt/version.py index a955fdae1..bc86c944f 100644 --- a/slack_bolt/version.py +++ b/slack_bolt/version.py @@ -1 +1 @@ -__version__ = "1.2.1" +__version__ = "1.2.2" From 11bfb074c50dc8bec8f004b0f0193544eea66a19 Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Tue, 19 Jan 2021 19:46:10 -0700 Subject: [PATCH 7/8] Fix PEP8 formatting --- slack_bolt/middleware/custom_middleware.py | 1 + 1 file changed, 1 insertion(+) diff --git a/slack_bolt/middleware/custom_middleware.py b/slack_bolt/middleware/custom_middleware.py index 17c550417..a24513c24 100644 --- a/slack_bolt/middleware/custom_middleware.py +++ b/slack_bolt/middleware/custom_middleware.py @@ -9,6 +9,7 @@ from .middleware import Middleware from slack_bolt.util.utils import name_for_callable + class CustomMiddleware(Middleware): app_name: str func: Callable[..., Any] From 53917de15043d48d0447bab9ba42a5580f8d700d Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Tue, 19 Jan 2021 20:06:06 -0700 Subject: [PATCH 8/8] Fixed return error in name_for_callable and added typing hints. --- slack_bolt/util/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/slack_bolt/util/utils.py b/slack_bolt/util/utils.py index cac7f0d0a..272c7d382 100644 --- a/slack_bolt/util/utils.py +++ b/slack_bolt/util/utils.py @@ -1,6 +1,6 @@ import copy import sys -from typing import Optional, Union, Dict, Any, Sequence +from typing import Optional, Union, Dict, Any, Sequence, Callable from slack_sdk import WebClient from slack_sdk.models import JsonObject @@ -59,8 +59,9 @@ def get_boot_message(development_server: bool = False) -> str: return "⚡️ Bolt app is running!" -def name_for_callable(func): +def name_for_callable(func: Callable) -> str: if hasattr(func, "__name__"): func_name = func.__name__ else: func_name = f"{func.__class__.__module__}.{func.__class__.__name__}" + return func_name