Skip to content

Commit

Permalink
Merge 5a9cb0a into 837c773
Browse files Browse the repository at this point in the history
  • Loading branch information
sarayourfriend authored Mar 15, 2022
2 parents 837c773 + 5a9cb0a commit efd3fb8
Show file tree
Hide file tree
Showing 7 changed files with 499 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,5 @@ jobs:
- name: Run tests for HTTP Mode adapters (asyncio-based libraries)
run: |
pip install -e ".[async]"
pip install "falcon>=3,<4"
pytest tests/adapter_tests_async/
100 changes: 100 additions & 0 deletions examples/falcon/async_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import falcon
import logging
import re
from slack_bolt.async_app import AsyncApp, AsyncRespond, AsyncAck
from slack_bolt.adapter.falcon import AsyncSlackAppResource

logging.basicConfig(level=logging.DEBUG)
app = AsyncApp()


# @app.command("/bolt-py-proto", [lambda body: body["team_id"] == "T03E94MJU"])
async def test_command(logger: logging.Logger, body: dict, ack: AsyncAck, respond: AsyncRespond):
logger.info(body)
await ack("thanks!")
await respond(
blocks=[
{
"type": "section",
"block_id": "b",
"text": {
"type": "mrkdwn",
"text": "You can add a button alongside text in your message. ",
},
"accessory": {
"type": "button",
"action_id": "a",
"text": {"type": "plain_text", "text": "Button"},
"value": "click_me_123",
},
}
]
)


app.command(re.compile(r"/hello-bolt-.+"))(test_command)


@app.shortcut("test-shortcut")
async def test_shortcut(ack, client, logger, body):
logger.info(body)
await ack()
res = await client.views_open(
trigger_id=body["trigger_id"],
view={
"type": "modal",
"callback_id": "view-id",
"title": {
"type": "plain_text",
"text": "My App",
},
"submit": {
"type": "plain_text",
"text": "Submit",
},
"close": {
"type": "plain_text",
"text": "Cancel",
},
"blocks": [
{
"type": "input",
"element": {"type": "plain_text_input"},
"label": {
"type": "plain_text",
"text": "Label",
},
}
],
},
)
logger.info(res)


@app.view("view-id")
async def view_submission(ack, body, logger):
logger.info(body)
await ack()


@app.action("a")
async def button_click(logger, action, ack, respond):
logger.info(action)
await ack()
await respond("Here is my response")


@app.event("app_mention")
async def handle_app_mentions(body, say, logger):
logger.info(body)
await say("What's up?")


api = falcon.asgi.App()
resource = AsyncSlackAppResource(app)
api.add_route("/slack/events", resource)

# pip install -r requirements.txt
# export SLACK_SIGNING_SECRET=***
# export SLACK_BOT_TOKEN=xoxb-***
# uvicorn --reload -h 0.0.0.0 -p 3000 async_app:api
102 changes: 102 additions & 0 deletions examples/falcon/async_oauth_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import falcon
import logging
import re
from slack_bolt.async_app import AsyncApp, AsyncRespond, AsyncAck
from slack_bolt.adapter.falcon import AsyncSlackAppResource

logging.basicConfig(level=logging.DEBUG)
app = AsyncApp()


# @app.command("/bolt-py-proto", [lambda body: body["team_id"] == "T03E94MJU"])
async def test_command(logger: logging.Logger, body: dict, ack: AsyncAck, respond: AsyncRespond):
logger.info(body)
await ack("thanks!")
await respond(
blocks=[
{
"type": "section",
"block_id": "b",
"text": {
"type": "mrkdwn",
"text": "You can add a button alongside text in your message. ",
},
"accessory": {
"type": "button",
"action_id": "a",
"text": {"type": "plain_text", "text": "Button"},
"value": "click_me_123",
},
}
]
)


app.command(re.compile(r"/hello-bolt-.+"))(test_command)


@app.shortcut("test-shortcut")
async def test_shortcut(ack, client, logger, body):
logger.info(body)
await ack()
res = await client.views_open(
trigger_id=body["trigger_id"],
view={
"type": "modal",
"callback_id": "view-id",
"title": {
"type": "plain_text",
"text": "My App",
},
"submit": {
"type": "plain_text",
"text": "Submit",
},
"close": {
"type": "plain_text",
"text": "Cancel",
},
"blocks": [
{
"type": "input",
"element": {"type": "plain_text_input"},
"label": {
"type": "plain_text",
"text": "Label",
},
}
],
},
)
logger.info(res)


