Re: difficulties with notmuch2 python bindings for alot

2020-06-14 Thread David Bremner
Floris Bruynooghe  writes:

> One thing that they encountered and don't yet understand is that they
> reported issues with leaking filedescriptors.  They used the bindings in
> a way where I expect it to only call notmuch_database_destroy() when
> they are done with it.  From reading notmuch.h I think that's correct
> and there's no need to call notmuch_database_close() first.  Yet someone
> reported that explicitly calling close helped.  Is the assumption I made
> of only calling destroy correct?

The first thing destroy does is call close. My read of the
notmuch_database_close code is that it is idempotent (calling multiple
times does not change anything).

d

___
notmuch mailing list
notmuch@notmuchmail.org
https://notmuchmail.org/mailman/listinfo/notmuch


Re: difficulties with notmuch2 python bindings for alot

2020-06-14 Thread Floris Bruynooghe
Hi Daniel,

On Tue 09 Jun 2020 at 09:19 -0400, Daniel Kahn Gillmor wrote:
> I see over on github that alot is trying to port to the notmuch2
> bindings, and having a few problems with it:
>
>  https://github.com/pazz/alot/pull/1511
>
> alot is an important consumer of the notmuch python bindings, and it
> would be really great to see them successfully transition to the
> notmuch2 module.
>
> Floris, if you (or anyone else with this particular knowledge) has a
> chance to take a look and help them sort out the remaining issues, that
> would be much appreciated!

Thanks for the pointer, I've pinged the issue offering help with the
bindings and had a look through the existing things they discussed.

One thing that they encountered and don't yet understand is that they
reported issues with leaking filedescriptors.  They used the bindings in
a way where I expect it to only call notmuch_database_destroy() when
they are done with it.  From reading notmuch.h I think that's correct
and there's no need to call notmuch_database_close() first.  Yet someone
reported that explicitly calling close helped.  Is the assumption I made
of only calling destroy correct?

Cheers,
Floris
___
notmuch mailing list
notmuch@notmuchmail.org
https://notmuchmail.org/mailman/listinfo/notmuch


python: Update tox.ini for python 3.8

2020-06-14 Thread Floris Bruynooghe
This was released a while ago, we should support it.


___
notmuch mailing list
notmuch@notmuchmail.org
https://notmuchmail.org/mailman/listinfo/notmuch


[PATCH] Update tox.ini for python3.8 and fix pypy3.6

2020-06-14 Thread Floris Bruynooghe
Python 3.8 has been released for a while now, make sure we keep
supporting it correctly.

PyPy 3.6 wasn not configured correctly.
---
 bindings/python-cffi/tox.ini | 5 -
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/bindings/python-cffi/tox.ini b/bindings/python-cffi/tox.ini
index 34148a11..7cf93be0 100644
--- a/bindings/python-cffi/tox.ini
+++ b/bindings/python-cffi/tox.ini
@@ -3,7 +3,7 @@ minversion = 3.0
 addopts = -ra --cov=notmuch2 --cov=tests
 
 [tox]
-envlist = py35,py36,py37,pypy35,pypy36
+envlist = py35,py36,py37,py38,pypy35,pypy36
 
 [testenv]
 deps =
@@ -14,3 +14,6 @@ commands = pytest --cov={envsitepackagesdir}/notmuch2 
{posargs}
 
 [testenv:pypy35]
 basepython = pypy3.5
+
+[testenv:pypy36]
+basepython = pypy3.6
-- 
2.27.0

___
notmuch mailing list
notmuch@notmuchmail.org
https://notmuchmail.org/mailman/listinfo/notmuch


[PATCH] Add missing set methods to tagsets

2020-06-14 Thread Floris Bruynooghe
Even though we use collections.abc.Set which implements all these
methods under their operator names, the actual named variations of
these methods are shockingly missing.  So let's add them manually.
---
 bindings/python-cffi/notmuch2/_tags.py  | 21 +
 bindings/python-cffi/tests/test_tags.py | 62 +
 2 files changed, 83 insertions(+)

