class TestSettingsManager(TestBase): #== Util ==# s = None """SettingsManager for main testing config file.""" def setUp(self): self.s = SettingsManager('unit/settings/various.json', self.get_dir()) #== Tests ==# def test_missing(self): """Missing config files must throw an error.""" self.expect_error(lambda:SettingsManager('does_not_exist.json'), 'Invalid path to settings file') def test_override(self): """User properties must override defaults.""" self.assertEqual(self.s.require('gal_db_path'), 'foo.bar') def test_require(self): """Retrieval of required non-empty properties.""" self.expect_error(lambda: self.s.require('12345'), 'missing key', 'Require throws error for missing key') self.assertEqual(self.s.require('data_dir'), 'data') def test_catlist(self): """Retrieval of lists.""" self.assertEqual(self.s.require('cats_unconditional', list), ['Foo','*Bar ']) def test_default_string(self): """String is expected by default.""" self.expect_error(lambda:self.s.require('cats_unconditional'), "Config file needed <type") def test_abspath(self): """Computation of absolute path from absolute and relative paths.""" self.assertEqual(self.s.abspath('test/abs'), '/tmp') actual = self.s.abspath('test/rel') expected = os.path.join(self.get_dir(), 'unit/settings/beyond') self.assertEqual(os.path.realpath(actual), os.path.realpath(expected))
class KPAWebGen(object): """Core code for gallery generator.""" #== Setup ==# # A sequence is an array of 1 or more actions. # Actions can be specified by names. def __init__(self): # source of lookup table actions = [('read', self.act_read), ('localweb', self.act_localweb), ('publish', self.act_publish)] # save off the ordered names and the lookup table self.action_names = zip(*actions)[0] self.action_lookup = dict(actions) # TODO: Python subproject shouldn't know about global directory structure root = '../../..' self.clj_path = ResourceManager.locate(root + '/clj') action_names = None """Names of program actions, in order.""" action_lookup = None """Dict of action names to functions.""" sequence_all = 'all' """Specifies that all actions should be taken (in order.)""" clj_path = None """Location of Clojure jar.""" settings = None """SettingsManager object.""" # String -> List<String> def get_sequence(self, seq_name): """Expand requested sequence into action names. If invalid sequence name, throw error with message about allowed names.""" if seq_name == self.sequence_all: return self.action_names if seq_name not in self.action_names: ls = ", ".join(list(self.action_names)+[self.sequence_all]) raise Exception("Sequence must be one of: %s" % (ls,)) return [seq_name] # String List<String> -> def dispatch(self, config_loc, sequence): """Dispatch on action names.""" self.settings = SettingsManager(config_loc) for name in sequence: print("Running action: %s" % name, file=sys.stdout) self.action_lookup[name]() def invoke_clojure_action(self, task): # We have to use lein instead of launching an uberjar since some of # the integration tests are in Clojure, and therefore need to call # the .py script... # Once everything is in Clojure, this won't be an issue. subprocess.check_call(['lein', 'trampoline', 'run', '--task', task, '--config', self.settings.config['_config_path'], '--debug', str(Global.debug).lower()], shell=False, cwd=self.clj_path) #== Actions ==# def act_read(self): """Update our DB with the latest KPA data.""" did = Updater(self.settings.abspath('kpa_dir'), self.settings.require('shadow_path')).update(self.force_read) if not did: print("Skipping read since DB has not changed.", file=sys.stdout) def act_localweb(self): """Generate local website.""" self.invoke_clojure_action('localweb') def act_publish(self): """Publish to remote website.""" self.invoke_clojure_action('publish-s3') #== Interfaces ==# force_read = False def main(self, args): """Validate arguments (minus script name) and call core code.""" logging.debug('Main args: %s' % args) if len(args) < 2: if len(args) != 1 or args[0] != '--help': print("Wrong number of arguments.", file=sys.stderr) self.die_usage() config_loc = args[0] sequence_name = args[1] if len(args) == 3 and args[2] == '--force-read': self.force_read = True try: sequence = self.get_sequence(sequence_name) except Exception as e: KPAWebGen.die_top("Could not determine requested action: " + str(e), Constants.ERR_ACTION_INVALID) try: self.dispatch(config_loc, sequence) except Exception as e: msg = "Error(%s): %s" % (e.__class__.__name__, e) KPAWebGen.die_top(msg, Constants.ERR_UNKNOWN_REASON) @staticmethod def die_usage(): usage = """Usage: kpawebgen --help kpawebgen config-file sequence [options] Options: --force-read Update the shadow DB even if it dopesn't appear to need it.""" KPAWebGen.die_top(usage, Constants.ERR_BAD_ARGS, isExc=False) @staticmethod def die_top(msgs=None, code=Constants.ERR_NORMAL_EXIT, isExc=True): """Die at the top level. Do not call from core code.""" if Global.debug and isExc: traceback.print_exc(file=sys.stdout) if isinstance(msgs, basestring): msgs = [msgs] if msgs: for m in msgs: print(m, file=sys.stderr) sys.exit(code)