How to pass data received via HTTP POST from a Python Flask script to a separate Python script for processing?
Great Success!
tl;dr:Basically, the solution was to create a Celery task that instantiated the bot instance from the Flask app using the Slack Events API. You set the task to start after the desired input has been entered, promptly return the required Response(200) back to Slack, while in the meantime the bot script (which starts up the RTM API web-socket) launches in parallel.
The nitty gritty:So, as stated above, it turns out that what was required was a queuing service of some sort. I ended up going with Celery for its relative ease at integrating with Heroku (where I host the Slack app) and its easy-to-follow documentation.
Developing your Slack app this way requires setting up and using the Slack Events API to receive the command ("play my_game" in this example) from the Slack channel the message was posted in. The Flask app (app.py) portion of the program listens for this event, and when the input matches up with what you're looking for, it launches the Celery task in parallel (in tasks.py, which instantiates a Bot() instance of bot.py in this example). :) Now the bot can listen and respond using both the Slack RTM API and the Slack Events API. This allows you to build rich applications/services within the Slack framework.
If you are looking to set up something similar, below are my project layout and the important code details. Feel free to use them as a template.
Project Layout:
- project_name_folder
- app_folder
- static_folder
- templates_folder
- __init__.py
- my_app.py
- bot.py
- tasks.py
- Procfile
- requirements.txt
- app_folder
__init__.py:
from celery import Celeryapp = Celery('tasks')import osapp.conf.update(BROKER_URL=os.environ['RABBITMQ_BIGWIG_URL']) # Heroku Celery broker
my_app.py:
from flask import Flask, request, Response, render_templateimport appfrom app import tasksapp = Flask(__name__)@app.route('/events', methods=['POST'])def events():"""Handles the inbound event of a post to the main Slack channel""" data = json.loads(request.data) try: for k, v in data['event'].iteritems(): ts = data['event']['ts'] channel = data['event']['channel'] user_id = data['event']['user'] team_id = data['team_id'] if 'play my_game' in str(v): tasks.launch_bot.delay(user_id, channel, ts, team_id) # launch the bot in parallel return Response(), 200 except Exception as e: raise
bot.py:
from slackclient import SlackClientclass Bot(): def main(): # opening the Slack web-socket connection READ_WEBSOCKET_DELAY = 1 # 1 second delay between reading from firehose if self.slack_client.rtm_connect(): while True: command, channel, user, ts = self.parse_slack_output() if command and channel: if channel not in self.channel_ids_to_name.keys(): # this (most likely) means that this channel is a PM with the bot self.handle_private_message(command, user, ts) else: self.handle_command(command, channel, user, ts) time.sleep(READ_WEBSOCKET_DELAY)
tasks.py:
import botfrom bot import Botfrom app import app@app.taskdef launch_bot(user_id, channel, ts, team_id):'''Instantiates the necessary objects to play a gameArgs: [user_id] (str) The id of the user from which the command was sent [channel] (str) The channel the command was posted in [ts] (str) The timestamp of the command''' print "launch_bot(user_id,channel)" app.control.purge() bot = Bot() bot.initialize(user_id, channel) bot.main()
Procfile (if using Heroku):
web: gunicorn --pythonpath app my_app:appworker: celery -A app.tasks worker -B --loglevel=DEBUG
Please let me know if you are having any issues. This took me a little while to figure out, and I would be happy to help you if you are banging your head on this one.