From 34cb8fe09bfee23662407b6ecf803970a61e3aac Mon Sep 17 00:00:00 2001 From: David Snopek Date: Mon, 22 Jul 2013 22:37:34 +0100 Subject: [PATCH] Did some refactoring moving the 'next_card' stuff into the CollectionHandler. --- AnkiServer/apps/rest_app.py | 117 +++++++++++++++++++------------- tests/test_rest_app.py | 129 ++++++++++++++++++++++-------------- 2 files changed, 148 insertions(+), 98 deletions(-) diff --git a/AnkiServer/apps/rest_app.py b/AnkiServer/apps/rest_app.py index 291f838..fa10422 100644 --- a/AnkiServer/apps/rest_app.py +++ b/AnkiServer/apps/rest_app.py @@ -37,11 +37,18 @@ class _RestHandlerWrapper(RestHandlerBase): return self.func(*args, **kw) class RestHandlerRequest(object): - def __init__(self, data, ids, session): + def __init__(self, app, data, ids, session): + self.app = app self.data = data self.ids = ids self.session = session + def copy(self): + return RestHandlerRequest(self.app, self.data.copy(), self.ids[:], self.session) + + def __eq__(self, other): + return self.app == other.app and self.data == other.data and self.ids == other.ids and self.session == other.session + class RestApp(object): """A WSGI app that implements RESTful operations on Collections, Decks and Cards.""" @@ -106,6 +113,14 @@ class RestApp(object): method = _RestHandlerWrapper(group.__class__.__name__ + '.' + name, method, group.hasReturnValue) self.add_handler(type, name, method) + def execute_handler(self, type, name, col, req): + """Executes the handler with the given type and name, passing in the col and req as arguments.""" + + handler, hasReturnValue = self._getHandler(type, name) + ret = handler(col, req) + if hasReturnValue: + return ret + def _checkRequest(self, req): """Raises an exception if the request isn't allowed or valid for some reason.""" if self.allowed_hosts != '*': @@ -245,7 +260,7 @@ class RestApp(object): # run it! col = self.collection_manager.get_collection(collection_path, self.setup_new_collection) - handler_request = RestHandlerRequest(data, ids, session) + handler_request = RestHandlerRequest(self, data, ids, session) try: output = col.execute(handler, [handler_request], {}, hasReturnValue) except Exception, e: @@ -344,6 +359,49 @@ class CollectionHandler(RestHandlerBase): def reset_scheduler(self, col, req): col.sched.reset() + button_labels = ['Easy', 'Good', 'Hard'] + + def _get_answer_buttons(self, col, card): + l = [] + + # Put the correct number of buttons + cnt = col.sched.answerButtons(card) + for idx in range(0, cnt - 1): + l.append(self.button_labels[idx]) + l.append('Again') + l.reverse() + + # Loop through and add the ease, estimated time (in seconds) and other info + return [{ + 'ease': ease, + 'label': label, + 'string_label': t(label), + 'interval': col.sched.nextIvl(card, ease), + 'string_interval': col.sched.nextIvlStr(card, ease), + } for ease, label in enumerate(l, 1)] + + def next_card(self, col, req): + if req.data.has_key('deck'): + deck = DeckHandler._get_deck(col, req.data['deck']) + col.decks.select(deck['id']) + + card = col.sched.getCard() + if card is None: + return None + + # put it into the card cache to be removed when we answer it + #if not req.session.has_key('cards'): + # req.session['cards'] = {} + #req.session['cards'][long(card.id)] = card + + card.startTimer() + + result = CardHandler._serialize(card) + result['answer_buttons'] = self._get_answer_buttons(col, card) + + return result + + @noReturnValue def answer_card(self, col, req): import time @@ -444,61 +502,26 @@ class NoteHandler(RestHandlerBase): class DeckHandler(RestHandlerBase): """Default handler group for 'deck' type.""" - button_labels = ['Easy', 'Good', 'Hard'] - - def _get_deck(self, col, ids): + @staticmethod + def _get_deck(col, val): try: - did = long(ids[1]) + did = long(val) deck = col.decks.get(did, False) except ValueError: - deck = col.decks.byName(ids[1]) + deck = col.decks.byName(val) if deck is None: - raise HTTPNotFound('No deck with id or name: ' + str(ids[1])) + raise HTTPNotFound('No deck with id or name: ' + str(val)) return deck - # Code stolen from aqt/reviewer.py - def _get_answer_buttons(self, col, card): - l = [] - - # Put the correct number of buttons - cnt = col.sched.answerButtons(card) - for idx in range(0, cnt - 1): - l.append(self.button_labels[idx]) - l.append('Again') - l.reverse() - - # Loop through and add the ease and estimated time (in seconds) - return [{ - 'ease': ease, - 'label': label, - 'string_label': t(label), - 'interval': col.sched.nextIvl(card, ease), - 'string_interval': col.sched.nextIvlStr(card, ease), - } for ease, label in enumerate(l, 1)] - def next_card(self, col, req): - deck = self._get_deck(col, req.ids) + req_copy = req.copy() + req_copy.data['deck'] = req.ids[1] + del req_copy.ids[1] - # TODO: maybe this should use col.renderQA()? - - col.decks.select(deck['id']) - card = col.sched.getCard() - if card is None: - return None - - # put it into the card cache to be removed when we answer it - #if not req.session.has_key('cards'): - # req.session['cards'] = {} - #req.session['cards'][long(card.id)] = card - - card.startTimer() - - result = CardHandler._serialize(card) - result['answer_buttons'] = self._get_answer_buttons(col, card) - - return result + # forward this to the CollectionHandler + return req.app.execute_handler('collection', 'next_card', col, req_copy) class CardHandler(RestHandlerBase): """Default handler group for 'card' type.""" diff --git a/tests/test_rest_app.py b/tests/test_rest_app.py index 299851a..14bd6f0 100644 --- a/tests/test_rest_app.py +++ b/tests/test_rest_app.py @@ -119,11 +119,13 @@ class CollectionTestBase(unittest.TestCase): self.temp_dir = tempfile.mkdtemp() self.collection_path = os.path.join(self.temp_dir, 'collection.anki2'); self.collection = anki.storage.Collection(self.collection_path) + self.mock_app = MagicMock() def tearDown(self): self.collection.close() self.collection = None shutil.rmtree(self.temp_dir) + self.mock_app.reset_mock() def add_note(self, data): from anki.notes import Note @@ -159,7 +161,7 @@ class CollectionHandlerTest(CollectionTestBase): def execute(self, name, data): ids = ['collection_name'] func = getattr(self.handler, name) - req = RestHandlerRequest(data, ids, {}) + req = RestHandlerRequest(self.mock_app, data, ids, {}) return func(self.collection, req) def test_list_decks(self): @@ -261,23 +263,88 @@ class CollectionHandlerTest(CollectionTestBase): # return everything to normal! anki.lang.setLang('en') + def test_next_card(self): + ret = self.execute('next_card', {}) + self.assertEqual(ret, None) + + # add a note programatically + self.add_default_note() + + # get the id for the one card and note on this collection + note_id = self.collection.findNotes('')[0] + card_id = self.collection.findCards('')[0] + + self.collection.sched.reset() + ret = self.execute('next_card', {}) + self.assertEqual(ret['id'], card_id) + self.assertEqual(ret['nid'], note_id) + self.assertEqual(ret['question'], 'The front') + self.assertEqual(ret['answer'], 'The front\n\n
\n\nThe back') + self.assertEqual(ret['answer_buttons'], [ + {'ease': 1, + 'label': 'Again', + 'string_label': 'Again', + 'interval': 60, + 'string_interval': '<1 minute'}, + {'ease': 2, + 'label': 'Good', + 'string_label': 'Good', + 'interval': 600, + 'string_interval': '<10 minutes'}, + {'ease': 3, + 'label': 'Easy', + 'string_label': 'Easy', + 'interval': 345600, + 'string_interval': '4 days'}]) + + def test_next_card_translation(self): + # add a note programatically + self.add_default_note() + + # get the card in Polish so we can test translation too + anki.lang.setLang('pl') + try: + ret = self.execute('next_card', {}) + finally: + anki.lang.setLang('en') + + self.assertEqual(ret['answer_buttons'], [ + {'ease': 1, + 'label': 'Again', + 'string_label': u'Znowu', + 'interval': 60, + 'string_interval': '<1 minuta'}, + {'ease': 2, + 'label': 'Good', + 'string_label': u'Dobra', + 'interval': 600, + 'string_interval': '<10 minut'}, + {'ease': 3, + 'label': 'Easy', + 'string_label': u'Łatwa', + 'interval': 345600, + 'string_interval': '4 dni'}]) + + def test_next_card_five_times(self): + self.add_default_note(5) + for idx in range(0, 5): + ret = self.execute('next_card', {}) + self.assertTrue(ret is not None) + def test_answer_card(self): import time self.add_default_note() # instantiate a deck handler to get the card - deck_handler = DeckHandler() - deck_request = RestHandlerRequest({}, ['c', '1'], {}) - card = deck_handler.next_card(self.collection, deck_request) + card = self.execute('next_card', {}) self.assertEqual(card['reps'], 0) - self.execute('answer_card', {'id': card['id'], 'ease': 2, 'timerStarted': time.time()}) # reset the scheduler and try to get the next card again - there should be none! self.collection.sched.reset() - card = deck_handler.next_card(self.collection, deck_request) + card = self.execute('next_card', {}) self.assertEqual(card['reps'], 1) class ImportExportHandlerTest(CollectionTestBase): @@ -293,7 +360,7 @@ class ImportExportHandlerTest(CollectionTestBase): def execute(self, name, data): ids = ['collection_name'] func = getattr(self.handler, name) - req = RestHandlerRequest(data, ids, {}) + req = RestHandlerRequest(self.mock_app, data, ids, {}) return func(self.collection, req) def generate_text_export(self): @@ -345,55 +412,15 @@ class DeckHandlerTest(CollectionTestBase): def execute(self, name, data): ids = ['collection_name', '1'] func = getattr(self.handler, name) - req = RestHandlerRequest(data, ids, {}) + req = RestHandlerRequest(self.mock_app, data, ids, {}) return func(self.collection, req) def test_next_card(self): + self.mock_app.execute_handler.return_value = None + ret = self.execute('next_card', {}) self.assertEqual(ret, None) - - # add a note programatically - self.add_default_note() - - # get the id for the one card and note on this collection - note_id = self.collection.findNotes('')[0] - card_id = self.collection.findCards('')[0] - - self.collection.sched.reset() - - # get the card in Polish so we can test translation too - anki.lang.setLang('pl') - try: - ret = self.execute('next_card', {}) - finally: - anki.lang.setLang('en') - - self.assertEqual(ret['id'], card_id) - self.assertEqual(ret['nid'], note_id) - self.assertEqual(ret['question'], 'The front') - self.assertEqual(ret['answer'], 'The front\n\n
\n\nThe back') - self.assertEqual(ret['answer_buttons'], [ - {'ease': 1, - 'label': 'Again', - 'string_label': u'Znowu', - 'interval': 60, - 'string_interval': '<1 minuta'}, - {'ease': 2, - 'label': 'Good', - 'string_label': u'Dobra', - 'interval': 600, - 'string_interval': '<10 minut'}, - {'ease': 3, - 'label': 'Easy', - 'string_label': u'Łatwa', - 'interval': 345600, - 'string_interval': '4 dni'}]) - - def test_next_card_five_times(self): - self.add_default_note(5) - for idx in range(0, 5): - ret = self.execute('next_card', {}) - self.assertTrue(ret is not None) + self.mock_app.execute_handler.assert_called_with('collection', 'next_card', self.collection, RestHandlerRequest(self.mock_app, {'deck': '1'}, ['collection_name'], {})) if __name__ == '__main__': unittest.main()