bots: Add dependencies management.
Adds the file api/bots_api/provision.py that installs dependencies
for bots using pip. This file is also used by run.py when running
a bot. However, for testing, you need to separately provision the bots.
ADD
bots/.gitignore
+bot_dependencies

ADD
bots/thesaurus/requirements.txt
+PyDictionary

MODIFY
bots/thesaurus/thesaurus.py
bots/thesaurus/thesaurus.py
from __future__ import print_function
import sys
import logging
-try:
from PyDictionary import PyDictionary as Dictionary
-except ImportError:
- logging.error("Dependency Missing!")
- sys.exit(0)
#Uses Python's Dictionary module
# pip install PyDictionary

ADD
bots_api/provision.py
+#!/usr/bin/env python
+
+from __future__ import absolute_import
+from __future__ import print_function
+
+import argparse
+import os
+import sys
+import pip
+
+def provision_bot(path_to_bot, force):
+ # type: (str, bool) -> None
+ req_path = os.path.join(path_to_bot, 'requirements.txt')
+ install_path = os.path.join(path_to_bot, 'bot_dependencies')
+ if os.path.isfile(req_path):
+ print('Installing dependencies...')
+ if not os.path.isdir(install_path):
+ os.makedirs(install_path)
+ # pip install -r $BASEDIR/requirements.txt -t $BASEDIR/bot_dependencies --quiet
+ rcode = pip.main(['install', '-r', req_path, '-t', install_path, '--quiet'])
+ if not rcode == 0:
+ print('Error. Check output of `pip install` above for details.')
+ if not force:
+ print('Use --force to try running anyway.')
+ sys.exit(rcode) # Use pip's exit code
+ else:
+ print('Installed.')
+ sys.path.insert(0, install_path)
+
+def dir_join(dir1, dir2):
+ # type: (str, str) -> str
+ return os.path.abspath(os.path.join(dir1, dir2))
+
+def run():
+ # type: () -> None
+ usage = '''
+ Installs dependencies of bots in api/bots directory. Add a
+ reuirements.txt file in a bot's folder before provisioning.
+
+ To provision all bots, use:
+ ./provision.py
+
+ To provision specific bots, use:
+ ./provision.py [names of bots]
+ Example: ./provision.py helloworld xkcd wikipedia
+
+ '''
+
+ bots_dir = dir_join(os.path.dirname(os.path.abspath(__file__)), '../bots')
+ available_bots = [b for b in os.listdir(bots_dir) if os.path.isdir(dir_join(bots_dir, b))]
+
+ parser = argparse.ArgumentParser(usage=usage)
+ parser.add_argument('bots_to_provision',
+ metavar='bots',
+ nargs='*',
+ default=available_bots,
+ help='specific bots to provision (default is all)')
+ parser.add_argument('--force',
+ default=False,
+ action="store_true",
+ help='Continue installation despite pip errors.')
+ options = parser.parse_args()
+ for bot in options.bots_to_provision:
+ provision_bot(os.path.join(dir_join(bots_dir, bot)), options.force)
+
+if __name__ == '__main__':
+ run()

MODIFY
bots_api/run.py
bots_api/run.py
@@ -7,6 +7,7 @@ import logging
import optparse
import os
import sys
+import provision
from types import ModuleType
our_dir = os.path.dirname(os.path.abspath(__file__))
@@ -14,8 +15,8 @@ sys.path.insert(0, our_dir)
from bot_lib import run_message_handler_for_bot
-def get_lib_module(bots_fn):
- # type: (str) -> ModuleType
+def validate_path(bots_fn):
+ # type: (str) -> None
bots_fn = os.path.realpath(bots_fn)
if not os.path.dirname(bots_fn).startswith(os.path.normpath(os.path.join(our_dir, "../bots"))):
print('Sorry, we will only import code from api/bots.')
@@ -24,6 +25,9 @@ def get_lib_module(bots_fn):
if not bots_fn.endswith('.py'):
print('Please use a .py extension for library files.')
sys.exit(1)
+
+def get_lib_module(bots_fn):
+ # type: (str) -> ModuleType
base_bots_fn = os.path.basename(os.path.splitext(bots_fn)[0])
sys.path.insert(1, os.path.dirname(bots_fn))
module_name = base_bots_fn
@@ -51,14 +55,24 @@ def run():
parser.add_option('--config-file',
action='store',
help='(alternate config file to ~/.zuliprc)')
+ parser.add_option('--provision',
+ action='store_true',
+ help='Install dependencies for the bot')
+ parser.add_option('--force',
+ action='store_true',
+ help='Try running the bot even if dependencies install fails.')
(options, args) = parser.parse_args()
if len(args) == 0:
print('You must specify a library!')
sys.exit(1)
+ bots_fn = args[0]
- lib_module = get_lib_module(bots_fn=args[0])
-
+ validate_path(bots_fn)
+ if options.provision:
+ print("Provisioning")
+ provision.provision_bot(os.path.dirname(bots_fn), options.force)
+ lib_module = get_lib_module(bots_fn)
if not options.quiet:
logging.basicConfig(stream=sys.stdout, level=logging.INFO)

MODIFY
bots_api/test-bots
bots_api/test-bots
@@ -53,6 +53,8 @@ if __name__ == '__main__':
suites = []
for bot_to_test in args.bots_to_test:
+ dep_path = os.path.join(bots_test_dir, bot_to_test, 'bot_dependencies')
+ sys.path.insert(0, dep_path)
try:
suites.append(loader.discover(start_dir = dir_join(bots_test_dir, bot_to_test),
top_level_dir = root_dir))

bots: Add dependencies management.

Adds the file api/bots_api/provision.py that installs dependencies
for bots using pip. This file is also used by run.py when running
a bot. However, for testing, you need to separately provision the bots.