@app.view("view-id")
async def view_submission(ack, body, logger):
logger.info(body)
await ack()


@app.action("a")
async def button_click(logger, action, ack, respond):
logger.info(action)
await ack()
await respond("Here is my response")


@app.event("app_mention")
async def handle_app_mentions(body, say, logger):
logger.info(body)
await say("What's up?")


api = falcon.asgi.App()
resource = AsyncSlackAppResource(app)
api.add_route("/slack/events", resource)

# pip install -r requirements.txt
# export SLACK_SIGNING_SECRET=***
# export SLACK_BOT_TOKEN=xoxb-***
# uvicorn --reload -h 0.0.0.0 -p 3000 async_oauth_app:api
api.add_route("/slack/install", resource)
api.add_route("/slack/oauth_redirect", resource)
3 changes: 2 additions & 1 deletion examples/falcon/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
falcon>=2,<3
gunicorn>=20,<21
gunicorn>=20,<21
uvicorn
1 change: 1 addition & 0 deletions slack_bolt/adapter/falcon/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
# Don't add async module imports here
from .resource import SlackAppResource
84 changes: 84 additions & 0 deletions slack_bolt/adapter/falcon/async_resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from datetime import datetime # type: ignore
from http import HTTPStatus

from falcon import version as falcon_version
from falcon.asgi import Request, Response
from slack_bolt import BoltResponse
from slack_bolt.async_app import AsyncApp
from slack_bolt.error import BoltError
from slack_bolt.oauth.async_oauth_flow import AsyncOAuthFlow
from slack_bolt.request.async_request import AsyncBoltRequest


class AsyncSlackAppResource:
"""
For use with ASGI Falcon Apps.
from slack_bolt.async_app import AsyncApp
app = AsyncApp()
import falcon
app = falcon.asgi.App()
app.add_route("/slack/events", AsyncSlackAppResource(app))
"""

def __init__(self, app: AsyncApp): # type: ignore
if falcon_version.__version__.startswith("2."):
raise BoltError("This ASGI compatible adapter requires Falcon version >= 3.0")

self.app = app

async def on_get(self, req: Request, resp: Response):
if self.app.oauth_flow is not None:
oauth_flow: AsyncOAuthFlow = self.app.oauth_flow
if req.path == oauth_flow.install_path:
bolt_resp = await oauth_flow.handle_installation(
await self._to_bolt_request(req)
)
await self._write_response(bolt_resp, resp)
return
elif req.path == oauth_flow.redirect_uri_path:
bolt_resp = await oauth_flow.handle_callback(
await self._to_bolt_request(req)
)
await self._write_response(bolt_resp, resp)
return

resp.status = "404"
resp.body = "The page is not found..."

async def on_post(self, req: Request, resp: Response):
bolt_req = await self._to_bolt_request(req)
bolt_resp = await self.app.async_dispatch(bolt_req)
await self._write_response(bolt_resp, resp)

async def _to_bolt_request(self, req: Request) -> AsyncBoltRequest:
return AsyncBoltRequest(
body=(await req.stream.read(req.content_length or 0)).decode("utf-8"),
query=req.query_string,
headers={k.lower(): v for k, v in req.headers.items()},
)

async def _write_response(self, bolt_resp: BoltResponse, resp: Response):
resp.text = bolt_resp.body
status = HTTPStatus(bolt_resp.status)
resp.status = str(f"{status.value} {status.phrase}")
resp.set_headers(bolt_resp.first_headers_without_set_cookie())
for cookie in bolt_resp.cookies():
for name, c in cookie.items():
expire_value = c.get("expires")
expire = (
datetime.strptime(expire_value, "%a, %d %b %Y %H:%M:%S %Z")
if expire_value
else None
)
resp.set_cookie(
name=name,
value=c.value,
expires=expire,
max_age=c.get("max-age"),
domain=c.get("domain"),
path=c.get("path"),
secure=True,
http_only=True,
)
Loading

0 comments on commit efd3fb8

Please sign in to comment.