Skip to content

Commit d0fcd23

Browse files
committed
fix: skip download for notes that return server side error
1 parent 64eb952 commit d0fcd23

File tree

2 files changed

+127
-17
lines changed

2 files changed

+127
-17
lines changed

evernote_backup/note_synchronizer.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import Any, Dict, Iterable, List, Optional, Tuple
88

99
from click import progressbar
10+
from evernote.edam.error.ttypes import EDAMErrorCode, EDAMSystemException
1011
from evernote.edam.notestore.ttypes import SyncChunk
1112
from evernote.edam.type.ttypes import LinkedNotebook, Note
1213

@@ -33,6 +34,10 @@ class WorkerStopException(Exception):
3334
"""Raise when workers are stopped"""
3435

3536

37+
class NoteDownloadException(Exception):
38+
"""Raise when downloading note fails"""
39+
40+
3641
def get_note_size(note: Note) -> int:
3742
size = note.contentLength
3843

@@ -146,13 +151,25 @@ def download_note(self, note_id: str) -> Note:
146151
for _ in range(retry_count):
147152
try:
148153
return self._note_client.get_note(note_id)
154+
except EDAMSystemException as e:
155+
if e.errorCode == EDAMErrorCode.RATE_LIMIT_REACHED:
156+
raise
157+
158+
error_code_name = EDAMErrorCode._VALUES_TO_NAMES.get(
159+
e.errorCode, f"UNKNOWN [{e.errorCode}]"
160+
)
161+
162+
raise NoteDownloadException(
163+
f"Remote server returned system error ({error_code_name} - {e.message})"
164+
f" while downloading note [{note_id}]"
165+
)
149166
except struct.error:
150167
logger.debug(
151168
f"Remote server returned bad data"
152169
f" while downloading note [{note_id}], retrying..."
153170
)
154171

