Fix handling of errors in several methods (#238)

* Fix error message in updateNoteFields method

* Fix error handling in cardsInfo, notesInfo methods

* Add more tests for updateNoteFields, notesInfo

* Fix error handling in several methods

Also fixed obvious bugs in replaceTags and replaceTagsInAllNotes
methods.

* Add more tests for cards methods

* Add more tests for notes methods

* Update documentation on cards methods
This commit is contained in:
Kirill Salnikov 2021-03-05 07:06:25 +03:00 committed by GitHub
parent a4d723de3e
commit b8a7151ed0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 31 deletions

View File

@ -95,17 +95,17 @@
} }
``` ```
* **areSuspended** * **suspended**
Returns an array indicating whether each of the given cards is suspended (in the same order). Check if card is suspended by its ID. Returns `true` if suspended, `false` otherwise.
*Sample request*: *Sample request*:
```json ```json
{ {
"action": "areSuspended", "action": "suspended",
"version": 6, "version": 6,
"params": { "params": {
"cards": [1483959291685, 1483959293217] "card": 1483959293217
} }
} }
``` ```
@ -113,7 +113,31 @@
*Sample result*: *Sample result*:
```json ```json
{ {
"result": [false, true], "result": true,
"error": null
}
```
* **areSuspended**
Returns an array indicating whether each of the given cards is suspended (in the same order). If card doesn't
exist returns `null`.
*Sample request*:
```json
{
"action": "areSuspended",
"version": 6,
"params": {
"cards": [1483959291685, 1483959293217, 1234567891234]
}
}
```
*Sample result*:
```json
{
"result": [false, true, null],
"error": null "error": null
} }
``` ```

View File