diff --git a/bindings/python-cffi/notmuch2/_tags.py 
b/bindings/python-cffi/notmuch2/_tags.py
index 212852a8..3b14c981 100644
--- a/bindings/python-cffi/notmuch2/_tags.py
+++ b/bindings/python-cffi/notmuch2/_tags.py
@@ -110,6 +110,27 @@ class ImmutableTagSet(base.NotmuchObject, 
collections.abc.Set):
 def __eq__(self, other):
 return tuple(sorted(self.iter())) == tuple(sorted(other.iter()))
 
+def issubset(self, other):
+return self <= other
+
+def issuperset(self, other):
+return self >= other
+
+def union(self, other):
+return self | other
+
+def intersection(self, other):
+return self & other
+
+def difference(self, other):
+return self - other
+
+def symmetric_difference(self, other):
+return self ^ other
+
+def copy(self):
+return set(self)
+
 def __hash__(self):
 return hash(tuple(self.iter()))
 
diff --git a/bindings/python-cffi/tests/test_tags.py 
b/bindings/python-cffi/tests/test_tags.py
index f12fa1e6..faf3947b 100644
--- a/bindings/python-cffi/tests/test_tags.py
+++ b/bindings/python-cffi/tests/test_tags.py
@@ -50,6 +50,22 @@ class TestImmutable:
 assert 'unread' in tagset
 assert 'foo' not in tagset
 
+def test_isdisjoint(self, tagset):
+assert tagset.isdisjoint(set(['spam', 'ham']))
+assert not tagset.isdisjoint(set(['inbox']))
+
+def test_issubset(self, tagset):
+assert {'inbox'} <= tagset
+assert {'inbox'}.issubset(tagset)
+assert tagset <= {'inbox', 'unread', 'spam'}
+assert tagset.issubset({'inbox', 'unread', 'spam'})
+
+def test_issuperset(self, tagset):
+assert {'inbox', 'unread', 'spam'} >= tagset
+assert {'inbox', 'unread', 'spam'}.issuperset(tagset)
+assert tagset >= {'inbox'}
+assert tagset.issuperset({'inbox'})
+
 def test_iter(self, tagset):
 expected = sorted(['unread', 'inbox'])
 found = []
@@ -78,18 +94,30 @@ class TestImmutable:
 assert isinstance(common, set)
 assert isinstance(common, collections.abc.Set)
 assert common == {'unread'}
+common = tagset.intersection({'unread'})
+assert isinstance(common, set)
+assert isinstance(common, collections.abc.Set)
+assert common == {'unread'}
 
 def test_or(self, tagset):
 res = tagset | {'foo'}
 assert isinstance(res, set)
 assert isinstance(res, collections.abc.Set)
 assert res == {'unread', 'inbox', 'foo'}
+res = tagset.union({'foo'})
+assert isinstance(res, set)
+assert isinstance(res, collections.abc.Set)
+assert res == {'unread', 'inbox', 'foo'}
 
 def test_sub(self, tagset):
 res = tagset - {'unread'}
 assert isinstance(res, set)
 assert isinstance(res, collections.abc.Set)
 assert res == {'inbox'}
+res = tagset.difference({'unread'})
+assert isinstance(res, set)
+assert isinstance(res, collections.abc.Set)
+assert res == {'inbox'}
 
 def test_rsub(self, tagset):
 res = {'foo', 'unread'} - tagset
@@ -102,6 +130,10 @@ class TestImmutable:
 assert isinstance(res, set)
 assert isinstance(res, collections.abc.Set)
 assert res == {'inbox', 'foo'}
+res = tagset.symmetric_difference({'unread', 'foo'})
+assert isinstance(res, set)
+assert isinstance(res, collections.abc.Set)
+assert res == {'inbox', 'foo'}
 
 def test_rxor(self, tagset):
 res = {'unread', 'foo'} ^ tagset
@@ -109,6 +141,12 @@ class TestImmutable:
 assert isinstance(res, collections.abc.Set)
 assert res == {'inbox', 'foo'}
 
+def test_copy(self, tagset):
+res = tagset.copy()
+assert isinstance(res, set)
+assert isinstance(res, collections.abc.Set)
+assert res == {'inbox', 'unread'}
+
 
 class TestMutableTagset:
 
@@ -175,3 +213,27 @@ class TestMutableTagset:
 msg.tags.to_maildir_flags()
 flags = msg.path.name.split(',')[-1]
 assert 'F' not in flags
