From bce0a6b991abd9a50f0c666ff0cab2318a78c6d2 Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 10:48:46 -0600 Subject: [PATCH 01/22] pep8 fix for util.py --- pyqi/util.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/pyqi/util.py b/pyqi/util.py index b82bad6..0dcbc3d 100644 --- a/pyqi/util.py +++ b/pyqi/util.py @@ -13,23 +13,25 @@ __credits__ = ["Greg Caporaso", "Jai Ram Rideout"] import importlib +import sys from os import remove from os.path import split, splitext -import sys -from subprocess import Popen, PIPE, STDOUT +from subprocess import Popen, PIPE + from pyqi.core.log import StdErrLogger from pyqi.core.exception import MissingVersionInfoError + def pyqi_system_call(cmd, shell=True, dry_run=False): """Call cmd and return (stdout, stderr, return_value). - cmd: can be either a string containing the command to be run, or a + cmd: can be either a string containing the command to be run, or a sequence of strings that are the tokens of the command. - shell: value passed directly to Popen (default: True). See Python's + shell: value passed directly to Popen (default: True). See Python's subprocess.Popen for a description of the shell parameter and how cmd is interpreted differently based on its value. dry_run: if True, print cmd and return ("", "", 0) (default: False) - + This function is ported from QIIME (http://www.qiime.org), previously named qiime_system_call. QIIME is a GPL project, but we obtained permission from the authors of this function to port it to pyqi (and keep it under @@ -48,12 +50,13 @@ def pyqi_system_call(cmd, shell=True, dry_run=False): universal_newlines=True, stdout=PIPE, stderr=PIPE) - # communicate pulls all stdout/stderr from the PIPEs to + # communicate pulls all stdout/stderr from the PIPEs to # avoid blocking -- don't remove this line! stdout, stderr = proc.communicate() return_value = proc.returncode return stdout, stderr, return_value + def remove_files(list_of_filepaths, error_on_missing=True): """Remove list of filepaths, optionally raising an error if any are missing @@ -69,8 +72,9 @@ def remove_files(list_of_filepaths, error_on_missing=True): missing.append(fp) if error_on_missing and missing: - raise OSError, "Some filepaths were not accessible: %s" % '\t'.join( - missing) + raise OSError("Some filepaths were not accessible: %s" % '\t'.join( + missing)) + def old_to_new_command(driver_name, project_title, local_argv): """Deprecate an old-style script. @@ -103,6 +107,7 @@ def old_to_new_command(driver_name, project_title, local_argv): return result_retval + def get_version_string(module_str): """Returns the version string found in the top-level module. @@ -124,6 +129,7 @@ def get_version_string(module_str): version_string = top_level_module.__version__ except AttributeError: raise MissingVersionInfoError("Module '%s' does not have the " - "__version__ attribute." % top_level_name) + "__version__ attribute." + % top_level_name) return version_string From 1926d5fef5d17ac7f6bd145b96a07d6350224573 Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 10:49:37 -0600 Subject: [PATCH 02/22] pep8ing --- pyqi/commands/code_header_generator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyqi/commands/code_header_generator.py b/pyqi/commands/code_header_generator.py index c3808a4..caf71e9 100644 --- a/pyqi/commands/code_header_generator.py +++ b/pyqi/commands/code_header_generator.py @@ -11,8 +11,9 @@ __credits__ = ["Jai Ram Rideout", "Daniel McDonald"] -from pyqi.core.command import (Command, CommandIn, CommandOut, - ParameterCollection) +from pyqi.core.command import (Command, CommandIn, CommandOut, + ParameterCollection) + class CodeHeaderGenerator(Command): BriefDescription = "Generate header code for use in a Python file" From c8fa0732236b6d9b4b319bceee79bd0fe99d37b6 Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 10:51:44 -0600 Subject: [PATCH 03/22] pep8ing --- pyqi/commands/make_bash_completion.py | 31 +++++++++++++++------------ 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/pyqi/commands/make_bash_completion.py b/pyqi/commands/make_bash_completion.py index f82b8a7..29d5f71 100644 --- a/pyqi/commands/make_bash_completion.py +++ b/pyqi/commands/make_bash_completion.py @@ -11,19 +11,21 @@ from __future__ import division __credits__ = ["Daniel McDonald", "Jai Ram Rideout", "Doug Wendel", - "Greg Caporaso"] + "Greg Caporaso"] import importlib -from pyqi.core.command import (Command, CommandIn, CommandOut, - ParameterCollection) +from pyqi.core.command import (Command, CommandIn, CommandOut, + ParameterCollection) from pyqi.core.interface import get_command_names, get_command_config + def _get_cfg_module(desc): """Load a module""" mod = importlib.import_module(desc) return mod -# Based on http://stackoverflow.com/questions/5302650/multi-level-bash-completion +# Based on +# http://stackoverflow.com/questions/5302650/multi-level-bash-completion script_fmt = """_%(driver)s_complete() { local cur prev @@ -34,7 +36,7 @@ def _get_cfg_module(desc): if [ $COMP_CWORD -gt 1 ]; then prev=${COMP_WORDS[1]} - fi + fi if [ $COMP_CWORD -eq 1 ]; then COMPREPLY=( $(compgen -W "%(command_list)s" -- $cur) ) @@ -56,11 +58,12 @@ def _get_cfg_module(desc): ;; """ + class BashCompletion(Command): BriefDescription = "Construct a bash completion script" - LongDescription = ("Construct a bash tab completion script that will search" - " through available commands and options") - + LongDescription = ("Construct a bash tab completion script that will " + "search through available commands and options") + CommandIns = ParameterCollection([ CommandIn(Name='command_config_module', DataType=str, Description="CLI command configuration module", @@ -77,7 +80,6 @@ class BashCompletion(Command): def run(self, **kwargs): driver = kwargs['driver_name'] cfg_mod_path = kwargs['command_config_module'] - cfg_mod = _get_cfg_module(cfg_mod_path) command_names = get_command_names(cfg_mod_path) command_list = ' '.join(command_names) @@ -89,14 +91,15 @@ def run(self, **kwargs): if cmd_cfg is not None: command_options = [] command_options.extend( - sorted(['--%s' % p.Name for p in cmd_cfg.inputs])) + sorted(['--%s' % p.Name for p in cmd_cfg.inputs])) opts = ' '.join(command_options) - commands.append(command_fmt % {'command':cmd, 'options':opts}) + commands.append(command_fmt % + {'command': cmd, 'options': opts}) all_commands = ''.join(commands) - return {'result':script_fmt % {'driver':driver, - 'commands':all_commands, - 'command_list':command_list}} + return {'result': script_fmt % {'driver': driver, + 'commands': all_commands, + 'command_list': command_list}} CommandConstructor = BashCompletion From 2419ede649469d01def818cee25ae8cc4616fb2b Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 10:55:33 -0600 Subject: [PATCH 04/22] pep8ing, explicitly ignoring an E501 --- pyqi/commands/make_command.py | 40 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/pyqi/commands/make_command.py b/pyqi/commands/make_command.py index 75a5489..35640e3 100644 --- a/pyqi/commands/make_command.py +++ b/pyqi/commands/make_command.py @@ -11,11 +11,10 @@ __credits__ = ["Daniel McDonald", "Greg Caporaso", "Doug Wendel", "Jai Ram Rideout"] -from pyqi.core.command import (Command, CommandIn, CommandOut, - ParameterCollection) +from pyqi.core.command import CommandIn, CommandOut, ParameterCollection from pyqi.commands.code_header_generator import CodeHeaderGenerator -command_imports = """from pyqi.core.command import (Command, CommandIn, CommandOut, +command_imports = """from pyqi.core.command import (Command, CommandIn, CommandOut, ParameterCollection)""" command_format = """class %s(Command): @@ -56,43 +55,44 @@ def test_run(self): if __name__ == '__main__': main()""" + class MakeCommand(CodeHeaderGenerator): BriefDescription = "Construct a stubbed out Command object" LongDescription = ("This command is intended to construct the basics of a " - "Command object so that a developer can dive straight into the " - "implementation of the command") + "Command object so that a developer can dive straight " + "into the implementation of the command") CommandIns = ParameterCollection( - CodeHeaderGenerator.CommandIns.Parameters + [ - CommandIn(Name='name', DataType=str, - Description='the name of the Command', Required=True), - CommandIn(Name='test_code', DataType=bool, - Description='create stubbed out unit test code', - Required=False, Default=False) - ] + CodeHeaderGenerator.CommandIns.Parameters + [ + CommandIn(Name='name', DataType=str, + Description='the name of the Command', Required=True), + CommandIn(Name='test_code', DataType=bool, + Description='create stubbed out unit test code', + Required=False, Default=False) + ] ) CommandOuts = ParameterCollection([ - CommandOut(Name='result',DataType=list, - Description='The resulting template') - ] + CommandOut(Name='result', DataType=list, + Description='The resulting template') + ] ) def run(self, **kwargs): code_header_lines = super(MakeCommand, self).run( - author=kwargs['author'], email=kwargs['email'], - license=kwargs['license'], copyright=kwargs['copyright'], - version=kwargs['version'], credits=kwargs['credits'])['result'] + author=kwargs['author'], email=kwargs['email'], + license=kwargs['license'], copyright=kwargs['copyright'], + version=kwargs['version'], credits=kwargs['credits'])['result'] result_lines = code_header_lines if kwargs['test_code']: result_lines.extend( - (test_format % {'name': kwargs['name']}).split('\n')) + (test_format % {'name': kwargs['name']}).split('\n')) else: result_lines.extend(command_imports.split('\n')) result_lines.append('') result_lines.extend((command_format % ( - kwargs['name'], kwargs['name'])).split('\n')) + kwargs['name'], kwargs['name'])).split('\n')) return {'result': result_lines} From 02680850cc85edaab25424bdb83ffdb813206b72 Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 11:00:35 -0600 Subject: [PATCH 05/22] pep8ing, explicitly ignoring E501s here --- tests/test_commands/test_make_bash_completion.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_commands/test_make_bash_completion.py b/tests/test_commands/test_make_bash_completion.py index aad1c08..9251af5 100644 --- a/tests/test_commands/test_make_bash_completion.py +++ b/tests/test_commands/test_make_bash_completion.py @@ -21,6 +21,7 @@ __credits__ = ["Daniel McDonald", "Jai Ram Rideout", "Doug Wendel", "Greg Caporaso"] + class BashCompletionTests(TestCase): def setUp(self): """Set up data for unit tests.""" @@ -72,8 +73,8 @@ def test_get_cfg_module(self): self.assertEqual(_get_cfg_module('pyqi'), pyqi) def test_run(self): - params = {'command_config_module':self.temp_module_name, - 'driver_name':'pyqi'} + params = {'command_config_module': self.temp_module_name, + 'driver_name': 'pyqi'} obs = self.cmd(**params) self.assertEqual(obs.keys(), ['result']) self.assertEqual(obs['result'], outputandstuff) @@ -89,7 +90,7 @@ def test_run(self): if [ $COMP_CWORD -gt 1 ]; then prev=${COMP_WORDS[1]} - fi + fi if [ $COMP_CWORD -eq 1 ]; then COMPREPLY=( $(compgen -W "make-bash-completion make-optparse" -- $cur) ) From 38e55948205f50098ea41514255967e57fe91efb Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 11:03:32 -0600 Subject: [PATCH 06/22] pep8ing, igonring E501 --- pyqi/commands/make_optparse.py | 44 ++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/pyqi/commands/make_optparse.py b/pyqi/commands/make_optparse.py index b99930a..7d89218 100644 --- a/pyqi/commands/make_optparse.py +++ b/pyqi/commands/make_optparse.py @@ -10,8 +10,8 @@ from __future__ import division from operator import attrgetter -from pyqi.core.command import (Command, CommandIn, CommandOut, - ParameterCollection) +from pyqi.core.command import (Command, CommandIn, CommandOut, + ParameterCollection) from pyqi.commands.code_header_generator import CodeHeaderGenerator __credits__ = ["Daniel McDonald", "Jai Ram Rideout", "Greg Caporaso", @@ -81,7 +81,7 @@ # # value will be made available to Handler. This name # # can be either an underscored or dashed version of the # # option name (e.g., 'output_fp' or 'output-fp') - # InputName='output-fp'), + # InputName='output-fp'), # # An example option that does not map to a CommandIn. # OptparseResult(Parameter=cmd_out_lookup('some_other_result'), @@ -109,16 +109,19 @@ default_block_format = """# Default=%(default)s, # implied by Parameter # DefaultDescription=%(default_description)s, # implied by Parameter""" + class MakeOptparse(CodeHeaderGenerator): BriefDescription = "Consume a Command, stub out an optparse configuration" - LongDescription = """Construct and stub out the basic optparse configuration for a given Command. This template provides comments and examples of what to fill in.""" - + LongDescription = "Construct and stub out the basic optparse "\ + "configuration for a given Command. This template "\ + "provides comments and examples of what to fill in." + CommandIns = ParameterCollection( CodeHeaderGenerator.CommandIns.Parameters + [ - CommandIn(Name='command', DataType=Command, - Description='an existing Command', Required=True), - CommandIn(Name='command_module', DataType=str, - Description='the Command source module', Required=True) + CommandIn(Name='command', DataType=Command, + Description='an existing Command', Required=True), + CommandIn(Name='command_module', DataType=str, + Description='the Command source module', Required=True) ] ) @@ -129,9 +132,9 @@ class MakeOptparse(CodeHeaderGenerator): def run(self, **kwargs): code_header_lines = super(MakeOptparse, self).run( - author=kwargs['author'], email=kwargs['email'], - license=kwargs['license'], copyright=kwargs['copyright'], - version=kwargs['version'], credits=kwargs['credits'])['result'] + author=kwargs['author'], email=kwargs['email'], + license=kwargs['license'], copyright=kwargs['copyright'], + version=kwargs['version'], credits=kwargs['credits'])['result'] result_lines = code_header_lines @@ -143,8 +146,8 @@ def run(self, **kwargs): default_block = '' else: default_fmt = { - 'default': repr(cmdin.Default), - 'default_description': repr(cmdin.DefaultDescription) + 'default': repr(cmdin.Default), + 'default_description': repr(cmdin.DefaultDescription) } default_block = default_block_format % default_fmt @@ -155,23 +158,22 @@ def run(self, **kwargs): action = 'store' data_type = cmdin.DataType - fmt = {'name':cmdin.Name, 'datatype':data_type, 'action':action, - 'required':str(cmdin.Required), - 'help':cmdin.Description, 'default_block':default_block} + fmt = {'name': cmdin.Name, 'datatype': data_type, 'action': action, + 'required': str(cmdin.Required), + 'help': cmdin.Description, 'default_block': default_block} cmdin_formatted.append(input_format % fmt) cmdout_formatted = [] for cmdin in sorted(kwargs['command'].CommandOuts.values(), key=attrgetter('Name')): - fmt = {'name':cmdin.Name} + fmt = {'name': cmdin.Name} cmdout_formatted.append(output_format % fmt) - cmdin_formatted = ''.join(cmdin_formatted) cmdout_formatted = ''.join(cmdout_formatted) - header_fmt = {'command_module':kwargs['command_module'], + header_fmt = {'command_module': kwargs['command_module'], 'input_fmt': cmdin_formatted, - 'output_fmt':cmdout_formatted} + 'output_fmt': cmdout_formatted} result_lines.extend((header_format % header_fmt).split('\n')) return {'result': result_lines} From 069a55f19fe37304f1b38815ef3c7395a4bb1955 Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 11:05:55 -0600 Subject: [PATCH 07/22] pep8ing --- pyqi/commands/make_release.py | 60 +++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/pyqi/commands/make_release.py b/pyqi/commands/make_release.py index 51103b4..0645ac7 100644 --- a/pyqi/commands/make_release.py +++ b/pyqi/commands/make_release.py @@ -29,21 +29,22 @@ import re from datetime import datetime, date from pyqi.util import pyqi_system_call -from pyqi.core.command import (Command, CommandIn, CommandOut, - ParameterCollection) +from pyqi.core.command import (Command, CommandIn, CommandOut, + ParameterCollection) + class MakeRelease(Command): BriefDescription = "Make the release" LongDescription = "Do all the things for a release" CommandIns = ParameterCollection([ CommandIn(Name='package_name', DataType=str, - Description='The name of the package to release', + Description='The name of the package to release', Required=True), - CommandIn(Name='real_run', DataType=bool, + CommandIn(Name='real_run', DataType=bool, Description='Perform a real run', Required=False, Default=False) ]) - + CommandOuts = ParameterCollection([]) RealRun = False _date_clean_re = re.compile(r'(\d+)(st|nd|rd|th)') @@ -65,7 +66,7 @@ def _parse_changelog(self, pkg_name): break match = re.search(r'released on (\w+\s+\d+\w+\s+\d+)', - change_info) + change_info) if match is None: continue @@ -84,15 +85,17 @@ def _parse_date(self, string): string = self._date_clean_re.sub(r'\1', string) return datetime.strptime(string, '%B %d %Y') - def _set_filename_version(self,filename,version_number,pattern): + def _set_filename_version(self, filename, version_number, pattern): changed = [] + def inject_version(match): before, old, after = match.groups() changed.append(True) return before + version_number + after with open(filename) as f: - contents = re.sub(r"""^(\s*%s\s*=\s*(?:'|"))(.+?)((?:'|"))(?sm)""" % - pattern, inject_version, f.read()) + contents = re.sub( + r"""^(\s*%s\s*=\s*(?:'|"))(.+?)((?:'|"))(?sm)""" % + pattern, inject_version, f.read()) if not changed: self._fail('Could not find %s in %s', pattern, filename) @@ -103,7 +106,7 @@ def inject_version(match): def _set_init_version(self, pkg_name, version): self._info('Setting __init__.py version to %s', version) - self._set_filename_version('%s/__init__.py' % pkg_name, version, + self._set_filename_version('%s/__init__.py' % pkg_name, version, '__version__') def _set_setup_version(self, version): @@ -119,11 +122,11 @@ def _build_and_upload(self): stdout, stderr, retval = pyqi_system_call(cmd, shell=False, dry_run=not self.RealRun) if retval is not 0: - self._fail("build and upload failed,\nSTDOUT:\n%s\n\nSTDERR:\n%s", + self._fail("build and upload failed,\nSTDOUT:\n%s\n\nSTDERR:\n%s", stdout, stderr) def _fail(self, message, *args): - sys.stderr.write('Error: ') + sys.stderr.write('Error: ') sys.stderr.write(message % args) sys.stderr.write('\n') sys.exit(1) @@ -137,13 +140,13 @@ def _get_git_tags(self): stdout, stderr, retval = pyqi_system_call(cmd, shell=False, dry_run=not self.RealRun) if retval is not 0: - self._fail("Could not git tag, \nSTDOUT:\n%s\n\nSTDERR:\n%s", + self._fail("Could not git tag, \nSTDOUT:\n%s\n\nSTDERR:\n%s", stdout, stderr) return stdout.splitlines() def _git_is_clean(self): - cmd = ['git','diff','--quiet'] + cmd = ['git', 'diff', '--quiet'] # always execute, even in dry run stdout, stderr, retval = pyqi_system_call(cmd, shell=False) @@ -155,7 +158,7 @@ def _make_git_commit(self, message, *args): stdout, stderr, retval = pyqi_system_call(cmd, shell=False, dry_run=not self.RealRun) if retval is not 0: - self._fail("Could not git commit, \nSTDOUT:\n%s\n\nSTDERR:\n%s", + self._fail("Could not git commit, \nSTDOUT:\n%s\n\nSTDERR:\n%s", stdout, stderr) def _make_git_tag(self, tag): @@ -164,32 +167,35 @@ def _make_git_tag(self, tag): stdout, stderr, retval = pyqi_system_call(cmd, shell=False, dry_run=not self.RealRun) if retval is not 0: - self._fail("Could not git tag, \nSTDOUT:\n%s\n\nSTDERR:\n%s",stdout, - stderr) + self._fail( + "Could not git tag, \nSTDOUT:\n%s\n\nSTDERR:\n%s", stdout, + stderr) def _get_git_branch(self): - cmd = ['git','rev-parse','--abbrev-ref','HEAD'] + cmd = ['git', 'rev-parse', '--abbrev-ref', 'HEAD'] # ignoring self.RealRun, always execute stdout, stderr, retval = pyqi_system_call(cmd, shell=False) if retval is not 0: - self._fail("Could not get git branch, \nSTDOUT:\n%s\n\nSTDERR:\n%s", - stdout, stderr) + self._fail( + "Could not get git branch, \nSTDOUT:\n%s\n\nSTDERR:\n%s", + stdout, stderr) return stdout.strip() def _git_push_branch(self): branch = self._get_git_branch() self._info('Pushing branch %s to origin', branch) - cmd = ['git','push','upstream', branch] + cmd = ['git', 'push', 'upstream', branch] stdout, stderr, retval = pyqi_system_call(cmd, shell=False, dry_run=not self.RealRun) if retval is not 0: - self._fail("Could not push branch %s, \nSTDOUT:\n%s\n\nSTDERR:\n%s", - stdout, stderr, branch) + self._fail( + "Could not push branch %s, \nSTDOUT:\n%s\n\nSTDERR:\n%s", + stdout, stderr, branch) def _git_push_tag(self, tag): self._info('Pushing tag "%s"', tag) - cmd = ['git','push','upstream',tag] + cmd = ['git', 'push', 'upstream', tag] stdout, stderr, retval = pyqi_system_call(cmd, shell=False, dry_run=not self.RealRun) if retval is not 0: @@ -222,9 +228,9 @@ def run(self, **kwargs): if version in tags: self._fail('Version "%s" is already tagged', version) if release_date.date() != date.today(): - self._fail('Release date is not today (%s != %s)', - release_date.strftime('%Y-%m-%d'), - date.today()) + self._fail('Release date is not today (%s != %s)', + release_date.strftime('%Y-%m-%d'), + date.today()) if not self._git_is_clean(): self._fail('You have uncommitted changes in git') From 83681d27c2c0d980453e2f5dd38277a8a8bdf0da Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 11:06:20 -0600 Subject: [PATCH 08/22] more pep8 --- pyqi/commands/serve_html_interface.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pyqi/commands/serve_html_interface.py b/pyqi/commands/serve_html_interface.py index 7376f92..65b1184 100644 --- a/pyqi/commands/serve_html_interface.py +++ b/pyqi/commands/serve_html_interface.py @@ -12,13 +12,15 @@ __credits__ = ["Evan Bolyen"] -from pyqi.core.command import (Command, CommandIn, CommandOut, ParameterCollection) +from pyqi.core.command import ( + Command, CommandIn, CommandOut, ParameterCollection) from pyqi.core.interfaces.html import start_server + class ServeHTMLInterface(Command): BriefDescription = "Start the HTMLInterface server" LongDescription = ("Start the HTMLInterface server and load the provided " - "interface_module and port") + "interface_module and port") CommandIns = ParameterCollection([ CommandIn(Name='port', DataType=int, Description='The port to run the server on', Required=False, @@ -30,10 +32,10 @@ class ServeHTMLInterface(Command): ]) CommandOuts = ParameterCollection([ - CommandOut(Name='result',DataType=str, - Description='Signals the termination of the HTMLInterface ' - 'server') - ]) + CommandOut(Name='result', DataType=str, + Description='Signals the termination of the HTMLInterface ' + 'server') + ]) def run(self, **kwargs): """Start the HTMLInterface server with the port and interface_module""" From 67c4a5c365729af65b68bb2e6f8387e50b259eef Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 11:08:21 -0600 Subject: [PATCH 09/22] and pep8 --- pyqi/core/command.py | 54 ++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/pyqi/core/command.py b/pyqi/core/command.py index b497a15..8711b4f 100644 --- a/pyqi/core/command.py +++ b/pyqi/core/command.py @@ -12,7 +12,6 @@ __credits__ = ["Greg Caporaso", "Daniel McDonald", "Doug Wendel", "Jai Ram Rideout"] -import sys, traceback import re from pyqi.core.log import NullLogger from pyqi.core.exception import (IncompetentDeveloperError, @@ -20,7 +19,9 @@ UnknownParameterError, MissingParameterError) + class Parameter(object): + """The ``Command`` variable type baseclass A ``Parameter`` is interface agnostic, and is used to describe an input @@ -29,14 +30,14 @@ class Parameter(object): def __init__(self, Name, DataType, Description, ValidateValue=None): """ - + ``Name`` should be a valid Python name so that users can supply either a dictionary as input or named arguments. ``DataType`` specifies the type that the input must be. The input should be an instance of type ``DataType``. - ``ValidateValue`` can be set as a function that will validate the + ``ValidateValue`` can be set as a function that will validate the value associated with a ``Parameter``. """ if not self._is_valid_name(Name): @@ -46,7 +47,6 @@ def __init__(self, Name, DataType, Description, ValidateValue=None): "start with a letter or " "underscore." % Name) - self.Name = Name self.DataType = DataType self.Description = Description @@ -65,29 +65,40 @@ def _pythonize(self, name): return name + class CommandIn(Parameter): + """A ``Command`` input variable type""" - def __init__(self, Name, DataType, Description, Required=False, + + def __init__(self, Name, DataType, Description, Required=False, Default=None, DefaultDescription=None, **kwargs): self.Required = Required self.Default = Default self.DefaultDescription = DefaultDescription - + if Required and Default is not None: raise IncompetentDeveloperError("Found required CommandIn '%s' " - "with default value '%r'. Required CommandIns cannot have " - "default values." % (Name, Default)) - + "with default value '%r'. " + "Required CommandIns cannot have " + "default values." % + (Name, Default)) + super(CommandIn, self).__init__(Name, DataType, Description, **kwargs) + class CommandOut(Parameter): + """A ``Command`` output variable type""" + def __init__(self, Name, DataType, Description, **kwargs): - super(CommandOut, self).__init__(Name, DataType, Description, + super(CommandOut, self).__init__(Name, DataType, Description, **kwargs) + class ParameterCollection(dict): + """A collection of parameters with dict like lookup""" + def __init__(self, Parameters): self.Parameters = Parameters @@ -109,15 +120,17 @@ def __setitem__(self, key, val): raise TypeError("ParameterCollections are immutable") __delattr__ = __setitem__ + class Command(object): + """Base class for ``Command`` - A ``Command`` is interface agnostic, knows how to run itself and knows + A ``Command`` is interface agnostic, knows how to run itself and knows about the arguments that it can take (via ``Parameters``). """ - BriefDescription = "" # 1 sentence description - LongDescription = """""" # longer, more detailed description + BriefDescription = "" # 1 sentence description + LongDescription = """""" # longer, more detailed description CommandIns = ParameterCollection([]) CommandOuts = ParameterCollection([]) @@ -129,7 +142,7 @@ def __call__(self, **kwargs): """Safely execute a ``Command``""" self_str = str(self.__class__) self._logger.info('Starting command: %s' % self_str) - + self._validate_kwargs(kwargs) self._set_defaults(kwargs) @@ -155,7 +168,7 @@ def __call__(self, **kwargs): def _validate_kwargs(self, kwargs): """Validate input kwargs prior to executing a ``Command`` - + This method can be overridden by subclasses. The baseclass defines only a basic validation. """ @@ -164,7 +177,7 @@ def _validate_kwargs(self, kwargs): # check required parameters for p in self.CommandIns.values(): if p.Required and p.Name not in kwargs: - err_msg = 'Missing required CommandIn %s in %s' % (p.Name, + err_msg = 'Missing required CommandIn %s in %s' % (p.Name, self_str) self._logger.fatal(err_msg) raise MissingParameterError(err_msg) @@ -172,7 +185,7 @@ def _validate_kwargs(self, kwargs): if p.Name in kwargs and p.ValidateValue: if not p.ValidateValue(kwargs[p.Name]): err_msg = "CommandIn %s cannot take value %s in %s" % \ - (p.Name, kwargs[p.Name], self_str) + (p.Name, kwargs[p.Name], self_str) self._logger.fatal(err_msg) raise ValueError(err_msg) @@ -182,7 +195,7 @@ def _validate_kwargs(self, kwargs): err_msg = 'Unknown CommandIn %s in %s' % (opt, self_str) self._logger.fatal(err_msg) raise UnknownParameterError(err_msg) - + def _validate_result(self, result): """Validate the result from a ``Command.run``""" self_str = str(self.__class__) @@ -206,13 +219,15 @@ def _set_defaults(self, kwargs): def run(self, **kwargs): """Exexcute a ``Command`` - + A ``Command`` must accept **kwargs to run, and must return a ``dict`` as a result. """ raise NotImplementedError("All subclasses must implement run.") # I do not like this + + def make_command_in_collection_lookup_f(obj): """Return a function for convenient ``CommandIns`` lookup. @@ -222,6 +237,7 @@ def lookup_f(name): return obj.CommandIns[name] return lookup_f + def make_command_out_collection_lookup_f(obj): """Return a function for convenient ``CommandOuts`` lookup. From 8d8e8a90d422befb6cfb8fdce72703216c42b28c Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 11:08:50 -0600 Subject: [PATCH 10/22] pep8 --- pyqi/core/exception.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyqi/core/exception.py b/pyqi/core/exception.py index 0a55a42..3772cbe 100644 --- a/pyqi/core/exception.py +++ b/pyqi/core/exception.py @@ -11,20 +11,26 @@ __credits__ = ["Greg Caporaso", "Daniel McDonald", "Doug Wendel", "Jai Ram Rideout"] + class CommandError(Exception): pass + class IncompetentDeveloperError(CommandError): pass + class MissingParameterError(CommandError): pass + class InvalidReturnTypeError(IncompetentDeveloperError): pass + class UnknownParameterError(IncompetentDeveloperError): pass + class MissingVersionInfoError(IncompetentDeveloperError): pass From f59fbf0fdd46867396a62940cd5fb5f2fcba69d9 Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 11:09:22 -0600 Subject: [PATCH 11/22] peping --- pyqi/core/factory.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyqi/core/factory.py b/pyqi/core/factory.py index f13278f..e255e7a 100644 --- a/pyqi/core/factory.py +++ b/pyqi/core/factory.py @@ -11,18 +11,24 @@ __credits__ = ["Greg Caporaso", "Daniel McDonald", "Doug Wendel", "Jai Ram Rideout"] + def general_factory(command_constructor, usage_examples, inputs, outputs, version, interface=None): """Generalized interface factory""" class IObject(interface): + """Dynamic interface object""" CommandConstructor = command_constructor + def _get_usage_examples(self): return usage_examples + def _get_inputs(self): return inputs + def _get_outputs(self): return outputs + def _get_version(self): return version From 0ba6e8031484adce132bf14d710d29662ea217e6 Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 11:12:19 -0600 Subject: [PATCH 12/22] peping, explicitly ignroing e501 --- pyqi/core/interface.py | 83 ++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/pyqi/core/interface.py b/pyqi/core/interface.py index c2e1cd1..ff1f8f5 100644 --- a/pyqi/core/interface.py +++ b/pyqi/core/interface.py @@ -13,11 +13,12 @@ import importlib from sys import exit, stderr -from ConfigParser import SafeConfigParser from glob import glob -from os.path import basename, dirname, expanduser, join +from os.path import basename, dirname, join + from pyqi.core.exception import IncompetentDeveloperError + class Interface(object): CommandConstructor = None @@ -33,7 +34,7 @@ def __init__(self, **kwargs): self._validate_usage_examples(self._get_usage_examples()) self._validate_inputs_outputs(self._get_inputs(), self._get_outputs()) - + def __call__(self, in_, *args, **kwargs): self._the_in_validator(in_) cmd_input = self._input_handler(in_, *args, **kwargs) @@ -79,10 +80,10 @@ def _validate_inputs_outputs(self, inputs, outputs): for ifout in outputs: if ifout.InputName is None: continue - + if ifout.InputName not in input_names: - raise IncompetentDeveloperError(\ - "Could not link %s to an input!" % ifout.InputName) + raise IncompetentDeveloperError( + "Could not link %s to an input!" % ifout.InputName) def _the_in_validator(self, in_): """The job securator""" @@ -104,7 +105,7 @@ def _output_handler(self, results): def _get_usage_examples(self): """Return a list of ``InterfaceUsageExample`` objects - + These are typically set in a command+interface specific configuration file and passed to ``pyqi.core.general_factory`` """ @@ -112,7 +113,7 @@ def _get_usage_examples(self): def _get_inputs(self): """Return a list of ``InterfaceOption`` objects - + These are typically set in a command+interface specific configuration file and passed to ``pyqi.core.general_factory`` """ @@ -120,7 +121,7 @@ def _get_inputs(self): def _get_outputs(self): """Return a list of ``InterfaceOutputOption`` objects - + These are typically set in a command+interface specific configuration file and passed to ``pyqi.core.general_factory`` """ @@ -128,33 +129,37 @@ def _get_outputs(self): def _get_version(self): """Return a version string, e.g., ``'0.1'`` - + This is typically set in a command+interface specific configuration file and passed to ``pyqi.core.general_factory`` """ raise NotImplementedError("Must define _get_version") + class InterfaceOption(object): + """Describes an option and what to do with it - - ``Parameter`` is a pyqi.core.command.Parameter instance, typically an + + ``Parameter`` is a pyqi.core.command.Parameter instance, typically an instance of a ``CommandIn`` or a ``CommandOut``. ``Type`` refers to the interface type, not the actually datatype of the ``Parameter``. For instance, a file path may be specified on a command - line interface for a BIOM table. The ``Parameter.Datatype`` is a BIOM - type, while the ``InterfaceOption.Type`` is a string or possibly a + line interface for a BIOM table. The ``Parameter.Datatype`` is a BIOM + type, while the ``InterfaceOption.Type`` is a string or possibly a ``FilePath`` object. - ``Handler`` is a function that either transforms the value associated with + ``Handler`` is a function that either transforms the value associated with the option into the ``Parameter.DataType`` (e.g., if ``Parameter`` is a - ``CommandIn``), or the result of a ``Command`` into something consumable - by the interface (e.g., if ``Parameter`` is a ``CommandOut``). + ``CommandIn``), or the result of a ``Command`` into something + consumable by the interface (e.g., if ``Parameter`` is a + ``CommandOut``). ``Name`` is the name of the ``InterfaceOption``, e.g,, 'input-fp' ``Help`` is a description of the ``InterfaceOption`` """ + def __init__(self, Parameter=None, Type=None, Handler=None, Name=None, Help=None): self.Parameter = Parameter - + if self.Parameter is None: if Name is None: raise IncompetentDeveloperError("Must specify a Name for the " @@ -171,8 +176,8 @@ def __init__(self, Parameter=None, Type=None, Handler=None, Name=None, # Transfer information from Parameter unless overridden here. self.Name = Parameter.Name if Name is None else Name self.Help = Parameter.Description if Help is None else Help - - # This information is never contained in a Parameter. + + # This information is never contained in a Parameter. self.Type = Type self.Handler = Handler @@ -186,6 +191,7 @@ def getParameterName(self): else: return self.Parameter.Name + class InterfaceInputOption(InterfaceOption): _primitive_mapping = { "None": None, @@ -203,8 +209,8 @@ class InterfaceInputOption(InterfaceOption): "frozenset": frozenset } - def __init__(self, Action=None, Required=False, Default=None, - ShortName=None, DefaultDescription=None, + def __init__(self, Action=None, Required=False, Default=None, + ShortName=None, DefaultDescription=None, convert_to_dashed_name=True, **kwargs): super(InterfaceInputOption, self).__init__(**kwargs) @@ -213,44 +219,52 @@ def __init__(self, Action=None, Required=False, Default=None, self.DefaultDescription = DefaultDescription self.ShortName = ShortName self.Action = Action - + if convert_to_dashed_name: self.Name = self.Name.replace('_', '-') if self.Required and self.Default is not None: raise IncompetentDeveloperError("Found required option '%s' " - "with default value '%s'. Required options cannot have " - "default values." % (self.Name, self.Default)) - + "with default value '%s'. " + "Required options cannot have " + "default values." % + (self.Name, self.Default)) + if self.Default is None and self.Parameter is not None: self.Default = self.Parameter.Default - self.DefaultDescription = self.Parameter.DefaultDescription + self.DefaultDescription = self.Parameter.DefaultDescription # If a parameter is required, the option is always required, but # if a parameter is not required, but the option does require it, # then we make the option required. - if self.Parameter is not None: + if self.Parameter is not None: self.Required = self.Parameter.Required if not self.Parameter.Required and Required: self.Required = True - + self._convert_primitive_strings() self._validate_option() def _convert_primitive_strings(self): - """Convert our Type to a python type object if it is a primitive string. + """Convert our Type to a python type if possible + Otherwise, leave unchanged""" self.Type = self._primitive_mapping.get(self.Type, self.Type) + class InterfaceOutputOption(InterfaceOption): + def __init__(self, InputName=None, **kwargs): super(InterfaceOutputOption, self).__init__(**kwargs) self.InputName = InputName -class InterfaceUsageExample(object): + +class InterfaceUsageExample(object): + """Provide structure to a usage example""" + def __init__(self, ShortDesc, LongDesc, Ex): self.ShortDesc = ShortDesc self.LongDesc = LongDesc @@ -262,6 +276,7 @@ def _validate_usage_example(self): """Interface specific usage example validation""" raise NotImplementedError("Must define in the subclass") + def get_command_names(config_base_name): """Return a list of available command names. @@ -278,7 +293,8 @@ def get_command_names(config_base_name): config_base_dir = dirname(config_base_module.__file__) - # from http://stackoverflow.com/questions/1057431/loading-all-modules-in-a-folder-in-python + # from + # http://stackoverflow.com/questions/1057431/loading-all-modules-in-a-folder-in-python command_names = CommandList() for f in glob(join(config_base_dir, '*.py')): command_name = basename(f) @@ -289,6 +305,7 @@ def get_command_names(config_base_name): return command_names + def get_command_config(command_config_module, cmd, exit_on_failure=True): """Get the configuration for a ``Command``""" cmd_cfg = None @@ -310,7 +327,9 @@ def get_command_config(command_config_module, cmd, exit_on_failure=True): return cmd_cfg, error_msg + class CommandList(list): + def __init__(self): super(CommandList, self).__init__() From c2a08dd473bb41738a1d5a601f5f3e280ebc4e62 Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 11:12:45 -0600 Subject: [PATCH 13/22] peping --- pyqi/core/log.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/pyqi/core/log.py b/pyqi/core/log.py index 80d1c24..5d8b70e 100644 --- a/pyqi/core/log.py +++ b/pyqi/core/log.py @@ -15,10 +15,13 @@ __credits__ = ["Greg Caporaso", "Daniel McDonald", "Doug Wendel", "Jai Ram Rideout"] + class InvalidLoggerError(Exception): pass + class Logger(object): + """Abstract logging interface""" DEBUG = 'DEBUG' INFO = 'INFO' @@ -47,13 +50,13 @@ def fatal(self, msg): def _debug(self, msg): raise NotImplementedError("All subclasses must implement debug.") - + def _info(self, msg): raise NotImplementedError("All subclasses must implement info.") - + def _warn(self, msg): raise NotImplementedError("All subclasses must implement warn.") - + def _fatal(self, msg): raise NotImplementedError("All subclasses must implement fatal.") @@ -69,19 +72,28 @@ def _format_line(self, level, msg): """Construct a logging line""" return '%s %s %s' % (self._get_timestamp(), level, msg) + class NullLogger(Logger): + """Ignore log messages""" + def _debug(self, msg): pass + def _info(self, msg): pass + def _warn(self, msg): pass + def _fatal(self, msg): pass + class StdErrLogger(Logger): + """Log messages directly to ``stderr``""" + def _debug(self, msg): stderr.write(self._format_line(self.DEBUG, msg) + '\n') From d543d6519535fc943ef060bf1215538252faa8ec Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 11:29:59 -0600 Subject: [PATCH 14/22] pep8ing --- pyqi/core/interfaces/optparse/__init__.py | 95 ++++++++++++++--------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/pyqi/core/interfaces/optparse/__init__.py b/pyqi/core/interfaces/optparse/__init__.py index dd43c8c..7db2f3d 100644 --- a/pyqi/core/interfaces/optparse/__init__.py +++ b/pyqi/core/interfaces/optparse/__init__.py @@ -12,27 +12,29 @@ "Rob Knight", "Doug Wendel", "Jai Ram Rideout", "Jose Antonio Navas Molina"] -import os import types from copy import copy from glob import glob from os.path import abspath, exists, isdir, isfile, split from optparse import (Option, OptionParser, OptionGroup, OptionValueError, OptionError) -from pyqi.core.interface import (Interface, InterfaceInputOption, +from pyqi.core.interface import (Interface, InterfaceInputOption, InterfaceOutputOption, InterfaceUsageExample) from pyqi.core.factory import general_factory from pyqi.core.exception import IncompetentDeveloperError -from pyqi.core.command import Parameter + class OptparseResult(InterfaceOutputOption): + def __init__(self, **kwargs): super(OptparseResult, self).__init__(**kwargs) def _validate_option(self): pass + class OptparseOption(InterfaceInputOption): + """An augmented option that expands a ``CommandIn`` into an Option""" def __init__(self, **kwargs): @@ -82,8 +84,11 @@ def getOptparseOption(self): default=self.Default) return option + class OptparseUsageExample(InterfaceUsageExample): + """Provide structure to a usage example""" + def _validate_usage_example(self): if self.ShortDesc is None: raise IncompetentDeveloperError("Must define ShortDesc") @@ -92,13 +97,15 @@ def _validate_usage_example(self): if self.Ex is None: raise IncompetentDeveloperError("Must define Ex") + class OptparseInterface(Interface): + """A command line interface""" DisallowPositionalArguments = True - HelpOnNoArguments = True + HelpOnNoArguments = True OptionalInputLine = '[] indicates optional input (order unimportant)' RequiredInputLine = '{} indicates required input (order unimportant)' - + def __init__(self, **kwargs): super(OptparseInterface, self).__init__(**kwargs) @@ -164,8 +171,9 @@ def _input_handler(self, in_, *args, **kwargs): # an error. if self.DisallowPositionalArguments and len(args) != 0: parser.error("Positional argument detected: %s\n" % str(args[0]) + - " Be sure all parameters are identified by their option name.\n" + - " (e.g.: include the '-i' in '-i INPUT_DIR')") + " Be sure all parameters are identified by their" + " option name.\n(e.g.: include the '-i' in " + "'-i INPUT_DIR')") # Test that all required options were provided. if required_opts: @@ -186,13 +194,13 @@ def _input_handler(self, in_, *args, **kwargs): if option.Parameter is not None: param_name = option.getParameterName() optparse_clean_name = \ - self._get_optparse_clean_name(option.Name) + self._get_optparse_clean_name(option.Name) if option.Handler is None: value = self._optparse_input[optparse_clean_name] else: value = option.Handler( - self._optparse_input[optparse_clean_name]) + self._optparse_input[optparse_clean_name]) cmd_input_kwargs[param_name] = value @@ -201,7 +209,7 @@ def _input_handler(self, in_, *args, **kwargs): def _build_usage_lines(self, required_options): """ Build the usage string from components """ line1 = 'usage: %prog [options] ' + \ - '{%s}' % ' '.join(['%s %s' % (str(rp),rp.Name.upper()) + '{%s}' % ' '.join(['%s %s' % (str(rp), rp.Name.upper()) for rp in required_options]) formatted_usage_examples = [] @@ -211,22 +219,22 @@ def _build_usage_lines(self, required_options): example = usage_example.Ex.strip() if short_description: - formatted_usage_examples.append('%s: %s\n %s' % + formatted_usage_examples.append('%s: %s\n %s' % (short_description, long_description, example)) else: formatted_usage_examples.append('%s\n %s' % - (long_description,example)) + (long_description, example)) formatted_usage_examples = '\n\n'.join(formatted_usage_examples) lines = (line1, - '', # Blank line + '', # Blank line self.OptionalInputLine, self.RequiredInputLine, - '', # Blank line + '', # Blank line self.CmdInstance.LongDescription, - '', # Blank line + '', # Blank line 'Example usage: ', 'Print help message and exit', ' %prog -h\n', @@ -240,15 +248,15 @@ def _output_handler(self, results): for output in self._get_outputs(): rk = output.Name - + if output.InputName is None: handled_results[rk] = output.Handler(rk, results[rk]) else: optparse_clean_name = \ - self._get_optparse_clean_name(output.InputName) + self._get_optparse_clean_name(output.InputName) opt_value = self._optparse_input[optparse_clean_name] handled_results[rk] = output.Handler(rk, results[rk], - opt_value) + opt_value) return handled_results @@ -256,24 +264,26 @@ def _get_optparse_clean_name(self, name): # optparse converts dashes to underscores in long option names. return name.replace('-', '_') + def optparse_factory(command_constructor, usage_examples, inputs, outputs, version): """Optparse command line interface factory - + command_constructor - a subclass of ``Command`` usage_examples - usage examples for using ``command_constructor`` via a command line interface. inputs - config ``inputs`` or a list of ``OptparseOptions`` - outputs - config ``outputs`` or a list of ``OptparseResults`` + outputs - config ``outputs`` or a list of ``OptparseResults`` version - config ``__version__`` (a version string) """ return general_factory(command_constructor, usage_examples, inputs, outputs, version, OptparseInterface) + def optparse_main(interface_object, local_argv): """Construct and execute an interface object""" optparse_cmd = interface_object() - result = optparse_cmd(local_argv[1:]) + _ = optparse_cmd(local_argv[1:]) return 0 # Definition of PyqiOption option type, a subclass of Option that contains @@ -289,32 +299,36 @@ def optparse_main(interface_object, local_argv): # TODO: this code needs to be refactored to better fit the pyqi framework. # Should probably get added to the OptparseInterface class. + def check_existing_filepath(option, opt, value): if not exists(value): raise OptionValueError( "option %s: file does not exist: %r" % (opt, value)) elif not isfile(value): raise OptionValueError( - "option %s: not a regular file (can't be a directory!): %r" % (opt, value)) + "option %s: not a regular file (can't be a directory!): %r" % + (opt, value)) else: return value + def check_existing_filepaths(option, opt, value): paths = [] for v in value.split(','): fps = glob(v) if len(fps) == 0: raise OptionValueError( - "No filepaths match pattern/name '%s'. " - "All patterns must be matched at least once." % v) + "No filepaths match pattern/name '%s'. " + "All patterns must be matched at least once." % v) else: paths.extend(fps) values = [] for v in paths: - check_existing_filepath(option,opt,v) + check_existing_filepath(option, opt, v) values.append(v) return values + def check_existing_dirpath(option, opt, value): if not exists(value): raise OptionValueError( @@ -325,6 +339,7 @@ def check_existing_dirpath(option, opt, value): else: return value + def check_existing_dirpaths(option, opt, value): paths = [] for v in value.split(','): @@ -341,31 +356,36 @@ def check_existing_dirpaths(option, opt, value): values.append(v) return values + def check_new_filepath(option, opt, value): if exists(value): if isdir(value): raise OptionValueError( - "option %s: output file exists and it is a directory: %r" %(opt, - value)) + "option %s: output file exists and it is a directory: %r" % + (opt, value)) return value - + + def check_new_dirpath(option, opt, value): if exists(value): if isfile(value): raise OptionValueError( - "option %s: output directory exists and it is a file: %r" %(opt, - value)) + "option %s: output directory exists and it is a file: %r" % + (opt, value)) return value - + + def check_existing_path(option, opt, value): if not exists(value): raise OptionValueError( "option %s: path does not exist: %r" % (opt, value)) return value - + + def check_new_path(option, opt, value): return value + def check_multiple_choice(option, opt, value): values = value.split(option.split_char) for v in values: @@ -376,6 +396,7 @@ def check_multiple_choice(option, opt, value): % (opt, v, choices)) return values + def check_blast_db(option, opt, value): db_dir, db_name = split(abspath(value)) if not exists(db_dir): @@ -386,8 +407,9 @@ def check_blast_db(option, opt, value): "option %s: not a directory: %r" % (opt, db_dir)) return value + class PyqiOption(Option): - ATTRS = Option.ATTRS + ['mchoices','split_char'] + ATTRS = Option.ATTRS + ['mchoices', 'split_char'] TYPES = Option.TYPES + ("existing_path", "new_path", @@ -411,7 +433,7 @@ class PyqiOption(Option): # for cases where the user passes one or more existing files # as a comma-separated list - paths are returned as a list TYPE_CHECKER["existing_filepaths"] = check_existing_filepaths - # for cases where the user is passing a new path to be + # for cases where the user is passing a new path to be # create (e.g., an output file) TYPE_CHECKER["new_filepath"] = check_new_filepath # for cases where the user is passing an existing directory @@ -420,7 +442,7 @@ class PyqiOption(Option): # for cases where the user passes one or more existing directories # as a comma-separated list - paths are returned as a list TYPE_CHECKER["existing_dirpaths"] = check_existing_dirpaths - # for cases where the user is passing a new directory to be + # for cases where the user is passing a new directory to be # create (e.g., an output dir which will contain many result files) TYPE_CHECKER["new_dirpath"] = check_new_dirpath # for cases where the user is passing one or more values @@ -435,7 +457,8 @@ def _check_multiple_choice(self): if self.type == "multiple_choice": if self.mchoices is None: raise OptionError( - "must supply a list of mchoices for type '%s'" % self.type, self) + "must supply a list of mchoices for type '%s'" % + self.type, self) elif type(self.mchoices) not in (types.TupleType, types.ListType): raise OptionError( "choices must be a list of strings ('%s' supplied)" From a2ed3d60b95b3eb83355e94afcf55165ad0e4cb0 Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 11:32:57 -0600 Subject: [PATCH 15/22] whitespace fix --- tests/test_commands/test_make_command.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_commands/test_make_command.py b/tests/test_commands/test_make_command.py index 94a680c..d1325b8 100644 --- a/tests/test_commands/test_make_command.py +++ b/tests/test_commands/test_make_command.py @@ -14,6 +14,7 @@ from unittest import TestCase, main from pyqi.commands.make_command import MakeCommand + class MakeCommandTests(TestCase): def setUp(self): """Set up a MakeCommand instance to use in the tests.""" @@ -52,7 +53,7 @@ def test_run_test_code_generation(self): __maintainer__ = "bob" __email__ = "bob@bob.bob" -from pyqi.core.command import (Command, CommandIn, CommandOut, +from pyqi.core.command import (Command, CommandIn, CommandOut, ParameterCollection) class Test(Command): From aad1e38cdae52432d668da7b1e355d69d63b654c Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 11:35:50 -0600 Subject: [PATCH 16/22] whitespace fix, ignoring E501 --- tests/test_commands/test_make_optparse.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/test_commands/test_make_optparse.py b/tests/test_commands/test_make_optparse.py index d36dc2f..12ff51e 100644 --- a/tests/test_commands/test_make_optparse.py +++ b/tests/test_commands/test_make_optparse.py @@ -14,7 +14,8 @@ from unittest import TestCase, main __credits__ = ["Daniel McDonald", "Jai Ram Rideout", "Doug Wendel", - "Greg Caporaso"] + "Greg Caporaso"] + class MakeOptparseTests(TestCase): def setUp(self): @@ -22,7 +23,7 @@ def setUp(self): def test_run(self): exp = win_text - + pc = CommandIn(Name='DUN', Required=True, DataType=str, Description="") bool_param = CommandIn(Name='imabool', DataType=bool, Description='zero or one', Required=False) @@ -31,17 +32,17 @@ class stubby: CommandIns = ParameterCollection([pc, bool_param]) CommandOuts = ParameterCollection([]) - obs = self.cmd(**{'command_module':'foobar', - 'command':stubby(), + obs = self.cmd(**{'command_module': 'foobar', + 'command': stubby(), 'author': 'bob', 'email': 'bob@bob.bob', 'license': 'very permissive license', 'copyright': 'what\'s that?', 'version': '1.0' - }) - + }) + self.assertEqual(obs['result'], exp.splitlines()) - + win_text = """#!/usr/bin/env python from __future__ import division @@ -136,7 +137,7 @@ class stubby: # # value will be made available to Handler. This name # # can be either an underscored or dashed version of the # # option name (e.g., 'output_fp' or 'output-fp') - # InputName='output-fp'), + # InputName='output-fp'), # # An example option that does not map to a CommandIn. # OptparseResult(Parameter=cmd_out_lookup('some_other_result'), From 8fdcd8543f430e62b48a56b915424b2fe1ec446d Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 11:37:09 -0600 Subject: [PATCH 17/22] pep8ing --- pyqi/core/interfaces/optparse/input_handler.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyqi/core/interfaces/optparse/input_handler.py b/pyqi/core/interfaces/optparse/input_handler.py index 69f116f..a52e778 100644 --- a/pyqi/core/interfaces/optparse/input_handler.py +++ b/pyqi/core/interfaces/optparse/input_handler.py @@ -17,12 +17,14 @@ __credits__ = ["Daniel McDonald", "Greg Caporaso", "Doug Wendel", "Jai Ram Rideout"] + def command_handler(option_value): """Dynamically load a Python object from a module and return an instance""" - module, klass = option_value.rsplit('.',1) + module, klass = option_value.rsplit('.', 1) mod = __import__(module, fromlist=[klass]) return getattr(mod, klass)() + def string_list_handler(option_value=None): """Split a comma-separated string into a list of strings.""" result = None @@ -30,6 +32,7 @@ def string_list_handler(option_value=None): result = option_value.split(',') return result + def file_reading_handler(option_value=None): """Open a filepath for reading.""" result = None @@ -37,6 +40,7 @@ def file_reading_handler(option_value=None): result = open(option_value, 'U') return result + def load_file_lines(option_value): """Return a list of strings, one per line in the file. @@ -45,6 +49,7 @@ def load_file_lines(option_value): with open(option_value, 'U') as f: return [line.strip() for line in f] + def load_file_contents(option_value): """Return the contents of a file as a single string.""" with open(option_value, 'U') as f: From 59fdd8a9eb4e3feaebd5ff880b94c01bb00a95fd Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Wed, 19 Mar 2014 11:38:01 -0600 Subject: [PATCH 18/22] pep8ing --- pyqi/core/interfaces/optparse/output_handler.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyqi/core/interfaces/optparse/output_handler.py b/pyqi/core/interfaces/optparse/output_handler.py index 94d7075..8c39f47 100644 --- a/pyqi/core/interfaces/optparse/output_handler.py +++ b/pyqi/core/interfaces/optparse/output_handler.py @@ -26,9 +26,10 @@ from pyqi.core.exception import IncompetentDeveloperError import os + def write_string(result_key, data, option_value=None): """Write a string to a file. - + A newline will be added to the end of the file. """ if option_value is None: @@ -42,9 +43,10 @@ def write_string(result_key, data, option_value=None): f.write(data) f.write('\n') + def write_list_of_strings(result_key, data, option_value=None): """Write a list of strings to a file, one per line. - + A newline will be added to the end of the file. """ if option_value is None: @@ -59,6 +61,7 @@ def write_list_of_strings(result_key, data, option_value=None): f.write(line) f.write('\n') + def print_list_of_strings(result_key, data, option_value=None): """Print a list of strings to stdout, one per line. @@ -67,6 +70,7 @@ def print_list_of_strings(result_key, data, option_value=None): for line in data: print line + def print_string(result_key, data, option_value=None): """Print the string @@ -74,6 +78,7 @@ def print_string(result_key, data, option_value=None): print "" print data + def write_or_print_string(result_key, data, option_value=None): """Write a string to a file. @@ -85,6 +90,7 @@ def write_or_print_string(result_key, data, option_value=None): else: write_string(result_key, data, option_value) + def write_or_print_list_of_strings(result_key, data, option_value=None): """Write a list of strings to a file, one per line. @@ -95,4 +101,3 @@ def write_or_print_list_of_strings(result_key, data, option_value=None): print_list_of_strings(result_key, data, option_value) else: write_list_of_strings(result_key, data, option_value) - From 726fdc3c3b75b66e50943d40052e7318a9915101 Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Fri, 9 May 2014 13:37:05 -0600 Subject: [PATCH 19/22] STY: autopep8 --- pyqi/core/container.py | 103 +++++---- pyqi/core/interfaces/html/__init__.py | 204 +++++++++++------- pyqi/core/interfaces/html/input_handler.py | 6 +- pyqi/core/interfaces/html/output_handler.py | 2 + .../html/config/make_bash_completion.py | 15 +- pyqi/interfaces/html/config/make_command.py | 29 +-- pyqi/interfaces/html/config/make_optparse.py | 26 +-- .../optparse/config/make_bash_completion.py | 4 +- .../optparse/config/make_command.py | 2 +- .../optparse/config/make_optparse.py | 12 +- .../optparse/config/serve_html_interface.py | 1 - pyqi/interfaces/optparse/input_handler.py | 2 +- pyqi/interfaces/optparse/output_handler.py | 2 +- 13 files changed, 240 insertions(+), 168 deletions(-) diff --git a/pyqi/core/container.py b/pyqi/core/container.py index d871dbc..73d336f 100644 --- a/pyqi/core/container.py +++ b/pyqi/core/container.py @@ -11,66 +11,73 @@ __credits__ = ["Greg Caporaso", "Daniel McDonald", "Doug Wendel", "Jai Ram Rideout"] + class ContainerError(Exception): pass - + + class CannotReadError(ContainerError): pass - + + class CannotWriteError(ContainerError): pass - + + class Passthrough(object): + """Basic pass through container class""" - _reserved = set(['_reserved','TypeName', 'Info']) + _reserved = set(['_reserved', 'TypeName', 'Info']) TypeName = "Passthrough" Info = None - + def __init__(self, *args, **kwargs): if 'Info' in kwargs: super(Passthrough, self).__setattr__('Info', kwargs['Info']) - + def _load_if_needed(self): raise NotImplementedError("Passthrough cannot issue I/O.") - + def __getattr__(self, attr): """Pass through to contained class if the attribute is not recognized""" if attr in super(Passthrough, self).__getattribute__('_reserved'): return super(Passthrough, self).__getattribute__(attr) - + self._load_if_needed() - + return getattr(self._object, attr) - + def __setattr__(self, attr, val): """Pass through to contained class if the attribute is not recognized""" if attr in super(Passthrough, self).__getattribute__('_reserved'): return super(Passthrough, self).__setattr__(attr, val) - + self._load_if_needed() - + setattr(self._object, attr, val) - + def __hasattr__(self, attr): """Pass through to contained class if the attribute is not recognized""" if attr in super(Passthrough, self).__getattribute__('_reserved'): return True - + self._load_if_needed() - + return hasattr(self._object, attr) + class PassthroughIO(Passthrough): _reserved = set(['_reserved', 'TypeName', '_reader', '_writer', - '_object', 'InPath', 'OutPath','read', 'write', + '_object', 'InPath', 'OutPath', 'read', 'write', '_load_if_needed', 'Info']) TypeName = "PassthroughIO" - + def __init__(self, *args, **kwargs): super(PassthroughIO, self).__init__(*args, **kwargs) - + if 'Object' in kwargs: - super(PassthroughIO, self).__setattr__('_object', kwargs['Object']) + super(PassthroughIO, self).__setattr__( + '_object', kwargs['Object']) else: self._object = None @@ -80,7 +87,8 @@ def __init__(self, *args, **kwargs): self.InPath = None if 'OutPath' in kwargs: - super(PassthroughIO, self).__setattr__('OutPath', kwargs['OutPath']) + super(PassthroughIO, self).__setattr__( + 'OutPath', kwargs['OutPath']) else: self.OutPath = None @@ -91,7 +99,7 @@ def _load_if_needed(self): self._object = self._reader(self, self.InPath) else: raise CannotReadError("No object and InPath is None.") - + def read(self): """Attempt to read""" if self._object is None: @@ -109,80 +117,98 @@ def write(self): self._writer(self, self.OutPath) self._object = None + class PassthroughRead(PassthroughIO): + def __init__(self, *args, **kwargs): if 'reader' in kwargs: - super(PassthroughRead, self).__setattr__('_reader', kwargs['reader']) + super(PassthroughRead, self).__setattr__( + '_reader', kwargs['reader']) else: raise ContainerError("A reader is required.") super(PassthroughRead, self).__init__(*args, **kwargs) + class PassthroughWrite(PassthroughIO): + def __init__(self, *args, **kwargs): if 'writer' in kwargs: - super(PassthroughWrite, self).__setattr__('_reader', kwargs['writer']) + super(PassthroughWrite, self).__setattr__( + '_reader', kwargs['writer']) else: raise ContainerError("A writer is required.") super(PassthroughWrite, self).__init__(*args, **kwargs) + class DelayRead(PassthroughRead): + """Contain an object and issue IO when an object attribute is requested""" TypeName = "DelayRead" - + + class DelayWrite(PassthroughWrite): + """Contain an object and issue IO with the container is no more""" TypeName = "DelayWrite" def __del__(self): self.write() + class ImmediateRead(PassthroughRead): + """Issue an immediate read on construction""" TypeName = "ImmediateRead" - + def __init__(self, *args, **kwargs): super(ImmediateRead, self).__init__(*args, **kwargs) - self.read() + self.read() + class ImmediateWrite(PassthroughWrite): TypeName = "ImmediateWrite" - + def __init__(self, *args, **kwargs): super(ImmediateWrite, self).__init__(*args, **kwargs) - self.write() + self.write() + def default_write_str(obj, path): - f = open(path,'w') + f = open(path, 'w') f.write(str(obj._object)) f.close() + def default_read_str(obj, path): f = open(path) return f.read() + def default_write_object(obj, path): f = open(path, 'w') f.write(repr(obj._object)) f.close() + def default_read_object(obj, path): f = open(path) - return f.read() # eval isn't safe... + return f.read() # eval isn't safe... + +IOType = {'ImmediateRead': ImmediateRead, + 'ImmediateWrite': ImmediateWrite, + 'DelayRead': DelayRead, + 'DelayWrite': DelayWrite} -IOType = {'ImmediateRead':ImmediateRead, - 'ImmediateWrite':ImmediateWrite, - 'DelayRead':DelayRead, - 'DelayWrite':DelayWrite} +IOLookup = {str: (default_read_str, default_write_str)} -IOLookup = {str:(default_read_str, default_write_str)} def WithIO(obj, IO_type=None, IO_lookup=None, **kwargs): if IO_type is None: raise ContainerError("IO_type is required.") - + if IO_type not in IOType: raise ContainerError("Unknown IO_type: %s" % IO_type) - + if kwargs is None: kwargs = {} @@ -196,11 +222,12 @@ def WithIO(obj, IO_type=None, IO_lookup=None, **kwargs): reader, writer = IO_lookup[obj_type] else: reader, writer = default_read_object, default_write_object - + kwargs['reader'] = reader kwargs['writer'] = writer return IOType[IO_type](**kwargs) + def WithoutIO(obj, **kwargs): return obj diff --git a/pyqi/core/interfaces/html/__init__.py b/pyqi/core/interfaces/html/__init__.py index eff4675..a6f3449 100644 --- a/pyqi/core/interfaces/html/__init__.py +++ b/pyqi/core/interfaces/html/__init__.py @@ -9,7 +9,7 @@ #----------------------------------------------------------------------------- __credits__ = ["Evan Bolyen", "Jai Ram Rideout", "Daniel McDonald", - "Greg Caporaso"] + "Greg Caporaso"] import os import types @@ -19,36 +19,50 @@ from copy import copy from glob import glob from os.path import abspath, exists, isdir, isfile, split -from pyqi.core.interface import (Interface, InterfaceOutputOption, InterfaceInputOption, - InterfaceUsageExample, get_command_names, get_command_config) +from pyqi.core.interface import ( + Interface, InterfaceOutputOption, InterfaceInputOption, + InterfaceUsageExample, get_command_names, get_command_config) from pyqi.core.factory import general_factory from pyqi.core.exception import IncompetentDeveloperError from pyqi.core.command import Parameter from pyqi.util import get_version_string + class HTMLResult(InterfaceOutputOption): + """Base class for results for an HTML config file""" + def __init__(self, MIMEType=None, **kwargs): super(HTMLResult, self).__init__(**kwargs) if MIMEType is None: - raise IncompetentDeveloperError("A valid MIMEType must be provided") - self.MIMEType = MIMEType; + raise IncompetentDeveloperError( + "A valid MIMEType must be provided") + self.MIMEType = MIMEType + class HTMLDownload(HTMLResult): + """Result class for downloading a file from the server""" - def __init__(self, FileExtension=None, FilenameLookup=None, DefaultFilename=None, + + def __init__( + self, FileExtension=None, FilenameLookup=None, DefaultFilename=None, MIMEType='application/octet-stream', **kwargs): super(HTMLDownload, self).__init__(MIMEType=MIMEType, **kwargs) self.FileExtension = FileExtension self.FilenameLookup = FilenameLookup self.DefaultFilename = DefaultFilename + class HTMLPage(HTMLResult): + """Result class for displaying a page for an HTML config file""" + def __init__(self, MIMEType='text/html', **kwargs): super(HTMLPage, self).__init__(MIMEType=MIMEType, **kwargs) + class HTMLInputOption(InterfaceInputOption): + """Define an input option for an HTML config file""" _type_handlers = { None: lambda: None, @@ -76,18 +90,21 @@ def get_html(self, prefix, value=""): """Return the HTML needed for user input given a default value""" if (not value) and (self.Default is not None): value = self.Default - - input_name = prefix + self.Name - string_input = lambda: '' % (input_name, value) - number_input = lambda: '' % (input_name, value) - #html input files cannot have default values. - #If the html interface worked as a data service, this would be possible as submit would be ajax. + input_name = prefix + self.Name + string_input = lambda: '' % (input_name, + value) + number_input = lambda: '' % ( + input_name, value) + + # html input files cannot have default values. + # If the html interface worked as a data service, this would be + # possible as submit would be ajax. upload_input = lambda: '' % input_name mchoice_input = lambda: ''.join( - [ ('(%s)' - % (choice, input_name, choice, 'checked="true"' if value == choice else '')) - for choice in self.Choices ] + [('(%s)' + % (choice, input_name, choice, 'checked="true"' if value == choice else '')) + for choice in self.Choices] ) input_switch = { @@ -103,19 +120,21 @@ def get_html(self, prefix, value=""): } return ''.join(['', - ('*' + self.Name) if self.Required else self.Name, + ('*' + + self.Name) if self.Required else self.Name, '', - input_switch[self.Type](), - '', + input_switch[self.Type](), + '', self.Help, - ' ' - ]) - + ' ' + ]) + def _validate_option(self): if self.Type not in self._type_handlers: - raise IncompetentDeveloperError("Unsupported Type in HTMLInputOption: %s" % self.Type) + raise IncompetentDeveloperError( + "Unsupported Type in HTMLInputOption: %s" % self.Type) - #From optparse's __init__.py, inside class PyqiOption + # From optparse's __init__.py, inside class PyqiOption if self.Type == "multiple_choice": if self.Choices is None: raise IncompetentDeveloperError( @@ -125,13 +144,16 @@ def _validate_option(self): "choices must be a list of strings ('%s' supplied)" % str(type(self.Choices)).split("'")[1], self) elif self.Choices is not None: - raise IncompetentDeveloperError("must not supply Choices for type %r" % self.type, self) + raise IncompetentDeveloperError( + "must not supply Choices for type %r" % self.type, self) + class HTMLInterface(Interface): + """An HTML interface""" - #Relative mapping wasn't working on a collegue's MacBook when pyqi was run outside of it's directory - #Until I understand why that was the case and how to fix it, I am putting the style css here. - #This is not a permanent solution. + # Relative mapping wasn't working on a collegue's MacBook when pyqi was run outside of it's directory + # Until I understand why that was the case and how to fix it, I am putting the style css here. + # This is not a permanent solution. css_style = '\n'.join([ 'html, body {', ' margin: 0px;', @@ -183,35 +205,36 @@ class HTMLInterface(Interface): 'a, a:visited, a:active{', ' color: rgb(32, 67, 92);', '}' - ]) + ]) def __init__(self, input_prefix="pyqi_", **kwargs): self._html_input_prefix = input_prefix self._html_interface_input = {} super(HTMLInterface, self).__init__(**kwargs) - - #Override + + # Override def __call__(self, in_, *args, **kwargs): self._the_in_validator(in_) cmd_input, errors = self._input_handler(in_, *args, **kwargs) if errors: return { - 'type': 'error', - 'errors': errors - } - else: + 'type': 'error', + 'errors': errors + } + else: cmd_result = self.CmdInstance(**cmd_input) - self._the_out_validator(cmd_result) + self._the_out_validator(cmd_result) return self._output_handler(cmd_result) def _validate_inputs_outputs(self, inputs, outputs): - super(HTMLInterface, self)._validate_inputs_outputs(inputs, outputs) - + super(HTMLInterface, self)._validate_inputs_outputs(inputs, outputs) + if len(outputs) > 1: raise IncompetentDeveloperError("There can be only one... output") - if not ( isinstance(outputs[0], HTMLPage) or isinstance(outputs[0], HTMLDownload) ): - raise IncompetentDeveloperError("Output must subclass HTMLPage or HTMLDownload") + if not (isinstance(outputs[0], HTMLPage) or isinstance(outputs[0], HTMLDownload)): + raise IncompetentDeveloperError( + "Output must subclass HTMLPage or HTMLDownload") def _validate_usage_examples(self, usage_examples): super(HTMLInterface, self)._validate_usage_examples(usage_examples) @@ -241,7 +264,7 @@ def _input_handler(self, in_, *args, **kwargs): formatted_input = {} for key in in_: - mod_key = key[ len(self._html_input_prefix): ] + mod_key = key[len(self._html_input_prefix):] formatted_input[mod_key] = in_[key] if not formatted_input[mod_key].value: formatted_input[mod_key] = None @@ -255,10 +278,12 @@ def _input_handler(self, in_, *args, **kwargs): errors.append("Error: %s is required." % option.Name) continue try: - formatted_input[option.Name] = option.cast_value(formatted_input[option.Name]) + formatted_input[option.Name] = option.cast_value( + formatted_input[option.Name]) except (ValueError, TypeError): - errors.append("Error: %s must be type %s" % (option.Name, option.Type) ); + errors.append("Error: %s must be type %s" % + (option.Name, option.Type)) if option.Parameter is not None: param_name = option.getParameterName() @@ -279,7 +304,7 @@ def _build_usage_lines(self, required_options): def _output_download_handler(self, output, handled_results): """Handle the output for type: 'download' """ - #Set up the filename for download + # Set up the filename for download filename = "unnamed_pyqi_output" extension = "" if output.FileExtension is not None: @@ -296,17 +321,17 @@ def _output_download_handler(self, output, handled_results): filehandle = filename + extension return { - 'type':'download', - 'filename':filehandle, - 'contents':handled_results - } + 'type': 'download', + 'filename': filehandle, + 'contents': handled_results + } def _output_page_handler(self, output, handled_results): """Handle the output for type: 'page' """ return { - 'type':'page', - 'mime_type':output.MIMEType, - 'contents':handled_results + 'type': 'page', + 'mime_type': output.MIMEType, + 'contents': handled_results } def _output_handler(self, results): @@ -319,11 +344,11 @@ def _output_handler(self, results): if output.InputName is None: handled_results = output.Handler(rk, results[rk]) else: - handled_results = output.Handler(rk, results[rk], - self._html_interface_input[output.InputName]) + handled_results = output.Handler(rk, results[rk], + self._html_interface_input[output.InputName]) else: handled_results = results[rk] - + if isinstance(output, HTMLDownload): return self._output_download_handler(output, handled_results) @@ -332,7 +357,8 @@ def _output_handler(self, results): def command_page_writer(self, write, errors, postvars): """Write an HTML page which contains a form for user input""" - write('%s' % self.CommandName) + write('%s' % + self.CommandName) write('') write('

