Skip to content

Commit e1b7a6f

Browse files
Fix chalice deployment bug introduced in #270 (#316)
* Fix chalice deployment bug introduced in #270 * Refactor LocalLambdaClient into seperate file * try/catch import when running from a deployed lambda * Updates from PR feedback, improve lazy lambda tests * Only import LocalLambdaClient if CLI and client not passed in * Add unittest for default lazy listener * Fix name of mocked lambda client in test_lazy_listeners_non_cli
1 parent c649e2b commit e1b7a6f

File tree

4 files changed

+106
-35
lines changed

4 files changed

+106
-35
lines changed

slack_bolt/adapter/aws_lambda/chalice_handler.py

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import logging
2-
import json
32
from os import getenv
3+
from typing import Optional
4+
5+
from botocore.client import BaseClient
46

57
from chalice.app import Request, Response, Chalice
6-
from chalice.config import Config
7-
from chalice.test import BaseClient, LambdaContext, InvokeResponse
88

99
from slack_bolt.adapter.aws_lambda.chalice_lazy_listener_runner import (
1010
ChaliceLazyListenerRunner,
@@ -17,38 +17,22 @@
1717
from slack_bolt.response import BoltResponse
1818

1919

20-
class LocalLambdaClient(BaseClient):
21-
"""Lambda client implementing `invoke` for use when running with Chalice CLI"""
22-
23-
def __init__(self, app: Chalice, config: Config) -> None:
24-
self._app = app
25-
self._config = config
26-
27-
def invoke(
28-
self,
29-
FunctionName: str = None,
30-
InvocationType: str = "Event",
31-
Payload: str = "{}",
32-
) -> InvokeResponse:
33-
scoped = self._config.scope(self._config.chalice_stage, FunctionName)
34-
lambda_context = LambdaContext(
35-
FunctionName, memory_size=scoped.lambda_memory_size
36-
)
37-
38-
with self._patched_env_vars(scoped.environment_variables):
39-
response = self._app(json.loads(Payload), lambda_context)
40-
return InvokeResponse(payload=response)
41-
42-
4320
class ChaliceSlackRequestHandler:
44-
def __init__(self, app: App, chalice: Chalice): # type: ignore
21+
def __init__(self, app: App, chalice: Chalice, lambda_client: Optional[BaseClient] = None): # type: ignore
4522
self.app = app
4623
self.chalice = chalice
4724
self.logger = get_bolt_app_logger(app.name, ChaliceSlackRequestHandler)
4825

49-
lambda_client = None
50-
if getenv("AWS_CHALICE_CLI_MODE") == "true":
51-
lambda_client = LocalLambdaClient(self.chalice, Config())
26+
if getenv("AWS_CHALICE_CLI_MODE") == "true" and lambda_client is None:
27+
try:
28+
from slack_bolt.adapter.aws_lambda.local_lambda_client import (
29+
LocalLambdaClient,
30+
)
31+
32+
lambda_client = LocalLambdaClient(self.chalice, None)
33+
except ImportError:
34+
logging.info("Failed to load LocalLambdaClient for CLI mode.")
35+
pass
5236

5337
self.app.listener_runner.lazy_listener_runner = ChaliceLazyListenerRunner(
5438
logger=self.logger, lambda_client=lambda_client

slack_bolt/adapter/aws_lambda/chalice_lazy_listener_runner.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import json
22
from logging import Logger
3-
from typing import Callable, Optional, Any
3+
from typing import Callable, Optional
44

55
import boto3
6+
from botocore.client import BaseClient
67

78
from slack_bolt import BoltRequest
89
from slack_bolt.lazy_listener import LazyListenerRunner
910

1011

1112
class ChaliceLazyListenerRunner(LazyListenerRunner):
12-
def __init__(self, logger: Logger, lambda_client: Optional[Any] = None):
13+
def __init__(self, logger: Logger, lambda_client: Optional[BaseClient] = None):
1314
self.lambda_client = lambda_client
1415
self.logger = logger
1516

@@ -38,4 +39,3 @@ def start(self, function: Callable[..., None], request: BoltRequest) -> None:
3839
InvocationType="Event",
3940
Payload=json.dumps(payload),
4041
)
41-
self.logger.info(invocation)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import json
2+
3+
from chalice.app import Chalice
4+
from chalice.config import Config
5+
from chalice.test import BaseClient, LambdaContext, InvokeResponse
6+
7+
8+
class LocalLambdaClient(BaseClient):
9+
"""Lambda client implementing `invoke` for use when running with Chalice CLI."""
10+
11+
def __init__(self, app: Chalice, config: Config) -> None:
12+
self._app = app
13+
self._config = config if config else Config()
14+
15+
def invoke(
16+
self,
17+
FunctionName: str = None,
18+
InvocationType: str = "Event",
19+
Payload: str = "{}",
20+
) -> InvokeResponse:
21+
scoped = self._config.scope(self._config.chalice_stage, FunctionName)
22+
lambda_context = LambdaContext(
23+
FunctionName, memory_size=scoped.lambda_memory_size
24+
)
25+
26+
with self._patched_env_vars(scoped.environment_variables):
27+
response = self._app(json.loads(Payload), lambda_context)
28+
return InvokeResponse(payload=response)

tests/adapter_tests/aws/test_aws_chalice.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from typing import Dict, Any
55
from urllib.parse import quote
66
from unittest import mock
7-
import logging
87

98
from chalice import Chalice, Response
109
from chalice.app import Request
@@ -18,6 +17,7 @@
1817
ChaliceSlackRequestHandler,
1918
not_found,
2019
)
20+
2121
from slack_bolt.app import App
2222
from slack_bolt.oauth.oauth_settings import OAuthSettings
2323
from tests.mock_web_api_server import (
@@ -315,13 +315,72 @@ def events() -> Response:
315315
return slack_handler.handle(chalice_app.current_request)
316316

317317
headers = self.build_headers(timestamp, body)
318-
client = Client(chalice_app, Config())
318+
client = Client(chalice_app)
319319
response = client.http.post("/slack/events", headers=headers, body=body)
320320

321321
assert response.status_code == 200, f"Failed request: {response.body}"
322322
assert_auth_test_count(self, 1)
323323
assert self.mock_received_requests["/chat.postMessage"] == 1
324324

325+
@mock.patch(
326+
"slack_bolt.adapter.aws_lambda.chalice_lazy_listener_runner.boto3",
327+
autospec=True,
328+
)
329+
def test_lazy_listeners_non_cli(self, mock_boto3):
330+
with mock.patch.dict(os.environ, {"AWS_CHALICE_CLI_MODE": "false"}):
331+
assert os.environ.get("AWS_CHALICE_CLI_MODE") == "false"
332+
333+
mock_lambda = mock.MagicMock() # mock of boto3.client('lambda')
334+
mock_boto3.client.return_value = mock_lambda
335+
app = App(
336+
client=self.web_client,
337+
signing_secret=self.signing_secret,
338+
process_before_response=True,
339+
)
340+
341+
def command_handler(ack):
342+
ack()
343+
344+
def say_it(say):
345+
say("Done!")
346+
347+
app.command("/hello-world")(ack=command_handler, lazy=[say_it])
348+
349+
input = (
350+
"token=verification_token"
351+
"&team_id=T111"
352+
"&team_domain=test-domain"
353+
"&channel_id=C111"
354+
"&channel_name=random"
355+
"&user_id=W111"
356+
"&user_name=primary-owner"
357+
"&command=%2Fhello-world"
358+
"&text=Hi"
359+
"&enterprise_id=E111"
360+
"&enterprise_name=Org+Name"
361+
"&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx"
362+
"&trigger_id=111.111.xxx"
363+
)
364+
timestamp, body = str(int(time())), input
365+
366+
chalice_app = Chalice(app_name="bolt-python-chalice")
367+
368+
slack_handler = ChaliceSlackRequestHandler(app=app, chalice=chalice_app)
369+
370+
@chalice_app.route(
371+
"/slack/events",
372+
methods=["POST"],
373+
content_types=["application/x-www-form-urlencoded", "application/json"],
374+
)
375+
def events() -> Response:
376+
return slack_handler.handle(chalice_app.current_request)
377+
378+
headers = self.build_headers(timestamp, body)
379+
client = Client(chalice_app)
380+
response = client.http.post("/slack/events", headers=headers, body=body)
381+
assert response
382+
assert mock_lambda.invoke.called
383+
325384
def test_oauth(self):
326385
app = App(
327386
client=self.web_client,

0 commit comments

Comments
 (0)