Skip to content

Commit

Permalink
Merge 3db8dea into 7e99262
Browse files Browse the repository at this point in the history
  • Loading branch information
vabs22 committed Jun 18, 2017
2 parents 7e99262 + 3db8dea commit 3674b5e
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 72 deletions.
70 changes: 0 additions & 70 deletions api/flask_bot_server.py

This file was deleted.

16 changes: 14 additions & 2 deletions api/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ def recur_expand(target_root, dir):
'Topic :: Communications :: Chat',
],
url='https://www.zulip.org/',
packages=['zulip'],
data_files=[('share/zulip/examples',
["examples/zuliprc",
"examples/create-user",
Expand All @@ -60,21 +59,27 @@ def recur_expand(target_root, dir):
entry_points={
'console_scripts': [
'zulip-send=zulip.send:main',
'zulip-bot-server=zulip.bot_server:main',
],
},
test_suite='tests',
) # type: Dict[str, Any]

setuptools_info = dict(
install_requires=['requests>=0.12.1',
'simplejson',
'six',
'typing>=3.5.2.2',
'flask>=0.12.2',
'mock>=2.0.0',
],
)

try:
from setuptools import setup
from setuptools import setup, find_packages
package_info.update(setuptools_info)
package_info['packages'] = find_packages()

except ImportError:
from distutils.core import setup
from distutils.version import LooseVersion
Expand All @@ -91,5 +96,12 @@ def recur_expand(target_root, dir):
print("requests >=0.12.1 is not installed", file=sys.stderr)
sys.exit(1)

package_list = ['zulip', 'bots_api', 'bots']
bots_dirs = os.listdir('bots')
for bot in bots_dirs:
if os.path.isdir(os.path.join('bots', bot)):
package_list.append('bots.' + bot)
package_info['packages'] = package_list


setup(**package_info)
Empty file added api/tests/__init__.py
Empty file.
63 changes: 63 additions & 0 deletions api/tests/test_bot_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import zulip.bot_server
import unittest
import mock
import json
from typing import Any

class BotServerTests(unittest.TestCase):
class MockMessageHandler(object):
def handle_message(self, message, bot_handler, state_handler):
# type: (Any, Any, Any) -> None
assert message == {'key': "test message"}

class MockLibModule(object):
def handler_class(self):
# type: () -> Any
return BotServerTests.MockMessageHandler()

def setUp(self):
# type: () -> None
zulip.bot_server.app.testing = True
self.app = zulip.bot_server.app.test_client()

@mock.patch('zulip.bot_server.BotHandlerApi')
def test_successful_request(self, mock_BotHandlerApi):
# type: (mock.Mock) -> None
zulip.bot_server.available_bots = ['testbot']
zulip.bot_server.bots_config = {
'testbot': {
'email': 'testbot-bot@zulip.com',
'key': '123456789qwertyuiop',
'site': 'http://localhost',
}
}
zulip.bot_server.bots_lib_module['testbot'] = BotServerTests.MockLibModule()
response = self.app.post('/bots/testbot', data=json.dumps(dict(message={'key': "test message"})))

assert response.status_code >= 200 and response.status_code < 300
assert mock_BotHandlerApi.called

def test_bot_not_supported(self):
# type: () -> None
if 'not_supported_bot' in zulip.bot_server.available_bots:
zulip.bot_server.available_bots.remove('not_supported_bot')
response = self.app.post('/bots/not_supported_bot', data=json.dumps(dict(message={'key': "test message"})))

assert response.status_code >= 400 and response.status_code < 500

def test_wrong_bot_credentials(self):
# type: () -> None
zulip.bot_server.available_bots = ['testbot']
zulip.bot_server.bots_config = {
'testbot': {
'email': 'testbot-bot@zulip.com',
'key': '123456789qwertyuiop',
'site': 'http://localhost',
}
}
response = self.app.post('/bots/testbot', data=json.dumps(dict(message={'key': "test message"})))

assert response.status_code >= 400 and response.status_code < 500

if __name__ == '__main__':
unittest.main()
103 changes: 103 additions & 0 deletions api/zulip/bot_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from __future__ import absolute_import
from __future__ import print_function

import sys
import json
import optparse
from flask import Flask, request
from importlib import import_module
from typing import Any, Dict, Mapping, Union, List, Tuple
from werkzeug.exceptions import BadRequest
from six.moves.configparser import SafeConfigParser

from zulip import Client
from bots_api.bot_lib import BotHandlerApi, StateHandler

bots_config = {} # type: Dict[str, Mapping[str, str]]
available_bots = [] # type: List[str]
bots_lib_module = {} # type: Dict[str, Any]

def read_config_file(config_file_path):
# type: (str) -> None
parser = SafeConfigParser()
parser.read(config_file_path)

for section in parser.sections():
bots_config[section] = {
"email": parser.get(section, 'email'),
"key": parser.get(section, 'key'),
"site": parser.get(section, 'site'),
}

def load_lib_modules():
# type: () -> None
for bot in available_bots:
try:
module_name = 'bots.{bot}.{bot}'.format(bot=bot)
bots_lib_module[bot] = import_module(module_name)
except ImportError:
print("\n Import Error: Bot \"{}\" doesn't exists. Please make sure you have set up the flaskbotrc "
"file correctly.\n".format(bot))
sys.exit(1)

app = Flask(__name__)

@app.route('/bots/<bot>', methods=['POST'])
def handle_bot(bot):
# type: (str) -> Union[str, BadRequest]
if bot not in available_bots:
return BadRequest("requested bot service {} not supported".format(bot))

client = Client(email=bots_config[bot]["email"],
api_key=bots_config[bot]["key"],
site=bots_config[bot]["site"])
try:
restricted_client = BotHandlerApi(client)
except SystemExit:
return BadRequest("Cannot fetch user profile for bot {}, make sure you have set up the flaskbotrc "
"file correctly.".format(bot))
message_handler = bots_lib_module[bot].handler_class()

# TODO: Handle stateful bots properly.
state_handler = StateHandler()

event = json.loads(request.data)
message_handler.handle_message(message=event["message"],
bot_handler=restricted_client,
state_handler=state_handler)
return "Success!"

def parse_args():
# type: () -> Tuple[Any, Any]
usage = '''
zulip-bot-server <path to flaskbotrc>
Example: zulip-bot-server ~/flaskbotrc
This program loads the bot configurations from the flaskbotrc
config file and loads the bot modules. It then starts the
server and fetches the requests to the above loaded modules
and returns the success/failure result. Please make sure you
have a current flaskbotrc file with the configurations of the
required bots. See lib/readme.md for more context.
'''

parser = optparse.OptionParser(usage=usage)
parser.add_option('--config-file',
action='store',
help='(config file for the zulip bot server (flaskbotrc))')
(options, args) = parser.parse_args()
return (options, args)

def main():
# type: () -> None
(options, args) = parse_args()
read_config_file(options.config_file)
global available_bots
available_bots = list(bots_config.keys())
load_lib_modules()

# Both zulip system and zulip-bot-server should be executed in same virtual environment.
# On executing on different environments, change the host address accordingly.
app.run(host="127.0.0.1", port=5002, debug=True)

if __name__ == '__main__':
main()

0 comments on commit 3674b5e

Please sign in to comment.