This is the repository with materials for the Building a practical Slack bot with Python & FastAPI training.
This training is using the following OS tools:
curl
- for making HTTP calls.jq
- for pretty printing JSON results.
But in case you don't have them, it's not a problem and we don't need them for the final solution.
To run everything from here, we recommend the following Python setup:
- Use the latest Python version (
3.9.6
by the time of writing). - Create a fresh virtual environment for the training.
- Create a fresh directory to contain everything for this training.
To achieve the points above, use the tools that you use everyday.
We recommend using:
We are going to need a test Slack workspace for this training.
The best approach here is to create a brand new one, but feel free to reuse already existing one.
Now, we are going to create one small FastAPI app and expose an endpoint to test with.
First, we need to install the dependencies:
pip install fastapi
pip install uvicorn[standard] # or uvicorn\[standard\] depending on your shell
Then, in the working directory, create a file called main.py
with the following contents:
from fastapi import Body, FastAPI, Request, Response
app = FastAPI()
@app.post("/echo")
async def echo(request: Request, response: Response, data=Body(...)):
raw_body = await request.body()
body = raw_body.decode("utf-8")
print(data)
return {
"data": data,
"raw_body": body,
"headers": request.headers
}
Now, lets run our server:
uvicorn main:app --reload
And test it by issuing a POST request via curl
(or any other HTTP client that you find suitable):
curl -s -X POST -H "Content-Type: application/json" -d '{"key": "value"}' http://localhost:8000/echo
Few things to note:
- We are using
-s
for a "silent" curl output, skipping the progress bar. - We are sending the
Content-Type
header, set toapplication/json
, because this triggers FastAPI to parse the JSON into thebody
argument.
An example response would look like that:
{
"data": {
"key": "value"
},
"raw_body": "{\"key\": \"value\"}",
"headers": {
"host": "localhost:8000",
"user-agent": "curl/7.58.0",
"accept": "*/*",
"content-type": "application/json",
"content-length": "16"
}
}
Now, we are going to setup an app within our new Slack workspace, so we can start communicating with our server.
- Navigate to https://api.slack.com/
- Click on the
Create an app
button - Click on the
Create New App
button - Select
From scratch
- Type
EuroPython Bot
in theApp Name
field - Select your newly created workspace
After following those steps, you should end up with a screen that looks something like that:
Before we continue further, we'll need 1 more thing.
Since we are going to listen for events from Slack & those events are going to be HTTP POST requests, we need a way to tell Slack how to find our localhost:8000
server.
One way of doing this is using ngrok
- a piece of software that's going to create a tunnel to our localhost & give us a public url, that we'll post in Slack.
Navigate to the download page and download ngrok for you OS & platform.
Once extracted, in a new shell, while our FastAPI server is running, type:
./ngrok http 8000
This will run ngrok
and present us with the public url. Copy that url.
Now, back to Slack setup:
- Go to the
Event Subscriptions
option. - Turn it on.
- Paste your
ngrok
url, pointing to the/echo
endpoint. In my case, that'shttps://c153a68641fd.ngrok.io/echo
- If our server &
ngrok
are running, you'll see a greenVerified
in Slack. Check thengrok
shell - you should see a request there. - Now click on
Subscribe to bot events
, clickAdd Bot User Event
and selectapp_mention
. - Hit
Save Changes
. - Leave the page open, since we'll come back here to add additional things.
Now, lets wire everything together, so we can start developing:
- In the app settings, navigate to
Settings
->Basic Information
- Click the
Install to Workspace
button. - Click
Allow
. - Every time we change the permissions or scopes, we'll have to redo this entire process.
Now, open up the Slack workspace:
- Add the bot (type
/add
in the channel & click on the action item) to a random channel. @
the bot and write something.- Check your FastAPI console.
The event payload that we received should look something like that:
{
"token": "C2lhacRaergBLDrNglaNtVYQ",
"team_id": "T028KF61U7R",
"api_app_id": "A0290D2N9B4",
"event": {
"client_msg_id": "7f025cc5-2f1c-41c9-9ad9-f78d86c13b49",
"type": "app_mention",
"text": "<@U0290LLS803> hello :wave:",
"user": "U028TEER6KY",
"ts": "1627050528.001000",
"team": "T028KF61U7R",
"blocks": [
{
"type": "rich_text",
"block_id": "UBr7f",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "user",
"user_id": "U0290LLS803"
},
{
"type": "text",
"text": " hello "
},
{
"type": "emoji",
"name": "wave"
}
]
}
]
}
],
"channel": "C028TEHTWKG",
"event_ts": "1627050528.001000"
},
"type": "event_callback",
"event_id": "Ev029Q5P36BA",
"event_time": 1627050528,
"authorizations": [
{
"enterprise_id": null,
"team_id": "T028KF61U7R",
"user_id": "U0290LLS803",
"is_bot": true,
"is_enterprise_install": false
}
],
"is_ext_shared_channel": false,
"event_context": "3-app_mention-T028KF61U7R-A0290D2N9B4-C028TEHTWKG"
}
This means we are now ready with our setup and can start adding funcitonality back.
Now, it's time for our tasks.
We want to implement the following general behavior:
- Whenever someone mentions our bot, we want to reply in the channel with a message.
- Whenever someone mentions our bot in a thread, we want to reply in the same thread with a message.
- [BONUS TASK] Whenever someone mentions our bot, start a new thread by replying to that user.
In order to do that, we want to be making HTTP calls to the Slack API via requests
, so we need to do:
pip install requests
Extracted documentation links to help you navigate to the proper stuff to look at:
- What is a Slack app?
- Listening to the
app_mention
event - Sending messages
- Retrieving individual messages
Making a call to Slack requires obtaining Bot User OAuth Token
from the settings page:
Since this is a secret, in order to manage secrets for our app, we recommend using .env
file and https://github.com/theskumar/python-dotenv for parsing it:
from dotenv import dotenv_values
config = dotenv_values(".env") # config = {"USER": "foo", "EMAIL": "[email protected]"}
Everything is great, but our API is public, meaning anyone can call it and start sending messages to our Slack.
We want to prevent this & your next task is to implement request verification!
That's about it.
Materials for further references:
- Since we are using FastAPI with
async
, you can go & replacerequests
with something that's async, like https://docs.aiohttp.org/en/stable/ - There's an offical Python Slack SDK that you can use.
- You can also use
bolt-python
which is a framework for building Slack apps. - The official documentation, of course