From 2645c9727641b0942a2dc81574c2963ce13ac7f5 Mon Sep 17 00:00:00 2001 From: Rohitt Vashishtha Date: Wed, 21 Jun 2017 20:03:09 +0530 Subject: [PATCH] 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. --- bots/.gitignore | 1 + bots/thesaurus/requirements.txt | 1 + bots/thesaurus/thesaurus.py | 6 +-- bots_api/provision.py | 67 +++++++++++++++++++++++++++++++++ bots_api/run.py | 22 +++++++++-- bots_api/test-bots | 2 + 6 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 bots/.gitignore create mode 100644 bots/thesaurus/requirements.txt create mode 100755 bots_api/provision.py diff --git a/bots/.gitignore b/bots/.gitignore new file mode 100644 index 0000000..307a6a6 --- /dev/null +++ b/bots/.gitignore @@ -0,0 +1 @@ +bot_dependencies diff --git a/bots/thesaurus/requirements.txt b/bots/thesaurus/requirements.txt new file mode 100644 index 0000000..d02d986 --- /dev/null +++ b/bots/thesaurus/requirements.txt @@ -0,0 +1 @@ +PyDictionary diff --git a/bots/thesaurus/thesaurus.py b/bots/thesaurus/thesaurus.py index 0638566..79cb720 100644 --- a/bots/thesaurus/thesaurus.py +++ b/bots/thesaurus/thesaurus.py @@ -2,11 +2,7 @@ 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) +from PyDictionary import PyDictionary as Dictionary #Uses Python's Dictionary module # pip install PyDictionary diff --git a/bots_api/provision.py b/bots_api/provision.py new file mode 100755 index 0000000..e265a29 --- /dev/null +++ b/bots_api/provision.py @@ -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() diff --git a/bots_api/run.py b/bots_api/run.py index f6e2f5a..afa95de 100755 --- a/bots_api/run.py +++ b/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) diff --git a/bots_api/test-bots b/bots_api/test-bots index 21d2f7a..8761625 100755 --- a/bots_api/test-bots +++ b/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))