%s

' % self.CommandName) - write(self._build_usage_lines([opt for opt in self._get_inputs() if opt.Required])) + write(self._build_usage_lines( + [opt for opt in self._get_inputs() if opt.Required])) - write('

An (*) denotes a required field.

') + write( + '

An (*) denotes a required field.

') for e in errors: write('
%s
' % e) @@ -363,32 +391,39 @@ def command_page_writer(self, write, errors, postvars): write('
') -def html_interface_factory(command_constructor, usage_examples, inputs, outputs, - version, command_name): - interface_class = general_factory(command_constructor, usage_examples, inputs, - outputs, version, HTMLInterface) + +def html_interface_factory( + command_constructor, usage_examples, inputs, outputs, + version, command_name): + interface_class = general_factory( + command_constructor, usage_examples, inputs, + outputs, version, HTMLInterface) interface_class.CommandName = command_name return interface_class + def get_cmd_obj(cmd_cfg_mod, cmd): """Get a ``Command`` object""" - cmd_cfg,_ = get_command_config(cmd_cfg_mod, cmd) + cmd_cfg, _ = get_command_config(cmd_cfg_mod, cmd) version_str = get_version_string(cmd_cfg_mod) cmd_class = html_interface_factory(cmd_cfg.CommandConstructor, [], - cmd_cfg.inputs, cmd_cfg.outputs, version_str, cmd) + cmd_cfg.inputs, cmd_cfg.outputs, version_str, cmd) cmd_obj = cmd_class() return cmd_obj + def get_http_handler(module): """Return a subclassed BaseHTTPRequestHandler with module in scope.""" module_commands = get_command_names(module) class HTMLInterfaceHTTPHandler(BaseHTTPRequestHandler): + """Handle incoming HTTP requests""" def __init__(self, *args, **kwargs): self._unrouted = True - #Apparently this is an 'oldstyle' class, which doesn't allow the use of super() + # Apparently this is an 'oldstyle' class, which doesn't allow the + # use of super() BaseHTTPRequestHandler.__init__(self, *args, **kwargs) def index(self, write): @@ -402,7 +437,7 @@ def index(self, write): write("

