def test_result_list_dataframe_returns_pandas_dict_if_called_directly( self): resultset = ResultList(name='test results') resultset.map_to(Issue()) result_issue = Issue() result_issue.description = 'This is a test item' resultset.append(result_issue) another_result_issue = Issue() another_result_issue.description = 'This is a test item' resultset.append(another_result_issue) self.assertIsInstance(resultset.dataframe, pd.DataFrame)
def test_table_renders_filter_results_multi_results_without_combine( self, mock_heading, mock_table): self.tearDown() Config = namedtuple('Config', 'rows columns style') with patch('pyccata.core.filter.Filter.results', new_callable=PropertyMock) as mock_results: results_set_one = ResultList(name='test results') result_issue = Issue() result_issue.description = 'This is a test item' results_set_one.append(result_issue) results_set_two = ResultList(name='another set of tests') another_result_issue = Issue() another_result_issue.description = 'This is a test item' results_set_two.append(another_result_issue) multi_results = MultiResultList() multi_results.combine = False multi_results.append(results_set_one) multi_results.append(results_set_two) mock_results.return_value = multi_results Filter = namedtuple('Filter', 'query max_results namespace') rows = Filter(query='project=mssportal', max_results=5, namespace='pyccata.core') columns = ['Name', 'Description'] config = Config(rows=rows, columns=columns, style='Light heading 1') table = Table(self._thread_manager, config) document = ReportManager() table.render(document) self.assertEquals(2, mock_table.call_count) self.assertEquals(2, mock_heading.call_count)
def _convert_results(results): """ Converts a ``jira.client.ResultList`` of ``jira.resources.Issues`` into a ``pyccata.core.resources.ResultList`` of ``pyccata.core.resources.Issues`` """ result_set = ResultList() result_set.total = results.total if hasattr(results, 'total') else len(results) for issue in results: item = Issue() item.key = getattr(issue, 'key') if hasattr(issue, 'key') else None item.summary = getattr(issue.fields, 'summary') if hasattr( issue.fields, 'summary') else None item.issuetype = getattr(issue.fields, 'issuetype') if hasattr( issue.fields, 'issuetype') else None item.created = getattr(issue.fields, 'created') if hasattr( issue.fields, 'created') else None item.updated = getattr(issue.fields, 'updated') if hasattr( issue.fields, 'updated') else None item.priority = getattr(issue.fields, 'priority') if hasattr( issue.fields, 'priority') else None item.description = getattr(issue.fields, 'description') if hasattr( issue.fields, 'description') else None item.status = getattr(issue.fields, 'status') if hasattr( issue.fields, 'status') else None item.project = getattr(issue.fields, 'project') if hasattr( issue.fields, 'project') else None item.fixVersions = getattr(issue.fields, 'fixVersions') if hasattr( issue.fields, 'fixVersions') else None item.resolution = getattr(issue.fields, 'resolution') if hasattr( issue.fields, 'resolution') else None item.resolutiondate = getattr( issue.fields, 'resolutiondate') if hasattr( issue.fields, 'resolutiondate') else None item.creator = getattr(issue.fields, 'creator') if hasattr( issue.fields, 'creator') else None item.assignee = getattr(issue.fields, 'assignee') if hasattr( issue.fields, 'assignee') else None item.attachments = Jira._get_attachments( getattr(issue.fields, 'attachment' ) if hasattr(issue.fields, 'attachment') else None) item.release_text = getattr( issue.fields, 'customfield_10600') if hasattr( issue.fields, 'customfield_10600') else None item.business_representative = getattr( issue.fields, 'customfield_10700') if hasattr( issue.fields, 'customfield_10700') else None item.rollout_instructions = getattr( issue.fields, 'customfield_10800') if hasattr( issue.fields, 'customfield_10800') else None item.rollback_instructions = getattr( issue.fields, 'customfield_10801') if hasattr( issue.fields, 'customfield_10801') else None if hasattr(issue.fields, 'customfield_10802'): item.pipelines = [ item.value for item in getattr(issue.fields, 'customfield_10802') ] result_set.append(item) return result_set
class ThreadableCommand(Threadable): """ This class can be used to process shell commands """ # pylint: disable=too-many-instance-attributes,too-many-public-methods # It is understood that this class requires a number of attributes and # accessor methods. MAX_PRIORITY = 10000 PRIORITY = 0 _redirect_regex = None _commands = None _redirects = None PRIORITY = 1000 _results = None _block = None @property def results(self): """ Get the results of command """ return self._results @property def threadmanager(self): """ Get the current loaded threadmanager """ return self._thread_manager @accepts(ThreadManager, tuple, append=bool) def __init__(self, threadmanager, config, append=True): """ Initialise the ThreadableCommand object """ self._thread_manager = threadmanager self._commands = [] self._observers = [] self._redirects = [] self._results = ResultList() # pylint: disable=protected-access # verification that Configuration is not initialised and initialise it # with the thread manager - this is desired behaviour against the singleton # to prevent it being loaded multiple times inside threading objects if Configuration._instance is None: Configuration._instance = self.threadmanager.configuration self.validate_setup(config) super().__init__(**config._asdict()) if append: self.threadmanager.append(self) @accepts( name=str, command=str, input_directory=(None, str), output_directory=(None, str), wait_for=(None, str, Threadable) ) def setup(self, name='', command='', input_directory=None, output_directory=None, wait_for=None): """ Sets up the thread and builds the command structure. @param name string @param command string @param input_directory string or None @param output_directory string or None @param wait_for string, None or ThreadableCommand If this method is overridden, the overriding method must call back up to parent or implement this functionality directly. Failure to do this will prevent the thread from executing. The flag `wait_for` will prevent the thread from executing until the wait_for thread has completed. This is useful in the event a command needs the complete output from another thread before it can work, for example, downloading data then parsing it. If `wait_for` is None, the thread will execute as soon as there is room in the pool. (Default pool size = ThreadManager.POOL_SIZE). """ # pylint: disable=arguments-differ # This method provides specific implementations of *args and **kwargs # pylint: disable=too-many-arguments # This method requires a larger number of arguments than the standard self.thread_name = name self._input_directory = Replacements().replace(input_directory) self._output_directory = Replacements().replace( output_directory, additional=ThreadableCommand.replacements(output_directory) ) self._build_command(command) self._block = wait_for @accepts(str) def _build_command(self, command_string): """ Breaks down a command string and returns a list of commands initialised for subprocess.Popen @param command_string string """ command_list = command_string.split(' | ') for command_structure in command_list: command = Command() redirect_regex = re.compile( r"(?P<command>.*?)( ((?P<redirect>[&\d]?)>+ ?&?(?P<filename>\S+)))( ?< ?(?P<infile>.*))?", re.DOTALL ) matches = [match.groupdict() for match in redirect_regex.finditer(command_structure)] if len(matches) == 0: try: structure = shlex.split(command_structure) except ValueError as exception: Logger().error('Failed to parse command \'' + command_structure + '\'') raise command.command = structure[0] command.arguments = structure[1:] else: try: structure = shlex.split(matches[0]['command']) except ValueError as exception: Logger().error('Failed to parse command \'' + matches[0]['command'] + '\'') raise exception command.command = structure[0] command.arguments = structure[1:] for match in matches: command.redirects.append( Redirect( redirect_input=( int(match['redirect']) if match['redirect'].isdigit() else match['redirect'] ), redirect_output=int(match['filename']) if match['filename'].isdigit() else match['filename'] ) ) self._commands.append(command) def run(self): """ Executes the current thread """ processes = [] for command in self._commands: last_pipe = processes[-1].stdout if len(processes) > 0 else None processes.append( Popen( [command.command] + command.arguments, stdin=last_pipe, stdout=command.stdout, stderr=command.stderr ) ) command.return_code = processes[-1].poll() if processes[-1].stdout is not None and hasattr(processes[-1].stdout, 'readline'): for line in iter(processes[-1].stdout.readline, b''): item = CommandLineResultItem() item.line = line.decode('utf8').strip() self._results.append(item) stderr = [] if processes[-1].stderr is not None: for line in iter(processes[-1].stderr.readline, b''): stderr.append(line.decode('utf8').strip()) processes[-1].communicate() if len(stderr) != 0: self.failure = ThreadFailedError(stderr) self._complete = True @staticmethod def replacements(string_to_search): """ Compiles a list of optional string replacements for command thread strings """ replacements = {} function_regex = re.compile( r'.*?[-_.]{1}\{(?P<what>.*?)\.(?P<command>.*?)\}', re.DOTALL ) matches = [match.groupdict() for match in function_regex.finditer(string_to_search)] if len(matches) == 0: return None for match in matches: string = '{0}.{1}'.format(match['what'], match['command']) what = match['what'] if Replacements().find(what.upper()): what = Replacements().replace('{' + what.upper() + '}') command = '{0}_helper'.format(match['command']) try: command = include(command, 'pyccata', 'helpers') except InvalidModuleError: Logger().error( 'Invalid helper method {0} specified for {1}'.format( match['command'], string ) ) raise replacements[string] = command(what) return replacements @staticmethod def logdir(): """ Get a log directory for the command output """ path = os.path.join(Replacements().replace('{BASE_PATH}'), 'log') if not os.path.exists(path): os.makedirs(path) return path