From 77346fb847e3a01601600ae29d9c6c6ed64277e6 Mon Sep 17 00:00:00 2001 From: karlicoss Date: Sun, 23 Oct 2022 00:13:47 +0100 Subject: [PATCH] make annotations defensive when the source book doesn't exist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit seems that it legit can happen, now kobuddy will create a special 'orphan' book object in this case e.g. ``` 17 Aug 2018 08:58 by . When we add up only the arrows that point more or less to the right, we get a series of dips and a substantial final arrow—accordi ng to the theory, we should now have a strong reflection! And indeed, we do—the theory is correct! Such a mirror is called a diffraction grating, and it works like a charm. ``` sometimes it's more cryptic though, e.g.: but there isn't much we can do about it, there isn't any other identifying information in Bookmark table ``` 17 Aug 2022 14:06 by By learning and ultimately understanding, we can find ways to create space for our authentic selves in the digital age How? ``` workaround for https://github.com/karlicoss/kobuddy/issues/18 --- src/kobuddy/__init__.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/kobuddy/__init__.py b/src/kobuddy/__init__.py index 51a23c6..7fd551b 100644 --- a/src/kobuddy/__init__.py +++ b/src/kobuddy/__init__.py @@ -77,6 +77,7 @@ def bid(self) -> str: if typing.TYPE_CHECKING: + # todo: since 3.8 can use from typing import Protocol everywhere from typing_extensions import Protocol else: Protocol = object @@ -324,7 +325,19 @@ def all(self) -> List[Book]: bset.update(l) return list(sorted(bset, key=lambda b: b.title)) - def add(self, book: Book): + def make_orphan_book(self, *, volumeid: str) -> Book: + # sometimes volumeid might be pretty cryptic, like a random uuid (e.g. for native kobo books) + # but there isn't much we can do about it -- there isn't any info in Bookmark table + orphan = Book( + title=f'', + author='', + content_id=volumeid, + isbn='fake_isbn_{volumeid}', + ) + self.add(orphan) + return orphan + + def add(self, book: Book) -> None: Books._reg(self.cid2books, book.content_id, book) Books._reg(self.isbn2books, book.isbn, book) Books._reg(self.title2books, book.title, book) @@ -842,6 +855,7 @@ def _iter_highlights(**kwargs) -> Iterator[Highlight]: logger = get_logger() books = _get_books() + # todo more_itertools? yielded: Set[Highlight] = set() for bfile in DATABASES: for h in _load_highlights(bfile, books=books): @@ -856,12 +870,19 @@ def _load_highlights(bfile: Path, books: Books): db = dataset_connect_ro(bfile) for bm in db.query('SELECT * FROM Bookmark'): volumeid = bm['VolumeID'] - book = books.by_content_id(volumeid) - assert book is not None, bm - # TODO make defensive? - # TODO could be example of useful defensiveness in a provider + mbook = books.by_content_id(volumeid) + if mbook is None: + # sometimes Kobo seems to recycle old books without recycling the corresponding bookmarks + # so we need to be a bit defensive here + # see https://github.com/karlicoss/kobuddy/issues/18 + book = books.make_orphan_book(volumeid=volumeid) + else: + book = mbook + # todo maybe in the future it could be a matter of error policy, i.e. throw vs yield exception vs use orphan object vs ignore + # could be example of useful defensiveness in a provider yield Highlight(bm, book=book) + def get_highlights(**kwargs) -> List[Highlight]: return list(sorted(_iter_highlights(**kwargs), key=lambda h: h.created))