def test_settings_2(self, mock_ip): # - if index is None, field and value are valid, it'll set for all IPs test_wm = WorkflowManager([u'a', u'b', u'c']) self.assertEqual(3, mock_ip.call_count) # to make sure we're using the mock, not real IP test_wm.settings(None, u'filter repeats', True) for i in xrange(3): self.assertEqual(True, test_wm._settings[i][u'filter repeats'])
def test_intervs_3(self, mock_guc, mock_rfa, mock_rep): """Ensure _intervs() calls everything in the right order, with the right args & settings. This uses the default *except* requires removing rests, so it's more complex.""" test_settings = {'simple or compound': 'compound', 'quality': False} test_pieces = [MagicMock(spec_set=IndexedPiece) for _ in range(3)] returns = [ pandas.DataFrame([pandas.Series(['3', 'Rest', '5'])], index=[['interval.IntervalIndexer'], ['0,1']]).T for i in range(len(test_pieces)) ] for piece in test_pieces: piece.get_data.side_effect = lambda *x: returns.pop(0) exp_series_ind = ('interval.IntervalIndexer', '0,1') expected_df = pandas.DataFrame( {exp_series_ind: pandas.Series(['3', '5'], index=[0, 2])}) exp_analyzers = [noterest.NoteRestIndexer, interval.IntervalIndexer] test_wc = WorkflowManager(test_pieces) test_wc.settings(None, 'count frequency', False) actual = test_wc._intervs() # pylint: disable=protected-access self.assertEqual(0, mock_guc.call_count) self.assertEqual(len(test_pieces), len(actual)) self.assertEqual(0, mock_rfa.call_count) self.assertEqual(0, mock_rep.call_count) for piece in test_pieces: piece.get_data.assert_called_once_with(exp_analyzers, test_settings) for i in range(len(actual)): self.assertSequenceEqual(list(expected_df.columns), list(actual[i].columns)) self.assertSequenceEqual(list(expected_df[exp_series_ind].index), list(actual[i][exp_series_ind].index)) self.assertSequenceEqual(list(expected_df[exp_series_ind].values), list(actual[i][exp_series_ind].values))
def test_intervs_3(self, mock_int, mock_nri, mock_rep, mock_rfa, mock_ror): # --> test whether _intervs() when we specify an impossible voice pair ('[[0, 1, 2]]') # 1.) prepare the test and mocks test_settings = {u'simple or compound': u'compound', u'quality': False} test_pieces = [MagicMock(IndexedPiece, name=x) for x in [u'test1', u'test2', u'test3']] the_dicts = [MagicMock(dict, name=u'get_data() piece' + str(i), return_value=[4]) \ for i in xrange(3)] returns = the_dicts def side_effect(*args): # NB: we need to accept "args" as a mock framework formality # pylint: disable=W0613 return returns.pop(0) for piece in test_pieces: piece.get_data.side_effect = side_effect mock_ror.return_value = [pandas.Series(['Rest', 'P5', 'm3']) for _ in xrange(len(test_pieces))] expected = [pandas.Series(['Rest', 'P5', 'm3']) for _ in xrange(len(test_pieces))] expected_pairs = [[0, 1, 2]] expected_error_message = WorkflowManager._REQUIRE_PAIRS_ERROR % len(expected_pairs[0]) # 2.) run the test test_wc = WorkflowManager(test_pieces) # (have to set the voice-pair settings) for i in xrange(len(test_pieces)): test_wc._settings[i][u'voice combinations'] = unicode(expected_pairs) # 3.) verify self.assertRaises(RuntimeError, test_wc._intervs) try: test_wc._intervs() except RuntimeError as runerr: self.assertEqual(expected_error_message, runerr.message)
def test_intervs_1(self, mock_guc, mock_rfa, mock_rep): """Ensure _intervs() calls everything in the right order, with the right args & settings. This test uses all the default settings.""" test_settings = {'simple or compound': 'compound', 'quality': False} test_pieces = [MagicMock(spec_set=IndexedPiece) for _ in range(3)] returns = ['get_data() {}'.format(i) for i in range(len(test_pieces))] for piece in test_pieces: piece.get_data.side_effect = lambda *x: returns.pop(0) expected = ['get_data() {}'.format(i) for i in range(len(test_pieces))] exp_analyzers = [noterest.NoteRestIndexer, interval.IntervalIndexer] test_wc = WorkflowManager(test_pieces) test_wc.settings(None, 'include rests', True) actual = test_wc._intervs() # pylint: disable=protected-access self.assertEqual(0, mock_guc.call_count) self.assertEqual(len(test_pieces), len(expected), len(actual)) self.assertEqual(0, mock_rep.call_count) mock_rfa.assert_called_once_with('interval.IntervalIndexer') for piece in test_pieces: piece.get_data.assert_called_once_with(exp_analyzers, test_settings) for i in range(len(actual)): # NB: in real use, _run_freq_agg() would aggregate a piece's voice pairs and save it in # self._result... but since that method's mocked out, we have to check here the # return of each piece's get_data() call self.assertSequenceEqual(expected[i], actual[i])
def test_intervs_1(self, mock_int, mock_nri, mock_rfa, mock_ror): # --> test whether _intervs() calls all those things in the right order, with the right # args, using all the default settings # 1.) prepare the test and mocks test_settings = {u'simple or compound': u'compound', u'quality': False} test_pieces = [MagicMock(IndexedPiece, name=x) for x in [u'test1', u'test2', u'test3']] returns = [MagicMock(dict, name=u'get_data() piece' + str(i), return_value=[4]) \ for i in xrange(3)] def side_effect(*args): # NB: we need to accept "args" as a mock framework formality # pylint: disable=W0613 return returns.pop(0) for piece in test_pieces: piece.get_data.side_effect = side_effect mock_ror.return_value = [pandas.Series(['Rest', 'P5', 'm3']) for _ in xrange(len(test_pieces))] expected = [pandas.Series(['P5', 'm3'], index=[1, 2]) for _ in xrange(len(test_pieces))] # 2.) run the test test_wc = WorkflowManager(test_pieces) actual = test_wc._intervs() # 3.) confirm everything was called in the right order for piece in test_pieces: self.assertEqual(1, piece.get_data.call_count) piece.get_data.assert_called_once_with([mock_nri, mock_int], test_settings) self.assertEqual(len(test_pieces), mock_ror.call_count) mock_rfa.assert_called_once_with() self.assertEqual(len(test_pieces), len(expected), len(actual)) for i in xrange(len(actual)): # NB: in real use, _run_freq_agg() would aggregate a piece's voice pairs, so we # wouldn't need to ask for the [0] index here... but also, this experiment shouldn't # call _run_freq_agg() anyway self.assertSequenceEqual(list(expected[i]), list(actual[i][0])) self.assertSequenceEqual(list(expected[i].index), list(actual[i][0].index))
def test_interval_ngrams_2(self, mock_two, mock_all, mock_var, mock_rfa): # --> same as test_interval_ngrams_1(), but with "count frequency" set to False # 1.) prepare mocks ind_pieces = [MagicMock(spec=IndexedPiece) for _ in xrange(3)] mock_rfa.return_value = u'mock_rfa() return value' mock_two.return_value = [u'mock_two() return value'] mock_all.return_value = [u'mock_all() return value'] mock_var.return_value = [u'mock_var() return value'] expected = [mock_all.return_value, mock_two.return_value, mock_var.return_value] # 2.) run the test test_wm = WorkflowManager(ind_pieces) test_wm.settings(0, u'voice combinations', u'all') test_wm.settings(1, u'voice combinations', u'all pairs') test_wm.settings(2, u'voice combinations', u'[[0, 1]]') test_wm.settings(None, 'count frequency', False) actual = test_wm._interval_ngrams() # 3.) verify the mocks # NB: in actual use, _run_freq_agg() would have the final say on the value of # test_wm._result... but it's mocked out, which means we can test whether # _interval_ngrams() puts the right stuff there self.assertEqual(3, len(test_wm._result)) for ret_val in expected: self.assertTrue(ret_val in test_wm._result) mock_two.assert_called_once_with(1) mock_all.assert_called_once_with(0) mock_var.assert_called_once_with(2) self.assertEqual(0, mock_rfa.call_count) self.assertEqual(expected, actual) self.assertSequenceEqual(expected, test_wm._result)
def test_intervs_3(self, mock_guc, mock_rfa, mock_rep): """Ensure _intervs() calls everything in the right order, with the right args & settings. This uses the default *except* requires removing rests, so it's more complex.""" test_settings = {'simple or compound': 'compound', 'quality': False} test_pieces = [MagicMock(spec_set=IndexedPiece) for _ in range(3)] returns = [pandas.DataFrame([pandas.Series(['3', 'Rest', '5'])], index=[['interval.IntervalIndexer'], ['0,1']]).T for i in range(len(test_pieces))] for piece in test_pieces: piece.get_data.side_effect = lambda *x: returns.pop(0) exp_series_ind = ('interval.IntervalIndexer', '0,1') expected_df = pandas.DataFrame({exp_series_ind: pandas.Series(['3', '5'], index=[0, 2])}) exp_analyzers = [noterest.NoteRestIndexer, interval.IntervalIndexer] test_wc = WorkflowManager(test_pieces) test_wc.settings(None, 'count frequency', False) actual = test_wc._intervs() # pylint: disable=protected-access self.assertEqual(0, mock_guc.call_count) self.assertEqual(len(test_pieces), len(actual)) self.assertEqual(0, mock_rfa.call_count) self.assertEqual(0, mock_rep.call_count) for piece in test_pieces: piece.get_data.assert_called_once_with(exp_analyzers, test_settings) for i in range(len(actual)): self.assertSequenceEqual(list(expected_df.columns), list(actual[i].columns)) self.assertSequenceEqual(list(expected_df[exp_series_ind].index), list(actual[i][exp_series_ind].index)) self.assertSequenceEqual(list(expected_df[exp_series_ind].values), list(actual[i][exp_series_ind].values))
def test_load_1(self): # that "get_data" is called correctly on each thing test_wc = WorkflowManager([]) test_wc._data = [mock.MagicMock(spec=IndexedPiece) for _ in xrange(5)] test_wc.load(u'pieces') for mock_piece in test_wc._data: mock_piece.get_data.assert_called_once_with([noterest.NoteRestIndexer]) self.assertTrue(test_wc._loaded)
def test_output_4(self): """ensure RuntimeError if self._result is None""" test_wc = WorkflowManager([]) test_wc._result = None # just in case self.assertRaises(RuntimeError, test_wc.output, 'R histogram') try: test_wc.output('R histogram') except RuntimeError as run_err: self.assertEqual(WorkflowManager._NO_RESULTS_ERROR, run_err.args[0])
def test_filter_dataframe_3(self): """test with top_x=3, threshold=5 (so the top_x still removes after threshold), name=auto""" test_wc = WorkflowManager([]) test_wc._result = pandas.DataFrame({'data': pandas.Series([i for i in range(10, 0, -1)])}) expected = pandas.DataFrame({'data': pandas.Series([10, 9, 8])}) actual = test_wc._filter_dataframe(top_x=3, threshold=5) self.assertEqual(len(expected.columns), len(actual.columns)) for i in expected.columns: self.assertSequenceEqual(list(expected[i].index), list(actual[i].index)) self.assertSequenceEqual(list(expected[i].values), list(actual[i].values))
def test_lilypond_1a(self): """error conditions: if 'count frequency' is True (but the lengths are okay)""" test_wm = WorkflowManager(['fake piece']) test_wm._data = ['fake IndexedPiece'] test_wm._result = ['fake results'] # test twice like this to make sure (1) the try/except will definitely catch something, and # (2) we're not getting hit by another RuntimeError, of which there could be many with self.assertRaises(RuntimeError) as run_err: test_wm._make_lilypond(['paths']) self.assertEqual(WorkflowManager._COUNT_FREQUENCY_MESSAGE, run_err.exception.args[0])
def test_settings_9(self, mock_ip): # - if trying to set 'offset interval' to 0, it should actually be set to None test_wm = WorkflowManager([u'a', u'b', u'c']) self.assertEqual(3, mock_ip.call_count) # to make sure we're using the mock, not real IP # "None" is default value, so first set to non-zero test_wm.settings(1, u'offset interval', 4.0) self.assertEqual(4.0, test_wm._settings[1][u'offset interval']) # now run our test test_wm.settings(1, u'offset interval', 0) self.assertEqual(None, test_wm._settings[1][u'offset interval'])
def test_get_dataframe_2(self): # test with name='asdf', top_x=3, threshold=auto test_wc = WorkflowManager([]) test_wc._result = pandas.Series([i for i in xrange(10, 0, -1)]) expected = pandas.DataFrame({'asdf': pandas.Series([10, 9, 8])}) actual = test_wc._get_dataframe('asdf', 3) self.assertEqual(len(expected.columns), len(actual.columns)) for i in expected.columns: self.assertSequenceEqual(list(expected.loc[:,i].index), list(actual.loc[:,i].index)) self.assertSequenceEqual(list(expected.loc[:,i].values), list(actual.loc[:,i].values))
def test_get_dataframe_4(self): # test with name=auto, top_x=5, threshold=7 (so threshold leaves fewer than 3 results) test_wc = WorkflowManager([]) test_wc._result = pandas.Series([i for i in xrange(10, 0, -1)]) expected = pandas.DataFrame({'data': pandas.Series([10, 9, 8])}) actual = test_wc._get_dataframe(top_x=5, threshold=7) self.assertEqual(len(expected.columns), len(actual.columns)) for i in expected.columns: self.assertSequenceEqual(list(expected.loc[:,i].index), list(actual.loc[:,i].index)) self.assertSequenceEqual(list(expected.loc[:,i].values), list(actual.loc[:,i].values))
def test_output_3(self): """ensure RuntimeError if there's an invalid instruction""" test_wc = WorkflowManager([]) test_wc._result = [5] # make sure that's not what causes it bad_instruction = 'eat dirt' self.assertRaises(RuntimeError, test_wc.output, bad_instruction) try: test_wc.output(bad_instruction) except RuntimeError as run_err: self.assertEqual(WorkflowManager._UNRECOGNIZED_INSTRUCTION.format(bad_instruction), run_err.args[0])
def test_filter_dataframe_5(self): """test with top_x=3, threshold=auto, name='asdf'; many input columns""" test_wc = WorkflowManager([]) test_wc._result = pandas.DataFrame({('1', 'b'): pandas.Series([i for i in range(10, 0, -1)]), ('1', 'z'): pandas.Series([i for i in range(10, 20)]), ('2', 'e'): pandas.Series([i for i in range(40, 900)])}) expected = pandas.DataFrame({'asdf': pandas.Series([10, 9, 8])}) actual = test_wc._filter_dataframe(top_x=3, name='asdf') self.assertEqual(len(expected.columns), len(actual.columns)) for i in expected.columns: self.assertSequenceEqual(list(expected[i].index), list(actual[i].index)) self.assertSequenceEqual(list(expected[i].values), list(actual[i].values))
def test_ngrams_6(self): # test madrigal51 with all-voice 2-grams and rests test_wm = WorkflowManager(['vis/tests/corpus/madrigal51.mxl']) test_wm.settings(0, 'voice combinations', 'all') test_wm.settings(None, 'include rests', True) test_wm.load('pieces') test_wm.settings(0, 'continuer', '_') actual = test_wm.run('interval n-grams') exp_ind = list(NGramsTests.EXPECTED_6.index) act_ind = list(actual.index) for ind_item in exp_ind: self.assertTrue(ind_item in act_ind) for ind_item in exp_ind: self.assertEqual(NGramsTests.EXPECTED_6[ind_item], actual[ind_item])
def test_ngrams_2(self): # test all two-part combinations of bwv77; 5-grams test_wm = WorkflowManager(['vis/tests/corpus/bwv77.mxl']) test_wm.load('pieces') test_wm.settings(0, 'voice combinations', 'all pairs') test_wm.settings(0, 'n', 5) test_wm.settings(0, 'continuer', '_') actual = test_wm.run('interval n-grams')[:10] exp_ind = list(NGramsTests.EXPECTED_2.index) act_ind = list(actual.index) for ind_item in exp_ind: self.assertTrue(ind_item in act_ind) for ind_item in exp_ind: self.assertEqual(NGramsTests.EXPECTED_2[ind_item], actual[ind_item])
def test_interval_ngrams_1(self, mock_two, mock_all, mock_var, mock_rfa): # --> test with three pieces, each of which requires a different helper # 1.) prepare mocks ind_pieces = [MagicMock(spec=IndexedPiece) for _ in xrange(3)] mock_rfa.return_value = u'mock_rfa() return value' mock_two.return_value = [u'mock_two() return value'] mock_all.return_value = [u'mock_all() return value'] mock_var.return_value = [u'mock_var() return value'] expected = [mock_all.return_value, mock_two.return_value, mock_var.return_value] # 2.) run the test test_wm = WorkflowManager(ind_pieces) test_wm.settings(0, u'voice combinations', u'all') test_wm.settings(1, u'voice combinations', u'all pairs') test_wm.settings(2, u'voice combinations', u'[[0, 1]]') actual = test_wm._interval_ngrams() # 3.) verify the mocks # NB: in actual use, _run_freq_agg() would have the final say on the value of # test_wm._result... but it's mocked out, which means we can test whether # _interval_ngrams() puts the right stuff there mock_two.assert_called_once_with(1) mock_all.assert_called_once_with(0) mock_var.assert_called_once_with(2) mock_rfa.assert_called_once_with() self.assertSequenceEqual(expected, actual) self.assertSequenceEqual(expected, test_wm._result)
def test_ngrams_4(self): # test all voices of bwv2; 3-grams; simple intervals test_wm = WorkflowManager(['vis/tests/corpus/bwv2.xml']) test_wm.load('pieces') test_wm.settings(0, 'voice combinations', 'all') test_wm.settings(0, 'n', 2) test_wm.settings(None, 'simple intervals', True) actual = test_wm.run('interval n-grams')[:10] exp_ind = list(NGramsTests.EXPECTED_4.index) act_ind = list(actual.index) for ind_item in exp_ind: self.assertTrue(ind_item in act_ind) for ind_item in exp_ind: self.assertEqual(NGramsTests.EXPECTED_4[ind_item], actual[ind_item])
def test_ngrams_1(self): # test the two highest voices of bwv77; 2-grams test_wm = WorkflowManager(['vis/tests/corpus/bwv77.mxl']) test_wm.load('pieces') test_wm.settings(0, 'voice combinations', '[[0, 1]]') test_wm.settings(0, 'n', 2) test_wm.settings(0, 'continuer', '_') actual = test_wm.run('interval n-grams') exp_ind = list(NGramsTests.EXPECTED_1.index) act_ind = list(actual.index) for ind_item in exp_ind: self.assertTrue(ind_item in act_ind) for ind_item in exp_ind: self.assertEqual(NGramsTests.EXPECTED_1[ind_item], actual[ind_item])
def test_histogram_1(self, mock_call, mock_gdf, mock_join): # with specified pathname; last experiment was intervals with 20 pieces; self._result is DF test_wc = WorkflowManager([]) test_wc._previous_exp = u'intervals' test_wc._data = [1 for _ in xrange(20)] test_wc._result = MagicMock(spec=pandas.DataFrame) path = u'pathname!' actual = test_wc._make_histogram(path) self.assertEqual(0, mock_gdf.call_count) expected_args = [u'Rscript', u'--vanilla', mock_join.return_value, path + u'.dta', path + u'.png', u'int', u'20'] mock_call.assert_called_once_with(expected_args) self.assertEqual(path + u'.png', actual) self.assertEqual(1, mock_join.call_count)
def test_ngrams_9c(self): # same as 9b but 'interval quality' is set to False (by default). test_wm = WorkflowManager(['vis/tests/corpus/Kyrie_short.krn']) test_wm.load() test_wm.settings(0, 'voice combinations', '[[1,3]]') test_wm.settings(0, 'n', 2) test_wm.settings(0, 'offset interval', 2.0) actual = test_wm.run('interval n-grams') exp_ind = list(NGramsTests.EXPECTED_9c.index) act_ind = list(actual.index) self.assertEqual(len(exp_ind), len(act_ind)) for ind_item in exp_ind: self.assertTrue(ind_item in act_ind) self.assertEqual(NGramsTests.EXPECTED_9c[ind_item], actual[ind_item])
def test_load_3(self): # NB: this is more of an integration test test_wc = WorkflowManager([u'vis/tests/corpus/try_opus.krn']) test_wc.load('pieces') self.assertEqual(3, len(test_wc)) # NOTE: we have to do this by digging until music21 imports metadata from **kern files, at # which point we'll be able to use our very own metadata() method exp_names = [u'Alex', u'Sarah', u'Emerald'] for i in xrange(3): # first Score gets some extra metadata which_el = 5 if i == 0 else 3 piece = test_wc._data[i]._import_score() self.assertTrue(isinstance(piece[which_el], GlobalReference)) self.assertEqual(u'COM', piece[which_el].code) self.assertEqual(exp_names[i], piece[which_el].value)
def test_run_off_rep_4(self, mock_off, mock_rep): # run offset and repeat indexer # setup workm = WorkflowManager(['', '', '']) workm._data = [None, MagicMock(spec=IndexedPiece), None] workm._data[1].get_data.return_value = 24 workm.settings(1, 'offset interval', 0.5) workm.settings(1, 'filter repeats', True) in_val = 42 # run actual = workm._run_off_rep(1, in_val) # test self.assertEqual(workm._data[1].get_data.return_value, actual) self.assertEqual(2, workm._data[1].get_data.call_count) workm._data[1].get_data.assert_any_call([mock_off], {'quarterLength': 0.5}, in_val) workm._data[1].get_data.assert_any_call([mock_rep], {}, workm._data[1].get_data.return_value)
def test_extra_pairs_5(self): # --> when there are lots of pairs, only some of which are desired, and there are invalid vert_ints = {'4,20': 0, '0,1': 1, '11,12': 4, '0,2': 2, '1,2': 3, '256,128': 12} combos = [[0, 1], [1, 2, 3, 4, 5], [0, 2], [1, 2], [9, 11, 43], [4]] expected = {'0,1': 1, '0,2': 2, '1,2': 3} actual = WorkflowManager._remove_extra_pairs(vert_ints, combos) self.assertSequenceEqual(expected, actual)
def test_output_2(self, mock_lily): # ensure output() calls _make_lilypond() as required # 1: prepare lily_path = u'the_path' mock_lily.return_value = lily_path test_wc = WorkflowManager([]) test_wc._previous_exp = u'intervals' test_wc._data = [1 for _ in xrange(20)] test_wc._result = MagicMock(spec=pandas.DataFrame) path = u'pathname!' expected_args = [path] # 2: run actual = test_wc.output('LilyPond', path) # 3: check self.assertEqual(lily_path, actual) mock_lily.assert_called_once_with(*expected_args)
def test_intervals_7(self): # TODO: add a frequency-counted test """same as test_6 *but* no quality""" # NB: the "expected" was hand-counted expected = pandas.read_csv(os.path.join(VIS_PATH, 'tests', 'expecteds', 'bwv77', 'SA_intervals_nq.csv'), comment='#', index_col=0, header=[0, 1], quotechar="'", dtype='object') test_wm = WorkflowManager([os.path.join(VIS_PATH, 'tests', 'corpus', 'bwv77.mxl')]) test_wm.load('pieces') test_wm.settings(0, 'voice combinations', '[[0, 1]]') test_wm.settings(None, 'count frequency', False) test_wm.settings(None, 'interval quality', False) actual = test_wm.run('intervals') self.assertEqual(1, len(actual)) actual = actual[0].dropna() self.assertDataFramesEqual(expected, actual)
def test_output_5(self, mock_table): """ensure output() calls export() as required""" # 1: prepare export_path = 'the_path' mock_table.return_value = export_path test_wc = WorkflowManager([]) test_wc._previous_exp = 'intervals' test_wc._data = [1 for _ in range(20)] test_wc._result = MagicMock(spec=pandas.DataFrame) path = 'pathname!' expected_args = ['Excel', path, None, None] # 2: run actual = test_wc.output('Excel', path) # 3: check self.assertEqual(export_path, actual) mock_table.assert_called_once_with(*expected_args)
def test_extra_pairs_2(self): # testing WorkflowManager._remove_extra_pairs() # --> when no pairs are desired vert_ints = {'0,1': 1, '0,2': 2, '1,2': 3} combos = [] expected = {} actual = WorkflowManager._remove_extra_pairs(vert_ints, combos) self.assertSequenceEqual(expected, actual)
def test_extra_pairs_3(self): # testing WorkflowManager._remove_extra_pairs() # --> when there are desired pairs, but they are not present vert_ints = {'0,1': 1, '0,2': 2, '1,2': 3} combos = [[4, 20], [11, 12]] expected = {} actual = WorkflowManager._remove_extra_pairs(vert_ints, combos) self.assertSequenceEqual(expected, actual)
def test_intervals_4(self): """test all combinations of madrigal51 with rests""" test_wm = WorkflowManager( [os.path.join(VIS_PATH, 'tests', 'corpus', 'madrigal51.mxl')]) test_wm.load('pieces') test_wm.settings(0, 'voice combinations', 'all pairs') test_wm.settings(None, 'include rests', True) actual = test_wm.run('intervals') self.assertEqual(1, len(actual.columns)) actual = actual['aggregator.ColumnAggregator'] exp_ind = list(IntervalsTests.EXPECTED_4.index) act_ind = list(actual.index) for ind_item in exp_ind: self.assertTrue(ind_item in act_ind) for ind_item in exp_ind: self.assertEqual(IntervalsTests.EXPECTED_4[ind_item], actual[ind_item])
def test_intervs_2(self, mock_guc, mock_rfa, mock_rep): """Ensure _intervs() calls everything in the right order, with the right args & settings. Same as test_intervs_1() but: - calls _remove_extra_pairs(), and - doesn't call _run_freq_agg().""" mock_guc.return_value = [[0, 1]] voice_combos = str(mock_guc.return_value) exp_voice_combos = ['0,1'] test_settings = {'simple or compound': 'compound', 'quality': False} test_pieces = [MagicMock(spec_set=IndexedPiece) for _ in range(3)] returns = ['get_data() {}'.format(i) for i in range(len(test_pieces))] for piece in test_pieces: piece.get_data.side_effect = lambda *x: returns.pop(0) exp_into_mock_rep = [ 'get_data() {}'.format(i) for i in range(len(test_pieces)) ] mock_rep_returns = [ 'IndP-{} no pairs'.format(i) for i in range(len(test_pieces)) ] mock_rep.side_effect = lambda *x: mock_rep_returns.pop(0) expected = [ 'IndP-{} no pairs'.format(i) for i in range(len(test_pieces)) ] exp_analyzers = [noterest.NoteRestIndexer, interval.IntervalIndexer] exp_mock_guc = [mock.call(i) for i in range(len(test_pieces))] test_wc = WorkflowManager(test_pieces) test_wc.settings(None, 'include rests', True) test_wc.settings(None, 'count frequency', False) test_wc.settings(None, 'voice combinations', voice_combos) actual = test_wc._intervs() # pylint: disable=protected-access self.assertSequenceEqual(exp_mock_guc, mock_guc.call_args_list) self.assertEqual(len(test_pieces), len(expected), len(actual)) self.assertEqual(0, mock_rfa.call_count) self.assertEqual(len(test_pieces), mock_rep.call_count) for i in range(len(test_pieces)): mock_rep.assert_any_call(exp_into_mock_rep[i], exp_voice_combos) for piece in test_pieces: piece.get_data.assert_called_once_with(exp_analyzers, test_settings) for i in range(len(actual)): self.assertSequenceEqual(expected[i], actual[i])
def test_intervals_5(self): """test all combinations of vis_Test_Piece""" test_wm = WorkflowManager( [os.path.join(VIS_PATH, 'tests', 'corpus', 'vis_Test_Piece.xml')]) test_wm.load('pieces') test_wm.settings(0, 'voice combinations', 'all pairs') test_wm.settings(None, 'include rests', True) expected = pandas.read_csv(os.path.join(VIS_PATH, 'tests', 'corpus', 'test_intervals_5.csv'), index_col=0) actual = test_wm.run('intervals') self.assertEqual(1, len(actual.columns)) self.assertSequenceEqual(list(expected.columns), list(actual.columns)) for col_name in expected.columns: self.assertEqual(len(expected[col_name]), len(actual[col_name])) for each_interval in expected[col_name].index: # NOTE: for whatever reason, the "expected" file always imports with an Int64Index, # so .loc() won't find things properly unless we give the int64 index to the # expected and the str index to the actual self.assertEqual(expected[col_name].loc[each_interval], actual[col_name].loc[str(each_interval)])
def test_intervals_6(self): # TODO: add a frequency-counted test """test Soprano and Alto of bwv77; with quality; not counting frequency""" # NB: the "expected" was hand-counted expected = pandas.read_csv(os.path.join(VIS_PATH, 'tests', 'expecteds', 'bwv77', 'SA_intervals.csv'), comment='#', index_col=0, header=[0, 1], quotechar="'") test_wm = WorkflowManager( [os.path.join(VIS_PATH, 'tests', 'corpus', 'bwv77.mxl')]) test_wm.load('pieces') test_wm.settings(0, 'voice combinations', '[[0, 1]]') test_wm.settings(None, 'count frequency', False) test_wm.settings(None, 'interval quality', True) actual = test_wm.run('intervals') self.assertEqual(1, len(actual)) actual = actual[0].dropna() self.assertDataFramesEqual(expected, actual)
def test_intervs_4(self): """Ensure _intervs() fails when given an impossible 'voice pair'.""" test_pieces = [MagicMock(spec_set=IndexedPiece) for _ in range(3)] returns = ['get_data() {}'.format(i) for i in range(len(test_pieces))] for piece in test_pieces: piece.get_data.side_effect = lambda *x: returns.pop(0) exp_err_msg = WorkflowManager._REQUIRE_PAIRS_ERROR.format(3) # pylint: disable=protected-access test_wc = WorkflowManager(test_pieces) test_wc.settings(None, 'include rests', True) test_wc.settings(None, 'voice combinations', '[[0, 1, 2]]') # that's not a pair! self.assertRaises(RuntimeError, test_wc._intervs) # pylint: disable=protected-access try: test_wc._intervs() # pylint: disable=protected-access except RuntimeError as run_err: self.assertEqual(exp_err_msg, run_err.args[0])
def test_intervals_1(self): """test the two highest voices of bwv77""" test_wm = WorkflowManager( [os.path.join(VIS_PATH, 'tests', 'corpus', 'bwv77.mxl')]) test_wm.load('pieces') test_wm.settings(0, 'voice combinations', '[[0, 1]]') actual = test_wm.run('intervals') self.assertEqual(1, len(actual.columns)) actual = actual['aggregator.ColumnAggregator'] exp_ind = list(IntervalsTests.EXPECTED_1.index) act_ind = list(actual.index) for ind_item in exp_ind: self.assertTrue(ind_item in act_ind) for ind_item in exp_ind: self.assertEqual(IntervalsTests.EXPECTED_1[ind_item], actual[ind_item])
def import_files(request): """ Called with the /api/import URL to load files into the WorkflowManager and return the metadata it discovers. """ filepaths = [ piece.file.path for piece in Piece.objects.filter(user_id=request.session.session_key) ] workm = WorkflowManager(filepaths) workm.load('pieces') request.session['workm'] = workm return [{ "Filename": os.path.basename(workm.metadata(i, 'pathname')), "Title": workm.metadata(i, 'title'), "Part Names": workm.metadata(i, 'parts'), "Offset": None, "Part Combinations": None, "Repeat Identical": False } for i in xrange(len(workm))], 200
class WorkflowWrapper(QAbstractTableModel): """ This is a wrapper class for the :class:`vis.workflow.WorkflowManager` that allows its use as a :class:`QAbstractTableModel` for PyQt4. This class only wraps :meth:`metadata` and :meth:`setting`, which are those methods needed by PyQt. For all other :class:`Workflowmanager` methods, access the internally-stored instance with :meth:`get_workflow_manager`. ** How to Use the WorkflowWrapper: ** The :class:`WorkflowWrapper` always returns valid values, and will not raise exceptions of its own. However, "valid" values are not always "correct" or "expected." We recommend you use the :class:`WorkflowWrapper` like this: #. Instantiate the object. #. Set the object as the model for its view. #. Call :meth:`insertRows` with the total number of pieces to be added. #. Call :meth:`setData` once per piece to set the pathname. #. Once each row has a pathname, the object will instantiate its internal :class:`WorkflowManager` instance and call its :meth:`load` method to import the score, run the :class:`NoteRestIndexer`, and save its metadata. #. Subsequent calls to :meth:`data` will return the most correct information available. ** How not to Use the WorkflowWrapper: ** We recommend you do not use the :class:`WorkflowWrapper` like this: * Do not add pieces by calling :meth:`insertRows` then :meth:`setData` with the pathname, then :meth:`insertRows` then :meth:`setData` with the pathname, and so on. In this case, the :class:`WorkflowWrapper` would create a new :class:`WorkflowManager` after each call to :meth:`setData`, which would import each piece many times. * Do not add pieces after you modify a metadata or setting field. When you add a piece, a new :class:`WorkflowManager` instance is created. The new instance replaces any customized settings and metadata with the default values. * Do not call :meth:`data` before you add the pathnames of all pieces. Real metadata is only available after the :class:`WorkflowManager` is created, which happens after all the pieces have a pathname. If you call :meth:`data` when there is no :class:`WorkflowManager`, the return value will always be ``None``. ** Columns in the Data Model: ** The :class:`WorkflowWrapper` creates a two-dimensional data model, where each row represents an :class:`IndexedPiece` stored in the :class:`WorkflowManager` and each column represents either a setting or a metadatum field. The following six fields are different for each piece, and should be displayed in the :class:`QTableView` widget: * filename * title * parts_list * offset_interval * parts_combinations * repeat_identical The :class:`WorkflowManager` additionally wraps these data fields, which are shared by all pieces, and will therefore not apear in the :class:`QTableView` widget: * quality * simple_ints Use class properties with those names to specify columns to :meth:`data` and :meth:`getData`. For example: >>> workm.data((0, WorkflowWrapper.title), Qt.DisplayRole) u'02_eleva' >>> workm.setData((0, WorkflowWrapper.title), u'Elevator Love Letter', Qt.EditRole) >>> workm.data((0, WorkflowWrapper.parts_list), Qt.DisplayRole) [u'Amy Milan', u'Torquil Campbell', u'Evan Cranley', u'Chris Seligman', u'Pat McGee'] """ # Public class variables to track which column has which data # NOTE: Update _num_cols whenever you change the number of columns, # since this variable is used by columnCount(). # NOTE: Update _header_names whenever you change the number or definition of # columns, since this variale is used by headerData(). _num_cols = 6 _header_names = [ 'Path', 'Title', 'List of Part Names', 'Offset Interval', 'Part Combinations', 'Repeat Identical' ] # displayed fields filename = 0 title = 1 parts_list = 2 offset_interval = 3 parts_combinations = 4 repeat_identical = 5 # non-displayed fields quality = 100 simple_ints = 101 # instead of DisplayRole; used to tell data() to return the list of part names as a list ListRole = 4000 # when a value hasn't been set, return a QVariant with this in it default_value = u'(unset)' def __init__(self, parent=QModelIndex()): """ Create a new :class:`WorkflowWrapper` instance. """ super(WorkflowWrapper, self).__init__() self._pathnames = [ ] # hold a list of pathnames for before the WorkflowManager self._workm = None # hold the WorkflowManager self._settings_changed = False # whether setData() was called (for settings_changed()) self._when_done_import = None # method to call when we're finished importing def rowCount(self, parent=QModelIndex()): """ Return the number of pieces in this list. If the internal :class:`WorkflowManager` exists, this is the number of piece stored there; otherwise it is the number of places for pathnames. :returns: The number of pieces in this list. :rtype: ``int`` """ if self._workm is None: return len(self._pathnames) else: return len(self._workm) def columnCount(self, parent=QModelIndex()): "Return the number of columns in this WorkflowWrapper." return WorkflowWrapper._num_cols def data(self, index, role): """ Get the data for the piece and metadatum or setting specified. Only the "parts_list" column responds to the WorkflowWrapper.ListRole, in which case a list of strings is returned instead of a comma-separated list of part names. :param index: The row-and-column index you wish to access. Either you can use a :class:`QModelIndex` or a 2-tuple where the first element is an ``int`` representing the index of the piece in the models, and the second element is one of the class properties described above in "Columns in the Data Model." :type index: :class:`QModelIndex` or 2-tuple of ``int`` :param role: Either Qt.DisplayRole or WorkflowWrapper.ListRole :type role: ``int`` :returns: The requested data or, if ``index`` or ``role`` is invalid, ``None``. :rtype: :class:`QVariant` .. note:: The method always returns a :class:`QVariant`. Access the Python object with the :meth:`toPyObject` method. .. note:: If the internal :class:`WorkflowManager` has not been instantiated, the return value is always ``None``. .. note:: This method never actually returns ``None``, but rather an empty :class:`QVariant` that will be ``None`` when you call :meth:`toPyObject` on it. """ if self._workm is None or (Qt.DisplayRole != role and WorkflowWrapper.ListRole != role): return QVariant() # Set the row and column row = None column = None if isinstance(index, QModelIndex): # if the QModelIndex is invalid, we won't bother with it if not index.isValid(): return QVariant() # otherwise, get the row and column from the QModelIndex row = index.row() column = index.column() else: row = index[0] column = index[1] # Verify the row and column if row >= self.rowCount() or column >= self._num_cols and \ (column != WorkflowWrapper.quality and column != WorkflowWrapper.simple_ints): return QVariant() post = None if Qt.DisplayRole == role: # displayed fields if WorkflowWrapper.filename == column: post = self._workm.metadata(row, u'pathname') elif WorkflowWrapper.title == column: post = self._workm.metadata(row, u'title') elif WorkflowWrapper.parts_list == column: post = u', '.join(self._workm.metadata(row, u'parts')) elif WorkflowWrapper.offset_interval == column: post = self._workm.settings(row, u'offset interval') elif WorkflowWrapper.parts_combinations == column: post = self._workm.settings(row, u'voice combinations') elif WorkflowWrapper.repeat_identical == column: # the wording in the GUI and WorkflowManager has opposite meanings post = not self._workm.settings(row, u'filter repeats') # non-displayed fields elif WorkflowWrapper.quality == column: post = self._workm.settings(None, u'interval quality') elif WorkflowWrapper.simple_ints == column: post = self._workm.settings(None, u'simple intervals') else: post = QVariant() elif WorkflowWrapper.ListRole == role: if WorkflowWrapper.parts_list == column: post = self._workm.metadata(row, u'parts') else: post = QVariant() else: post = QVariant() if not isinstance(post, QVariant): if post is None: post = WorkflowWrapper.default_value post = QVariant(post) return post def headerData(self, section, orientation, role): """ Return the column names for a WorkflowWrapper instance. Arguments: - section: the index of the column you want the name of - orientation: should be Qt.Horizontal; Qt.Vertical is ignored - role: should be Qt.DisplayRole; others are ignored If the section index is out of range, or the orientation or role is different than expected, an empty QVariant is returned. """ # All of the column titles are stored as class variables. I decided to # use the class name here, rather than "self," just in case they were # accidentally changed in this instance. We do not want to allow that. if Qt.Horizontal == orientation and Qt.DisplayRole == role and \ 0 <= section < WorkflowWrapper._num_cols: return WorkflowWrapper._header_names[section] else: return QVariant() def setData(self, index, value, role): """ Set the data for the piece and metadatum or setting specified. If the internal :class:`WorkflowManager` has not yet been created and this is the last pathname added, instantiate the :class:`WorkflowManager` and call :meth:`load`. :param index: The row-and-column index you wish to access. Either you can use a :class:`QModelIndex` or a 2-tuple where the first element is an ``int`` representing the index of the piece in the models, and the second element is one of the class properties described above in "Columns in the Data Model." :type index: :class:`QModelIndex` or 2-tuple of ``int`` :param value: The desired value of the setting or metadatum. If you submit a :class:`QVariant`, we will call :meth:`toPyObject` before sending to the :class:`WorkflowManager`. :type value: :class:`QVariant` or any :param role: This should be Qt.EditRole. :type role: :class:`EditRole` :returns: Whether the data was successfully set. :rtype: ``True`` or ``False`` .. note:: If the internal :class:`WorkflowManager` has not been instantiated, you can only set the ``pathname`` field. All other calls to :meth:`setData` will fail. """ if Qt.EditRole != role: return False # Set the row and column row = None column = None if isinstance(index, QModelIndex): # if the QModelIndex is invalid, we won't bother with it if not index.isValid(): return False # otherwise, get the row and column from the QModelIndex row = index.row() column = index.column() else: row = index[0] column = index[1] index = self.createIndex(row, column) # Verify the row and column if row >= self.rowCount() or column >= self._num_cols and \ (column != WorkflowWrapper.quality and column != WorkflowWrapper.simple_ints): return False set_val = value.toPyObject() if isinstance(value, QVariant) else value # ensure we're trying to set a valid thing if self._workm is None: if WorkflowWrapper.filename != column: return False else: self._pathnames[row] = set_val ch_ind_1, ch_ind_2 = None, None if all(self._pathnames): self._workm = WorkflowManager(self._pathnames) self._workm.load(u'pieces') # now that we imported, all the data's changed ch_ind_1 = self.createIndex(0, 0) ch_ind_2 = self.createIndex(len(self), self._num_cols - 1) # let the GUI know self._when_done_import() else: # only one cell has changed ch_ind_1 = ch_ind_2 = self.createIndex(row, column) self.dataChanged.emit(ch_ind_1, ch_ind_2) return True # displayed fields if WorkflowWrapper.filename == column: self._workm.metadata(row, u'pathname', set_val) elif WorkflowWrapper.title == column: self._workm.metadata(row, u'title', set_val) elif WorkflowWrapper.parts_list == column: self._workm.metadata(row, u'parts', set_val) elif WorkflowWrapper.offset_interval == column: self._workm.settings(row, u'offset interval', set_val) elif WorkflowWrapper.parts_combinations == column: self._workm.settings(row, u'voice combinations', set_val) elif WorkflowWrapper.repeat_identical == column: # the wording in the GUI and WorkflowManager has opposite meanings self._workm.settings(row, u'filter repeats', not set_val) # non-displayed fields elif WorkflowWrapper.quality == column: self._workm.settings(None, u'interval quality', set_val) elif WorkflowWrapper.simple_ints == column: self._workm.settings(None, u'simple intervals', set_val) else: return False self._settings_changed = True self.dataChanged.emit(index, index) return True def insertRows(self, row, count, parent=QModelIndex()): """ Append new rows to the data model. Yes---append, not insert. :param row: An argument that will be ignored. :type row: any :param count: The number of rows you want to append. :type count: ``int`` .. note:: If the internal :class:`WorkflowManager` already exists, it is destroyed and all metadata and settings are lost. .. note:: We recommend you add all the rows you will need before you call :meth:`setData` to set the pathnames of the newly-added rows. """ if self._workm is not None: self._workm = None self.beginInsertRows(parent, len(self._pathnames), len(self._pathnames) + count - 1) self._pathnames.extend([None for _ in xrange(count)]) self.endInsertRows() def removeRows(self, row, count, parent=QModelIndex()): """ This is the opposite of insertRows(), and the arguments work in the same way. """ pass def __len__(self): "Alias for rowCount()." return self.rowCount() def __getitem__(self, index): "It's __getitem__(), what do you want?!" return self._workm[index] def connect_workflow_signals(self, finished_import): """ Connect the WorkflowManager's PyQt signals to the methods or functions given here. "finished_import" is for the WorkM's "finished_import" signal. """ print('--> type: ' + str(finished_import)) # DEBUG self._when_done_import = finished_import #self._workm.finished_import.connect(finished_import) #pass def get_workflow_manager(self): """ Get the internal :class:`WorkflowManager` instance. """ return self._workm def settings_changed(self): """ Know whether there has been a call to :meth:`setData` since the last time this method was called. """ ret = self._settings_changed self._settings_changed = False return ret
def setData(self, index, value, role): """ Set the data for the piece and metadatum or setting specified. If the internal :class:`WorkflowManager` has not yet been created and this is the last pathname added, instantiate the :class:`WorkflowManager` and call :meth:`load`. :param index: The row-and-column index you wish to access. Either you can use a :class:`QModelIndex` or a 2-tuple where the first element is an ``int`` representing the index of the piece in the models, and the second element is one of the class properties described above in "Columns in the Data Model." :type index: :class:`QModelIndex` or 2-tuple of ``int`` :param value: The desired value of the setting or metadatum. If you submit a :class:`QVariant`, we will call :meth:`toPyObject` before sending to the :class:`WorkflowManager`. :type value: :class:`QVariant` or any :param role: This should be Qt.EditRole. :type role: :class:`EditRole` :returns: Whether the data was successfully set. :rtype: ``True`` or ``False`` .. note:: If the internal :class:`WorkflowManager` has not been instantiated, you can only set the ``pathname`` field. All other calls to :meth:`setData` will fail. """ if Qt.EditRole != role: return False # Set the row and column row = None column = None if isinstance(index, QModelIndex): # if the QModelIndex is invalid, we won't bother with it if not index.isValid(): return False # otherwise, get the row and column from the QModelIndex row = index.row() column = index.column() else: row = index[0] column = index[1] index = self.createIndex(row, column) # Verify the row and column if row >= self.rowCount() or column >= self._num_cols and \ (column != WorkflowWrapper.quality and column != WorkflowWrapper.simple_ints): return False set_val = value.toPyObject() if isinstance(value, QVariant) else value # ensure we're trying to set a valid thing if self._workm is None: if WorkflowWrapper.filename != column: return False else: self._pathnames[row] = set_val ch_ind_1, ch_ind_2 = None, None if all(self._pathnames): self._workm = WorkflowManager(self._pathnames) self._workm.load(u'pieces') # now that we imported, all the data's changed ch_ind_1 = self.createIndex(0, 0) ch_ind_2 = self.createIndex(len(self), self._num_cols - 1) # let the GUI know self._when_done_import() else: # only one cell has changed ch_ind_1 = ch_ind_2 = self.createIndex(row, column) self.dataChanged.emit(ch_ind_1, ch_ind_2) return True # displayed fields if WorkflowWrapper.filename == column: self._workm.metadata(row, u'pathname', set_val) elif WorkflowWrapper.title == column: self._workm.metadata(row, u'title', set_val) elif WorkflowWrapper.parts_list == column: self._workm.metadata(row, u'parts', set_val) elif WorkflowWrapper.offset_interval == column: self._workm.settings(row, u'offset interval', set_val) elif WorkflowWrapper.parts_combinations == column: self._workm.settings(row, u'voice combinations', set_val) elif WorkflowWrapper.repeat_identical == column: # the wording in the GUI and WorkflowManager has opposite meanings self._workm.settings(row, u'filter repeats', not set_val) # non-displayed fields elif WorkflowWrapper.quality == column: self._workm.settings(None, u'interval quality', set_val) elif WorkflowWrapper.simple_ints == column: self._workm.settings(None, u'simple intervals', set_val) else: return False self._settings_changed = True self.dataChanged.emit(index, index) return True