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.
This commit is contained in:
Rohitt Vashishtha 2017-06-21 20:03:09 +05:30 committed by showell
parent bafbd3689b
commit 2645c97276
6 changed files with 90 additions and 9 deletions

1
bots/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
bot_dependencies

View file

@ -0,0 +1 @@
PyDictionary

View file

@ -2,11 +2,7 @@
from __future__ import print_function from __future__ import print_function
import sys import sys
import logging import logging
try:
from PyDictionary import PyDictionary as Dictionary from PyDictionary import PyDictionary as Dictionary
except ImportError:
logging.error("Dependency Missing!")
sys.exit(0)
#Uses Python's Dictionary module #Uses Python's Dictionary module
# pip install PyDictionary # pip install PyDictionary

67
bots_api/provision.py Executable file
View file

@ -0,0 +1,67 @@
#!/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()

View file

@ -7,6 +7,7 @@ import logging
import optparse import optparse
import os import os
import sys import sys
import provision
from types import ModuleType from types import ModuleType
our_dir = os.path.dirname(os.path.abspath(__file__)) 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 from bot_lib import run_message_handler_for_bot
def get_lib_module(bots_fn): def validate_path(bots_fn):
# type: (str) -> ModuleType # type: (str) -> None
bots_fn = os.path.realpath(bots_fn) bots_fn = os.path.realpath(bots_fn)
if not os.path.dirname(bots_fn).startswith(os.path.normpath(os.path.join(our_dir, "../bots"))): 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.') 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'): if not bots_fn.endswith('.py'):
print('Please use a .py extension for library files.') print('Please use a .py extension for library files.')
sys.exit(1) sys.exit(1)
def get_lib_module(bots_fn):
# type: (str) -> ModuleType
base_bots_fn = os.path.basename(os.path.splitext(bots_fn)[0]) base_bots_fn = os.path.basename(os.path.splitext(bots_fn)[0])
sys.path.insert(1, os.path.dirname(bots_fn)) sys.path.insert(1, os.path.dirname(bots_fn))
module_name = base_bots_fn module_name = base_bots_fn
@ -51,14 +55,24 @@ def run():
parser.add_option('--config-file', parser.add_option('--config-file',
action='store', action='store',
help='(alternate config file to ~/.zuliprc)') 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() (options, args) = parser.parse_args()
if len(args) == 0: if len(args) == 0:
print('You must specify a library!') print('You must specify a library!')
sys.exit(1) 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: if not options.quiet:
logging.basicConfig(stream=sys.stdout, level=logging.INFO) logging.basicConfig(stream=sys.stdout, level=logging.INFO)

View file

@ -53,6 +53,8 @@ if __name__ == '__main__':
suites = [] suites = []
for bot_to_test in args.bots_to_test: 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: try:
suites.append(loader.discover(start_dir = dir_join(bots_test_dir, bot_to_test), suites.append(loader.discover(start_dir = dir_join(bots_test_dir, bot_to_test),
top_level_dir = root_dir)) top_level_dir = root_dir))