Skip to content

Commit 42fa6a9

Browse files
author
Shay DeWael
authored
Add Advanced Sections to documentation (#104)
Add advanced sections documentation
1 parent de93088 commit 42fa6a9

17 files changed

+526
-185
lines changed

docs/_advanced/adapters.md

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,26 @@
11
---
2-
title: Using in Web frameworks
2+
title: Adapters
33
lang: en
44
slug: adapters
55
order: 0
66
---
77

88
<div class="section-content">
9-
`App#start()` starts a Web server process using the [`http.server` standard library](https://docs.python.org/3/library/http.server.html). As mentioned in its document, it is not recommended to use the module for production.
9+
Adapters are responsible for handling and parsing incoming events from Slack to conform to <a href="https://github.com/slackapi/bolt-python/blob/main/slack_bolt/request/request.py">`BoltRequest`</a>, then dispatching those events to your Bolt app.
1010

11-
Once you're done with local development, it's about time to choose the right Web framework and production-ready server.
11+
By default, Bolt will use the built-in <a href="https://docs.python.org/3/library/http.server.html">`HTTPSever`</a> adapter. While this is okay for local development, <b>it is not recommended for production</b>. Bolt for Python includes a collection of built-in adapters that can be imported and used with your app. The built-in adapters support a variety of popular Python frameworks including Flask, Django, and Starlette among others. Adapters support the use of any production-ready web server of your choice.
1212

13-
Let's try using [Flask](https://flask.palletsprojects.com/) framework along with the [Gunicorn](https://gunicorn.org/) WSGI HTTP server here.
14-
15-
```bash
16-
pip install slack_bolt flask gunicorn
17-
export SLACK_SIGNING_SECRET=***
18-
export SLACK_BOT_TOKEN=xoxb-***
19-
# Save the source code as main.py
20-
gunicorn --bind :3000 --workers 1 --threads 2 --timeout 0 main:flask_app
21-
```
22-
23-
We currently support the following frameworks. As long as a Web framework works, you can run Bolt app in any web servers.
24-
25-
* [Django](https://www.djangoproject.com/)
26-
* [Flask](https://flask.palletsprojects.com/)
27-
* [Starlette](https://www.starlette.io/) & [FastAPI](https://fastapi.tiangolo.com/)
28-
* [Sanic](https://sanicframework.org/)
29-
* [Tornado](https://www.tornadoweb.org/)
30-
* [Falcon](https://falcon.readthedocs.io/)
31-
* [Bottle](https://bottlepy.org/)
32-
* [CherryPy](https://cherrypy.org/)
33-
* [Pyramid](https://trypyramid.com/)
34-
35-
Check [samples](https://github.com/slackapi/bolt-python/tree/main/samples) in the GitHub repository to learn how to configure your app with frameworks.
13+
To use an adapter, you'll create an app with the framework of your choosing and import its corresponding adapter. Then you'll initalize the adapter instance and call its function that handles and parses incoming events.
3614

15+
The full list adapters, as well as configuration and sample usage, can be found within the repository's <a href="https://github.com/slackapi/bolt-python/tree/main/samples">`samples` folder</a>.
3716
</div>
3817

3918
```python
4019
from slack_bolt import App
41-
app = App()
20+
app = App(
21+
signing_secret=os.environ.get("SIGNING_SECRET"),
22+
token=os.environ.get("SLACK_BOT_TOKEN")
23+
)
4224

4325
# There is nothing specific to Flask here!
4426
# App is completely framework/runtime agnostic

docs/_advanced/async.md

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
---
2-
title: Async Bolt
2+
title: Using async
33
lang: en
4-
slug: advanced-async
5-
order: 1
4+
slug: async
5+
order: 2
66
---
77

88
<div class="section-content">
9-
In the Basic concepts section, all the code snippets are not in the asynchronous programming style. You may be wondering if Bolt for Python is not available for asynchronous frameworks and their runtime such as the standard `asyncio` library.
10-
11-
No worries! You can use Bolt with [Starlette](https://www.starlette.io/), [FastAPI](https://fastapi.tiangolo.com/), [Sanic](https://sanicframework.org/), [AIOHTTP](https://docs.aiohttp.org/), and whatever you want to use.
12-
13-
`AsyncApp` internally relies on AIOHTTP for making HTTP requests to Slack API servers. So, to use the async version of Bolt, add `aiohttp` to `requirements.txt` or run `pip install aiohttp`.
14-
15-
You can find sample projects in [samples](https://github.com/slackapi/bolt-python/tree/main/samples) directory in the GitHub repository.
9+
To use the async version of Bolt, you can import and initialize an `AsyncApp` instance (rather than `App`). `AsyncApp` relies on <a href="https://docs.aiohttp.org/">AIOHTTP</a> to make API requests, which means you'll need to install `aiohttp` (by adding to `requirements.txt` or running `pip install aiohttp`).
1610

11+
Sample async projects can be found within the repository's <a href="https://github.com/slackapi/bolt-python/tree/main/samples">`samples` folder</a>.
1712
</div>
1813

1914
```python
20-
# required: pip install aiohttp
15+
# Requirement: install aiohttp
2116
from slack_bolt.async_app import AsyncApp
2217
app = AsyncApp()
2318

@@ -36,25 +31,23 @@ if __name__ == "__main__":
3631

3732
<details class="secondary-wrapper">
3833
<summary class="section-head" markdown="0">
39-
<h4 class="section-head">Using other async frameworks</h4>
34+
<h4 class="section-head">Using other frameworks</h4>
4035
</summary>
4136

4237
<div class="secondary-content" markdown="0">
4338

44-
`AsyncApp#start()` internally uses [AIOHTTP](https://docs.aiohttp.org/)'s web server feature. However, this doesn't mean you have to use AIOHTTP. `AsyncApp` can handle incoming requests from Slack using any other frameworks.
39+
Internally `AsyncApp#start()` implements a [`AIOHTTP`](https://docs.aiohttp.org/) web server. If you prefer, you can use a framework other than `AIOHTTP` to handle incoming requests.
40+
41+
This example uses [Sanic](https://sanicframework.org/), but the full list of adapters are in the [`adapter` folder](https://github.com/slackapi/bolt-python/tree/main/slack_bolt/adapter).
4542

46-
The code snippet in this section is an example using [Sanic](https://sanicframework.org/). You can start your Slack app server-side built with Sanic only by running the following commands.
43+
The following commands install the necessary requirements and starts the Sanic server on port 3000.
4744

4845
```bash
46+
# Install requirements
4947
pip install slack_bolt sanic uvicorn
50-
export SLACK_SIGNING_SECRET=***
51-
export SLACK_BOT_TOKEN=xoxb-***
52-
# save the source as async_app.py
48+
# Save the source as async_app.py
5349
uvicorn async_app:api --reload --port 3000 --log-level debug
5450
```
55-
56-
If you would like to use other frameworks, check the list of the built-in adapters [here](https://github.com/slackapi/bolt-python/tree/main/slack_bolt/adapter). If your favorite framework is available there, you can use Bolt with it in a similar way.
57-
5851
</div>
5952

6053
```python
@@ -85,5 +78,4 @@ async def endpoint(req: Request):
8578
if __name__ == "__main__":
8679
api.run(host="0.0.0.0", port=int(os.environ.get("PORT", 3000)))
8780
```
88-
8981
</details>

docs/_advanced/authorization.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
title: Authorization
3+
lang: en
4+
slug: authorization
5+
order: 5
6+
---
7+
8+
<div class="section-content">
9+
Authorization is the process of determining which Slack credentials should be available while processing an incoming Slack event.
10+
11+
Apps installed on a single workspace can simply pass their bot token into the `App` constructor using the `token` parameter. However, if your app will be installed on multiple workspaces, you have two options. The easier option is to use the built-in OAuth support. This will handle setting up OAuth routes and verifying state. Read the section on [authenticating with OAuth](#authenticating-oauth) for details.
12+
13+
For a more custom solution, you can set the `authorize` parameter to a function upon `App` instantiation. The `authorize` function should return [an instance of `AuthorizeResult`](https://github.com/slackapi/bolt-python/blob/main/slack_bolt/authorization/authorize_result.py), which contains information about who and where the event is coming from.
14+
15+
`AuthorizeResult` should have a few specific properties, all of type `str`:
16+
- Either **`bot_token`** (xoxb) *or* **`user_token`** (xoxp) are **required**. Most apps will use `bot_token` by default. Passing a token allows built-in functions (like `say()`) to work.
17+
- **`bot_user_id`** and **`bot_id`**, if using a `bot_token`.
18+
- **`enterprise_id`** and **`team_id`**, which can be found in events sent to your app.
19+
- **`user_id`** only when using `user_token`.
20+
</div>
21+
22+
```python
23+
import os
24+
from slack_bolt import App
25+
# Import the AuthorizationResult class
26+
from slack_bolt.authorization import AuthorizationResult
27+
28+
# This is just an example (assumes there are no user tokens)
29+
# You should store authorizations in a secure DB
30+
installations = [
31+
{
32+
"enterprise_id": "E1234A12AB",
33+
"team_id": "T12345",
34+
"bot_token": "xoxb-123abc",
35+
"bot_id": "B1251",
36+
"bot_user_id": "U12385"
37+
},
38+
{
39+
"team_id": "T77712",
40+
"bot_token": "xoxb-102anc",
41+
"bot_id": "B5910",
42+
"bot_user_id": "U1239",
43+
"enterprise_id": "E1234A12AB"
44+
}
45+
]
46+
47+
def authorize(enterprise_id, team_id, logger):
48+
49+
# You can implement your own logic to fetch token here
50+
51+
for (team in installations):
52+
# enterprise_id doesn't exist for some teams
53+
is_valid_enterprise = True if (("enterprise_id" not in team) or (enterprise_id == team["enterprise_id"])) else False
54+
55+
if ((is_valid_enterprise == True) and (team["team_id"] == team_id)):
56+
# Return an instance of AuthorizeResult
57+
# If you don't store bot_id and bot_user_id, could also call `from_auth_test_response` with your bot_token to automatically fetch them
58+
return AuthorizeResult(
59+
enterprise_id=enterprise_id,
60+
team_id=team_id,
61+
bot_token=team["bot_token"],
62+
bot_id=team["bot_id"],
63+
bot_user_id=team["bot_user_id"]
64+
)
65+
66+
logger.error("No authorization information was found")
67+
68+
app = App(
69+
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
70+
authorize=authorize
71+
)
72+
```

docs/_advanced/context.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
title: Adding context
3+
lang: en
4+
slug: context
5+
order: 7
6+
---
7+
8+
<div class="section-content">
9+
All listeners have access to a `context` dictionary, which can be used to enrich events with additional information. Bolt automatically attaches information that is included in the incoming event, like `user_id`, `team_id`, `channel_id`, and `enterprise_id`.
10+
11+
`context` is just a dictionary, so you can directly modify it.
12+
</div>
13+
14+
```python
15+
# Listener middleware to fetch tasks from external system using userId
16+
def fetch_tasks(context, event, next):
17+
user = event["user"]
18+
19+
try:
20+
# Assume get_tasks fetchs list of tasks from DB corresponding to user ID
21+
user_tasks = db.get_tasks(user)
22+
tasks = user_tasks
23+
except Exception:
24+
# get_tasks() raises exception because no tasks are found
25+
tasks = []
26+
finally:
27+
# Put user's tasks in context
28+
context["tasks"] = tasks
29+
next()
30+
31+
# Listener middleware to create a list of section blocks
32+
def create_sections(context, next):
33+
task_blocks = []
34+
35+
# Loops through tasks added to context in previous middleware
36+
for task in context["tasks"]:
37+
task_blocks.append(
38+
{
39+
"type": "section",
40+
"text": {
41+
"type": "mrkdwn",
42+
"text": f"*{task['title']}*\n{task['body']}"
43+
},
44+
"accessory": {
45+
"type": "button",
46+
"text": {
47+
"type": "plain_text",
48+
"text": "See task"
49+
},
50+
"url": task["url"],
51+
}
52+
}
53+
)
54+
55+
# Put list of blocks in context
56+
context["blocks"] = task_blocks
57+
next()
58+
59+
# Listen for user opening app home
60+
# Include fetch_tasks middleware
61+
@app.event(
62+
event = "app_home_opened",
63+
middleware = [fetch_tasks, create_sections]
64+
)
65+
def show_tasks(event, client, context):
66+
# Publish view to user's home tab
67+
client.views_publish(
68+
user_id=event["user"],
69+
view={
70+
"type": "home",
71+
"blocks": context["blocks"]
72+
}
73+
)
74+
```

docs/_advanced/custom_adapters.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
title: Custom adapters
3+
lang: en
4+
slug: custom-adapters
5+
order: 1
6+
---
7+
8+
<div class="section-content">
9+
[Adapters](#adapters) are flexible and can be adjusted based on the framework you prefer. There are two necessary components of adapters:
10+
11+
- `__init__(app: App)`: Constructor that accepts and stores an instance of the Bolt `App`.
12+
- `handle(req: Request)`: Function (typically named `handle()`) that receives incoming Slack requests, parses them to conform to an instance of [`BoltRequest`](https://github.com/slackapi/bolt-python/blob/main/slack_bolt/request/request.py), then dispatches them to the stored Bolt app.
13+
14+
`BoltRequest` instantiation accepts four parameters:
15+
16+
| Parameter | Description | Required? |
17+
|-----------|-------------|-----------|
18+
| `body: str` | The raw request body | **Yes** |
19+
| `query: any` | The query string data | No |
20+
| `headers: Dict[str, Union[str, List[str]]]` | Request headers | No |
21+
| `context: Dict[str, str]` | Any context for the request | No |
22+
23+
`BoltRequest` will return [an instance of `BoltResponse`](https://github.com/slackapi/bolt-python/blob/main/slack_bolt/response/response.py) from the Bolt app.
24+
25+
For more in-depth examples of custom adapters, look at the implementations of the [built-in adapters](https://github.com/slackapi/bolt-python/tree/main/slack_bolt/adapter).
26+
</div>
27+
28+
```python
29+
# Necessary imports for Flask
30+
from flask import Request, Response, make_response
31+
32+
from slack_bolt.app import App
33+
from slack_bolt.request import BoltRequest
34+
from slack_bolt.response import BoltResponse
35+
36+
# This example is a simplified version of the Flask adapter
37+
# For a more detailed, complete example, look in the adapter folder
38+
# github.com/slackapi/bolt-python/blob/main/slack_bolt/adapter/flask/handler.py
39+
40+
# Takes in an HTTP request and converts it to a standard BoltRequest
41+
def to_bolt_request(req: Request) -> BoltRequest:
42+
return BoltRequest(
43+
body=req.get_data(as_text=True),
44+
query=req.query_string.decode("utf-8"),
45+
headers=req.headers,
46+
)
47+
48+
# Takes in a BoltResponse and converts it to a standard Flask response
49+
def to_flask_response(bolt_resp: BoltResponse) -> Response:
50+
resp: Response = make_response(bolt_resp.body, bolt_resp.status)
51+
for k, values in bolt_resp.headers.items():
52+
for v in values:
53+
resp.headers.add_header(k, v)
54+
return resp
55+
56+
# Instantiated from your app
57+
# Accepts a Flask app
58+
class SlackRequestHandler:
59+
def __init__(self, app: App):
60+
self.app = app
61+
62+
# handle() will be called from your Flask app
63+
# when you receive a request from Slack
64+
def handle(self, req: Request) -> Response:
65+
# This example does not cover OAuth
66+
if req.method == "POST":
67+
# Dispatch the request for Bolt to handle and route
68+
bolt_resp: BoltResponse = self.app.dispatch(to_bolt_request(req))
69+
return to_flask_response(bolt_resp)
70+
71+
return make_response("Not Found", 404)
72+
```

docs/_advanced/errors.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
title: Handling errors
3+
lang: en
4+
slug: errors
5+
order: 3
6+
---
7+
8+
<div class="section-content">
9+
If an error occurs in a listener, you can handle it directly using a `try`/`except` block. Errors associated with your app will be of type `BoltError`. Errors associated with calling Slack APIs will be of type `SlackApiError`.
10+
11+
By default, the global error handler will log all non-handled exceptions to the console. To handle global errors yourself, you can attach a global error handler to your app using the `app.error(fn)` function.
12+
</div>
13+
14+
```python
15+
@app.error
16+
def custom_error_handler(error, body, logger):
17+
logger.exception(f"Error: {error}")
18+
logger.info(f"Request body: {body}")
19+
```

0 commit comments

Comments
 (0)