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
4 changes: 4 additions & 0 deletions slack_bolt/kwargs_injection/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ def handle_buttons(args):
"""`next()` utility function, which tells the middleware chain that it can continue with the next one"""
next_: Callable[[], None]
"""An alias of `next()` for avoiding the Python built-in method overrides in middleware functions"""
error: Optional[Exception]
"""The exception raised by a listener or middleware; only set in error handlers"""

def __init__(
self,
Expand Down Expand Up @@ -144,6 +146,7 @@ def __init__(
# the naming conflict with the built-in one affects
# only the internals of this method
next: Callable[[], None],
error: Optional[Exception] = None,
**kwargs, # noqa
):
self.logger: logging.Logger = logger
Expand Down Expand Up @@ -177,3 +180,4 @@ def __init__(

self.next: Callable[[], None] = next
self.next_: Callable[[], None] = next
self.error: Optional[Exception] = error
4 changes: 4 additions & 0 deletions slack_bolt/kwargs_injection/async_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ async def handle_buttons(args):
"""`next()` utility function, which tells the middleware chain that it can continue with the next one"""
next_: Callable[[], Awaitable[None]]
"""An alias of `next()` for avoiding the Python built-in method overrides in middleware functions"""
error: Optional[Exception]
"""The exception raised by a listener or middleware; only set in error handlers"""

def __init__(
self,
Expand Down Expand Up @@ -140,6 +142,7 @@ def __init__(
save_thread_context: Optional[AsyncSaveThreadContext] = None,
say_stream: Optional[AsyncSayStream] = None,
next: Callable[[], Awaitable[None]],
error: Optional[Exception] = None,
**kwargs, # noqa
):
self.logger: Logger = logger
Expand Down Expand Up @@ -173,3 +176,4 @@ def __init__(

self.next: Callable[[], Awaitable[None]] = next
self.next_: Callable[[], Awaitable[None]] = next
self.error: Optional[Exception] = error
11 changes: 6 additions & 5 deletions slack_bolt/kwargs_injection/async_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,6 @@ def build_async_required_kwargs(
# error handler
"error": error, # Exception
}
if not next_keys_required:
all_available_args.pop("next")
all_available_args.pop("next_")

all_available_args["payload"] = (
all_available_args["options"]
or all_available_args["shortcut"]
Expand Down Expand Up @@ -98,7 +94,12 @@ def build_async_required_kwargs(
# We are sure that we should skip manipulating this arg
required_arg_names.pop(0)

kwargs: Dict[str, Any] = {k: v for k, v in all_available_args.items() if k in required_arg_names}
# next/next_ are only injectable where a middleware chain exists.
# They stay in all_available_args so an `args` object can still be built.
injectable_names = set(required_arg_names)
if not next_keys_required:
injectable_names -= {"next", "next_"}
kwargs: Dict[str, Any] = {k: v for k, v in all_available_args.items() if k in injectable_names}
found_arg_names = kwargs.keys()
for name in required_arg_names:
if name == "args":
Expand Down
11 changes: 6 additions & 5 deletions slack_bolt/kwargs_injection/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@ def build_required_kwargs(
# error handler
"error": error, # Exception
}
if not next_keys_required:
all_available_args.pop("next")
all_available_args.pop("next_")

all_available_args["payload"] = (
all_available_args["options"]
or all_available_args["shortcut"]
Expand Down Expand Up @@ -97,7 +93,12 @@ def build_required_kwargs(
# We are sure that we should skip manipulating this arg
required_arg_names.pop(0)

kwargs: Dict[str, Any] = {k: v for k, v in all_available_args.items() if k in required_arg_names}
# next/next_ are only injectable where a middleware chain exists.
# They stay in all_available_args so an `args` object can still be built.
injectable_names = set(required_arg_names)
if not next_keys_required:
injectable_names -= {"next", "next_"}
kwargs: Dict[str, Any] = {k: v for k, v in all_available_args.items() if k in injectable_names}
found_arg_names = kwargs.keys()
for name in required_arg_names:
if name == "args":
Expand Down
22 changes: 21 additions & 1 deletion tests/scenario_tests/test_error_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from slack_sdk import WebClient
from slack_sdk.signature import SignatureVerifier

from slack_bolt import BoltRequest, BoltResponse
from slack_bolt import Args, BoltRequest, BoltResponse
from slack_bolt.app import App
from slack_bolt.error import BoltUnhandledRequestError
from tests.mock_web_api_server import (
Expand Down Expand Up @@ -153,6 +153,26 @@ def failing_listener():
assert response.status == 500
assert response.headers["x-test-result"] == ["1"]

def test_custom_with_args(self):
called = []

def error_handler(args: Args):
assert isinstance(args.error, Exception)
args.response.headers["x-test-result"] = ["1"]
called.append(True)

def failing_listener():
raise Exception("Something wrong!")

app = App(client=self.web_client, signing_secret=self.signing_secret)
app.error(error_handler)
app.action("a")(failing_listener)

response = app.dispatch(self.build_valid_request())
assert response.status == 500
assert response.headers["x-test-result"] == ["1"]
assert called == [True]

def test_unhandled_errors(self):
app = App(
client=self.web_client,
Expand Down
22 changes: 22 additions & 0 deletions tests/scenario_tests_async/test_error_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from slack_sdk.web.async_client import AsyncWebClient

from slack_bolt import BoltResponse
from slack_bolt.kwargs_injection.async_args import AsyncArgs
from slack_bolt.async_app import AsyncApp
from slack_bolt.error import BoltUnhandledRequestError
from slack_bolt.request.async_request import AsyncBoltRequest
Expand Down Expand Up @@ -163,6 +164,27 @@ async def failing_listener():
assert response.status == 500
assert response.headers["x-test-result"] == ["1"]

@pytest.mark.asyncio
async def test_custom_with_args(self):
called = []

async def error_handler(args: AsyncArgs):
assert isinstance(args.error, Exception)
args.response.headers["x-test-result"] = ["1"]
called.append(True)

async def failing_listener():
raise Exception("Something wrong!")

app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret)
app.error(error_handler)
app.action("a")(failing_listener)

response = await app.async_dispatch(self.build_valid_request())
assert response.status == 500
assert response.headers["x-test-result"] == ["1"]
assert called == [True]

@pytest.mark.asyncio
async def test_unhandled_errors(self):
app = AsyncApp(
Expand Down