@ -32,9 +32,12 @@ import anki
import anki.exporting import anki.exporting
import anki.storage import anki.storage
import aqt import aqt
from anki.cards import Card
from anki.exporting import AnkiPackageExporter from anki.exporting import AnkiPackageExporter
from anki.importing import AnkiPackageImporter from anki.importing import AnkiPackageImporter
from anki.notes import Note
from anki.rsbackend import NotFoundError
from anki.utils import joinFields, intTime, guid64, fieldChecksum from anki.utils import joinFields, intTime, guid64, fieldChecksum
from . import web, util from . import web, util
@ -280,6 +283,17 @@ class AnkiConnect:
return 0 return 0
def getCard(self, card_id: int) -> Card:
try:
return self.collection().getCard(card_id)
except NotFoundError:
raise NotFoundError('Card was not found: {}'.format(card_id))
def getNote(self, note_id: int) -> Note:
try:
return self.collection().getNote(note_id)
except NotFoundError:
raise NotFoundError('Note was not found: {}'.format(note_id))
# #
# Miscellaneous # Miscellaneous
@ -599,9 +613,7 @@ class AnkiConnect:
@util.api() @util.api()
def updateNoteFields(self, note): def updateNoteFields(self, note):
ankiNote = self.collection().getNote(note['id']) ankiNote = self.getNote(note['id'])
if ankiNote is None:
raise Exception('note was not found: {}'.format(note['id']))
for name, value in note['fields'].items(): for name, value in note['fields'].items():
if name in ankiNote: if name in ankiNote:
@ -646,10 +658,14 @@ class AnkiConnect:
self.window().progress.start() self.window().progress.start()
for nid in notes: for nid in notes:
note = self.collection().getNote(nid) try:
note = self.getNote(nid)
except NotFoundError:
continue
if note.hasTag(tag_to_replace): if note.hasTag(tag_to_replace):
note.delTag(tag_to_replace) note.delTag(tag_to_replace)
note.addtag(replace_with_tag) note.addTag(replace_with_tag)
note.flush() note.flush()
self.window().requireReset() self.window().requireReset()
@ -661,11 +677,12 @@ class AnkiConnect:
def replaceTagsInAllNotes(self, tag_to_replace, replace_with_tag): def replaceTagsInAllNotes(self, tag_to_replace, replace_with_tag):
self.window().progress.start() self.window().progress.start()
collection = self.collection()
for nid in collection.db.list('select id from notes'): for nid in collection.db.list('select id from notes'):
note = collection.getNote(nid) note = self.getNote(nid)
if note.hasTag(tag_to_replace): if note.hasTag(tag_to_replace):
note.delTag(tag_to_replace) note.delTag(tag_to_replace)
note.addtag(replace_with_tag) note.addTag(replace_with_tag)
note.flush() note.flush()
self.window().requireReset() self.window().requireReset()
@ -677,12 +694,13 @@ class AnkiConnect:
def setEaseFactors(self, cards, easeFactors): def setEaseFactors(self, cards, easeFactors):
couldSetEaseFactors = [] couldSetEaseFactors = []
for i, card in enumerate(cards): for i, card in enumerate(cards):
ankiCard = self.collection().getCard(card) try:
if ankiCard is None: ankiCard = self.getCard(card)
except NotFoundError:
couldSetEaseFactors.append(False) couldSetEaseFactors.append(False)
else: continue
couldSetEaseFactors.append(True)
couldSetEaseFactors.append(True)
ankiCard.factor = easeFactors[i] ankiCard.factor = easeFactors[i]
ankiCard.flush() ankiCard.flush()
@ -693,7 +711,12 @@ class AnkiConnect:
def getEaseFactors(self, cards): def getEaseFactors(self, cards):
easeFactors = [] easeFactors = []
for card in cards: for card in cards:
ankiCard = self.collection().getCard(card) try:
ankiCard = self.getCard(card)
except NotFoundError:
easeFactors.append(None)
continue
easeFactors.append(ankiCard.factor) easeFactors.append(ankiCard.factor)
return easeFactors return easeFactors
@ -726,7 +749,7 @@ class AnkiConnect:
@util.api() @util.api()
def suspended(self, card): def suspended(self, card):
card = self.collection().getCard(card) card = self.getCard(card)
return card.queue == -1 return card.queue == -1
@ -734,7 +757,10 @@ class AnkiConnect:
def areSuspended(self, cards): def areSuspended(self, cards):
suspended = [] suspended = []
for card in cards: for card in cards:
suspended.append(self.suspended(card)) try:
suspended.append(self.suspended(card))
except NotFoundError:
suspended.append(None)
return suspended return suspended
@ -994,7 +1020,7 @@ class AnkiConnect:
result = [] result = []
for cid in cards: for cid in cards:
try: try:
card = self.collection().getCard(cid) card = self.getCard(cid)
model = card.model() model = card.model()
note = card.note() note = card.note()
fields = {} fields = {}
@ -1025,8 +1051,8 @@ class AnkiConnect:
'lapses': card.lapses, 'lapses': card.lapses,
'left': card.left, 'left': card.left,
}) })
except TypeError as e: except NotFoundError:
# Anki will give a TypeError if the card ID does not exist. # Anki will give a NotFoundError if the card ID does not exist.
# Best behavior is probably to add an 'empty card' to the # Best behavior is probably to add an 'empty card' to the
# returned result, so that the items of the input and return # returned result, so that the items of the input and return
# lists correspond. # lists correspond.
@ -1127,7 +1153,7 @@ class AnkiConnect:
result = [] result = []
for nid in notes: for nid in notes:
try: try:
note = self.collection().getNote(nid) note = self.getNote(nid)
model = note.model() model = note.model()
fields = {} fields = {}
@ -1143,8 +1169,8 @@ class AnkiConnect:
'modelName': model['name'], 'modelName': model['name'],
'cards': self.collection().db.list('select id from cards where nid = ? order by ord', note.id) 'cards': self.collection().db.list('select id from cards where nid = ? order by ord', note.id)
}) })
except TypeError as e: except NotFoundError:
# Anki will give a TypeError if the note ID does not exist. # Anki will give a NotFoundError if the note ID does not exist.
# Best behavior is probably to add an 'empty card' to the # Best behavior is probably to add an 'empty card' to the
# returned result, so that the items of the input and return # returned result, so that the items of the input and return
# lists correspond. # lists correspond.

View File