155-
raise RuntimeError(
172+
raise NoteDownloadException(
156173
f"Failed to download note [{note_id}] after {retry_count} attempts!"
157174
)
158175

@@ -379,12 +396,24 @@ def _process_download_chunk(
379396
for note_f in as_completed(note_futures):
380397
f_exc = note_f.exception()
381398
if f_exc is not None:
382-
logger.critical(
383-
f"Exception caught while downloading note"
384-
f" '{note_futures[note_f]}'!"
385-
)
386-
387-
raise f_exc
399+
if isinstance(f_exc, NoteDownloadException):
400+
logger.error(f_exc)
401+
logger.warning(
402+
f"Note '{note_futures[note_f]}' will be skipped for this run"
403+
" and retried during the next sync."
404+
)
405+
notes_bar.update(1)
406+
continue
407+
elif isinstance(f_exc, EDAMSystemException):
408+
# Only for rate limit error
409+
raise f_exc
410+
else:
411+
logger.critical(
412+
f"Unknown exception caught while downloading note"
413+
f" '{note_futures[note_f]}'!"
414+
)
415+
416+
raise f_exc
388417

389418
note = note_f.result(timeout=120) # noqa: WPS432
390419
self.storage.notes.add_note(note)

tests/test_op_sync.py

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from hashlib import md5
44

55
import pytest
6+
from evernote.edam.error.ttypes import EDAMErrorCode, EDAMSystemException
67
from evernote.edam.type.ttypes import (
78
Data,
89
LinkedNotebook,
@@ -588,15 +589,15 @@ def fake_slow_get_note(note_guid):
588589

589590

590591
@pytest.mark.usefixtures("fake_init_db")
591-
def test_sync_exception_while_download(
592-
cli_invoker, mock_evernote_client, fake_storage, mocker
592+
def test_sync_unknown_exception_while_download(
593+
cli_invoker, mock_evernote_client, fake_storage, mocker, caplog
593594
):
594-
test_notes = [Note(guid=f"id{i}", title="test") for i in range(100)]
595+
test_notes = [Note(guid=f"id{i}", title="test") for i in range(10)]
595596

596597
mock_evernote_client.fake_notes.extend(test_notes)
597598

598599
def fake_get_note(note_guid):
599-
if note_guid == "id10":
600+
if note_guid == "id3":
600601
raise RuntimeError("Test error")
601602

602603
return Note(
@@ -613,22 +614,99 @@ def fake_get_note(note_guid):
613614
)
614615
mock_get_note.side_effect = fake_get_note
615616

617+
# with caplog.at_level(logging.DEBUG):
616618
with pytest.raises(RuntimeError) as excinfo:
617619
cli_invoker("sync", "--database", "fake_db")
618620

619621
assert str(excinfo.value) == "Test error"
622+
assert "Unknown exception caught" in caplog.text
623+
624+
625+
@pytest.mark.usefixtures("fake_init_db")
626+
def test_sync_edam_exception_while_download(
627+
cli_invoker, mock_evernote_client, fake_storage, mocker, caplog
628+
):
629+
test_notes = [Note(guid=f"id{i}", title="test") for i in range(10)]
630+
631+
mock_evernote_client.fake_notes.extend(test_notes)
632+
633+
def fake_get_note(note_guid):
634+
if note_guid == "id3":
635+
raise EDAMSystemException(
636+
errorCode=EDAMErrorCode.INTERNAL_ERROR, message="Test error"
637+
)
638+
639+
return Note(
640+
guid=note_guid,
641+
title="test",
642+
content="test",
643+
notebookGuid="test",
644+
contentLength=100,
645+
active=True,
646+
)
647+
648+
mock_get_note = mocker.patch(
649+
"evernote_backup.evernote_client_sync.EvernoteClientSync.get_note"
650+
)
651+
mock_get_note.side_effect = fake_get_note
652+
653+
cli_invoker("sync", "--database", "fake_db")
654+
655+
assert "INTERNAL_ERROR - Test error" in caplog.text
656+
assert (
657+
"Note 'test' will be skipped for this run and retried during the next sync"
658+
in caplog.text
659+
)
660+
661+
662+
@pytest.mark.usefixtures("fake_init_db")
663+
def test_sync_edam_rate_limit_exception_while_download(
664+
cli_invoker, mock_evernote_client, fake_storage, mocker, caplog
665+
):
666+
test_notes = [Note(guid=f"id{i}", title="test") for i in range(10)]
667+
668+
mock_evernote_client.fake_notes.extend(test_notes)
669+
670+
def fake_get_note(note_guid):
671+
if note_guid == "id3":
672+
raise EDAMSystemException(
673+
errorCode=EDAMErrorCode.RATE_LIMIT_REACHED,
674+
message="Test rate limit",
675+
rateLimitDuration=10,
676+
)
677+
678+
return Note(
679+
guid=note_guid,
680+
title="test",
681+
content="test",
682+
notebookGuid="test",
683+
contentLength=100,
684+
active=True,
685+
)
686+
687+
mock_get_note = mocker.patch(
688+
"evernote_backup.evernote_client_sync.EvernoteClientSync.get_note"
689+
)
690+
mock_get_note.side_effect = fake_get_note
691+
692+
with pytest.raises(EDAMSystemException) as excinfo:
693+
cli_invoker("sync", "--database", "fake_db")
694+
695+
assert excinfo.value.message == "Test rate limit"
696+
assert excinfo.value.errorCode == EDAMErrorCode.RATE_LIMIT_REACHED
697+
assert excinfo.value.rateLimitDuration == 10
620698

621699

622700
@pytest.mark.usefixtures("fake_init_db")
623701
def test_sync_exception_while_download_retry_fail(
624-
cli_invoker, mock_evernote_client, fake_storage, mocker
702+
cli_invoker, mock_evernote_client, fake_storage, mocker, caplog
625703
):
626-
test_notes = [Note(guid=f"id{i}", title="test") for i in range(100)]
704+
test_notes = [Note(guid=f"id{i}", title="test") for i in range(10)]
627705

628706
mock_evernote_client.fake_notes.extend(test_notes)
629707

630708
def fake_get_note(note_guid):
631-
if note_guid == "id10":
709+
if note_guid == "id3":
632710
raise struct.error
633711

634712
return Note(
@@ -645,10 +723,13 @@ def fake_get_note(note_guid):
645723
)
646724
mock_get_note.side_effect = fake_get_note
647725

648-
with pytest.raises(RuntimeError) as excinfo:
649-
cli_invoker("sync", "--database", "fake_db")
726+
cli_invoker("sync", "--database", "fake_db")
650727

651-
assert "Failed to download note" in str(excinfo.value)
728+
assert "Failed to download note [id3] after 5 attempts" in caplog.text
729+
assert (
730+
"Note 'test' will be skipped for this run and retried during the next sync"
731+
in caplog.text
732+
)
652733

653734

654735
@pytest.mark.usefixtures("fake_init_db")

0 commit comments

Comments
 (0)