+
+def test_isdisjoint(self, tagset):
+assert tagset.isdisjoint(set(['spam', 'ham']))
+assert not tagset.isdisjoint(set(['inbox']))
+
+def test_issubset(self, tagset):
+assert {'inbox'} <= tagset
+assert {'inbox'}.issubset(tagset)
+assert not {'spam'} <= tagset
+assert not {'spam'}.issubset(tagset)
+assert tagset <= {'inbox', 'unread', 'spam'}
+assert tagset.issubset({'inbox', 'unread', 'spam'})
+assert 

python-cffi: add missing tagset methods

2020-06-14 Thread Floris Bruynooghe
This issue was found by alot's porting efforts.  It seems these
were simply missing.


___
notmuch mailing list
notmuch@notmuchmail.org
https://notmuchmail.org/mailman/listinfo/notmuch


[PATCH] Support aborting the atomic context

2020-06-14 Thread Floris Bruynooghe
Since it is possible to use an atomic context to abort a number of
changes support this usage.  Because the only way to actually abort
the transaction is to close the database this must also do so.
---
 bindings/python-cffi/notmuch2/_database.py  | 16 +++-
 bindings/python-cffi/tests/test_database.py |  5 +
 2 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/bindings/python-cffi/notmuch2/_database.py 
b/bindings/python-cffi/notmuch2/_database.py
index 95f59ca0..c851f0a5 100644
--- a/bindings/python-cffi/notmuch2/_database.py
+++ b/bindings/python-cffi/notmuch2/_database.py
@@ -641,6 +641,7 @@ class AtomicContext:
 def __init__(self, db, ptr_name):
 self._db = db
 self._ptr = lambda: getattr(db, ptr_name)
+self._exit_fn = lambda: None
 
 def __del__(self):
 self._destroy()
@@ -656,13 +657,17 @@ class AtomicContext:
 ret = capi.lib.notmuch_database_begin_atomic(self._ptr())
 if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
 raise errors.NotmuchError(ret)
+self._exit_fn = self._end_atomic
 return self
 
-def __exit__(self, exc_type, exc_value, traceback):
+def _end_atomic(self):
 ret = capi.lib.notmuch_database_end_atomic(self._ptr())
 if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
 raise errors.NotmuchError(ret)
 
+def __exit__(self, exc_type, exc_value, traceback):
+self._exit_fn()
+
 def force_end(self):
 """Force ending the atomic section.
 
@@ -681,6 +686,15 @@ class AtomicContext:
 if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
 raise errors.NotmuchError(ret)
 
+def abort(self):
+"""Abort the transaction.
+
+Aborting a transaction will not commit any of the changes, but
+will also implicitly close the database.
+"""
+self._exit_fn = lambda: None
+self._db.close()
+
 
 @functools.total_ordering
 class DbRevision:
diff --git a/bindings/python-cffi/tests/test_database.py 
b/bindings/python-cffi/tests/test_database.py
index e3a8344d..aa2cbdc7 100644
--- a/bindings/python-cffi/tests/test_database.py
+++ b/bindings/python-cffi/tests/test_database.py
@@ -127,6 +127,11 @@ class TestAtomic:
 with pytest.raises(errors.UnbalancedAtomicError):
 ctx.force_end()
 
+def test_abort(self, db):
+with db.atomic() as txn:
+txn.abort()
+assert db.closed
+
 
 class TestRevision:
 
-- 
2.27.0

___
notmuch mailing list
notmuch@notmuchmail.org
https://notmuchmail.org/mailman/listinfo/notmuch


[PATCH] Support aborting the atomic context

2020-06-14 Thread Floris Bruynooghe
This is an implementation of what was suggested in
id:87v9k96xtl@powell.devork.be It closes the database as that is
the only safe way to do this afaik.

Currently when the database is closed there are still a bunch of
operations which can result in segfaults.  Yet the API also promises
that some operations are still valid, basically those which only
access already previously retrieved information.  It would be nice to
find a good solution for this in the python bindings, but I don't yet
know what this would be.


___
notmuch mailing list
notmuch@notmuchmail.org
https://notmuchmail.org/mailman/listinfo/notmuch