burrito-0.9.1/0000755000076500000240000000000012527661551014153 5ustar caporasostaff00000000000000burrito-0.9.1/burrito/0000755000076500000240000000000012527661551015641 5ustar caporasostaff00000000000000burrito-0.9.1/burrito/__init__.py0000644000076500000240000000056112527660643017755 0ustar caporasostaff00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2014--, burrito development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- __version__ = "0.9.1" burrito-0.9.1/burrito/parameters.py0000644000076500000240000004434412527660643020370 0ustar caporasostaff00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2014--, burrito development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- from copy import deepcopy from collections import Mapping def is_not_None(x): """Returns True if x is not None""" return x is not None class ParameterError(ValueError): """Error raised when field in parameter is bad""" pass class FilePath(str): """ Hold paths for proper handling Paths in this sense are filenames, directory paths, or filepaths. Some examples include: file.txt ./path/to/file.txt ./path/to/dir/ /path/to/file.txt . / The purpose of this class is to allow all paths to be handled the same since they sometimes need to be treated differently than simple strings. For example, if a path has a space in it, and it is being passed to system, it needs to be wrapped in quotes. But, you wouldn't want it as a string wrapped in quotes b/c, e.g., isabs('"/absolute/path"') == False, b/c the first char is a ", not a /. * This would make more sense to call Path, but that conflicts with the ResultPath.Path attribute. I'm not sure what to do about this and want to see what others think. Once finalized, a global replace should take care of making the switch. """ def __new__(cls, path): try: return str.__new__(cls, path.strip('"')) except AttributeError: return str.__new__(cls, '') def __str__(self): """ wrap self in quotes, or return the empty string if self == '' """ if self == '': return '' return ''.join(['"', self, '"']) def __add__(self, other): return FilePath(''.join([self, other])) class Parameter(object): """Stores information regarding a parameter to an application. An abstract class. """ def __init__(self, Prefix, Name, Value=None, Delimiter=None, Quote=None, IsPath=None): """Initialize the Parameter object. Prefix: the character(s) preceding the name of the parameter (eg. '-' for a '-a' parameter) Name: the name of the parameter (eg. 'a' for a '-a' parameter) Value: the value of the parameter (eg. '9' in a '-t=9' parameter) The value is also used in subclasses to turn parameters on and off Delimiter: the character separating the identifier and the value, (eg. '=' for a '-t=9' command or ' ' for a '-t 9' parameter) Quote: the character to use when quoting the value (eg. "\"" for a '-l="hello" parameter). At this point asymmetrical quotes are not possible (ie. [4]) WARNING: You must escape the quote in most cases. IsPath: boolean indicating whether Value is a file path, and should therefore be cast to a FilePath object WARNING: Don't set Quote='"' and set IsPath=True. This would result in two sets of double quotes being wrapped around the path when it is printed, and the application would most likely fail. We explicitly disallow this with: if self.IsPath and self.Quote == '"': self.Quote = None Id: The combination of Prefix and Name is called the identifier (Id) of the parameter. (eg. '-a' for a '-a' parameter, or '-t' for a '-t=9' parameter) This is intended to be an abstract class and has no use otherwise. To subclass Parameter, the subclass should implement the following methods: __str__(): returns the parameter as a string when turned on, or as an empty string when turned off on(): turns the parameter on isOn(): return True if a parameter is on, otherwise False off(): turns the parameter off isOff(): return True if a parameter is off, otherwise False Whether a parameter is on or off can be specified in different ways in subclasses, your isOn() and isOff() methods should define this. Optionally you can overwrite __init__, but you should be sure to either call the superclass init or handle the setting of the self._default attribute (or things will break!) """ self.Name = Name self.Prefix = Prefix self.Delimiter = Delimiter self.Quote = Quote self.Value = Value self.IsPath = IsPath if self.IsPath and self.Quote == '"': self.Quote = None def _get_id(self): """Construct and return the identifier""" return ''.join(map(str, filter(is_not_None, [self.Prefix, self.Name]))) Id = property(_get_id) def __eq__(self, other): """Return True if two parameters are equal""" return (self.IsPath == other.IsPath) and\ (self.Name == other.Name) and\ (self.Prefix == other.Prefix) and\ (self.Delimiter == other.Delimiter) and \ (self.Quote == other.Quote) and \ (self.Value == other.Value) def __ne__(self, other): """Return True if two parameters are not equal to each other""" return not self == other class FlagParameter(Parameter): """Stores information regarding a flag parameter to an application""" def __init__(self, Prefix, Name, Value=False): """Initialize a FlagParameter object Prefix: the character(s) preceding the name of the parameter (eg. '-' for a '-a' parameter) Name: the name of the parameter (eg. 'a' for a '-a' parameter) Value: determines whether the flag is turned on or not; should be True to turn on, or False to turn off, False by default Id: The combination of Prefix and Name is called the identifier (Id) of the parameter. (eg. '-a' for a '-a' parameter, or '-t' for a '-t=9' parameter) Usage: f = FlagParameter(Prefix='-',Name='a') Parameter f is turned off by default, so it won't be used by the application until turned on. or f = FlagParameter(Prefix='+',Name='d',Value=True) Parameter f is turned on. It will be used by the application. """ super(FlagParameter, self).__init__(Name=Name, Prefix=Prefix, Value=Value, Delimiter=None, Quote=None) def __str__(self): """Return the parameter as a string. When turned on: string representation of the parameter When turned off: empty string """ if self.isOff(): return '' else: return ''.join(map(str, [self.Prefix, self.Name])) def isOn(self): """Returns True if the FlagParameter is turned on. A FlagParameter is turned on if its Value is True or evaluates to True. A FlagParameter is turned off if its Value is False or evaluates to False. """ if self.Value: return True return False def isOff(self): """Returns True if the parameter is turned off A FlagParameter is turned on if its Value is True or evaluates to True. A FlagParameter is turned off if its Value is False or evaluates to False. """ return not self.isOn() def on(self): """Turns the FlagParameter ON by setting its Value to True""" self.Value = True def off(self): """Turns the FlagParameter OFF by setting its Value to False""" self.Value = False class ValuedParameter(Parameter): """Stores information regarding a valued parameter to an application""" def __init__(self, Prefix, Name, Value=None, Delimiter=None, Quote=None, IsPath=False): """Initialize a ValuedParameter object. Prefix: the character(s) preceding the name of the parameter (eg. '-' for a '-a' parameter) Name: the name of the parameter (eg. 'a' for a '-a' parameter) Value: the value of the parameter (eg. '9' in a '-t=9' parameter) Delimiter: the character separating the identifier and the value, (eg. '=' for a '-t=9' command or ' ' for a '-t 9' parameter) Quote: the character to use when quoting the value (eg. "\"" for a '-l="hello" parameter). At this point asymmetrical quotes are not possible (ie. [4]) WARNING: You must escape the quote in most cases. IsPath: boolean indicating whether Value is a file path, and should therefore be cast to a FilePath object Id: The combination of Prefix and Name is called the identifier (Id) of the parameter. (eg. '-a' for a '-a' parameter, or '-t' for a '-t=9' parameter) Default: the default value of the parameter; this is defined as what is passed into init for Value and can not be changed after object initialization Usage: v = ValuedParameter(Prefix='-',Name='a',Delimiter=' ',Value=3) the parameter is turned on by default (value=3) and will be used by the application as '-a 3'. or v = ValuedParameter(Prefix='-',Name='d',Delimiter='=') the parameter is turned off by default and won't be used by the application unless turned on with some value. """ if IsPath and Value: Value = FilePath(Value) super(ValuedParameter, self).__init__(Name=Name, Prefix=Prefix, Value=Value, Delimiter=Delimiter, Quote=Quote, IsPath=IsPath) self._default = Value def __str__(self): """Return the parameter as a string When turned on: string representation of the parameter When turned off: empty string """ if self.isOff(): return '' else: parts = [self.Prefix, self.Name, self.Delimiter, self.Quote, self.Value, self.Quote] return ''.join(map(str, filter(is_not_None, parts))) def __eq__(self, other): """Return True if two parameters are equal""" return (self.Name == other.Name) and\ (self.Prefix == other.Prefix) and\ (self.Delimiter == other.Delimiter) and \ (self.Quote == other.Quote) and \ (self.Value == other.Value) and\ (self._default == other._default) def _get_default(self): """Get the default value of the ValuedParameter Accessed as a property to avoid the user changing this after initialization. """ return self._default Default = property(_get_default) def reset(self): """Reset Value of the ValuedParameter to the default""" self.Value = self._default def isOn(self): """Returns True if the ValuedParameter is turned on A ValuedParameter is turned on if its Value is not None. A ValuedParameter is turned off if its Value is None. """ if self.Value is not None: return True return False def isOff(self): """Returns True if the ValuedParameter is turned off A ValuedParameter is turned on if its Value is not None. A ValuedParameter is turned off if its Value is None. """ return not self.isOn() def on(self, val): """Turns the ValuedParameter ON by setting its Value to val An attempt to turn the parameter on with value 'None' will result in an error, since this is the same as turning the parameter off. """ if val is None: raise ParameterError("Turning the ValuedParameter on with value " "None is the same as turning it off. " "Use another value.") elif self.IsPath: self.Value = FilePath(val) else: self.Value = val def off(self): """Turns the ValuedParameter OFF by setting its Value to None""" self.Value = None class MixedParameter(ValuedParameter): """Stores information regarding a mixed parameter to an application A mixed parameter is a mix between a FlagParameter and a ValuedParameter. When its Value is False, the parameter will be turned off. When its Value is set to None, the parameter will behave like a flag. When its Value is set to anything but None or False, it will behave like a ValuedParameter. Example: RNAfold [-d[0|1]] You can give either '-d' or '-d0' or '-d1' as input. """ def __init__(self, Prefix, Name, Value=False, Delimiter=None, Quote=None, IsPath=False): """Initialize a MixedParameter object Prefix: the character(s) preceding the name of the parameter (eg. '-' for a '-a' parameter) Name: the name of the parameter (eg. 'a' for a '-a' parameter) Value: the value of the parameter (eg. '9' in a '-t=9' parameter) Delimiter: the character separating the identifier and the value, (eg. '=' for a '-t=9' command or ' ' for a '-t 9' parameter) Quote: the character to use when quoting the value (eg. "\"" for a '-l="hello" parameter). At this point asymmetrical quotes are not possible (ie. [4]) WARNING: You must escape the quote in most cases. IsPath: boolean indicating whether Value is a file path, and should therefore be cast to a FilePath object Id: The combination of Prefix and Name is called the identifier (Id) of the parameter. (eg. '-a' for a '-a' parameter, or '-t' for a '-t=9' parameter) Default: the default value of the parameter; this is defined as what is passed into init for Value and can not be changed after object initialization Usage: m = MixedParameter(Prefix='-',Name='a',Delimiter=' ',Value=3) the parameter is turned on by default (value=3) and will be used by the application as '-a 3'. or m = MixedParameter(Prefix='-',Name='d',Delimiter='=',Value=None) the parameter is turned on by default as a flag parameter and will be used by the application as '-d'. or m = MixedParameter(Prefix='-',Name='d',Delimiter='=') the parameter is turned off by default (Value=False) and won't be used by the application unless turned on with some value. """ if IsPath and Value: Value = FilePath(Value) super(MixedParameter, self).__init__(Name=Name, Prefix=Prefix, Value=Value, Delimiter=Delimiter, Quote=Quote, IsPath=IsPath) def __str__(self): """Return the parameter as a string When turned on: string representation of the parameter When turned off: empty string """ if self.isOff(): return '' elif self.Value is None: return ''.join(map(str, [self.Prefix, self.Name])) else: parts = [self.Prefix, self.Name, self.Delimiter, self.Quote, self.Value, self.Quote] return ''.join(map(str, filter(is_not_None, parts))) def isOn(self): """Returns True if the MixedParameter is turned on A MixedParameter is turned on if its Value is not False. A MixedParameter is turned off if its Value is False. A MixedParameter is used as flag when its Value is None. A MixedParameter is used as ValuedParameter when its Value is anything but None or False. """ if self.Value is not False: return True return False def isOff(self): """Returns True if the MixedParameter is turned off A MixedParameter is turned on if its Value is not False. A MixedParameter is turned off if its Value is False. """ return not self.isOn() def on(self, val=None): """Turns the MixedParameter ON by setting its Value to val An attempt to turn the parameter on with value 'False' will result in an error, since this is the same as turning the parameter off. Turning the MixedParameter ON without a value or with value 'None' will let the parameter behave as a flag. """ if val is False: raise ParameterError("Turning the ValuedParameter on with value " "False is the same as turning it off. Use " "another value.") elif self.IsPath: self.Value = FilePath(val) else: self.Value = val def off(self): """Turns the MixedParameter OFF by setting its Value to False""" self.Value = False class Parameters(Mapping): """Parameters is a dictionary of Parameter objects. Parameters provides a mask that lets the user lookup and access parameters by its synonyms. """ def __init__(self, parameters={}, synonyms={}): """Initialize the Parameters object. parameters: a dictionary of Parameter objects keyed by their identifier synonyms: a dictionary of synonyms. Keys are synonyms, values are parameter identifiers. """ self._parameters = deepcopy(parameters) self._synonyms = deepcopy(synonyms) def __len__(self): return len(self._parameters) def __iter__(self): return iter(self._parameters) def __getitem__(self, key): try: key = self._synonyms[key] except KeyError: # the key is not a synonym pass return self._parameters[key] def all_off(self): """Turns all parameters in the dictionary off""" for v in self._parameters.values(): v.off() burrito-0.9.1/burrito/tests/0000755000076500000240000000000012527661551017003 5ustar caporasostaff00000000000000burrito-0.9.1/burrito/tests/__init__.py0000644000076500000240000000053212527660643021115 0ustar caporasostaff00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2014--, burrito development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- burrito-0.9.1/burrito/tests/test_parameters.py0000644000076500000240000006360312527660643022570 0ustar caporasostaff00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2014--, burrito development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- from unittest import TestCase, main from burrito.parameters import (FlagParameter, ValuedParameter, MixedParameter, Parameters, ParameterError, FilePath) class FlagParameterTests(TestCase): """ Tests of the FlagParameter class """ def setUp(self): """Setup some variables for the tests to use """ self.p_modify_prefix = [FlagParameter(Name='d', Prefix='-'), FlagParameter(Name='d', Prefix='--'), FlagParameter(Name='d', Prefix='')] self.p_modify_name = [FlagParameter(Name='d', Prefix='-'), FlagParameter(Name='D', Prefix='-'), FlagParameter(Name=4, Prefix='-'), FlagParameter(Name='abcdef', Prefix='-')] self.p_On = [FlagParameter(Name='d', Prefix='-', Value=True), FlagParameter(Name='d', Prefix='-', Value=5), FlagParameter(Name='d', Prefix='-', Value=[1]), FlagParameter(Name='d', Prefix='-', Value='F')] self.p_Off = [FlagParameter(Name='d', Prefix='-', Value=False), FlagParameter(Name='d', Prefix='-', Value=None), FlagParameter(Name='d', Prefix='-', Value=[]), FlagParameter(Name='d', Prefix='-', Value=0), FlagParameter(Name='d', Prefix='-', Value='')] self.ID_tests = [FlagParameter(Name='d', Prefix='-'), FlagParameter(Name='d', Prefix=''), FlagParameter(Name='', Prefix='-'), FlagParameter(Name=4, Prefix='-'), FlagParameter(Name=None, Prefix='-'), FlagParameter(Name=4, Prefix=None), FlagParameter(Name='abcdef', Prefix='-')] def test_init(self): """FlagParameter: init functions as expected """ param = FlagParameter(Name='a', Prefix='-', Value=42) self.assertEqual(param.Name, 'a') self.assertEqual(param.Prefix, '-') self.assertEqual(param.Value, 42) self.assertEqual(param.Delimiter, None) self.assertEqual(param.Quote, None) self.assertEqual(param.Id, '-a') def test_init_defaults(self): """FlagParameter: init functions as expected with default values""" p = FlagParameter(Name='a', Prefix='-') self.assertEqual(p.Name, 'a') self.assertEqual(p.Prefix, '-') self.assertEqual(p.Value, False) self.assertEqual(p.Delimiter, None) self.assertEqual(p.Quote, None) self.assertEqual(p.Id, '-a') def test_get_id(self): """FlagParameter: _get_id functions as expected """ expected_results = ['-d', 'd', '-', '-4', '-', '4', '-abcdef'] for param, exp in zip(self.ID_tests, expected_results): self.assertEqual(param._get_id(), exp) def test_eq(self): """FlagParameter: eq functions as expected """ p1 = FlagParameter(Name='a', Prefix='-', Value=True) p2 = FlagParameter(Name='a', Prefix='-', Value=True) p3 = FlagParameter(Name='a', Prefix='-') p4 = FlagParameter(Name='i', Prefix='-', Value=True) p5 = FlagParameter(Name='a', Prefix='--', Value=True) assert p1 == p2 assert not p1 == p3 assert not p1 == p4 assert not p1 == p5 assert not p3 == p4 assert not p3 == p5 assert not p4 == p5 def test_ne(self): """FlagParameter: ne functions as expected """ p1 = FlagParameter(Name='a', Prefix='-', Value=True) p2 = FlagParameter(Name='a', Prefix='-', Value=True) p3 = FlagParameter(Name='a', Prefix='-') p4 = FlagParameter(Name='i', Prefix='-', Value=True) p5 = FlagParameter(Name='a', Prefix='--', Value=True) assert not p1 != p2 assert p1 != p3 assert p1 != p4 assert p1 != p5 assert p3 != p4 assert p3 != p5 assert p4 != p5 def test_isOn_True(self): """FlagParameter: isOn functions as expected with True Values """ for param in self.p_On: assert param.isOn() def test_isOn_False(self): """FlagParameter: isOn functions as expected with False Values """ for param in self.p_Off: assert not param.isOn() def test_isOff_True(self): """FlagParameter: isOff functions as expected with True values """ for param in self.p_Off: assert param.isOff() def test_isOff_False(self): """FlagParameter: isOff functions as expected with False values """ for param in self.p_On: assert not param.isOff() def test_on(self): """FlagParameter: on functions as expected """ for param in self.p_On + self.p_Off: param.on() assert param.isOn() def test_off(self): """FlagParameter: off functions as expected """ for param in self.p_On + self.p_Off: param.off() assert param.isOff() def test_str_modify_prefix(self): """FlagParameter: str functions as expected with different prefixes """ expected_results = ['-d', '--d', 'd'] for param, exp in zip(self.p_modify_prefix, expected_results): param.on() self.assertEqual(str(param), exp) def test_str_modify_name(self): """FlagParameter: str functions as expected with different names """ expected_results = ['-d', '-D', '-4', '-abcdef'] for param, exp in zip(self.p_modify_name, expected_results): param.on() self.assertEqual(str(param), exp) class ValuedParameterTests(TestCase): """ Tests of the ValuedParameter class """ constructor = ValuedParameter s = 'Valued' def setUp(self): """Setup some variables for the tests to use """ self.p_modify_prefix = [self.constructor(Name='d', Prefix='-'), self.constructor(Name='d', Prefix='--'), self.constructor(Name='d', Prefix='')] self.p_modify_name = [self.constructor(Name='d', Prefix='-'), self.constructor(Name='D', Prefix='-'), self.constructor(Name=4, Prefix='-'), self.constructor(Name='abcdef', Prefix='-')] self.p_On = [self.constructor(Name='d', Prefix='-', Value=True), self.constructor(Name='d', Prefix='-', Value=5), self.constructor(Name='d', Prefix='-', Value=[1]), self.constructor(Name='d', Prefix='-', Value=False), self.constructor(Name='d', Prefix='-', Value='F')] self.p_Off = [self.constructor(Name='d', Prefix='-', Value=None)] self.p_full = [self.constructor(Name='a', Prefix='-', Value=42, Delimiter=' ', Quote='\'')] self.p_default = [self.constructor(Name='a', Prefix='-')] self.p_modified_prefix = [self.constructor(Name='d', Prefix='-'), self.constructor(Name='d', Prefix='--'), self.constructor(Name='d', Prefix='')] self.p_modified_name = [self.constructor(Name='d', Prefix='-'), self.constructor(Name='D', Prefix='-'), self.constructor(Name=4, Prefix='-'), self.constructor(Name='abcdef', Prefix='-')] self.p_modified_delimiter =\ [self.constructor(Name='d', Prefix='-', Value=42), self.constructor(Name='d', Prefix='-', Value=42, Delimiter=''), self.constructor(Name='d', Prefix='-', Value=42, Delimiter=' '), self.constructor(Name='d', Prefix='-', Value=42, Delimiter=9), self.constructor(Name='d', Prefix='-', Value=42, Delimiter='=')] self.p_modified_value =\ [self.constructor(Name='d', Prefix='-', Value=42, Delimiter=' '), self.constructor( Name='d', Prefix='-', Value='pbl', Delimiter=' '), self.constructor( Name='d', Prefix='-', Value='2-2', Delimiter=' '), self.constructor(Name='d', Prefix='-', Value='evo/t.txt', Delimiter=' '), self.constructor(Name='d', Prefix='-', Value='\'', Delimiter=' ')] self.p_modified_quote =\ [self.constructor(Name='d', Prefix='-', Value=42, Quote=''), self.constructor(Name='d', Prefix='-', Value=42), self.constructor(Name='d', Prefix='-', Value=42, Quote=' '), self.constructor(Name='d', Prefix='-', Value=42, Quote='\''), self.constructor(Name='d', Prefix='-', Value=42, Quote='\"'), self.constructor(Name='d', Prefix='-', Value=42, Quote='x')] self.ID_tests = [self.constructor(Name='d', Prefix='-'), self.constructor(Name='d', Prefix=''), self.constructor(Name='', Prefix='-'), self.constructor(Name=4, Prefix='-'), self.constructor(Name=None, Prefix='-'), self.constructor(Name=4, Prefix=None), self.constructor(Name='abcdef', Prefix='-')] self.p_modified_is_path =\ [self.constructor(Name='d', Prefix='-', Delimiter=' ', Value='test.txt', IsPath=True), self.constructor(Name='d', Prefix='-', Delimiter=' ', Value='test.txt', IsPath=False), self.constructor(Name='d', Prefix='-', Delimiter=' ', Value='test.txt', Quote='"', IsPath=True)] def test_init(self): """Parameter: init functions as expected """ for param in self.p_full: self.assertEqual(param.Name, 'a') self.assertEqual(param.Prefix, '-') self.assertEqual(param.Value, 42) self.assertEqual(param.Delimiter, ' ') self.assertEqual(param.Quote, '\'') self.assertEqual(param.Id, '-a') def test_init_defaults(self): """Parameter: init functions as expected with default values""" for p in self.p_default: self.assertEqual(p.Name, 'a') self.assertEqual(p.Prefix, '-') self.assertEqual(p.Value, None) self.assertEqual(p.Delimiter, None) self.assertEqual(p.Quote, None) self.assertEqual(p.Id, '-a') def test_get_id(self): """Parameter: _get_id functions as expected """ expected_results = ['-d', 'd', '-', '-4', '-', '4', '-abcdef'] for param, exp in zip(self.ID_tests, expected_results): self.assertEqual(param._get_id(), exp) def test_eq(self): """Parameter: eq functions as expected """ p1 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'', Delimiter='=') p2 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'', Delimiter='=') p3 = self.constructor(Name='dsf', Prefix='-', Value=42, Quote='\'', Delimiter='=') p4 = self.constructor(Name='a', Prefix='--', Value=42, Quote='\'', Delimiter='=') p5 = self.constructor(Name='a', Prefix='-', Value=942, Quote='\'', Delimiter='=') p6 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\"', Delimiter='=') p7 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'', Delimiter='!!!') p8 = self.constructor(Name='wwwww', Prefix='-------') p9 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'', Delimiter='=', IsPath=True) assert p1 == p2 assert not p1 == p3 assert not p1 == p4 assert not p1 == p5 assert not p1 == p6 assert not p1 == p7 assert not p1 == p8 assert not p1 == p9 # test default setting p5.Value = 42 assert not p1 == p5 def test_ne(self): """Parameter: ne functions as expected """ p1 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'', Delimiter='=') p2 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'', Delimiter='=') p3 = self.constructor(Name='dsf', Prefix='-', Value=42, Quote='\'', Delimiter='=') p4 = self.constructor(Name='a', Prefix='--', Value=42, Quote='\'', Delimiter='=') p5 = self.constructor(Name='a', Prefix='-', Value=942, Quote='\'', Delimiter='=') p6 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\"', Delimiter='=') p7 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'', Delimiter='!!!') p8 = self.constructor(Name='wwwww', Prefix='-------') p9 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'', Delimiter='=', IsPath=True) assert not p1 != p2 assert p1 != p3 assert p1 != p4 assert p1 != p5 assert p1 != p6 assert p1 != p7 assert p1 != p8 assert p1 != p9 # test default setting p5.Value = 42 assert p1 != p5 def test_get_default(self): """Parameter: default behaves as expected """ p1 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'', Delimiter='=') self.assertEqual(p1._get_default(), 42) p1.Value = 43 self.assertEqual(p1._get_default(), 42) def test_get_default_w_IsPath(self): """Parameter: default is a FilePath object when IsPath is set """ p = self.constructor( Name='a', Prefix='-', Value='test.txt', Quote='\'', Delimiter='=', IsPath=True) self.assertEqual(p._get_default(), 'test.txt') self.assertEqual(p.Default, 'test.txt') p.Value = 'test2.txt' self.assertEqual(p._get_default(), 'test.txt') self.assertEqual(p.Default, 'test.txt') assert isinstance(p._get_default(), FilePath) assert isinstance(p.Default, FilePath) def test_reset(self): """Parameter: reset correctly set Value to _default """ p1 = self.constructor(Name='a', Prefix='-', Value=42, Quote='\'', Delimiter='=') p1.Value = 43 self.assertNotEqual(p1.Default, p1.Value) p1.reset() self.assertEqual(p1.Default, p1.Value) def test_isOn_True(self): """Parameter: isOn functions as expected with True Values """ for param in self.p_On: assert param.isOn() def test_isOn_False(self): """Parameter: isOn functions as expected with False Values """ for param in self.p_Off: assert not param.isOn() def test_isOff_True(self): """Parameter: isOff functions as expected with True values """ for param in self.p_Off: assert param.isOff() def test_isOff_False(self): """Parameter: isOff functions as expected with False values """ for param in self.p_On: assert not param.isOff() def test_on(self): """Parameter: on functions as expected """ for param in self.p_On + self.p_Off: param.on('a') assert param.isOn() p = self.p_On[0] self.assertRaises(ParameterError, p.on, None) def test_off(self): """Parameter: off functions as expected """ for param in self.p_On + self.p_Off: param.off() assert param.isOff() def test_str_off(self): """Parameter: str() prints empty string when off """ for p in self.p_Off: self.assertEqual(str(p), '') def test_str_modify_prefix(self): """Parameter: str functions as expected with different prefixes """ expected_results = ['-d', '--d', 'd'] for param, exp in zip(self.p_modified_prefix, expected_results): param.on('') self.assertEqual(str(param), exp) def test_str_modify_name(self): """Parameter: str functions as expected with different names """ expected_results = ['-d', '-D', '-4', '-abcdef'] for param, exp in zip(self.p_modified_name, expected_results): param.on('') self.assertEqual(str(param), exp) def test_str_modify_delimiter(self): """Parameter: str functions as expected with different delimiter """ expected_results = ['-d42', '-d42', '-d 42', '-d942', '-d=42'] for param, exp in zip(self.p_modified_delimiter, expected_results): self.assertEqual(str(param), exp) def test_str_modify_values(self): """Parameter: str functions as expected with different values """ expected_results = ['-d 42', '-d pbl', '-d 2-2', '-d evo/t.txt', '-d \''] for param, exp in zip(self.p_modified_value, expected_results): self.assertEqual(str(param), exp) def test_str_modify_quotes(self): """Parameter: str functions as expected with different quotes """ expected_results = ['-d42', '-d42', '-d 42 ', '-d\'42\'', '-d\"42\"', '-dx42x'] for param, exp in zip(self.p_modified_quote, expected_results): self.assertEqual(str(param), exp) def test_str_modify_is_path(self): """Parameter: str functions as expected with different IsPath """ expected_results = ['-d "test.txt"', '-d test.txt', '-d "test.txt"'] for param, exp in zip(self.p_modified_is_path, expected_results): self.assertEqual(str(param), exp) def test_str_full(self): """Parameter: str functions as expected with all values non-default """ for p in self.p_full: self.assertEqual(str(p), '-a \'42\'') class MixedParameterTests(ValuedParameterTests): """ Tests of the MixedParameter class """ constructor = MixedParameter def setUp(self): """Setup some variables for the tests to use """ super(MixedParameterTests, self).setUp() self.p_On = [self.constructor(Name='d', Prefix='-', Value=True), self.constructor(Name='d', Prefix='-', Value=5), self.constructor(Name='d', Prefix='-', Value=[1]), self.constructor(Name='d', Prefix='-', Value=None), self.constructor(Name='d', Prefix='-', Value='F')] self.p_Off = [self.constructor(Name='d', Prefix='-', Value=False)] # This is different from the superclass variable b/c we need to make # sure that specifying IsPath with Value=None functions as expected self.p_modified_is_path =\ [self.constructor(Name='d', Prefix='-', Delimiter=' ', Value='test.txt', IsPath=True), self.constructor(Name='d', Prefix='-', Delimiter=' ', Value='test.txt', Quote='"', IsPath=True), self.constructor(Name='d', Prefix='-', Delimiter=' ', Value='test.txt', IsPath=False), self.constructor(Name='d', Prefix='-', Delimiter=' ', Value=None, IsPath=True), self.constructor(Name='d', Prefix='-', Delimiter=' ', Value=None, IsPath=False)] def test_on(self): """Parameter: on functions as expected """ for param in self.p_On + self.p_Off: param.on('a') assert param.isOn() p = self.p_On[0] self.assertRaises(ParameterError, p.on, False) def test_init_defaults(self): """MixedParameter: init functions as expected with default values""" for p in self.p_default: self.assertEqual(p.Name, 'a') self.assertEqual(p.Prefix, '-') self.assertEqual(p.Value, False) self.assertEqual(p.Delimiter, None) self.assertEqual(p.Quote, None) self.assertEqual(p.Id, '-a') self.assertEqual(p.IsPath, False) def test_str_all_modes(self): """MixedParameter: str() functions in various modes """ p = MixedParameter(Prefix='-', Name='d', Delimiter='=', Quote=']') self.assertEqual(str(p), '') p.on() self.assertEqual(str(p), '-d') p.on('a') self.assertEqual(str(p), '-d=]a]') def test_str_modify_is_path(self): """MixedParameter: str functions as expected with different IsPath """ # This is different from the superclass test b/c we need to make # sure that specifying IsPath with Value=None functions as expected expected_results = ['-d "test.txt"', '-d "test.txt"', '-d test.txt', '-d', '-d'] for param, exp in zip(self.p_modified_is_path, expected_results): self.assertEqual(str(param), exp) class ParametersTests(TestCase): """Tests of the Parameters class""" def setUp(self): self.fp = FlagParameter(Prefix='-', Name='d') self.vp = ValuedParameter(Name='p', Prefix='-', Value=[1]) self.mp = MixedParameter(Prefix='--', Name='k', Delimiter=' ') self.all_params = {self.fp.Id: self.fp, self.vp.Id: self.vp, self.mp.Id: self.mp} self.p1 = Parameters() self.p2 = Parameters(self.all_params) self._synonyms = {'Pino': '-p', 'K': 'k'} self.p3 = Parameters(self.all_params, self._synonyms) def test_init(self): """Parameters: init functions as expected""" self.assertEqual(self.p1, {}) self.assertEqual(self.p2, self.all_params) self.assertEqual(self.p3, self.all_params) def test_lookup(self): """Parameters: test ability to lookup """ self.assertEqual(self.p2['-p'], self.vp) self.assertEqual(self.p3['Pino'], self.vp) def test_immutability(self): """Parameters: attempt to modify object raises error """ try: self.p2['-p'] = 42 except TypeError: pass else: raise AttributeError("Parameters shouldn't support assignment.") try: del self.p2['-p'] except TypeError: pass else: raise AttributeError("Parameters shouldn't support deletion.") def test_all_off(self): """Parameters: all_off() should turn all parameters off""" p = self.p2 # turn everything on for v in p.values(): try: v.on(3) except TypeError: v.on() self.assertTrue(v.isOn()) # turn everything off p.all_off() for v in p.values(): self.assertTrue(v.isOff()) class FilePathTests(TestCase): """ Tests of the FilePath class """ def setUp(self): """ Initialize variables to be used by tests """ self.filename = 'filename.txt' self.relative_dir_path = 'a/relative/path/' self.relative_dir_path_no_trailing_slash = 'a/relative/path' self.relative_file_path = 'a/relative/filepath.txt' self.absolute_dir_path = '/absolute/path/' self.absolute_file_path = '/absolute/filepath.txt' self.all_paths = [self.filename, self.relative_dir_path, self.relative_file_path, self.absolute_dir_path, self.absolute_file_path] def test_init(self): """FilePath: initialization returns w/o error """ for p in self.all_paths: self.assertEqual(FilePath(p), p) self.assertEqual(FilePath(''), '') def test_str(self): """FilePath: str wraps path in quotes """ # Do one explicit test (for sanity), then automatically run # through the examples self.assertEqual(str(FilePath(self.filename)), '"filename.txt"') for p in self.all_paths: self.assertEqual(str(FilePath(p)), '"' + p + '"') def test_str_path_is_None(self): """FilePath: str return empty string when path is None """ self.assertEqual(str(FilePath(None)), '') def test_add(self): """FilePath: add (or joining of paths) functions as expected """ actual = FilePath(self.relative_dir_path) + FilePath(self.filename) expected = FilePath('a/relative/path/filename.txt') self.assertEqual(actual, expected) # result is a FilePath assert isinstance(actual, FilePath) # appending a string to a FilePath results in a FilePath actual = FilePath(self.relative_dir_path) + 'filename.txt' expected = FilePath('a/relative/path/filename.txt') self.assertEqual(actual, expected) # result is a FilePath assert isinstance(actual, FilePath) def test_FilePath_identity_preserved(self): """FilePath: trivial actions on FilePaths yeild original FilePath """ p = FilePath(self.filename) # Creating FilePath from FilePath results in FilePath # equal to original self.assertEqual(FilePath(p), p) for p in self.all_paths: self.assertEqual(FilePath(p), p) # Appending an empty FilePath to a FilePath results in FilePath # equal to original self.assertEqual(p + FilePath(''), p) if __name__ == '__main__': main() burrito-0.9.1/burrito/tests/test_util.py0000644000076500000240000015512512527660643021403 0ustar caporasostaff00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2014--, burrito development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- from tempfile import gettempdir from os import remove, system, rmdir, getcwd, walk from os.path import exists from copy import deepcopy from burrito.parameters import (FilePath, ValuedParameter, FlagParameter, MixedParameter) from unittest import TestCase from burrito.util import (CommandLineApplication, ResultPath, ApplicationError, ParameterIterBase, ParameterCombinations, cmdline_generator, ApplicationNotFoundError, get_tmp_filename, guess_input_handler, which) class ParameterCombinationsTests(TestCase): def setUp(self): """Setup for ParameterCombinations tests""" self.mock_app = ParameterCombinationsApp self.params = {'-flag1': True, '--value1': list(range(0, 5)), '-delim': list(range(0, 2)), '-mix1': [None] + list(range(0, 3))} self.always_on = ['--value1'] self.param_iter = ParameterCombinations(self.mock_app, self.params, self.always_on) def test_init_generator(self): """Tests generator capabilities""" all_params = list(self.param_iter) self.assertEqual(len(all_params), 150) params = {'-flag1': True, '--value1': 1, '-delim': ['choice1', 'choice2']} always_on = ['-flag1', '-delim'] param_iter = ParameterCombinations(self.mock_app, params, always_on) exp = [deepcopy(self.mock_app._parameters), deepcopy(self.mock_app._parameters), deepcopy(self.mock_app._parameters), deepcopy(self.mock_app._parameters)] # default is on in all these cases exp[0]['-flag1'].on() exp[0]['--value1'].on(1) exp[0]['-delim'].on('choice1') exp[1]['-flag1'].on() exp[1]['--value1'].on(1) exp[1]['-delim'].on('choice2') exp[2]['-flag1'].on() exp[2]['--value1'].off() exp[2]['-delim'].on('choice1') exp[3]['-flag1'].on() exp[3]['--value1'].off() exp[3]['-delim'].on('choice2') obs = list(param_iter) self.assertEqual(obs, exp) def test_reset(self): """Resets the iterator""" first = list(self.param_iter) self.assertRaises(StopIteration, lambda: next(self.param_iter)) self.param_iter.reset() second = list(self.param_iter) self.assertEqual(first, second) class ParameterIterBaseTests(TestCase): def setUp(self): """Setup for ParameterIterBase tests""" self.mock_app = ParameterCombinationsApp self.params = {'-flag1': True, '--value1': list(range(0, 5)), '-delim': list(range(0, 2)), '-mix1': [None] + list(range(0, 3))} self.always_on = ['--value1'] self.param_base = ParameterIterBase(self.mock_app, self.params, self.always_on) def test_init(self): """Test constructor""" exp_params = {'-flag1': [True, False], '--value1': list(range(0, 5)), '-delim': list(range(0, 2)) + [False], '-mix1': [None, 0, 1, 2] + [False]} self.assertEqual(exp_params, dict(zip(self.param_base._keys, self.param_base._values))) self.params['asdasda'] = 5 self.assertRaises(ValueError, ParameterIterBase, self.mock_app, self.params, self.always_on) self.params.pop('asdasda') self.always_on.append('asdasd') self.assertRaises(ValueError, ParameterIterBase, self.mock_app, self.params, self.always_on) def test_make_app_params(self): """Returns app parameters with expected values set""" values = [0, 0, True, None] exp = deepcopy(self.mock_app._parameters) exp['-flag1'].on() exp['--value1'].on(0) exp['-delim'].on(0) exp['-mix1'].on(None) obs = self.param_base._make_app_params(values) self.assertEqual(obs, exp) state = [4, False, False, False] exp = deepcopy(self.mock_app._parameters) exp['-flag1'].off() exp['--value1'].on(4) exp['-delim'].off() exp['-mix1'].off() obs = self.param_base._make_app_params(state) self.assertEqual(obs, exp) class CommandLineGeneratorTests(TestCase): def setUp(self): self.abs_path_to_bin = '/bin/path' self.abs_path_to_cmd = '/cmd/path' self.abs_path_to_input = '/input/path' self.abs_path_to_output = '/output/path' self.abs_path_to_stdout = '/stdout/path' self.abs_path_to_stderr = '/stderr/path' self.app = ParameterCombinationsApp params = {'-flag1': True, '-delim': ['choice1', 'choice2']} always_on = ['-delim'] self.mock_app = ParameterCombinationsApp self.param_iter = ParameterCombinations( self.mock_app, params, always_on) def test_cmdline_generator_easy(self): """Returns parameter combinations commandlines""" cmdgen = cmdline_generator(self.param_iter, PathToBin=self.abs_path_to_bin, PathToCmd=self.abs_path_to_cmd, PathsToInputs=self.abs_path_to_input, PathToOutput=self.abs_path_to_output, PathToStdout=self.abs_path_to_stdout, PathToStderr=self.abs_path_to_stderr, UniqueOutputs=False, InputParam='-input', OutputParam='-output') bin = self.abs_path_to_bin cmd = self.abs_path_to_cmd inputfile = self.abs_path_to_input outputfile = self.abs_path_to_output stdout = self.abs_path_to_stdout stderr = self.abs_path_to_stderr exp = [' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '-flag1', '-input="%s"' % inputfile, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])] exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '-input="%s"' % inputfile, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2', '-flag1', '-input="%s"' % inputfile, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2', '-input="%s"' % inputfile, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) cmdlines = list(cmdgen) self.assertEqual(cmdlines, exp) def test_cmdline_generator_hard(self): """Returns parameter combinations commandlines. Test stdin/stdout""" cmdgen = cmdline_generator(self.param_iter, PathToBin=self.abs_path_to_bin, PathToCmd=self.abs_path_to_cmd, PathsToInputs=self.abs_path_to_input, PathToOutput=self.abs_path_to_output, PathToStdout=self.abs_path_to_stdout, PathToStderr=self.abs_path_to_stderr, UniqueOutputs=True, InputParam=None, OutputParam=None) bin = self.abs_path_to_bin cmd = self.abs_path_to_cmd inputfile = self.abs_path_to_input outputfile = self.abs_path_to_output stderr = self.abs_path_to_stderr # the extra '' is intentionally added. When stdout is used for actual # output, the stdout_ param gets set to '' which results in an extra # space being generated on the cmdline. this should be benign # across operating systems exp = [' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '-flag1', '< "%s"' % inputfile, '> "%s"0' % outputfile, '', '2> "%s"' % stderr])] exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '< "%s"' % inputfile, '> "%s"1' % outputfile, '', '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2', '-flag1', '< "%s"' % inputfile, '> "%s"2' % outputfile, '', '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2', '< "%s"' % inputfile, '> "%s"3' % outputfile, '', '2> "%s"' % stderr])) cmdlines = list(cmdgen) self.assertEqual(cmdlines, exp) def test_cmdline_generator_stdout_stderr_off(self): """Returns cmdlines with stdout and stderr disabled""" cmdgen = cmdline_generator(self.param_iter, PathToBin=self.abs_path_to_bin, PathToCmd=self.abs_path_to_cmd, PathsToInputs=self.abs_path_to_input, PathToOutput=self.abs_path_to_output, PathToStdout=None, PathToStderr=None, UniqueOutputs=False, InputParam='-input', OutputParam='-output') bin = self.abs_path_to_bin cmd = self.abs_path_to_cmd inputfile = self.abs_path_to_input outputfile = self.abs_path_to_output exp = [' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '-flag1', '-input="%s"' % inputfile, '-output="%s"' % outputfile, '', ''])] exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '-input="%s"' % inputfile, '-output="%s"' % outputfile, '', ''])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2', '-flag1', '-input="%s"' % inputfile, '-output="%s"' % outputfile, '', ''])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2', '-input="%s"' % inputfile, '-output="%s"' % outputfile, '', ''])) cmdlines = list(cmdgen) self.assertEqual(cmdlines, exp) def test_cmdline_generator_multiple_inputs(self): """Tests the cmdline_generator for multiple input support""" paths_to_inputs = ['/some/dir/a', '/some/dir/b'] cmdgen = cmdline_generator(self.param_iter, PathToBin=self.abs_path_to_bin, PathToCmd=self.abs_path_to_cmd, PathsToInputs=paths_to_inputs, PathToOutput=self.abs_path_to_output, PathToStdout=self.abs_path_to_stdout, PathToStderr=self.abs_path_to_stderr, UniqueOutputs=False, InputParam='-input', OutputParam='-output') bin = self.abs_path_to_bin cmd = self.abs_path_to_cmd inputfile1 = paths_to_inputs[0] inputfile2 = paths_to_inputs[1] outputfile = self.abs_path_to_output stdout = self.abs_path_to_stdout stderr = self.abs_path_to_stderr exp = [' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '-flag1', '-input="%s"' % inputfile1, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])] exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '-flag1', '-input="%s"' % inputfile2, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '-input="%s"' % inputfile1, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '-input="%s"' % inputfile2, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2', '-flag1', '-input="%s"' % inputfile1, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2', '-flag1', '-input="%s"' % inputfile2, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2', '-input="%s"' % inputfile1, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2', '-input="%s"' % inputfile2, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) cmdlines = list(cmdgen) self.assertEqual(cmdlines, exp) def test_cmdline_generator_multiple_input_stdin(self): """Tests cmdline_generator for multiple inputs over stdin""" paths_to_inputs = ['/some/dir/a', '/some/dir/b'] cmdgen = cmdline_generator(self.param_iter, PathToBin=self.abs_path_to_bin, PathToCmd=self.abs_path_to_cmd, PathsToInputs=paths_to_inputs, PathToOutput=self.abs_path_to_output, PathToStdout=self.abs_path_to_stdout, PathToStderr=self.abs_path_to_stderr, UniqueOutputs=False, InputParam=None, OutputParam='-output') bin = self.abs_path_to_bin cmd = self.abs_path_to_cmd inputfile1 = paths_to_inputs[0] inputfile2 = paths_to_inputs[1] outputfile = self.abs_path_to_output stdout = self.abs_path_to_stdout stderr = self.abs_path_to_stderr exp = [' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '-flag1', '< "%s"' % inputfile1, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])] exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '-flag1', '< "%s"' % inputfile2, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '< "%s"' % inputfile1, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice1', '< "%s"' % inputfile2, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2', '-flag1', '< "%s"' % inputfile1, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2', '-flag1', '< "%s"' % inputfile2, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2', '< "%s"' % inputfile1, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) exp.append(' '.join([bin, cmd, '-default=42', '-delimaaachoice2', '< "%s"' % inputfile2, '-output="%s"' % outputfile, '> "%s"' % stdout, '2> "%s"' % stderr])) cmdlines = list(cmdgen) self.assertEqual(cmdlines, exp) def test_cmdline_generator_missing_input_output_paths(self): # missing input with self.assertRaises(ValueError): list(cmdline_generator(None, PathsToInputs=None)) with self.assertRaises(ValueError): list(cmdline_generator(None, PathsToInputs='')) with self.assertRaises(ValueError): list(cmdline_generator(None, PathsToInputs=[])) # missing output with self.assertRaises(ValueError): list(cmdline_generator(None, PathsToInputs=['/foo/bar/baz'], PathToOutput=None)) with self.assertRaises(ValueError): list(cmdline_generator(None, PathsToInputs=['/foo/bar/baz'], PathToOutput='')) class CommandLineApplicationTests(TestCase): """Tests for the CommandLineApplication class""" def setUp(self): """setUp for all CommandLineApplication tests""" f = open('/tmp/CLAppTester.py', 'w') f.write(script) f.close() system('chmod 777 /tmp/CLAppTester.py') # create a copy of the script with a space in the name f = open('/tmp/CLApp Tester.py', 'w') f.write(script) f.close() system('chmod 777 "/tmp/CLApp Tester.py"') self.app_no_params = CLAppTester() self.app_no_params_no_stderr = CLAppTester(SuppressStderr=True) self.app_params = CLAppTester({'-F': 'p_file.txt'}) self.app_params_space_in_command =\ CLAppTester_space_in_command({'-F': 'p_file.txt'}) self.app_params_no_stderr = CLAppTester({'-F': 'p_file.txt'}, SuppressStderr=True) self.app_params_no_stdout = CLAppTester({'-F': 'p_file.txt'}, SuppressStdout=True) self.app_params_input_as_file = \ CLAppTester({'-F': 'p_file.txt'}, InputHandler='_input_as_lines') self.app_params_WorkingDir = CLAppTester({'-F': 'p_file.txt'}, WorkingDir='/tmp/test') self.app_params_WorkingDir_w_space = \ CLAppTester({'-F': 'p_file.txt'}, WorkingDir='/tmp/test space') self.app_params_TmpDir = CLAppTester({'-F': 'p_file.txt'}, TmpDir='/tmp/tmp2') self.app_params_TmpDir_w_space = \ CLAppTester({'-F': 'p_file.txt'}, TmpDir='/tmp/tmp space') self.data = 42 def test_base_command(self): """CLAppTester: BaseCommand correctly composed """ # No parameters on app = CLAppTester() self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py') # ValuedParameter on/off app.Parameters['-F'].on('junk.txt') self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py -F "junk.txt"') app.Parameters['-F'].off() self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py') # ValuedParameter accessed by synonym turned on/off app.Parameters['File'].on('junk.txt') self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py -F "junk.txt"') app.Parameters['File'].off() self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py') # Try multiple parameters, must check for a few different options # because parameters are printed in arbitrary order app.Parameters['-F'].on('junk.txt') app.Parameters['--duh'].on() self.assertTrue( app.BaseCommand == 'cd "/tmp/"; /tmp/CLAppTester.py -F "junk.txt" --duh' or app.BaseCommand == 'cd "/tmp/"; /tmp/CLAppTester.py --duh -F "junk.txt"') # Space in _command app = CLAppTester_space_in_command() self.assertEqual(app.BaseCommand, 'cd "/tmp/"; "/tmp/CLApp Tester.py"') def test_getHelp(self): """CLAppTester: getHelp() functions as expected """ app = CLAppTester() self.assertEqual(app.getHelp(), 'Duh') def test_handle_app_result_build_failure(self): """_handle_app_result_build_failure on CommandLineAppResult() failure """ app = CLAppTester_bad_fixed_file() self.assertRaises(ApplicationError, app) app = CLAppTester_bad_fixed_file_w_handler() self.assertEqual(app(), "Called self._handle_app_result_build_failure") def test_error_on_missing_executable(self): """CLAppTester: Useful error message on executable not found """ # fake command via self._command class Blah(CLAppTester): _command = 'fake_command_jasdlkfsadlkfskladfkladf' self.assertRaises(ApplicationNotFoundError, Blah) # real command but bad path via self._command class Blah(CLAppTester): _command = '/not/a/real/path/ls' self.assertRaises(ApplicationNotFoundError, Blah) # alt _error_on_missing_command function works as expected class Blah(CLAppTester): _command = 'ls' def _error_on_missing_application(self, data): raise ApplicationNotFoundError self.assertRaises(ApplicationNotFoundError, Blah) class Blah(CLAppTester): _command = 'fake_app_asfasdasdasdasdasd' def _error_on_missing_application(self, data): pass # no error raised Blah() def test_no_p_no_d(self): """CLAppTester: parameters turned off, no data""" app = self.app_no_params # test_init assert app.Parameters['-F'].isOff() self.assertEqual(app.InputHandler, '_input_as_string') assert not app.SuppressStderr # test_command self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py') # test_result result = app() self.assertEqual(result['StdOut'].read(), 'out\n') self.assertEqual(result['StdErr'].read(), 'I am stderr\n') self.assertEqual(result['ExitStatus'], 0) self.assertEqual(result['fixed_file'].read(), 'I am fixed file') self.assertEqual(result['base_dep_1'].read(), 'base dependent 1') self.assertEqual(result['base_dep_2'].read(), 'base dependent 2') self.assertEqual(result['parameterized_file'], None) result.cleanUp() def test_no_p_data_as_str(self): """CLAppTester: parameters turned off, data as string""" app = self.app_no_params # test_init assert app.Parameters['-F'].isOff() self.assertEqual(app.InputHandler, '_input_as_string') assert not app.SuppressStderr # test_command self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py') # test_result result = app(self.data) self.assertEqual(result['StdOut'].read(), 'out 43\n') self.assertEqual(result['StdErr'].read(), 'I am stderr\n') self.assertEqual(result['ExitStatus'], 0) self.assertEqual(result['fixed_file'].read(), 'I am fixed file') self.assertEqual(result['base_dep_1'].read(), 'base dependent 1') self.assertEqual(result['base_dep_2'].read(), 'base dependent 2') self.assertEqual(result['parameterized_file'], None) result.cleanUp() def test_p_data_as_str_suppress_stderr(self): """CLAppTester: parameters turned on, data as string, suppress stderr """ app = self.app_params_no_stderr # test_init assert app.Parameters['-F'].isOn() self.assertEqual(app.InputHandler, '_input_as_string') assert app.SuppressStderr # test_command self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py -F "p_file.txt"') # test_result result = app(self.data) self.assertEqual(result['StdOut'].read(), '') self.assertEqual(result['StdErr'], None) self.assertEqual(result['ExitStatus'], 0) self.assertEqual(result['fixed_file'].read(), 'I am fixed file') self.assertEqual(result['base_dep_1'].read(), 'base dependent 1') self.assertEqual(result['base_dep_2'].read(), 'base dependent 2') self.assertEqual(result['parameterized_file'].read(), 'out 43 p_file.txt') result.cleanUp() def test_p_data_as_str_suppress_stdout(self): """CLAppTester: parameters turned on, data as string, suppress stdout """ app = self.app_params_no_stdout # test_init assert app.Parameters['-F'].isOn() self.assertEqual(app.InputHandler, '_input_as_string') assert app.SuppressStdout # test_command self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py -F "p_file.txt"') # test_result result = app(self.data) self.assertEqual(result['StdOut'], None) self.assertEqual(result['StdErr'].read(), 'I am stderr\n') self.assertEqual(result['ExitStatus'], 0) self.assertEqual(result['fixed_file'].read(), 'I am fixed file') self.assertEqual(result['base_dep_1'].read(), 'base dependent 1') self.assertEqual(result['base_dep_2'].read(), 'base dependent 2') self.assertEqual(result['parameterized_file'].read(), 'out 43 p_file.txt') result.cleanUp() def test_p_no_data(self): """CLAppTester: parameters turned on, no data""" app = self.app_params # test_init assert app.Parameters['-F'].isOn() self.assertEqual(app.InputHandler, '_input_as_string') assert not app.SuppressStderr # test_command self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py -F "p_file.txt"') # test_result result = app() self.assertEqual(result['StdOut'].read(), '') self.assertEqual(result['StdErr'].read(), 'I am stderr\n') self.assertEqual(result['ExitStatus'], 0) self.assertEqual(result['fixed_file'].read(), 'I am fixed file') self.assertEqual(result['base_dep_1'].read(), 'base dependent 1') self.assertEqual(result['base_dep_2'].read(), 'base dependent 2') self.assertEqual(result['parameterized_file'].read(), 'out p_file.txt') result.cleanUp() def test_p_space_in_command(self): """CLAppTester: parameters turned on, no data, space in command""" app = self.app_params_space_in_command # test_init assert app.Parameters['-F'].isOn() self.assertEqual(app.InputHandler, '_input_as_string') assert not app.SuppressStderr # test_command self.assertEqual(app.BaseCommand, 'cd "/tmp/"; "/tmp/CLApp Tester.py" -F "p_file.txt"') # test_result result = app() self.assertEqual(result['StdOut'].read(), '') self.assertEqual(result['StdErr'].read(), 'I am stderr\n') self.assertEqual(result['ExitStatus'], 0) self.assertEqual(result['fixed_file'].read(), 'I am fixed file') self.assertEqual(result['base_dep_1'].read(), 'base dependent 1') self.assertEqual(result['base_dep_2'].read(), 'base dependent 2') self.assertEqual(result['parameterized_file'].read(), 'out p_file.txt') result.cleanUp() def test_p_data_as_str(self): """CLAppTester: parameters turned on, data as str""" app = self.app_params # test_init assert app.Parameters['-F'].isOn() self.assertEqual(app.InputHandler, '_input_as_string') assert not app.SuppressStderr # test_command self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py -F "p_file.txt"') # test_result result = app(self.data) self.assertEqual(result['StdOut'].read(), '') self.assertEqual(result['StdErr'].read(), 'I am stderr\n') self.assertEqual(result['ExitStatus'], 0) self.assertEqual(result['fixed_file'].read(), 'I am fixed file') self.assertEqual(result['base_dep_1'].read(), 'base dependent 1') self.assertEqual(result['base_dep_2'].read(), 'base dependent 2') self.assertEqual(result['parameterized_file'].read(), 'out 43 p_file.txt') result.cleanUp() def test_p_data_as_file(self): """CLAppTester: parameters turned on, data as file""" app = self.app_params_input_as_file # test_init assert app.Parameters['-F'].isOn() self.assertEqual(app.InputHandler, '_input_as_lines') assert not app.SuppressStderr # test_command # we don't test the command in this case, because we don't know what # the name of the input file is. # test_result result = app([self.data]) self.assertEqual(result['StdOut'].read(), '') self.assertEqual(result['StdErr'].read(), 'I am stderr\n') self.assertEqual(result['ExitStatus'], 0) self.assertEqual(result['fixed_file'].read(), 'I am fixed file') self.assertEqual(result['base_dep_1'].read(), 'base dependent 1') self.assertEqual(result['base_dep_2'].read(), 'base dependent 2') self.assertEqual(result['parameterized_file'].read(), 'out 43 p_file.txt') result.cleanUp() def test_WorkingDir(self): """CLAppTester: WorkingDir functions as expected """ system('cp /tmp/CLAppTester.py /tmp/test/CLAppTester.py') app = self.app_params_WorkingDir # test_init assert app.Parameters['-F'].isOn() self.assertEqual(app.InputHandler, '_input_as_string') assert not app.SuppressStderr # WorkingDir is what we expect self.assertEqual(app.WorkingDir, '/tmp/test/') # test_command self.assertEqual(app.BaseCommand, ('cd "/tmp/test/"; /tmp/CLAppTester.py ' '-F "p_file.txt"')) # test_result result = app() self.assertEqual(result['StdOut'].read(), '') self.assertEqual(result['StdErr'].read(), 'I am stderr\n') self.assertEqual(result['ExitStatus'], 0) self.assertEqual(result['fixed_file'].read(), 'I am fixed file') self.assertEqual(result['base_dep_1'].read(), 'base dependent 1') self.assertEqual(result['base_dep_2'].read(), 'base dependent 2') # Make sure that the parameterized file is in the correct place self.assertEqual(result['parameterized_file'].name, '/tmp/test/p_file.txt') self.assertEqual(result['parameterized_file'].read(), 'out p_file.txt') result.cleanUp() def test_WorkingDir_w_space(self): """CLAppTester: WorkingDir w/ space in path functions as expected """ system('cp /tmp/CLAppTester.py "/tmp/test space/CLAppTester.py"') app = self.app_params_WorkingDir_w_space # test_init assert app.Parameters['-F'].isOn() self.assertEqual(app.InputHandler, '_input_as_string') assert not app.SuppressStderr # WorkingDir is what we expect self.assertEqual(app.WorkingDir, '/tmp/test space/') # test_command self.assertEqual(app.BaseCommand, ('cd "/tmp/test space/"; /tmp/CLAppTester.py' ' -F "p_file.txt"')) # test_result result = app() self.assertEqual(result['StdOut'].read(), '') self.assertEqual(result['StdErr'].read(), 'I am stderr\n') self.assertEqual(result['ExitStatus'], 0) self.assertEqual(result['fixed_file'].read(), 'I am fixed file') self.assertEqual(result['base_dep_1'].read(), 'base dependent 1') self.assertEqual(result['base_dep_2'].read(), 'base dependent 2') # Make sure that the parameterized file is in the correct place self.assertEqual(result['parameterized_file'].name, '/tmp/test space/p_file.txt') self.assertEqual(result['parameterized_file'].read(), 'out p_file.txt') result.cleanUp() def test_TmpDir(self): """CLAppTester: Alternative TmpDir functions as expected""" app = self.app_params_TmpDir # test_init assert app.Parameters['-F'].isOn() self.assertEqual(app.InputHandler, '_input_as_string') assert not app.SuppressStderr # TmpDir is what we expect self.assertEqual(app.TmpDir, '/tmp/tmp2/') # test_command self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py -F "p_file.txt"') # test_result result = app() self.assertEqual(result['StdOut'].read(), '') self.assertEqual(result['StdErr'].read(), 'I am stderr\n') self.assertEqual(result['ExitStatus'], 0) self.assertEqual(result['fixed_file'].read(), 'I am fixed file') self.assertEqual(result['base_dep_1'].read(), 'base dependent 1') self.assertEqual(result['base_dep_2'].read(), 'base dependent 2') # Make sure that the parameterized file is in the correct place self.assertEqual(result['parameterized_file'].name, '/tmp/p_file.txt') self.assertEqual(result['parameterized_file'].read(), 'out p_file.txt') result.cleanUp() def test_TmpDir_w_space(self): """CLAppTester: TmpDir functions as expected w space in name""" app = self.app_params_TmpDir_w_space # test_init assert app.Parameters['-F'].isOn() self.assertEqual(app.InputHandler, '_input_as_string') assert not app.SuppressStderr # TmpDir is what we expect self.assertEqual(app.TmpDir, '/tmp/tmp space/') # test_command self.assertEqual(app.BaseCommand, 'cd "/tmp/"; /tmp/CLAppTester.py -F "p_file.txt"') # test_result result = app() self.assertEqual(result['StdOut'].read(), '') self.assertEqual(result['StdErr'].read(), 'I am stderr\n') self.assertEqual(result['ExitStatus'], 0) self.assertEqual(result['fixed_file'].read(), 'I am fixed file') self.assertEqual(result['base_dep_1'].read(), 'base dependent 1') self.assertEqual(result['base_dep_2'].read(), 'base dependent 2') # Make sure that the parameterized file is in the correct place self.assertEqual(result['parameterized_file'].name, '/tmp/p_file.txt') self.assertEqual(result['parameterized_file'].read(), 'out p_file.txt') result.cleanUp() def test_input_as_string(self): """CLAppTester: _input_as_string functions as expected """ self.assertEqual(self.app_no_params._input_as_string('abcd'), 'abcd') self.assertEqual(self.app_no_params._input_as_string(42), '42') self.assertEqual(self.app_no_params._input_as_string(None), 'None') self.assertEqual(self.app_no_params._input_as_string([1]), '[1]') self.assertEqual(self.app_no_params._input_as_string({'a': 1}), "{'a': 1}") def test_input_as_lines_from_string(self): """CLAppTester: _input_as_lines functions as expected w/ data as str """ filename = self.app_no_params._input_as_lines('abcd') self.assertEqual(filename[0], '/') f = open(filename) self.assertEqual(f.readline(), 'a\n') self.assertEqual(f.readline(), 'b\n') self.assertEqual(f.readline(), 'c\n') self.assertEqual(f.readline(), 'd') f.close() remove(filename) def test_input_as_lines_from_list(self): """CLAppTester: _input_as_lines functions as expected w/ data as list """ filename = self.app_no_params._input_as_lines(['line 1', None, 3]) self.assertEqual(filename[0], '/') f = open(filename) self.assertEqual(f.readline(), 'line 1\n') self.assertEqual(f.readline(), 'None\n') self.assertEqual(f.readline(), '3') f.close() remove(filename) def test_input_as_lines_from_list_w_newlines(self): """CLAppTester: _input_as_lines functions w/ data as list w/ newlines """ filename = self.app_no_params._input_as_lines(['line 1\n', None, 3]) self.assertEqual(filename[0], '/') f = open(filename) self.assertEqual(f.readline(), 'line 1\n') self.assertEqual(f.readline(), 'None\n') self.assertEqual(f.readline(), '3') f.close() remove(filename) def test_input_as_multiline_string(self): """CLAppTester: _input_as_multiline_string functions as expected """ filename = self.app_no_params._input_as_multiline_string( 'line 1\nNone\n3') self.assertEqual(filename[0], '/') f = open(filename) self.assertEqual(f.readline(), 'line 1\n') self.assertEqual(f.readline(), 'None\n') self.assertEqual(f.readline(), '3') f.close() remove(filename) def test_input_as_lines_from_list_single_entry(self): """CLAppTester: _input_as_lines functions as expected w/ 1 element list """ filename = self.app_no_params._input_as_lines(['line 1']) self.assertEqual(filename[0], '/') f = open(filename) self.assertEqual(f.readline(), 'line 1') f.close() remove(filename) def test_input_as_multiline_string_single_line(self): """CLAppTester: _input_as_multiline_string functions w/ single line """ # functions as expected with single line string filename = self.app_no_params._input_as_multiline_string( 'line 1') self.assertEqual(filename[0], '/') f = open(filename) self.assertEqual(f.readline(), 'line 1') f.close() remove(filename) def test_getTmpFilename_non_default(self): """TmpFilename handles alt tmp_dir, prefix and suffix properly""" app = CLAppTester() obs = app.getTmpFilename(include_class_id=False) self.assertTrue(obs.startswith(app.TmpDir + 'tmp')) self.assertTrue(obs.endswith('.txt')) obs = app.getTmpFilename(tmp_dir="/tmp/blah", prefix="app_ctl_test", suffix='.test', include_class_id=False) self.assertTrue(obs.startswith('/tmp/blah/app_ctl_test')) self.assertTrue(obs.endswith('.test')) def test_getTmpFilename_defaults_to_no_class_id(self): """CLAppTester: getTmpFilename doesn't include class id by default """ # I want to explicitly test for this so people don't forget to # set the default to False if they change it for testing purposes app = CLAppTester() self.assertFalse(app.getTmpFilename(). startswith(app.TmpDir + 'tmpCLAppTester')) self.assertTrue(app.getTmpFilename(include_class_id=True). startswith(app.TmpDir + 'tmpCLAppTester')) def test_input_as_path(self): """CLAppTester: _input_as_path casts data to FilePath""" actual = self.app_no_params._input_as_path('test.pdb') self.assertEqual(actual, 'test.pdb') self.assertEqual(str(actual), '"test.pdb"') actual = self.app_no_params._input_as_path('te st.pdb') self.assertEqual(actual, 'te st.pdb') self.assertEqual(str(actual), '"te st.pdb"') actual = self.app_no_params._input_as_path('./test.pdb') self.assertEqual(actual, './test.pdb') self.assertEqual(str(actual), '"./test.pdb"') actual = self.app_no_params._input_as_path('/this/is/a/test.pdb') self.assertEqual(actual, '/this/is/a/test.pdb') self.assertEqual(str(actual), '"/this/is/a/test.pdb"') actual = self.app_no_params._input_as_path('/this/i s/a/test.pdb') self.assertEqual(actual, '/this/i s/a/test.pdb') self.assertEqual(str(actual), '"/this/i s/a/test.pdb"') def test_input_as_paths(self): """CLAppTester: _input_as_paths casts each input to FilePath """ input = ['test.pdb'] actual = self.app_no_params._input_as_paths(input) expected = '"test.pdb"' self.assertEqual(actual, expected) input = ['test1.pdb', 'test2.pdb'] actual = self.app_no_params._input_as_paths(input) expected = '"test1.pdb" "test2.pdb"' self.assertEqual(actual, expected) input = ['/path/to/test1.pdb', 'test2.pdb'] actual = self.app_no_params._input_as_paths(input) expected = '"/path/to/test1.pdb" "test2.pdb"' self.assertEqual(actual, expected) input = ['test1.pdb', '/path/to/test2.pdb'] actual = self.app_no_params._input_as_paths(input) expected = '"test1.pdb" "/path/to/test2.pdb"' self.assertEqual(actual, expected) input = ['/path/to/test1.pdb', '/path/to/test2.pdb'] actual = self.app_no_params._input_as_paths(input) expected = '"/path/to/test1.pdb" "/path/to/test2.pdb"' self.assertEqual(actual, expected) input = ['/pa th/to/test1.pdb', '/path/to/te st2.pdb'] actual = self.app_no_params._input_as_paths(input) expected = '"/pa th/to/test1.pdb" "/path/to/te st2.pdb"' self.assertEqual(actual, expected) def test_absolute(self): """CLAppTester: _absolute converts relative paths to absolute paths """ absolute = self.app_no_params._absolute self.assertEqual(absolute('/tmp/test.pdb'), '/tmp/test.pdb') self.assertEqual(absolute('test.pdb'), '/tmp/test.pdb') def test_working_dir_setting(self): """CLAppTester: WorkingDir is set correctly """ app = CLAppTester_no_working_dir() self.assertEqual(app.WorkingDir, getcwd() + '/') def test_error_raised_on_command_None(self): """CLAppTester: An error is raises when _command == None """ app = CLAppTester() app._command = None self.assertRaises(ApplicationError, app._get_base_command) def test_rejected_exit_status(self): """CLAppTester_reject_exit_status results in useful error """ app = CLAppTester_reject_exit_status() self.assertRaises(ApplicationError, app) def test_getTmpFilename(self): """TmpFilename should return filename of correct length""" app = CLAppTester() obs = app.getTmpFilename(include_class_id=True) # leaving the strings in this statement so it's clear where the # expected length comes from self.assertEqual(len(obs), len(app.TmpDir) + app.TmpNameLen + len('tmp') + len('CLAppTester') + len('.txt')) assert obs.startswith(app.TmpDir) chars = set(obs[18:]) assert len(chars) > 1 obs = app.getTmpFilename(include_class_id=False) # leaving the strings in this statement so it's clear where the # expected length comes from self.assertEqual(len(obs), len(app.TmpDir) + app.TmpNameLen + len('tmp') + len('.txt')) assert obs.startswith(app.TmpDir) def test_getTmpFilename_prefix_suffix_result_constructor(self): """TmpFilename: result has correct prefix, suffix, type""" app = CLAppTester() obs = app.getTmpFilename(prefix='blah', include_class_id=False) self.assertTrue(obs.startswith(app.TmpDir + 'blah')) obs = app.getTmpFilename(suffix='.blah', include_class_id=False) self.assertTrue(obs.endswith('.blah')) # Prefix defaults to not include the class name obs = app.getTmpFilename(include_class_id=False) self.assertFalse(obs.startswith(app.TmpDir + 'tmpCLAppTester')) self.assertTrue(obs.endswith('.txt')) # including class id functions correctly obs = app.getTmpFilename(include_class_id=True) self.assertTrue(obs.startswith(app.TmpDir + 'tmpCLAppTester')) self.assertTrue(obs.endswith('.txt')) # result as FilePath obs = app.getTmpFilename(result_constructor=FilePath) self.assertEqual(type(obs), FilePath) # result as str (must check that result is a str and is not a FilePath # since a FilePath is a str) obs = app.getTmpFilename(result_constructor=str) self.assertEqual(type(obs), str) self.assertNotEqual(type(obs), FilePath) class ConvenienceFunctionTests(TestCase): """ """ def setUp(self): """ """ self.tmp_dir = gettempdir() self.tmp_name_len = 20 def test_guess_input_handler(self): """guess_input_handler should correctly identify input""" gih = guess_input_handler self.assertEqual(gih('abc.txt'), '_input_as_string') self.assertEqual(gih('>ab\nTCAG'), '_input_as_multiline_string') self.assertEqual(gih(['ACC', 'TGA'], True), '_input_as_seqs') self.assertEqual(gih(['>a', 'ACC', '>b', 'TGA']), '_input_as_lines') self.assertEqual(gih([('a', 'ACC'), ('b', 'TGA')]), '_input_as_seq_id_seq_pairs') self.assertEqual(gih([]), '_input_as_lines') def test_get_tmp_filename(self): """get_tmp_filename should return filename of correct length Adapted from the CommandLineApplication tests of the member function """ obs = get_tmp_filename() # leaving the strings in this statement so it's clear where the # expected length comes from self.assertEqual(len(obs), len(self.tmp_dir) + len('/') + self.tmp_name_len + len('tmp') + len('.txt')) self.assertTrue(obs.startswith(self.tmp_dir)) # different results on different calls self.assertNotEqual(get_tmp_filename(), get_tmp_filename()) obs = get_tmp_filename() # leaving the strings in this statement so it's clear where the # expected length comes from self.assertEqual(len(obs), len(self.tmp_dir) + len('/') + self.tmp_name_len + len('tmp') + len('.txt')) assert obs.startswith(self.tmp_dir) def test_get_tmp_filename_prefix_suffix_constructor(self): """get_tmp_filename: result has correct prefix, suffix, type Adapted from the CommandLineApplication tests of the member function """ obs = get_tmp_filename(prefix='blah') self.assertTrue(obs.startswith('%s/blah' % self.tmp_dir)) obs = get_tmp_filename(suffix='.blah') self.assertTrue(obs.endswith('.blah')) # result as FilePath obs = get_tmp_filename(result_constructor=FilePath) self.assertEqual(type(obs), FilePath) # result as str (must check that result is a str and is not a FilePath # since a FilePath is a str) obs = get_tmp_filename(result_constructor=str) self.assertEqual(type(obs), str) self.assertNotEqual(type(obs), FilePath) def test_which_found(self): """Test finding filepath for executable that can be found.""" obs = which('ls') self.assertTrue(obs is not None) self.assertTrue(exists(obs)) def test_which_not_found(self): """Test finding filepath for executable that cannot be found.""" obs = which('thiscommandhadbetternotexist') self.assertTrue(obs is None) def test_which_env_var(self): """Test finding filepath using an env_var other than the default.""" obs = which('ls', env_var='THISENVVARHADBETTERNOTEXIST') self.assertTrue(obs is None) def teardown_module(): """This will remove the test script.""" for dir, n, fnames in walk('/tmp/test/'): for f in fnames: try: remove(dir + f) except OSError: pass remove('/tmp/CLAppTester.py') remove('/tmp/test space/CLAppTester.py') remove('/tmp/CLApp Tester.py') rmdir('/tmp/tmp space') rmdir('/tmp/test') rmdir('/tmp/test space') rmdir('/tmp/tmp2') rmdir('/tmp/blah') # =====================END OF TESTS================================== script = """#!/usr/bin/env python #This is a test script intended to test the CommandLineApplication #class and CommandLineAppResult class from __future__ import absolute_import, division, print_function from sys import argv, stderr, stdin from os import isatty out_file_name = None input_arg = None # parse input try: if argv[1] == '-F': out_file_name = argv[2] except IndexError: pass try: if out_file_name: input_arg = argv[3] else: input_arg = argv[1] except IndexError: pass # Create the output string out = 'out' # get input try: f = open(str(input_arg)) data = int(f.readline().strip()) except IOError: try: data = int(input_arg) except TypeError: data = None if data: data = str(data + 1) out = ' '.join([out,data]) # Write base dependent output files base = 'BASE' f = open('/tmp/' + base + '.1','w') f.writelines(['base dependent 1']) f.close() f = open('/tmp/' + base + '.2','w') f.writelines(['base dependent 2']) f.close() # If output to file, open the file and write output to it if out_file_name: filename = argv[2] f = open(''.join([out_file_name]),'w') out = ' '.join([out,out_file_name]) f.writelines(out) f.close() else: print(out) #generate some stderr print('I am stderr', file=stderr) # Write the fixed file f = open('/tmp/fixed.txt','w') f.writelines(['I am fixed file']) f.close() """ class CLAppTester(CommandLineApplication): _parameters = { '-F': ValuedParameter(Prefix='-', Name='F', Delimiter=' ', Value=None, Quote="\""), '--duh': FlagParameter(Prefix='--', Name='duh')} _command = '/tmp/CLAppTester.py' _synonyms = {'File': '-F', 'file': '-F'} _working_dir = '/tmp' def _get_result_paths(self, data): if self.Parameters['-F'].isOn(): param_path = ''.join( [self.WorkingDir, self.Parameters['-F'].Value]) else: param_path = None result = {} result['fixed_file'] = ResultPath(Path='/tmp/fixed.txt') result['parameterized_file'] = \ ResultPath(Path=param_path, IsWritten=self.Parameters['-F'].isOn()) result['base_dep_1'] = ResultPath(Path=self._build_name(suffix='.1')) result['base_dep_2'] = ResultPath(Path=self._build_name(suffix='.2')) return result def _build_name(self, suffix): return '/tmp/BASE' + suffix def getHelp(self): return """Duh""" class CLAppTester_no_working_dir(CLAppTester): _working_dir = None class CLAppTester_reject_exit_status(CLAppTester): def _accept_exit_status(self, exit_status): return False class CLAppTester_bad_fixed_file(CLAppTester): def _get_result_paths(self, data): if self.Parameters['-F'].isOn(): param_path = ''.join( [self.WorkingDir, self.Parameters['-F'].Value]) else: param_path = None result = {} result['fixed_file'] = ResultPath(Path='/tmp/fixed.txt') result['fixed_file_bad'] = ResultPath(Path='/tmp/i_dont_exist.txt') result['parameterized_file'] = \ ResultPath(Path=param_path, IsWritten=self.Parameters['-F'].isOn()) result['base_dep_1'] = ResultPath(Path=self._build_name(suffix='.1')) result['base_dep_2'] = ResultPath(Path=self._build_name(suffix='.2')) return result class CLAppTester_bad_fixed_file_w_handler(CLAppTester_bad_fixed_file): def _handle_app_result_build_failure( self, out, err, exit_status, result_paths): return "Called self._handle_app_result_build_failure" class CLAppTester_space_in_command(CLAppTester): _command = '"/tmp/CLApp Tester.py"' class ParameterCombinationsApp(CommandLineApplication): """ParameterCombinations mock application to wrap""" _command = 'testcmd' _parameters = {'-flag1': FlagParameter(Prefix='-', Name='flag1'), '-flag2': FlagParameter(Prefix='-', Name='flag2'), '--value1': ValuedParameter(Prefix='--', Name='value1'), '-value2': ValuedParameter(Prefix='-', Name='value2'), '-mix1': MixedParameter(Prefix='-', Name='mix1'), '-mix2': MixedParameter(Prefix='-', Name='mix2'), '-delim': ValuedParameter(Prefix='-', Name='delim', Delimiter='aaa'), '-default': ValuedParameter(Prefix='-', Name='default', Value=42, Delimiter='='), '-input': ValuedParameter(Prefix='-', Name='input', Delimiter='='), '-output': ValuedParameter(Prefix='-', Name='output', Delimiter='=')} if __name__ == '__main__': from nose import runmodule runmodule() burrito-0.9.1/burrito/util.py0000644000076500000240000007501612527660643017202 0ustar caporasostaff00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2014--, burrito development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- from future.utils import implements_iterator import os from os import remove, system, mkdir, getcwd from os.path import isabs, exists from random import choice from tempfile import gettempdir from copy import deepcopy from itertools import product from burrito.parameters import Parameters, FilePath # the following are used to create temp file names from string import ascii_letters, digits _all_chars = ascii_letters + digits def which(executable_name, env_var='PATH'): """Equivalent to ``which executable_name`` in a *nix environment. Will return ``None`` if ``executable_name`` cannot be found in ``env_var`` or if ``env_var`` is not set. Otherwise will return the first match in ``env_var``. Note: this function will likely not work on Windows. Code taken and modified from: http://www.velocityreviews.com/forums/ t689526-python-library-call-equivalent-to-which-command.html """ exec_fp = None if env_var in os.environ: paths = os.environ[env_var] for path in paths.split(os.pathsep): curr_exec_fp = os.path.join(path, executable_name) if os.access(curr_exec_fp, os.X_OK): exec_fp = curr_exec_fp break return exec_fp class ApplicationError(OSError): pass class ApplicationNotFoundError(ApplicationError): pass class ResultPath(object): """ Hold a file path a boolean value specifying whether file was written """ def __init__(self, Path, IsWritten=True): """ Initialize the ResultPath object Path: a string representing the absolute or relative path where the file can be found IsWritten: a boolean specifying whether the file has been written, default = True """ self.Path = FilePath(Path) self.IsWritten = IsWritten class CommandLineAppResult(dict): """ Class for holding the result of a CommandLineApplication run """ def __init__(self, out, err, exit_status, result_paths): """Initialization of CommandLineAppResult out: a file handler to the file containing the stdout err: a file handler to the file containing the stderr exit_status: the exit status of the program, 0 if run ok, 1 else. result_paths: dictionary containing ResultPath objects for each output file that could be written """ self['StdOut'] = out self['StdErr'] = err self['ExitStatus'] = exit_status self.file_keys = result_paths.keys() for key, value in result_paths.items(): if value.IsWritten: try: self[key] = open(value.Path) except IOError: raise ApplicationError('Could not open %s' % value.Path) else: self[key] = None def cleanUp(self): """ Delete files that are written by CommandLineApplication from disk WARNING: after cleanUp() you may still have access to part of your result data, but you should be aware that if the file size exceeds the size of the buffer you will only have part of the file. To be safe, you should not use cleanUp() until you are done with the file or have copied it to a different location. """ file_keys = self.file_keys for item in file_keys: if self[item] is not None: self[item].close() remove(self[item].name) # remove input handler temp files if hasattr(self, "_input_filename"): remove(self._input_filename) def __del__(self): """ Delete temporary files created by the CommandLineApplication """ if self['StdOut'] is not None: remove(self['StdOut'].name) if self['StdErr'] is not None: remove(self['StdErr'].name) class Application(object): """ Generic Class for controlling an application """ _command = None _command_delimiter = ' ' _parameters = {} _synonyms = {} def __init__(self, params=None): """ params: a dict of parameters which should be turned on where the key is either the parameter id or a synonym for the parameter and the value is either the value for the parameter or None """ self.Parameters = Parameters(self._parameters, self._synonyms) if params: for key, v in params.items(): try: self.Parameters[key].on(v) except TypeError: self.Parameters[key].on() class CommandLineApplication(Application): """ Generic class for controlling command line applications """ _input_handler = '_input_as_string' _suppress_stderr = False _suppress_stdout = False _working_dir = None def __init__(self, params=None, InputHandler=None, SuppressStderr=None, SuppressStdout=None, WorkingDir=None, TmpDir=gettempdir(), TmpNameLen=20, HALT_EXEC=False): """ Initialize the CommandLineApplication object params: a dictionary mapping the Parameter id or synonym to its value (or None for FlagParameters or MixedParameters in flag mode) for Parameters that should be turned on InputHandler: this is the method to be run on data when it is passed into call. This should be a string containing the method name. The default is _input_as_string which casts data to a string before appending it to the command line argument SuppressStderr: if set to True, will route standard error to /dev/null, False by default SuppressStdout: if set to True, will route standard out to /dev/null, False by default WorkingDir: the directory where you want the application to run, default is the current working directory, but is useful to change in cases where the program being run creates output to its current working directory and you either don't want it to end up where you are running the program, or the user running the script doesn't have write access to the current working directory WARNING: WorkingDir MUST be an absolute path! TmpDir: the directory where temp files will be created TmpNameLen: the length of the temp file name HALT_EXEC: if True, raises exception w/ command output just before execution, doesn't clean up temp files. Default False. """ # Determine if the application is installed, and raise an error if not self._error_on_missing_application(params) # set attributes to parameter that was passed in or class default if InputHandler is not None: self.InputHandler = InputHandler else: self.InputHandler = self._input_handler if SuppressStderr is not None: self.SuppressStderr = SuppressStderr else: self.SuppressStderr = self._suppress_stderr if SuppressStdout is not None: self.SuppressStdout = SuppressStdout else: self.SuppressStdout = self._suppress_stdout if WorkingDir is not None: working_dir = WorkingDir else: working_dir = self._working_dir or getcwd() self.WorkingDir = FilePath(working_dir) if not TmpDir.endswith("/"): TmpDir += "/" self.TmpDir = FilePath(TmpDir) self.TmpNameLen = TmpNameLen self.HaltExec = HALT_EXEC # create a variable to hold the name of the file being used as # input to the application. this is important especially when # you are using an input handler which creates a temporary file # and the output filenames are based on the input filenames self._input_filename = None super(CommandLineApplication, self).__init__(params=params) def __call__(self, data=None, remove_tmp=True): """Run the application with the specified kwargs on data data: anything that can be cast into a string or written out to a file. Usually either a list of things or a single string or number. input_handler will be called on this data before it is passed as part of the command-line argument, so by creating your own input handlers you can customize what kind of data you want your application to accept remove_tmp: if True, removes tmp files """ input_handler = self.InputHandler suppress_stdout = self.SuppressStdout suppress_stderr = self.SuppressStderr if suppress_stdout: outfile = FilePath('/dev/null') else: outfile = self.getTmpFilename(self.TmpDir) if suppress_stderr: errfile = FilePath('/dev/null') else: errfile = FilePath(self.getTmpFilename(self.TmpDir)) if data is None: input_arg = '' else: input_arg = getattr(self, input_handler)(data) # Build up the command, consisting of a BaseCommand followed by # input and output (file) specifications command = self._command_delimiter.join(filter(None, [self.BaseCommand, str(input_arg), '>', str(outfile), '2>', str(errfile)])) if self.HaltExec: raise AssertionError("Halted exec with command:\n" + command) # The return value of system is a 16-bit number containing the signal # number that killed the process, and then the exit status. # We only want to keep the exit status so do a right bitwise shift to # get rid of the signal number byte exit_status = system(command) >> 8 # Determine if error should be raised due to exit status of # appliciation if not self._accept_exit_status(exit_status): raise ApplicationError('Unacceptable application exit ' + 'status: %s\n' % str(exit_status) + 'Command:\n%s\n' % command + 'StdOut:\n%s\n' % open(outfile).read() + 'StdErr:\n%s\n' % open(errfile).read()) # open the stdout and stderr if not being suppressed out = None if not suppress_stdout: out = open(outfile, "r") err = None if not suppress_stderr: err = open(errfile, "r") result_paths = self._get_result_paths(data) try: result = \ CommandLineAppResult(out, err, exit_status, result_paths=result_paths) except ApplicationError: result = \ self._handle_app_result_build_failure(out, err, exit_status, result_paths) # Clean up the input file if one was created if remove_tmp: if self._input_filename: remove(self._input_filename) self._input_filename = None return result def _handle_app_result_build_failure( self, out, err, exit_status, result_paths): """Called if ApplicationError raised on building CommandLineAppResult This is useful for checking log files or other special handling in cases when expected files aren't present. """ raise ApplicationError("Error constructing CommandLineAppResult.") def _input_as_string(self, data): """ Return data as a string """ return str(data) def _input_as_multiline_string(self, data): """Write a multiline string to a temp file and return the filename. data: a multiline string to be written to a file. * Note: the result will be the filename as a FilePath object (which is a string subclass). """ filename = self._input_filename = \ FilePath(self.getTmpFilename(self.TmpDir)) data_file = open(filename, 'w') data_file.write(data) data_file.close() return filename def _input_as_lines(self, data): """ Write a seq of lines to a temp file and return the filename string data: a sequence to be written to a file, each element of the sequence will compose a line in the file * Note: the result will be the filename as a FilePath object (which is a string subclass). * Note: '\n' will be stripped off the end of each sequence element before writing to a file in order to avoid multiple new lines accidentally be written to a file """ filename = self._input_filename = \ FilePath(self.getTmpFilename(self.TmpDir)) filename = FilePath(filename) data_file = open(filename, 'w') data_to_file = '\n'.join([str(d).strip('\n') for d in data]) data_file.write(data_to_file) data_file.close() return filename def _input_as_path(self, data): """ Return data as string with the path wrapped in quotes data: path or filename, most likely as a string * Note: the result will be the filename as a FilePath object (which is a string subclass). """ return FilePath(data) def _input_as_paths(self, data): """ Return data as a space delimited string with each path quoted data: paths or filenames, most likely as a list of strings """ return self._command_delimiter.join( map(str, map(self._input_as_path, data))) def _absolute(self, path): """ Convert a filename to an absolute path """ path = FilePath(path) if isabs(path): return path else: # these are both Path objects, so joining with + is acceptable return self.WorkingDir + path def _get_base_command(self): """ Returns the full command string input_arg: the argument to the command which represents the input to the program, this will be a string, either representing input or a filename to get input from tI""" command_parts = [] # Append a change directory to the beginning of the command to change # to self.WorkingDir before running the command # WorkingDir should be in quotes -- filenames might contain spaces cd_command = ''.join(['cd ', str(self.WorkingDir), ';']) if self._command is None: raise ApplicationError('_command has not been set.') command = self._command parameters = self.Parameters command_parts.append(cd_command) command_parts.append(command) command_parts.append(self._command_delimiter.join(filter( None, (map(str, parameters.values()))))) return self._command_delimiter.join(command_parts).strip() BaseCommand = property(_get_base_command) def _get_WorkingDir(self): """Gets the working directory""" return self._curr_working_dir def _set_WorkingDir(self, path): """Sets the working directory Appends a slash to the end of path The reasoning behind this is that the user may or may not pass in a path with a '/' at the end. Since having multiple '/' at the end doesn't hurt anything, it's convienient to be able to rely on it, and not have to check for it """ self._curr_working_dir = FilePath(path) + '/' try: mkdir(self.WorkingDir) except OSError: # Directory already exists pass WorkingDir = property(_get_WorkingDir, _set_WorkingDir) def _error_on_missing_application(self, params): """ Raise an ApplicationNotFoundError if the app is not accessible This method checks in the system path (usually $PATH) or for the existence of self._command. If self._command is not found in either place, an ApplicationNotFoundError is raised to inform the user that the application they are trying to access is not available. This method should be overwritten when self._command does not represent the relevant executable (e.g., self._command = 'prog -a') or in more complex cases where the file to be executed may be passed as a parameter (e.g., with java jar files, where the jar file is passed to java via '-jar'). It can also be overwritten to by-pass testing for application presence by never raising an error. """ command = self._command # strip off " characters, in case we got a FilePath object found_in_path = which(command.strip('"')) is not None if not (exists(command) or found_in_path): raise ApplicationNotFoundError("Cannot find %s. Is it installed? " "Is it in your path?" % command) def _accept_exit_status(self, exit_status): """ Return False to raise an error due to exit_status of applciation This method should be overwritten if you'd like to raise an error based on certain exit statuses of the application that was run. The default is that no value of exit_status will raise an error. """ return True def _get_result_paths(self, data): """ Return dict of ResultPath objects representing all possible output This method should be overwritten if the application creates output other than stdout and stderr. This dictionary will have keys based on the name that you'd like to access the file by in the CommandLineAppResult object that will be created, and the values which are ResultPath objects. For an example of how this should be written see the rnaview or vienna_package classes. WARNING: be sure that the path that you give a file is accurate from any directory where the program could be running. For that reason, absolute paths are very good. Relative paths can also be used as long as you are careful. For cases where the application leaves files in the current working directory, you should append self.WorkingDir to the beginning of the file name. It would be a very bad idea to just use a file name as the path, in some cases that you might not be testing for. """ return {} def getTmpFilename(self, tmp_dir=None, prefix='tmp', suffix='.txt', include_class_id=False, result_constructor=FilePath): """ Return a temp filename tmp_dir: directory where temporary files will be stored prefix: text to append to start of file name suffix: text to append to end of file name include_class_id: if True, will append a class identifier (built from the class name) to the filename following prefix. This is False by default b/c there is some string processing overhead in getting the class name. This will probably be most useful for testing: if temp files are being left behind by tests, you can turn this on in here (temporarily) to find out which tests are leaving the temp files. result_constructor: the constructor used to build the result (default: cogent.app.parameters.FilePath). Note that joining FilePath objects with one another or with strings, you must use the + operator. If this causes trouble, you can pass str as the the result_constructor. """ # check not none if not tmp_dir: tmp_dir = self.TmpDir # if not current directory, append "/" if not already on path elif not tmp_dir.endswith("/"): tmp_dir += "/" if include_class_id: # Append the classname to the prefix from the class name # so any problematic temp files can be associated with # the class that created them. This should be especially # useful for testing, but is turned off by default to # avoid the string-parsing overhead. class_id = str(self.__class__()) prefix = ''.join([prefix, class_id[class_id.rindex('.') + 1: class_id.index(' ')]]) try: mkdir(tmp_dir) except OSError: # Directory already exists pass # note: it is OK to join FilePath objects with + return result_constructor(tmp_dir) + result_constructor(prefix) + \ result_constructor(''.join([choice(_all_chars) for i in range(self.TmpNameLen)])) +\ result_constructor(suffix) @implements_iterator class ParameterIterBase(object): """Base class for parameter iteration objects This class provides base functionality for parameter iteration objects. A parameter iteration object acts like a generator and returns parameter dicts of varying values. The specific keys and ranges of values can be specified. Subclasses of this object implement the way in which the parameter values are chosen.""" def __init__(self, Application, Parameters, AlwaysOn=None): """Initialize the ParameterIterBase Application : A CommandLineApplication subclass Parameters : A dict keyed by the application paramter, value by the range of parameters to enumerate over. For FlagParameters, unless specified in AlwaysOn, the value will cycle between True/False (on/off). For MixedParameters, include [None] specifically to utilize flag functionality. AlwaysOn : List of parameters that will always be on Parameters is checked against the applications known parameters, but only performed superficially: only keys are validated. AlwaysOn values must have entries within Parameters. NOTE: If the parameter is not specified in AlwaysOn, a False value is appended so that the parameter can be turned off. Multiple False states for a parameter will result if False is specified without adding the parameter to AlwaysOn. If a parameter has a default value, then that parameter is implicitly always on. """ self.AppParams = Application._parameters # Validate Parameters param_set = set(Parameters.keys()) app_param_set = set(self.AppParams.keys()) if not param_set.issubset(app_param_set): not_present = str(param_set.difference(app_param_set)) raise ValueError( "Parameter(s) %s not present in app" % not_present) # Validate AlwaysOn alwayson_set = set(AlwaysOn) if not alwayson_set.issubset(param_set): not_present = str(alwayson_set.difference(param_set)) raise ValueError("AlwaysOn value(s) %s not in Parameters" % not_present) # Make sure all values are lists for k, v in Parameters.items(): if not isinstance(v, list): Parameters[k] = [v] _my_params = Parameters # Append "off states" to relevant parameters for k in param_set.difference(alwayson_set): _my_params[k].append(False) # Create seperate key/value lists preserving index relation self._keys, self._values = zip(*sorted(_my_params.items())) # Construct generator self._generator = self._init_generator() def _init_generator(self): """Must be implemented in the subclass""" pass def _make_app_params(self, values): """Returns app's param dict with values set as described by values """ # A deep copy is necessary. Otherwise the dict values refer to # the same object. app_params = deepcopy(self.AppParams) for key, value in zip(self._keys, values): if value is False: app_params[key].off() elif value is True: app_params[key].on() else: app_params[key].on(value) return app_params def __iter__(self): return self def __next__(self): return next(self._generator) def reset(self): self._generator = self._init_generator() class ParameterCombinations(ParameterIterBase): """Iterates over all combinations of parameters lexiographically""" def _init_generator(self): """Iterates over all possible combinations of parameters This method iterates over the cartesian product of parameter values """ for vals in product(*self._values): yield self._make_app_params(vals) def cmdline_generator(param_iter, PathToBin=None, PathToCmd=None, PathsToInputs=None, PathToOutput=None, PathToStderr='/dev/null', PathToStdout='/dev/null', UniqueOutputs=False, InputParam=None, OutputParam=None): """Generates command lines that can be used in a cluster environment param_iter : ParameterIterBase subclass instance PathToBin : Absolute location primary command (i.e. Python) PathToCmd : Absolute location of the command PathsToInputs : Absolute location(s) of input file(s) PathToOutput : Absolute location of output file PathToStderr : Path to stderr PathToStdout : Path to stdout UniqueOutputs : Generate unique tags for output files InputParam : Application input parameter (if not specified, assumes stdin is to be used) OutputParam : Application output parameter (if not specified, assumes stdout is to be used) """ # Make sure we have input(s) and output if not PathsToInputs: raise ValueError("No input file(s) specified.") if not PathToOutput: raise ValueError("No output file specified.") if not isinstance(PathsToInputs, list): PathsToInputs = [PathsToInputs] # PathToBin and PathToCmd can be blank if PathToBin is None: PathToBin = '' if PathToCmd is None: PathToCmd = '' # stdout_ and stderr_ do not have to be redirected if PathToStdout is None: stdout_ = '' else: stdout_ = '> "%s"' % PathToStdout if PathToStderr is None: stderr_ = '' else: stderr_ = '2> "%s"' % PathToStderr # Output can be redirected to stdout or specified output argument if OutputParam is None: output = '> "%s"' % PathToOutput stdout_ = '' else: output_param = param_iter.AppParams[OutputParam] output_param.on('"%s"' % PathToOutput) output = str(output_param) output_param.off() output_count = 0 base_command = ' '.join([PathToBin, PathToCmd]) for params in param_iter: # Support for multiple input files for inputfile in PathsToInputs: cmdline = [base_command] cmdline.extend(sorted(filter(None, map(str, params.values())))) # Input can come from stdin or specified input argument if InputParam is None: input = '< "%s"' % inputfile else: input_param = params[InputParam] input_param.on('"%s"' % inputfile) input = str(input_param) input_param.off() cmdline.append(input) if UniqueOutputs: cmdline.append(''.join([output, str(output_count)])) output_count += 1 else: cmdline.append(output) cmdline.append(stdout_) cmdline.append(stderr_) yield ' '.join(cmdline) def get_tmp_filename(tmp_dir=gettempdir(), prefix="tmp", suffix=".txt", result_constructor=FilePath): """ Generate a temporary filename and return as a FilePath object tmp_dir: the directory to house the tmp_filename prefix: string to append to beginning of filename Note: It is very useful to have prefix be descriptive of the process which is creating the temporary file. For example, if your temp file will be used to build a temporary blast database, you might pass prefix=TempBlastDB suffix: the suffix to be appended to the temp filename result_constructor: the constructor used to build the result filename (default: cogent.app.parameters.FilePath). Note that joining FilePath objects with one another or with strings, you must use the + operator. If this causes trouble, you can pass str as the the result_constructor. """ # check not none if not tmp_dir: tmp_dir = "" # if not current directory, append "/" if not already on path elif not tmp_dir.endswith("/"): tmp_dir += "/" chars = "abcdefghigklmnopqrstuvwxyz" picks = chars + chars.upper() + "0123456790" return result_constructor(tmp_dir) + result_constructor(prefix) +\ result_constructor("%s%s" % (''.join([choice(picks) for i in range(20)]), suffix)) def guess_input_handler(seqs, add_seq_names=False): """Returns the name of the input handler for seqs.""" if isinstance(seqs, str): if '\n' in seqs: # can't be a filename... return '_input_as_multiline_string' else: # assume it was a filename return '_input_as_string' if isinstance(seqs, list) and len(seqs) and isinstance(seqs[0], tuple): return '_input_as_seq_id_seq_pairs' if add_seq_names: return '_input_as_seqs' return '_input_as_lines' burrito-0.9.1/burrito.egg-info/0000755000076500000240000000000012527661551017333 5ustar caporasostaff00000000000000burrito-0.9.1/burrito.egg-info/dependency_links.txt0000644000076500000240000000000112527661551023401 0ustar caporasostaff00000000000000 burrito-0.9.1/burrito.egg-info/PKG-INFO0000644000076500000240000000544312527661551020436 0ustar caporasostaff00000000000000Metadata-Version: 1.1 Name: burrito Version: 0.9.1 Summary: Framework for wrapping and controlling command-line applications. Home-page: https://github.com/biocore/burrito Author: burrito development team Author-email: gregcaporaso@gmail.com License: BSD Description: burrito ======= |Build Status| |Coverage Status| burrito, canonically pronounced *boar-eee-toe*, is a Python framework for wrapping and controlling command-line applications. What's with the name? --------------------- This tool allows developers to wrap command-line applications, just as burritos wrap delicious foods. Both hide the potentially unsightly details. Installation ------------ To install burrito:: pip install burrito Running the tests ----------------- To run burrito's unit tests:: nosetests The pre-history of burrito -------------------------- burrito is derived from the `application controller framework `__ code, which was originally added to `PyCogent `__ and later moved to `scikit-bio `__. The contributors and/or copyright holders have agreed to make the code they wrote for PyCogent available under the BSD license. The original authors of the application controller framework code in PyCogent are Greg Caporaso (`@gregcaporaso `__), Sandra Smit, Micah Hamady, and Rob Knight (`@rob-knight `__). .. |Build Status| image:: https://travis-ci.org/biocore/burrito.svg?branch=master :target: https://travis-ci.org/biocore/burrito .. |Coverage Status| image:: https://coveralls.io/repos/biocore/burrito/badge.png :target: https://coveralls.io/r/biocore/burrito Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Utilities Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Operating System :: Unix Classifier: Operating System :: POSIX Classifier: Operating System :: MacOS :: MacOS X burrito-0.9.1/burrito.egg-info/requires.txt0000644000076500000240000000005612527661551021734 0ustar caporasostaff00000000000000future [test] nose >= 0.10.1 flake8 coverallsburrito-0.9.1/burrito.egg-info/SOURCES.txt0000644000076500000240000000053612527661551021223 0ustar caporasostaff00000000000000CHANGELOG.md LICENSE MANIFEST.in README.rst setup.py burrito/__init__.py burrito/parameters.py burrito/util.py burrito.egg-info/PKG-INFO burrito.egg-info/SOURCES.txt burrito.egg-info/dependency_links.txt burrito.egg-info/requires.txt burrito.egg-info/top_level.txt burrito/tests/__init__.py burrito/tests/test_parameters.py burrito/tests/test_util.pyburrito-0.9.1/burrito.egg-info/top_level.txt0000644000076500000240000000001012527661551022054 0ustar caporasostaff00000000000000burrito burrito-0.9.1/CHANGELOG.md0000644000076500000240000000111212527660643015760 0ustar caporasostaff00000000000000# burrito changelog ## Version 0.9.1 (2015-05-22) * Updated default temporary directory from ``/tmp`` to python's ``tempfile.gettempdir()``. This should address many of the issues with temporary files being written to ``/tmp``, which sometimes doesn't exist, doesn't provide a lot of storage, or is not shared across cluster nodes. It is still possible that individual burrito fillings (i.e., ``CommandLineApplication`` derived classes) can hard code ``/tmp``, so care should be taken when writing those derived classes to avoid that. ## Version 0.9.0 (2014-08-04) Initial release. burrito-0.9.1/LICENSE0000644000076500000240000000273312527660643015166 0ustar caporasostaff00000000000000Copyright (c) 2014, burrito development team. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the names burrito or biocore nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. burrito-0.9.1/MANIFEST.in0000644000076500000240000000016212527660643015711 0ustar caporasostaff00000000000000include CHANGELOG.md include LICENSE include README.rst graft burrito global-exclude *.pyc global-exclude *.pyo burrito-0.9.1/PKG-INFO0000644000076500000240000000544312527661551015256 0ustar caporasostaff00000000000000Metadata-Version: 1.1 Name: burrito Version: 0.9.1 Summary: Framework for wrapping and controlling command-line applications. Home-page: https://github.com/biocore/burrito Author: burrito development team Author-email: gregcaporaso@gmail.com License: BSD Description: burrito ======= |Build Status| |Coverage Status| burrito, canonically pronounced *boar-eee-toe*, is a Python framework for wrapping and controlling command-line applications. What's with the name? --------------------- This tool allows developers to wrap command-line applications, just as burritos wrap delicious foods. Both hide the potentially unsightly details. Installation ------------ To install burrito:: pip install burrito Running the tests ----------------- To run burrito's unit tests:: nosetests The pre-history of burrito -------------------------- burrito is derived from the `application controller framework `__ code, which was originally added to `PyCogent `__ and later moved to `scikit-bio `__. The contributors and/or copyright holders have agreed to make the code they wrote for PyCogent available under the BSD license. The original authors of the application controller framework code in PyCogent are Greg Caporaso (`@gregcaporaso `__), Sandra Smit, Micah Hamady, and Rob Knight (`@rob-knight `__). .. |Build Status| image:: https://travis-ci.org/biocore/burrito.svg?branch=master :target: https://travis-ci.org/biocore/burrito .. |Coverage Status| image:: https://coveralls.io/repos/biocore/burrito/badge.png :target: https://coveralls.io/r/biocore/burrito Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Utilities Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Operating System :: Unix Classifier: Operating System :: POSIX Classifier: Operating System :: MacOS :: MacOS X burrito-0.9.1/README.rst0000644000076500000240000000272112527660643015645 0ustar caporasostaff00000000000000burrito ======= |Build Status| |Coverage Status| burrito, canonically pronounced *boar-eee-toe*, is a Python framework for wrapping and controlling command-line applications. What's with the name? --------------------- This tool allows developers to wrap command-line applications, just as burritos wrap delicious foods. Both hide the potentially unsightly details. Installation ------------ To install burrito:: pip install burrito Running the tests ----------------- To run burrito's unit tests:: nosetests The pre-history of burrito -------------------------- burrito is derived from the `application controller framework `__ code, which was originally added to `PyCogent `__ and later moved to `scikit-bio `__. The contributors and/or copyright holders have agreed to make the code they wrote for PyCogent available under the BSD license. The original authors of the application controller framework code in PyCogent are Greg Caporaso (`@gregcaporaso `__), Sandra Smit, Micah Hamady, and Rob Knight (`@rob-knight `__). .. |Build Status| image:: https://travis-ci.org/biocore/burrito.svg?branch=master :target: https://travis-ci.org/biocore/burrito .. |Coverage Status| image:: https://coveralls.io/repos/biocore/burrito/badge.png :target: https://coveralls.io/r/biocore/burrito burrito-0.9.1/setup.cfg0000644000076500000240000000007312527661551015774 0ustar caporasostaff00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 burrito-0.9.1/setup.py0000644000076500000240000000354612527660643015676 0ustar caporasostaff00000000000000#!/usr/bin/env python # ---------------------------------------------------------------------------- # Copyright (c) 2014--, burrito development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- from setuptools import find_packages, setup __version__ = "0.9.1" classes = """ Development Status :: 5 - Production/Stable Intended Audience :: Developers License :: OSI Approved :: BSD License Topic :: Software Development :: Libraries Topic :: Software Development :: Libraries :: Python Modules Topic :: Utilities Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 Operating System :: Unix Operating System :: POSIX Operating System :: MacOS :: MacOS X """ classifiers = [s.strip() for s in classes.split('\n') if s] description = ('Framework for wrapping and controlling command-line ' 'applications.') with open('README.rst') as f: long_description = f.read() setup(name='burrito', version=__version__, license='BSD', description=description, long_description=long_description, author="burrito development team", author_email="gregcaporaso@gmail.com", maintainer="burrito development team", maintainer_email="gregcaporaso@gmail.com", url='https://github.com/biocore/burrito', test_suite='nose.collector', packages=find_packages(), install_requires=['future'], extras_require={'test': ["nose >= 0.10.1", "flake8", "coveralls"]}, classifiers=classifiers)