Raise it as a newly added OperationInvalidatedError exception.
---
bindings/python-cffi/notmuch2/_base.py | 24 +++++-
bindings/python-cffi/notmuch2/_build.py | 6 ++
bindings/python-cffi/notmuch2/_errors.py | 3 +
bindings/python-cffi/notmuch2/_message.py | 3 +-
bindings/python-cffi/notmuch2/_thread.py | 3 +-
bindings/python-cffi/tests/test_database.py | 84 +++++++++++++++++++++
6 files changed, 118 insertions(+), 5 deletions(-)
diff --git a/bindings/python-cffi/notmuch2/_base.py
b/bindings/python-cffi/notmuch2/_base.py
index 1b0b5b7e..3ce960f7 100644
--- a/bindings/python-cffi/notmuch2/_base.py
+++ b/bindings/python-cffi/notmuch2/_base.py
@@ -182,11 +182,16 @@ class NotmuchIter(NotmuchObject,
collections.abc.Iterator):
_iter_p = MemoryPointer()
def __init__(self, parent, iter_p,
- *, fn_destroy, fn_valid, fn_get, fn_next):
+ *, fn_destroy, fn_valid, fn_get, fn_next,
+ fn_status = None):
+ # exactly one of those must be provided
+ assert(bool(fn_valid) != bool(fn_status))
+
self._parent = parent
self._iter_p = iter_p
self._fn_destroy = fn_destroy
self._fn_valid = fn_valid
+ self._fn_status = fn_status
self._fn_get = fn_get
self._fn_next = fn_next
@@ -212,6 +217,17 @@ class NotmuchIter(NotmuchObject, collections.abc.Iterator):
pass
self._iter_p = None
+ def _check_status(self):
+ if self._fn_valid:
+ if not self._fn_valid(self._iter_p):
+ raise StopIteration
+ else:
+ status = self._fn_status(self._iter_p)
+ if status == capi.lib.NOTMUCH_STATUS_ITERATOR_EXHAUSTED:
+ raise StopIteration
+ elif status > 0:
+ raise errors.NotmuchError(status)
+
def __iter__(self):
"""Return the iterator itself.
@@ -222,9 +238,11 @@ class NotmuchIter(NotmuchObject, collections.abc.Iterator):
return self
def __next__(self):
- if not self._fn_valid(self._iter_p):
- raise StopIteration()
+ self._check_status()
obj_p = self._fn_get(self._iter_p)
+ if obj_p == capi.ffi.NULL:
+ self._check_status()
+ raise StopIteration
self._fn_next(self._iter_p)
return obj_p
diff --git a/bindings/python-cffi/notmuch2/_build.py
b/bindings/python-cffi/notmuch2/_build.py
index 0429691a..2f3152c6 100644
--- a/bindings/python-cffi/notmuch2/_build.py
+++ b/bindings/python-cffi/notmuch2/_build.py
@@ -56,6 +56,8 @@ ffibuilder.cdef(
NOTMUCH_STATUS_BAD_QUERY_SYNTAX,
NOTMUCH_STATUS_NO_MAIL_ROOT,
NOTMUCH_STATUS_CLOSED_DATABASE,
+ NOTMUCH_STATUS_ITERATOR_EXHAUSTED,
+ NOTMUCH_STATUS_OPERATION_INVALIDATED,
NOTMUCH_STATUS_LAST_STATUS
} notmuch_status_t;
typedef enum {
@@ -187,6 +189,8 @@ ffibuilder.cdef(
notmuch_bool_t
notmuch_threads_valid (notmuch_threads_t *threads);
+ notmuch_status_t
+ notmuch_threads_status (notmuch_threads_t *threads);
notmuch_thread_t *
notmuch_threads_get (notmuch_threads_t *threads);
void
@@ -221,6 +225,8 @@ ffibuilder.cdef(
notmuch_bool_t
notmuch_messages_valid (notmuch_messages_t *messages);
+ notmuch_status_t
+ notmuch_messages_status (notmuch_messages_t *messages);
notmuch_message_t *
notmuch_messages_get (notmuch_messages_t *messages);
void
diff --git a/bindings/python-cffi/notmuch2/_errors.py
b/bindings/python-cffi/notmuch2/_errors.py
index 17c3ad9c..483e794b 100644
--- a/bindings/python-cffi/notmuch2/_errors.py
+++ b/bindings/python-cffi/notmuch2/_errors.py
@@ -28,6 +28,8 @@ class NotmuchError(Exception):
ReadOnlyDatabaseError,
capi.lib.NOTMUCH_STATUS_XAPIAN_EXCEPTION:
XapianError,
+ capi.lib.NOTMUCH_STATUS_OPERATION_INVALIDATED:
+ OperationInvalidatedError,
capi.lib.NOTMUCH_STATUS_FILE_ERROR:
FileError,
capi.lib.NOTMUCH_STATUS_FILE_NOT_EMAIL:
@@ -92,6 +94,7 @@ class NotmuchError(Exception):
class OutOfMemoryError(NotmuchError): pass
class ReadOnlyDatabaseError(NotmuchError): pass
class XapianError(NotmuchError): pass
+class OperationInvalidatedError(XapianError): pass
class FileError(NotmuchError): pass
class FileNotEmailError(NotmuchError): pass
class DuplicateMessageIdError(NotmuchError): pass
diff --git a/bindings/python-cffi/notmuch2/_message.py
b/bindings/python-cffi/notmuch2/_message.py
index 79485238..e31e0c3c 100644
--- a/bindings/python-cffi/notmuch2/_message.py
+++ b/bindings/python-cffi/notmuch2/_message.py
@@ -707,7 +707,8 @@ class MessageIter(base.NotmuchIter):
self._msg_cls = msg_cls
super().__init__(parent, msgs_p,
fn_destroy=capi.lib.notmuch_messages_destroy,
- fn_valid=capi.lib.notmuch_messages_valid,
+ fn_valid=None,
+ fn_status=capi.lib.notmuch_messages_status,
fn_get=capi.lib.notmuch_messages_get,
fn_next=capi.lib.notmuch_messages_move_to_next)
diff --git a/bindings/python-cffi/notmuch2/_thread.py
b/bindings/python-cffi/notmuch2/_thread.py
index e883f308..af49d2bf 100644
--- a/bindings/python-cffi/notmuch2/_thread.py
+++ b/bindings/python-cffi/notmuch2/_thread.py
@@ -185,7 +185,8 @@ class ThreadIter(base.NotmuchIter):
self._db = db
super().__init__(parent, threads_p,
fn_destroy=capi.lib.notmuch_threads_destroy,
- fn_valid=capi.lib.notmuch_threads_valid,
+ fn_valid=None,
+ fn_status=capi.lib.notmuch_threads_status,
fn_get=capi.lib.notmuch_threads_get,
fn_next=capi.lib.notmuch_threads_move_to_next)
diff --git a/bindings/python-cffi/tests/test_database.py
b/bindings/python-cffi/tests/test_database.py
index 1557235d..c3b0c2e4 100644
--- a/bindings/python-cffi/tests/test_database.py
+++ b/bindings/python-cffi/tests/test_database.py
@@ -296,6 +296,41 @@ class TestQuery:
with dbmod.Database(maildir.path, 'rw',
config=notmuch2.Database.CONFIG.EMPTY) as db:
yield db
+ def _db_modified(self, maildir, notmuch, ret_prepare=None):
+ # populate the database for the initial query
+ with dbmod.Database.create(maildir.path,
config=notmuch2.Database.CONFIG.EMPTY) as db:
+ for i in range(32):
+ pathname = maildir.deliver(body = str(i))[1]
+ msg = db.add(str(pathname))[0]
+ msg.tags.add(str(i))
+
+ with dbmod.Database(maildir.path, 'ro',
config=notmuch2.Database.CONFIG.EMPTY) as db:
+ # prepare value to be returned to caller
+ ret = ret_prepare(db) if ret_prepare else db
+
+ # modify the database sufficiently to trigger
DatabaseModifiedException
+ for i in range(16):
+ with dbmod.Database(maildir.path, 'rw',
config=notmuch2.Database.CONFIG.EMPTY) as db_rw:
+ pathname = maildir.deliver(body = str(i))[1]
+ db_rw.add(str(pathname))
+
+ yield ret
+
+ @pytest.fixture
+ def db_modified(self, maildir, notmuch):
+ "A db triggering DatabaseModifiedException."
+ yield from self._db_modified(maildir, notmuch)
+
+ @pytest.fixture
+ def db_modified_messages(self, maildir, notmuch):
+ "A tuple of (db, messages) triggering DatabaseModifiedException."
+ yield from self._db_modified(maildir, notmuch, lambda db: (db,
db.messages('*')))
+
+ @pytest.fixture
+ def db_modified_threads(self, maildir, notmuch):
+ "A tuple of (db, threads) triggering DatabaseModifiedException."
+ yield from self._db_modified(maildir, notmuch, lambda db: (db,
db.threads('*')))
+
def test_count_messages(self, db):
assert db.count_messages('*') == 3
@@ -371,3 +406,52 @@ class TestQuery:
assert isinstance(msg, notmuch2.Message)
assert msg.alive
del msg
+
+ def test_operation_invalidated_query(self, db_modified):
+ # Test OperationInvalidatedError raised by instantiating the query.
+ for attempt in 1, 2:
+ try:
+ for msg in db_modified.messages('*'):
+ pass
+ break
+ except notmuch2.OperationInvalidatedError:
+ if attempt == 1:
+ db_modified.reopen()
+ continue
+
+ raise
+
+ def test_operation_invalidated_messages(self, db_modified_messages):
+ # Test OperationInvalidatedError raised by iterating over query
results;
+ # the query itself is created while the database is still usable.
+ db, messages = db_modified_messages
+
+ for attempt in 1, 2:
+ try:
+ for msg in messages:
+ pass
+ break
+ except notmuch2.OperationInvalidatedError:
+ if attempt == 1:
+ db.reopen()
+ messages = db.messages('*')
+ continue
+
+ raise
+
+ def test_operation_invalidated_threads(self, db_modified_threads):
+ db, threads = db_modified_threads
+
+ for attempt in 1, 2:
+ try:
+ for t in threads:
+ for msg in t:
+ pass
+ break
+ except notmuch2.OperationInvalidatedError:
+ if attempt == 1:
+ db.reopen()
+ threads = db.threads('*')
+ continue
+
+ raise
--
2.39.5
_______________________________________________
notmuch mailing list -- [email protected]
To unsubscribe send an email to [email protected]