Available Commands:

") write("
    ") for command in module_commands: - write( '
  • %s
  • '%(command, command) ) + write('
  • %s
  • ' % (command, command)) write("
") write("") @@ -413,9 +448,9 @@ def route(self, path, output_writer): self.send_header('Content-type', 'text/html') self.end_headers() output_writer(self.wfile.write) - + self.wfile.close() - self._unrouted = False; + self._unrouted = False def command_route(self, command): """Define a route for a command and write the command page""" @@ -426,7 +461,7 @@ def command_route(self, command): self.send_header('Content-type', 'text/html') self.end_headers() cmd_obj.command_page_writer(self.wfile.write, [], {}) - + self.wfile.close() self._unrouted = False @@ -438,17 +473,18 @@ def post_route(self, command, postvars): result = cmd_obj(postvars) except Exception as e: result = { - 'type':'error', - 'errors':[e] + 'type': 'error', + 'errors': [e] } - + if result['type'] == 'error': self.send_response(400) self.send_header('Content-type', 'text/html') self.end_headers() - cmd_obj.command_page_writer(self.wfile.write, result['errors'], postvars) + cmd_obj.command_page_writer( + self.wfile.write, result['errors'], postvars) - elif result['type'] == 'page': + elif result['type'] == 'page': self.send_response(200) self.send_header('Content-type', result['mime_type']) self.end_headers() @@ -456,11 +492,13 @@ def post_route(self, command, postvars): elif result['type'] == 'download': self.send_response(200) - self.send_header('Content-type', 'application/octet-stream') - self.send_header('Content-disposition', 'attachment; filename='+result['filename']) + self.send_header( + 'Content-type', 'application/octet-stream') + self.send_header('Content-disposition', + 'attachment; filename=' + result['filename']) self.end_headers() self.wfile.write(result['contents']) - + self.wfile.close() self._unrouted = False @@ -480,8 +518,9 @@ def do_GET(self): self.route("/index", self.index) self.route("/home", self.index) - def r(write):#host.domain.tld/help - write("This is still a very in development interface, there is no help.") + def r(write): # host.domain.tld/help + write( + "This is still a very in development interface, there is no help.") self.route("/help", r) for command in module_commands: @@ -492,19 +531,20 @@ def r(write):#host.domain.tld/help def do_POST(self): """Handle POST requests""" postvars = FieldStorage(fp=self.rfile, - headers=self.headers, - environ={'REQUEST_METHOD':'POST', - 'CONTENT_TYPE':self.headers['Content-Type']}) + headers=self.headers, + environ={'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': self.headers['Content-Type']}) for command in module_commands: self.post_route(command, postvars) self.end_routes() - return HTMLInterfaceHTTPHandler -#This will generally be called from a generated command. +# This will generally be called from a generated command. + + def start_server(port, module): """Start a server for the HTMLInterface on the specified port""" interface_server = HTTPServer(("", port), get_http_handler(module)) diff --git a/pyqi/core/interfaces/html/input_handler.py b/pyqi/core/interfaces/html/input_handler.py index 7cedd9f..443a09d 100644 --- a/pyqi/core/interfaces/html/input_handler.py +++ b/pyqi/core/interfaces/html/input_handler.py @@ -13,6 +13,7 @@ from pyqi.core.exception import IncompetentDeveloperError + def load_file_lines(option_value): """Return a list of strings, one per line in the file. @@ -20,12 +21,13 @@ def load_file_lines(option_value): """ if not hasattr(option_value, 'read'): raise IncompetentDeveloperError("Input type must be a file object.") - + return [line.strip() for line in option_value] + def load_file_contents(option_value): """Return the contents of a file as a single string.""" if not hasattr(option_value, 'read'): raise IncompetentDeveloperError("Input type must be a file object.") - + return option_value.read() diff --git a/pyqi/core/interfaces/html/output_handler.py b/pyqi/core/interfaces/html/output_handler.py index b24ad00..2868aeb 100644 --- a/pyqi/core/interfaces/html/output_handler.py +++ b/pyqi/core/interfaces/html/output_handler.py @@ -11,10 +11,12 @@ __credits__ = ["Evan Bolyen"] + def newline_list_of_strings(result_key, data, option_value=None): """Return a string from a list of strings while appending newline""" return "\n".join(data) + def html_list_of_strings(result_key, data, option_value=None): """Return a string from a list of strings while appending an html break""" return "
".join(data) diff --git a/pyqi/interfaces/html/config/make_bash_completion.py b/pyqi/interfaces/html/config/make_bash_completion.py index 93d208d..880d97c 100644 --- a/pyqi/interfaces/html/config/make_bash_completion.py +++ b/pyqi/interfaces/html/config/make_bash_completion.py @@ -13,7 +13,7 @@ from pyqi.core.interfaces.html import (HTMLInputOption, HTMLDownload, HTMLPage) from pyqi.core.interfaces.html.output_handler import newline_list_of_strings from pyqi.core.command import (make_command_in_collection_lookup_f, - make_command_out_collection_lookup_f) + make_command_out_collection_lookup_f) from pyqi.commands.make_bash_completion import CommandConstructor cmd_in_lookup = make_command_in_collection_lookup_f(CommandConstructor) @@ -23,12 +23,11 @@ HTMLInputOption(Parameter=cmd_in_lookup('command_config_module')), HTMLInputOption(Parameter=cmd_in_lookup('driver_name')), HTMLInputOption(Parameter=None, - Name='download-file', - Required=True, - Help='The name of the bash completion script to download. (e.g. my_file)') + Name='download-file', + Required=True, + Help='The name of the bash completion script to download. (e.g. my_file)') ] -outputs = [ HTMLDownload(Parameter=cmd_out_lookup('result'), - FilenameLookup='download-file', - FileExtension='.sh') ] - +outputs = [HTMLDownload(Parameter=cmd_out_lookup('result'), + FilenameLookup='download-file', + FileExtension='.sh')] diff --git a/pyqi/interfaces/html/config/make_command.py b/pyqi/interfaces/html/config/make_command.py index a1ac0ec..eb670c5 100644 --- a/pyqi/interfaces/html/config/make_command.py +++ b/pyqi/interfaces/html/config/make_command.py @@ -14,7 +14,7 @@ from pyqi.core.interfaces.optparse.input_handler import string_list_handler from pyqi.core.interfaces.html.output_handler import newline_list_of_strings from pyqi.core.command import (make_command_in_collection_lookup_f, - make_command_out_collection_lookup_f) + make_command_out_collection_lookup_f) from pyqi.commands.make_command import CommandConstructor cmd_in_lookup = make_command_in_collection_lookup_f(CommandConstructor) @@ -26,25 +26,26 @@ HTMLInputOption(Parameter=cmd_in_lookup('email')), HTMLInputOption(Parameter=cmd_in_lookup('license')), HTMLInputOption(Parameter=cmd_in_lookup('copyright')), - HTMLInputOption(Parameter=cmd_in_lookup('version'), Name='command-version'), + HTMLInputOption(Parameter=cmd_in_lookup('version'), + Name='command-version'), HTMLInputOption(Parameter=cmd_in_lookup('credits'), - Handler=string_list_handler, - Help='comma-separated list of other authors'), + Handler=string_list_handler, + Help='comma-separated list of other authors'), HTMLInputOption(Parameter=cmd_in_lookup('test_code'), - Type=bool, - Help='Should a stubbed out python test file be generated instead'), + Type=bool, + Help='Should a stubbed out python test file be generated instead'), HTMLInputOption(Parameter=None, - Name='download-file', - Required=True, - Help='The name of the file to download which contains generated Python code. (e.g. my_command)') + Name='download-file', + Required=True, + Help='The name of the file to download which contains generated Python code. (e.g. my_command)') ] -outputs = [ HTMLDownload(Parameter=cmd_out_lookup('result'), - Handler=newline_list_of_strings, - FilenameLookup='download-file', - FileExtension='.py') ] +outputs = [HTMLDownload(Parameter=cmd_out_lookup('result'), + Handler=newline_list_of_strings, + FilenameLookup='download-file', + FileExtension='.py')] -#Comment out the above and uncomment the below for an example of a page. +# Comment out the above and uncomment the below for an example of a page. # [ HTMLPage(Parameter=cmd_out_lookup('result'), # Handler=newline_list_of_strings) ] diff --git a/pyqi/interfaces/html/config/make_optparse.py b/pyqi/interfaces/html/config/make_optparse.py index 95eeaea..25432b3 100644 --- a/pyqi/interfaces/html/config/make_optparse.py +++ b/pyqi/interfaces/html/config/make_optparse.py @@ -15,32 +15,32 @@ from pyqi.core.interfaces.optparse.input_handler import string_list_handler from pyqi.core.interfaces.html.output_handler import newline_list_of_strings from pyqi.core.command import (make_command_in_collection_lookup_f, - make_command_out_collection_lookup_f) + make_command_out_collection_lookup_f) from pyqi.commands.make_optparse import CommandConstructor cmd_in_lookup = make_command_in_collection_lookup_f(CommandConstructor) cmd_out_lookup = make_command_out_collection_lookup_f(CommandConstructor) inputs = [ - HTMLInputOption(Parameter=cmd_in_lookup('command'), + HTMLInputOption(Parameter=cmd_in_lookup('command'), Handler=command_handler), HTMLInputOption(Parameter=cmd_in_lookup('command_module')), HTMLInputOption(Parameter=cmd_in_lookup('author')), HTMLInputOption(Parameter=cmd_in_lookup('email')), HTMLInputOption(Parameter=cmd_in_lookup('license')), HTMLInputOption(Parameter=cmd_in_lookup('copyright')), - HTMLInputOption(Parameter=cmd_in_lookup('version'), Name='command-version'), + HTMLInputOption(Parameter=cmd_in_lookup('version'), + Name='command-version'), HTMLInputOption(Parameter=cmd_in_lookup('credits'), - Handler=string_list_handler, - Help='comma-separated list of other authors'), + Handler=string_list_handler, + Help='comma-separated list of other authors'), HTMLInputOption(Parameter=None, - Name='download-file', - Required=True, - Help='The name of the file to download which contains generated Python code. (e.g. my_optparse_config)') + Name='download-file', + Required=True, + Help='The name of the file to download which contains generated Python code. (e.g. my_optparse_config)') ] -outputs = [ HTMLDownload(Parameter=cmd_out_lookup('result'), - Handler=newline_list_of_strings, - FilenameLookup='download-file', - FileExtension='.py') ] - +outputs = [HTMLDownload(Parameter=cmd_out_lookup('result'), + Handler=newline_list_of_strings, + FilenameLookup='download-file', + FileExtension='.py')] diff --git a/pyqi/interfaces/optparse/config/make_bash_completion.py b/pyqi/interfaces/optparse/config/make_bash_completion.py index 39a7390..a8113b2 100644 --- a/pyqi/interfaces/optparse/config/make_bash_completion.py +++ b/pyqi/interfaces/optparse/config/make_bash_completion.py @@ -9,14 +9,14 @@ #----------------------------------------------------------------------------- __credits__ = ["Daniel McDonald", "Jai Ram Rideout", "Doug Wendel", - "Greg Caporaso"] + "Greg Caporaso"] from pyqi.core.interfaces.optparse import (OptparseOption, OptparseUsageExample, OptparseResult) from pyqi.core.interfaces.optparse.output_handler import write_string from pyqi.core.command import (make_command_in_collection_lookup_f, - make_command_out_collection_lookup_f) + make_command_out_collection_lookup_f) from pyqi.commands.make_bash_completion import CommandConstructor cmd_in_lookup = make_command_in_collection_lookup_f(CommandConstructor) diff --git a/pyqi/interfaces/optparse/config/make_command.py b/pyqi/interfaces/optparse/config/make_command.py index ae17928..49fa86a 100644 --- a/pyqi/interfaces/optparse/config/make_command.py +++ b/pyqi/interfaces/optparse/config/make_command.py @@ -55,7 +55,7 @@ ] outputs = [ - ### InputName is used to tie this output to output-fp, which is an input... + # InputName is used to tie this output to output-fp, which is an input... OptparseResult(Parameter=cmd_out_lookup('result'), Handler=write_list_of_strings, InputName='output-fp') diff --git a/pyqi/interfaces/optparse/config/make_optparse.py b/pyqi/interfaces/optparse/config/make_optparse.py index 2cad401..cbfed99 100644 --- a/pyqi/interfaces/optparse/config/make_optparse.py +++ b/pyqi/interfaces/optparse/config/make_optparse.py @@ -12,13 +12,14 @@ "Jai Ram Rideout"] from pyqi.core.command import Command -from pyqi.core.interfaces.optparse import (OptparseOption, OptparseUsageExample, +from pyqi.core.interfaces.optparse import ( + OptparseOption, OptparseUsageExample, OptparseResult) from pyqi.core.interfaces.optparse.input_handler import (command_handler, string_list_handler) from pyqi.core.interfaces.optparse.output_handler import write_list_of_strings from pyqi.core.command import (make_command_in_collection_lookup_f, - make_command_out_collection_lookup_f) + make_command_out_collection_lookup_f) from pyqi.commands.make_optparse import CommandConstructor cmdin_lookup = make_command_in_collection_lookup_f(CommandConstructor) @@ -28,9 +29,10 @@ OptparseUsageExample(ShortDesc="Create an optparse config template", LongDesc="Construct the beginning of an optparse configuration file based on the Parameters required by the Command.", Ex='%prog -c pyqi.commands.make_optparse.MakeOptparse -m pyqi.commands.make_optparse -a "some author" --copyright "Copyright 2013, The pyqi project" -e "foo@bar.com" -l BSD --config-version "0.1" --credits "someone else","and another person" -o pyqi/interfaces/optparse/config/make_optparse.py'), - OptparseUsageExample(ShortDesc="Create a different optparse config template", - LongDesc="Construct the beginning of an optparse configuration file based on the Parameters required by the Command. This command corresponds to the pyqi tutorial example where a sequence_collection_summarizer command line interface is created for a SequenceCollectionSummarizer Command.", - Ex='%prog -c sequence_collection_summarizer.SequenceCollectionSummarizer -m sequence_collection_summarizer -a "Greg Caporaso" --copyright "Copyright 2013, Greg Caporaso" -e "gregcaporaso@gmail.com" -l BSD --config-version 0.0.1 -o summarize_sequence_collection.py') + OptparseUsageExample( + ShortDesc="Create a different optparse config template", + LongDesc="Construct the beginning of an optparse configuration file based on the Parameters required by the Command. This command corresponds to the pyqi tutorial example where a sequence_collection_summarizer command line interface is created for a SequenceCollectionSummarizer Command.", + Ex='%prog -c sequence_collection_summarizer.SequenceCollectionSummarizer -m sequence_collection_summarizer -a "Greg Caporaso" --copyright "Copyright 2013, Greg Caporaso" -e "gregcaporaso@gmail.com" -l BSD --config-version 0.0.1 -o summarize_sequence_collection.py') ] inputs = [ diff --git a/pyqi/interfaces/optparse/config/serve_html_interface.py b/pyqi/interfaces/optparse/config/serve_html_interface.py index 4133f8c..f5df4ba 100644 --- a/pyqi/interfaces/optparse/config/serve_html_interface.py +++ b/pyqi/interfaces/optparse/config/serve_html_interface.py @@ -22,7 +22,6 @@ cmdout_lookup = make_command_out_collection_lookup_f(CommandConstructor) - usage_examples = [ OptparseUsageExample(ShortDesc="Start html interface", LongDesc="Starts an html interface server on the specified --port and --interface-module", diff --git a/pyqi/interfaces/optparse/input_handler.py b/pyqi/interfaces/optparse/input_handler.py index e5b0061..d1db5ff 100644 --- a/pyqi/interfaces/optparse/input_handler.py +++ b/pyqi/interfaces/optparse/input_handler.py @@ -12,4 +12,4 @@ __credits__ = ["Greg Caporaso", "Daniel McDonald", "Doug Wendel", "Jai Ram Rideout"] -## Store project/inteface specific input handlers here +# Store project/inteface specific input handlers here diff --git a/pyqi/interfaces/optparse/output_handler.py b/pyqi/interfaces/optparse/output_handler.py index 7cad4b9..74cb9f3 100644 --- a/pyqi/interfaces/optparse/output_handler.py +++ b/pyqi/interfaces/optparse/output_handler.py @@ -12,4 +12,4 @@ __credits__ = ["Greg Caporaso", "Daniel McDonald", "Doug Wendel", "Jai Ram Rideout"] -## Store project/inteface specific output handlers here +# Store project/inteface specific output handlers here From af8a0445e0ae865f0477f0dacba4a0ebc35962d6 Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Fri, 9 May 2014 13:37:35 -0600 Subject: [PATCH 20/22] STY: autopep8 --- .../test_code_header_generator.py | 2 + .../test_make_bash_completion.py | 1 + tests/test_commands/test_make_command.py | 1 + tests/test_commands/test_make_optparse.py | 1 + tests/test_core/test_command.py | 47 +++++--- tests/test_core/test_interface.py | 10 +- .../test_html/test_input_handler.py | 23 ++-- .../test_html/test_output_handler.py | 7 +- .../test_optparse/test_init.py | 111 +++++++++++------- .../test_optparse/test_input_handler.py | 2 + .../test_optparse/test_output_handler.py | 8 +- tests/test_util.py | 2 + 12 files changed, 136 insertions(+), 79 deletions(-) diff --git a/tests/test_commands/test_code_header_generator.py b/tests/test_commands/test_code_header_generator.py index b1c91e7..d6c591c 100644 --- a/tests/test_commands/test_code_header_generator.py +++ b/tests/test_commands/test_code_header_generator.py @@ -14,7 +14,9 @@ from unittest import TestCase, main from pyqi.commands.code_header_generator import CodeHeaderGenerator + class CodeHeaderGeneratorTests(TestCase): + def setUp(self): """Set up a CodeHeaderGenerator instance to use in the tests.""" self.cmd = CodeHeaderGenerator() diff --git a/tests/test_commands/test_make_bash_completion.py b/tests/test_commands/test_make_bash_completion.py index 9251af5..2788ee8 100644 --- a/tests/test_commands/test_make_bash_completion.py +++ b/tests/test_commands/test_make_bash_completion.py @@ -23,6 +23,7 @@ class BashCompletionTests(TestCase): + def setUp(self): """Set up data for unit tests.""" self.cmd = BashCompletion() diff --git a/tests/test_commands/test_make_command.py b/tests/test_commands/test_make_command.py index d1325b8..85e8975 100644 --- a/tests/test_commands/test_make_command.py +++ b/tests/test_commands/test_make_command.py @@ -16,6 +16,7 @@ class MakeCommandTests(TestCase): + def setUp(self): """Set up a MakeCommand instance to use in the tests.""" self.cmd = MakeCommand() diff --git a/tests/test_commands/test_make_optparse.py b/tests/test_commands/test_make_optparse.py index 12ff51e..7cd0604 100644 --- a/tests/test_commands/test_make_optparse.py +++ b/tests/test_commands/test_make_optparse.py @@ -18,6 +18,7 @@ class MakeOptparseTests(TestCase): + def setUp(self): self.cmd = MakeOptparse() diff --git a/tests/test_core/test_command.py b/tests/test_core/test_command.py index b2e70e2..2148ef3 100644 --- a/tests/test_core/test_command.py +++ b/tests/test_core/test_command.py @@ -14,18 +14,21 @@ from unittest import TestCase, main from pyqi.core.command import CommandIn, CommandOut, ParameterCollection, Command -from pyqi.core.exception import (IncompetentDeveloperError, - UnknownParameterError, +from pyqi.core.exception import (IncompetentDeveloperError, + UnknownParameterError, MissingParameterError) + class CommandTests(TestCase): + def setUp(self): class stubby(Command): CommandIns = ParameterCollection([ - CommandIn('a',int,'', Required=True), - CommandIn('b',int,'', Required=False, Default=5), - CommandIn('c',int,'', Required=False, Default=10, - ValidateValue=lambda x: x == 10)]) + CommandIn('a', int, '', Required=True), + CommandIn('b', int, '', Required=False, Default=5), + CommandIn('c', int, '', Required=False, Default=10, + ValidateValue=lambda x: x == 10)]) + def run(self, **kwargs): return {} self.stubby = stubby @@ -45,39 +48,42 @@ class foo(Command): Required=True), CommandIn('b', str, 'help2', Required=False)]) + def run(self, **kwargs): return {} obs = foo() self.assertEqual(len(obs.Parameters), 2) - self.assertEqual(obs.run(bar={'a':10}), {}) + self.assertEqual(obs.run(bar={'a': 10}), {}) def test_validate_kwargs(self): stub = self.stubby() - kwargs = {'a':10, 'b':20} - + kwargs = {'a': 10, 'b': 20} + # should work stub._validate_kwargs(kwargs) - kwargs = {'b':20} + kwargs = {'b': 20} self.assertRaises(MissingParameterError, stub._validate_kwargs, kwargs) - - kwargs = {'a':10, 'b':20, 'c':10} + + kwargs = {'a': 10, 'b': 20, 'c': 10} stub._validate_kwargs(kwargs) - kwargs = {'a':10, 'b':20, 'c':20} + kwargs = {'a': 10, 'b': 20, 'c': 20} self.assertRaises(ValueError, stub._validate_kwargs, kwargs) def test_set_defaults(self): stub = self.stubby() - kwargs = {'a':10} - exp = {'a':10,'b':5,'c':10} - + kwargs = {'a': 10} + exp = {'a': 10, 'b': 5, 'c': 10} + stub._set_defaults(kwargs) self.assertEqual(kwargs, exp) + class ParameterTests(TestCase): + def test_init(self): """Jog the init""" obj = CommandIn('a', str, 'help', Required=False) @@ -90,10 +96,12 @@ def test_init(self): self.assertRaises(IncompetentDeveloperError, CommandIn, 'a', str, 'help', True, 'x') + class ParameterCollectionTests(TestCase): + def setUp(self): - self.pc = ParameterCollection([CommandIn('foo',str, 'help')]) - + self.pc = ParameterCollection([CommandIn('foo', str, 'help')]) + def test_init(self): """Jog the init""" params = [CommandIn('a', str, 'help', Required=False), @@ -111,7 +119,8 @@ def test_init(self): def test_getitem(self): self.assertRaises(UnknownParameterError, self.pc.__getitem__, 'bar') - self.assertEqual(self.pc['foo'].Name, 'foo') # make sure we can getitem + # make sure we can getitem + self.assertEqual(self.pc['foo'].Name, 'foo') def test_setitem(self): self.assertRaises(TypeError, self.pc.__setitem__, 'bar', 10) diff --git a/tests/test_core/test_interface.py b/tests/test_core/test_interface.py index 63e05c0..c98e478 100644 --- a/tests/test_core/test_interface.py +++ b/tests/test_core/test_interface.py @@ -16,10 +16,12 @@ from pyqi.core.interface import get_command_names, get_command_config import pyqi.interfaces.optparse.config.make_bash_completion + class TopLevelTests(TestCase): + def test_get_command_names(self): """Test that command names are returned from a config directory.""" - exp = ['make-bash-completion', 'make-command', 'make-optparse', + exp = ['make-bash-completion', 'make-command', 'make-optparse', 'make-release', 'serve-html-interface'] obs = get_command_names('pyqi.interfaces.optparse.config') self.assertEqual(obs, exp) @@ -31,14 +33,14 @@ def test_get_command_names(self): def test_get_command_config(self): """Test successful and unsuccessful module loading.""" cmd_cfg, error_msg = get_command_config( - 'pyqi.interfaces.optparse.config', 'make_bash_completion') + 'pyqi.interfaces.optparse.config', 'make_bash_completion') self.assertEqual(cmd_cfg, pyqi.interfaces.optparse.config.make_bash_completion) self.assertEqual(error_msg, None) cmd_cfg, error_msg = get_command_config( - 'hopefully.nonexistent.python.module', 'umm', - exit_on_failure=False) + 'hopefully.nonexistent.python.module', 'umm', + exit_on_failure=False) self.assertEqual(cmd_cfg, None) self.assertEqual(error_msg, 'No module named hopefully.nonexistent.' 'python.module.umm') diff --git a/tests/test_core/test_interfaces/test_html/test_input_handler.py b/tests/test_core/test_interfaces/test_html/test_input_handler.py index e22dafe..5b138d2 100644 --- a/tests/test_core/test_interfaces/test_html/test_input_handler.py +++ b/tests/test_core/test_interfaces/test_html/test_input_handler.py @@ -14,16 +14,18 @@ from unittest import TestCase, main from pyqi.core.exception import IncompetentDeveloperError from pyqi.core.interfaces.html.input_handler import (load_file_lines, - load_file_contents) + load_file_contents) + class HTMLInputHandlerTests(TestCase): + def setUp(self): self.file_like_object = StringIO() - #Note the whitespace, this tests strip() + # Note the whitespace, this tests strip() self.file_like_object.write("This is line 1\n") self.file_like_object.write(" This is line 2\n") self.file_like_object.write("This is line 3 \n") - #Place it at the beginning of the file again + # Place it at the beginning of the file again self.file_like_object.seek(0) def tearDown(self): @@ -32,21 +34,24 @@ def tearDown(self): def test_load_file_lines(self): """Correctly returns file lines as a list of strings""" # can't load a string, etc... - self.assertRaises(IncompetentDeveloperError, load_file_lines, 'This is not a file') + self.assertRaises(IncompetentDeveloperError, + load_file_lines, 'This is not a file') result = load_file_lines(self.file_like_object) - self.assertEqual(result, - ["This is line 1", + self.assertEqual(result, + ["This is line 1", "This is line 2", "This is line 3"]) def test_load_file_contents(self): """Correctly returns file contents""" # can't load a string, etc... - self.assertRaises(IncompetentDeveloperError, load_file_contents, 'This is not a file') + self.assertRaises(IncompetentDeveloperError, + load_file_contents, 'This is not a file') result = load_file_contents(self.file_like_object) - #Note the whitespace - self.assertEqual(result, "This is line 1\n This is line 2\nThis is line 3 \n") + # Note the whitespace + self.assertEqual( + result, "This is line 1\n This is line 2\nThis is line 3 \n") if __name__ == '__main__': main() diff --git a/tests/test_core/test_interfaces/test_html/test_output_handler.py b/tests/test_core/test_interfaces/test_html/test_output_handler.py index abb59d1..dcdee86 100644 --- a/tests/test_core/test_interfaces/test_html/test_output_handler.py +++ b/tests/test_core/test_interfaces/test_html/test_output_handler.py @@ -12,18 +12,19 @@ from unittest import TestCase, main from pyqi.core.interfaces.html.output_handler import (newline_list_of_strings, - html_list_of_strings) + html_list_of_strings) + class HTMLOutputHandlerTests(TestCase): def test_newline_list_of_strings(self): """Correctly returns a list of strings to delimited by '\n'.""" - result = newline_list_of_strings('foo', ['bar','bay','baz']) + result = newline_list_of_strings('foo', ['bar', 'bay', 'baz']) self.assertEqual(result, 'bar\nbay\nbaz') def test_html_list_of_strings(self): """Correctly returns a list of strings delimited by '
'.""" - result = html_list_of_strings('foo', ['bar','bay','baz']) + result = html_list_of_strings('foo', ['bar', 'bay', 'baz']) self.assertEqual(result, 'bar
bay
baz') if __name__ == '__main__': diff --git a/tests/test_core/test_interfaces/test_optparse/test_init.py b/tests/test_core/test_interfaces/test_optparse/test_init.py index 9e85823..48f3c9f 100644 --- a/tests/test_core/test_interfaces/test_optparse/test_init.py +++ b/tests/test_core/test_interfaces/test_optparse/test_init.py @@ -33,11 +33,14 @@ from os import remove, rmdir from os.path import commonprefix + class OptparseResultTests(TestCase): # Nothing to test yet pass + class OptparseOptionTests(TestCase): + def setUp(self): p = CommandIn('number', int, 'some int', Required=False, Default=42, DefaultDescription='forty-two') @@ -45,7 +48,7 @@ def setUp(self): self.opt1 = OptparseOption(Parameter=p, Type=int) # Without associated parameter. - self.opt2 = OptparseOption(Parameter=None, Type=int, Handler=None, + self.opt2 = OptparseOption(Parameter=None, Type=int, Handler=None, ShortName='n', Name='number', Required=False, Help='help!!!') @@ -68,7 +71,9 @@ def test_str(self): obs = str(self.opt2) self.assertEqual(obs, exp) + class OptparseUsageExampleTests(TestCase): + def test_init(self): obj = OptparseUsageExample(ShortDesc='a', LongDesc='b', Ex='c') self.assertEqual(obj.ShortDesc, 'a') @@ -78,13 +83,16 @@ def test_init(self): with self.assertRaises(IncompetentDeveloperError): _ = OptparseUsageExample('a', 'b', Ex=None) + def oh(key, data, opt_value=None): return data * 2 + class OptparseInterfaceTests(TestCase): + def setUp(self): self.interface = fabulous() - + def test_init(self): self.assertRaises(IncompetentDeveloperError, OptparseInterface) @@ -99,7 +107,7 @@ def test_validate_inputs(self): _ = DuplicateOptionMappings() def test_input_handler(self): - obs = self.interface._input_handler(['--c','foo']) + obs = self.interface._input_handler(['--c', 'foo']) self.assertEqual(obs.items(), [('c', 'foo')]) def test_build_usage_lines(self): @@ -107,21 +115,25 @@ def test_build_usage_lines(self): self.assertEqual(obs, usage_lines) def test_output_handler(self): - results = {'itsaresult':20} + results = {'itsaresult': 20} obs = self.interface._output_handler(results) - self.assertEqual(obs, {'itsaresult':40}) + self.assertEqual(obs, {'itsaresult': 40}) + class GeneralTests(TestCase): + def setUp(self): self.obj = optparse_factory(ghetto, - [OptparseUsageExample('a','b','c')], - [OptparseOption(Type=str, - Parameter=ghetto.CommandIns['c'], - ShortName='n')], - [OptparseResult(Type=str, - Parameter=ghetto.CommandOuts['itsaresult'], - Handler=oh)], - '2.0-dev') + [OptparseUsageExample('a', 'b', 'c')], + [OptparseOption(Type=str, + Parameter=ghetto.CommandIns[ + 'c'], + ShortName='n')], + [OptparseResult(Type=str, + Parameter=ghetto.CommandOuts[ + 'itsaresult'], + Handler=oh)], + '2.0-dev') def test_optparse_factory(self): # exercise it @@ -131,12 +143,14 @@ def test_optparse_main(self): # exercise it _ = optparse_main(self.obj, ['testing', '--c', 'bar']) + class ghetto(Command): CommandIns = ParameterCollection([CommandIn('c', str, 'b')]) CommandOuts = ParameterCollection([CommandOut('itsaresult', str, 'x')]) def run(self, **kwargs): - return {'itsaresult':10} + return {'itsaresult': 10} + class fabulous(OptparseInterface): CommandConstructor = ghetto @@ -147,7 +161,7 @@ def _get_inputs(self): ShortName='n')] def _get_usage_examples(self): - return [OptparseUsageExample('a','b','c')] + return [OptparseUsageExample('a', 'b', 'c')] def _get_outputs(self): return [OptparseResult(Parameter=ghetto.CommandOuts['itsaresult'], Handler=oh)] @@ -156,22 +170,31 @@ def _get_version(self): return '2.0-dev' # Doesn't have any usage examples... + + class NoUsageExamples(fabulous): + def _get_usage_examples(self): return [] # More than one option mapping to the same Parameter... + + class DuplicateOptionMappings(fabulous): + def _get_inputs(self): return [ OptparseOption(Type=str, - Parameter=self.CommandConstructor.CommandIns['c'], ShortName='n'), + Parameter=self.CommandConstructor.CommandIns[ + 'c'], ShortName='n'), OptparseOption(Parameter=self.CommandConstructor.CommandIns['c'], - Name='i-am-a-duplicate') + Name='i-am-a-duplicate') ] + class TypeCheckTests(TestCase): + def setUp(self): self._paths_to_clean_up = [] self._dirs_to_clean_up = [] @@ -189,15 +212,15 @@ def test_check_existing_filepath(self): self.assertEqual(obs, tmp_path) # Check that raises an error when the file doesn't exists self.assertRaises(OptionValueError, check_existing_filepath, option, - '-f', '/hopefully/a/non/existing/file') + '-f', '/hopefully/a/non/existing/file') # Check that raises an error when the path exists and is a directory tmp_dirpath = mkdtemp() self._dirs_to_clean_up = [tmp_dirpath] self.assertRaises(OptionValueError, check_existing_filepath, option, - '-f', tmp_dirpath) + '-f', tmp_dirpath) def test_check_existing_filepaths(self): - # Check that returns a list with the paths, in the same order as + # Check that returns a list with the paths, in the same order as # the input comma separated list tmp_f1, tmp_path1 = mkstemp(prefix='pyqi_tmp_') tmp_f2, tmp_path2 = mkstemp(prefix='pyqi_tmp_') @@ -214,17 +237,18 @@ def test_check_existing_filepaths(self): self.assertEqual(set(obs), set(exp)) # Check that raises an error when the wildcard does not match any file self.assertRaises(OptionValueError, check_existing_filepaths, option, - '-f', '/hopefully/a/non/existing/path*') + '-f', '/hopefully/a/non/existing/path*') # Check that raises an error when one of the files does not exist - value = ",".join([tmp_path1,tmp_path2,'/hopefully/a/non/existing/file']) + value = ",".join( + [tmp_path1, tmp_path2, '/hopefully/a/non/existing/file']) self.assertRaises(OptionValueError, check_existing_filepaths, option, - '-f', value) + '-f', value) # Check that raises an error when one of the paths is a folder tmp_dirpath = mkdtemp() self._dirs_to_clean_up = [tmp_dirpath] value = ",".join([tmp_path1, tmp_path2, tmp_dirpath]) self.assertRaises(OptionValueError, check_existing_filepaths, option, - '-f', value) + '-f', value) def test_check_existing_dirpath(self): # Check that returns the correct value when the directory exists @@ -235,12 +259,12 @@ def test_check_existing_dirpath(self): self.assertEqual(obs, tmp_dirpath) # Check that raises an error when the folder doesn't exists self.assertRaises(OptionValueError, check_existing_dirpath, option, - '-f', '/hopefully/a/non/existing/directory') + '-f', '/hopefully/a/non/existing/directory') # Check that raises an error when the path exists and is a file tmp_f, tmp_path = mkstemp() self._paths_to_clean_up = [tmp_path] self.assertRaises(OptionValueError, check_existing_dirpath, option, - '-f', tmp_path) + '-f', tmp_path) def test_check_existing_dirpaths(self): # Check that returns a list with the paths, in the same order as the @@ -260,18 +284,18 @@ def test_check_existing_dirpaths(self): self.assertEqual(set(obs), set(exp)) # Check that raises an error when the wildcard does not match any path self.assertRaises(OptionValueError, check_existing_dirpaths, option, - '-f', '/hopefully/a/non/existing/path*') + '-f', '/hopefully/a/non/existing/path*') # Check that raises an error when one of the directories does not exist value = ",".join([tmp_dirpath1, tmp_dirpath2, - '/hopefully/a/non/existing/path*']) + '/hopefully/a/non/existing/path*']) self.assertRaises(OptionValueError, check_existing_dirpaths, option, - '-f', value) + '-f', value) # Check that raises an error when one of the paths is a file tmp_f, tmp_path = mkstemp() self._paths_to_clean_up = [tmp_path] value = ",".join([tmp_dirpath1, tmp_dirpath2, tmp_path]) self.assertRaises(OptionValueError, check_existing_dirpaths, option, - '-f', value) + '-f', value) def test_check_new_filepath(self): # Check that it doesn't raise an error if the path does not exist @@ -288,7 +312,7 @@ def test_check_new_filepath(self): tmp_dirpath = mkdtemp() self._dirs_to_clean_up = [tmp_dirpath] self.assertRaises(OptionValueError, check_new_filepath, option, '-n', - tmp_dirpath) + tmp_dirpath) def test_check_new_dirpath(self): # Check that it doesn't raise an error if the path does not exist @@ -296,7 +320,8 @@ def test_check_new_dirpath(self): exp = '/hopefully/a/non/existing/dir' obs = check_new_dirpath(option, '-n', exp) self.assertEqual(obs, exp) - # Check that it doesn't raise an error if the path exists and is a directory + # Check that it doesn't raise an error if the path exists and is a + # directory tmp_dirpath = mkdtemp() self._dirs_to_clean_up = [tmp_dirpath] obs = check_new_dirpath(option, '-n', tmp_dirpath) @@ -305,7 +330,7 @@ def test_check_new_dirpath(self): tmp_f, tmp_path = mkstemp() self._paths_to_clean_up = [tmp_path] self.assertRaises(OptionValueError, check_new_dirpath, option, '-n', - tmp_path) + tmp_path) def test_check_existing_path(self): # Check that it doesn't raise an error if an existing file is passed @@ -314,14 +339,15 @@ def test_check_existing_path(self): self._paths_to_clean_up = [tmp_path] obs = check_existing_path(option, '-p', tmp_path) self.assertEqual(obs, tmp_path) - # Check that it doesn't raise an error if an existing directory is passed + # Check that it doesn't raise an error if an existing directory is + # passed tmp_dirpath = mkdtemp() self._dirs_to_clean_up = [tmp_dirpath] obs = check_existing_path(option, '-p', tmp_dirpath) self.assertEqual(obs, tmp_dirpath) # Check that it raises an error if the path doesn't exist self.assertRaises(OptionValueError, check_existing_path, option, '-n', - '/hopefully/a/non/existing/path') + '/hopefully/a/non/existing/path') def test_check_new_path(self): # Really? Too much work... @@ -333,21 +359,24 @@ def test_check_new_path(self): def test_check_multiple_choice(self): # Check that it doesn't raise an error when the value is in the list option = PyqiOption('-m', '--multiple', type="multiple_choice", - mchoices=['choice_A', 'choice_B', 'choice_C']) + mchoices=['choice_A', 'choice_B', 'choice_C']) exp = ["choice_B"] obs = check_multiple_choice(option, '-m', exp[0]) self.assertEqual(obs, exp) - exp = ["choice_B","choice_C"] + exp = ["choice_B", "choice_C"] obs = check_multiple_choice(option, '-m', ",".join(exp)) self.assertEqual(obs, exp) # Check that it raises an error when the value is not in the list - self.assertRaises(OptionValueError, check_multiple_choice, option, '-n', + self.assertRaises( + OptionValueError, check_multiple_choice, option, '-n', "choice_not_listed") - self.assertRaises(OptionValueError, check_multiple_choice, option, '-n', + self.assertRaises( + OptionValueError, check_multiple_choice, option, '-n', "choice_A,choice_not_listed") def test_check_blast_db(self): - # Check that it doesn't raise an error when a blastdb-like prefix is passed + # Check that it doesn't raise an error when a blastdb-like prefix is + # passed option = PyqiOption('-b', '--blast_test', type="blast_db") tmp_f1, tmp_path1 = mkstemp(prefix='pyqi_tmp_') tmp_f2, tmp_path2 = mkstemp(prefix='pyqi_tmp_') @@ -357,7 +386,7 @@ def test_check_blast_db(self): self.assertEqual(obs, exp) # Check that raises an error if the base folder does not exist self.assertRaises(OptionValueError, check_blast_db, option, '-b', - '/hopefully/a/non/existing/path') + '/hopefully/a/non/existing/path') usage_lines = """usage: %prog [options] {} diff --git a/tests/test_core/test_interfaces/test_optparse/test_input_handler.py b/tests/test_core/test_interfaces/test_optparse/test_input_handler.py index 94b6199..6ddf106 100644 --- a/tests/test_core/test_interfaces/test_optparse/test_input_handler.py +++ b/tests/test_core/test_interfaces/test_optparse/test_input_handler.py @@ -15,7 +15,9 @@ from pyqi.core.interfaces.optparse.input_handler import command_handler from pyqi.commands.make_optparse import MakeOptparse + class OptparseInputHandlerTests(TestCase): + def setUp(self): pass diff --git a/tests/test_core/test_interfaces/test_optparse/test_output_handler.py b/tests/test_core/test_interfaces/test_optparse/test_output_handler.py index c4c9a82..0f539e3 100644 --- a/tests/test_core/test_interfaces/test_optparse/test_output_handler.py +++ b/tests/test_core/test_interfaces/test_optparse/test_output_handler.py @@ -18,10 +18,12 @@ from tempfile import mkdtemp from unittest import TestCase, main from pyqi.core.interfaces.optparse.output_handler import (write_string, - write_list_of_strings, print_list_of_strings) + write_list_of_strings, print_list_of_strings) from pyqi.core.exception import IncompetentDeveloperError + class OutputHandlerTests(TestCase): + def setUp(self): self.output_dir = mkdtemp() self.fp = os.path.join(self.output_dir, 'test_file.txt') @@ -32,7 +34,7 @@ def tearDown(self): def test_write_string(self): """Correctly writes a string to file.""" # can't write without a path - self.assertRaises(IncompetentDeveloperError, write_string, 'a','b') + self.assertRaises(IncompetentDeveloperError, write_string, 'a', 'b') write_string('foo', 'bar', self.fp) with open(self.fp, 'U') as obs_f: @@ -57,7 +59,7 @@ def test_print_list_of_strings(self): # Save stdout and replace it with something that will capture the print # statement. Note: this code was taken from here: # http://stackoverflow.com/questions/4219717/how-to-assert-output- - # with-nosetest-unittest-in-python/4220278#4220278 + # with-nosetest-unittest-in-python/4220278#4220278 saved_stdout = sys.stdout try: out = StringIO() diff --git a/tests/test_util.py b/tests/test_util.py index 30462b5..2e0285c 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -17,7 +17,9 @@ from pyqi.util import get_version_string from pyqi.core.exception import MissingVersionInfoError + class UtilTests(TestCase): + def test_get_version_string(self): """Test extracting a version string given a module string.""" exp = pyqi.__version__ From cd882089f0b1fb4d4658cc2fb717d83f91f3e39d Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Fri, 9 May 2014 13:39:43 -0600 Subject: [PATCH 21/22] STY: autopep8 --- setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 0667db3..b412c33 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ long_description = """pyqi (canonically pronounced pie chee) is a Python framework designed to support wrapping general commands in multiple types of interfaces, including at the command line, HTML, and API levels.""" setup(name='pyqi', - cmdclass={'build_py':build_py}, + cmdclass={'build_py': build_py}, version=__version__, license=__license__, description='pyqi: expose your interface', @@ -81,9 +81,9 @@ ], scripts=glob('scripts/pyqi*'), install_requires=[], - extras_require={'test':["nose >= 0.10.1", - "tox >= 1.6.1"], - 'doc':"Sphinx >= 0.3" - }, + extras_require={'test': ["nose >= 0.10.1", + "tox >= 1.6.1"], + 'doc': "Sphinx >= 0.3" + }, classifiers=classifiers ) From 891b9b6445c904d28533a0f50dada0b365a32786 Mon Sep 17 00:00:00 2001 From: Daniel McDonald Date: Fri, 9 May 2014 13:45:13 -0600 Subject: [PATCH 22/22] STY: pep8 --- tests/test_core/test_command.py | 3 +- .../test_optparse/test_init.py | 28 ++++++++++--------- .../test_optparse/test_output_handler.py | 4 +-- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/tests/test_core/test_command.py b/tests/test_core/test_command.py index 2148ef3..7c1b337 100644 --- a/tests/test_core/test_command.py +++ b/tests/test_core/test_command.py @@ -13,7 +13,8 @@ "Jai Ram Rideout"] from unittest import TestCase, main -from pyqi.core.command import CommandIn, CommandOut, ParameterCollection, Command +from pyqi.core.command import (CommandIn, CommandOut, ParameterCollection, + Command) from pyqi.core.exception import (IncompetentDeveloperError, UnknownParameterError, MissingParameterError) diff --git a/tests/test_core/test_interfaces/test_optparse/test_init.py b/tests/test_core/test_interfaces/test_optparse/test_init.py index 48f3c9f..bc7ea73 100644 --- a/tests/test_core/test_interfaces/test_optparse/test_init.py +++ b/tests/test_core/test_interfaces/test_optparse/test_init.py @@ -123,17 +123,18 @@ def test_output_handler(self): class GeneralTests(TestCase): def setUp(self): - self.obj = optparse_factory(ghetto, - [OptparseUsageExample('a', 'b', 'c')], - [OptparseOption(Type=str, - Parameter=ghetto.CommandIns[ - 'c'], - ShortName='n')], - [OptparseResult(Type=str, - Parameter=ghetto.CommandOuts[ - 'itsaresult'], - Handler=oh)], - '2.0-dev') + self.obj = optparse_factory( + ghetto, + [OptparseUsageExample('a', 'b', 'c')], + [OptparseOption( + Type=str, + Parameter=ghetto.CommandIns['c'], + ShortName='n')], + [OptparseResult( + Type=str, + Parameter=ghetto.CommandOuts['itsaresult'], + Handler=oh)], + '2.0-dev') def test_optparse_factory(self): # exercise it @@ -164,7 +165,8 @@ def _get_usage_examples(self): return [OptparseUsageExample('a', 'b', 'c')] def _get_outputs(self): - return [OptparseResult(Parameter=ghetto.CommandOuts['itsaresult'], Handler=oh)] + return [OptparseResult(Parameter=ghetto.CommandOuts['itsaresult'], + Handler=oh)] def _get_version(self): return '2.0-dev' @@ -395,7 +397,7 @@ def test_check_blast_db(self): -Example usage: +Example usage: Print help message and exit %prog -h diff --git a/tests/test_core/test_interfaces/test_optparse/test_output_handler.py b/tests/test_core/test_interfaces/test_optparse/test_output_handler.py index 0f539e3..89d1c66 100644 --- a/tests/test_core/test_interfaces/test_optparse/test_output_handler.py +++ b/tests/test_core/test_interfaces/test_optparse/test_output_handler.py @@ -17,8 +17,8 @@ from shutil import rmtree from tempfile import mkdtemp from unittest import TestCase, main -from pyqi.core.interfaces.optparse.output_handler import (write_string, - write_list_of_strings, print_list_of_strings) +from pyqi.core.interfaces.optparse.output_handler import ( + write_string, write_list_of_strings, print_list_of_strings) from pyqi.core.exception import IncompetentDeveloperError