@ -24,6 +24,8 @@ class TestCards(unittest.TestCase):
def runTest(self): def runTest(self):
incorrectId = 1234
# findCards # findCards
cardIds = util.invoke('findCards', query='deck:test') cardIds = util.invoke('findCards', query='deck:test')
self.assertEqual(len(cardIds), 1) self.assertEqual(len(cardIds), 1)
@ -33,18 +35,24 @@ class TestCards(unittest.TestCase):
easeFactors = [EASE_TO_TRY for card in cardIds] easeFactors = [EASE_TO_TRY for card in cardIds]
couldGetEaseFactors = util.invoke('setEaseFactors', cards=cardIds, easeFactors=easeFactors) couldGetEaseFactors = util.invoke('setEaseFactors', cards=cardIds, easeFactors=easeFactors)
self.assertEqual([True for card in cardIds], couldGetEaseFactors) self.assertEqual([True for card in cardIds], couldGetEaseFactors)
couldGetEaseFactors = util.invoke('setEaseFactors', cards=[incorrectId], easeFactors=[EASE_TO_TRY])
self.assertEqual([False], couldGetEaseFactors)
# getEaseFactors # getEaseFactors
easeFactorsFound = util.invoke('getEaseFactors', cards=cardIds) easeFactorsFound = util.invoke('getEaseFactors', cards=cardIds)
self.assertEqual(easeFactors, easeFactorsFound) self.assertEqual(easeFactors, easeFactorsFound)
easeFactorsFound = util.invoke('getEaseFactors', cards=[incorrectId])
self.assertEqual([None], easeFactorsFound)
# suspend # suspend
util.invoke('suspend', cards=cardIds) util.invoke('suspend', cards=cardIds)
self.assertRaises(Exception, lambda: util.invoke('suspend', cards=[incorrectId]))
# areSuspended (part 1) # areSuspended (part 1)
suspendedStates = util.invoke('areSuspended', cards=cardIds) suspendedStates = util.invoke('areSuspended', cards=cardIds)
self.assertEqual(len(cardIds), len(suspendedStates)) self.assertEqual(len(cardIds), len(suspendedStates))
self.assertNotIn(False, suspendedStates) self.assertNotIn(False, suspendedStates)
self.assertEqual([None], util.invoke('areSuspended', cards=[incorrectId]))
# unsuspend # unsuspend
util.invoke('unsuspend', cards=cardIds) util.invoke('unsuspend', cards=cardIds)
@ -73,6 +81,9 @@ class TestCards(unittest.TestCase):
self.assertEqual(len(cardsInfo), len(cardIds)) self.assertEqual(len(cardsInfo), len(cardIds))
for i, cardInfo in enumerate(cardsInfo): for i, cardInfo in enumerate(cardsInfo):
self.assertEqual(cardInfo['cardId'], cardIds[i]) self.assertEqual(cardInfo['cardId'], cardIds[i])
cardsInfo = util.invoke('cardsInfo', cards=[incorrectId])
self.assertEqual(len(cardsInfo), 1)
self.assertDictEqual(cardsInfo[0], dict())
# forgetCards # forgetCards
util.invoke('forgetCards', cards=cardIds) util.invoke('forgetCards', cards=cardIds)

View File

@ -91,15 +91,22 @@ class TestNotes(unittest.TestCase):
util.invoke('removeTags', notes=[noteId], tags='tag2') util.invoke('removeTags', notes=[noteId], tags='tag2')
# updateNoteFields # updateNoteFields
incorrectId = 1234
noteUpdateIncorrectId = {'id': incorrectId, 'fields': {'Front': 'front2', 'Back': 'back2'}}
self.assertRaises(Exception, lambda: util.invoke('updateNoteFields', note=noteUpdateIncorrectId))
noteUpdate = {'id': noteId, 'fields': {'Front': 'front2', 'Back': 'back2'}} noteUpdate = {'id': noteId, 'fields': {'Front': 'front2', 'Back': 'back2'}}
util.invoke('updateNoteFields', note=noteUpdate) util.invoke('updateNoteFields', note=noteUpdate)
# replaceTags
util.invoke('replaceTags', notes=[noteId, incorrectId], tag_to_replace='tag1', replace_with_tag='new_tag')
# notesInfo (part 2) # notesInfo (part 2)
noteInfos = util.invoke('notesInfo', notes=[noteId]) noteInfos = util.invoke('notesInfo', notes=[noteId, incorrectId])
self.assertEqual(len(noteInfos), 1) self.assertEqual(len(noteInfos), 2)
self.assertDictEqual(noteInfos[1], dict()) # Test that returns empty dict if incorrect id was passed
noteInfo = noteInfos[0] noteInfo = noteInfos[0]
self.assertSetEqual(set(noteInfo['tags']), {'tag1'}) self.assertSetEqual(set(noteInfo['tags']), {'new_tag'})
self.assertIn('tag1', noteInfo['tags']) self.assertIn('new_tag', noteInfo['tags'])
self.assertNotIn('tag2', noteInfo['tags']) self.assertNotIn('tag2', noteInfo['tags'])
self.assertEqual(noteInfo['fields']['Front']['value'], 'front2') self.assertEqual(noteInfo['fields']['Front']['value'], 'front2')
self.assertEqual(noteInfo['fields']['Back']['value'], 'back2') self.assertEqual(noteInfo['fields']['Back']['value'], 'back2')
@ -115,6 +122,15 @@ class TestNotes(unittest.TestCase):
for noteId in noteIds: for noteId in noteIds:
self.assertNotEqual(noteId, None) self.assertNotEqual(noteId, None)
# replaceTagsInAllNotes
currentTag = notes1[0]['tags'][0]
new_tag = 'new_tag'
util.invoke('replaceTagsInAllNotes', tag_to_replace=currentTag, replace_with_tag=new_tag)
noteInfos = util.invoke('notesInfo', notes=noteIds)
for noteInfo in noteInfos:
self.assertIn(new_tag, noteInfo['tags'])
self.assertNotIn(currentTag, noteInfo['tags'])
# canAddNotes (part 2) # canAddNotes (part 2)
noteStates = util.invoke('canAddNotes', notes=notes2) noteStates = util.invoke('canAddNotes', notes=notes2)
self.assertNotIn(True, noteStates) self.assertNotIn(True, noteStates)