[PATCH 2/2] Make messages returned by Thread objects owned
This reverses the logic of StandaloneMessage to instead create a OwnedMessage. Only the Thread class allows retrieving messages more then once so it can explicitly create such messages. The added test fails with SIGABRT without the fix for the message re-use in threads being present. --- bindings/python-cffi/notmuch2/_database.py | 6 +-- bindings/python-cffi/notmuch2/_message.py | 55 - bindings/python-cffi/notmuch2/_thread.py| 8 ++- bindings/python-cffi/tests/test_database.py | 11 + 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index f14eac78..95f59ca0 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -399,7 +399,7 @@ class Database(base.NotmuchObject): capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID] if ret not in ok: raise errors.NotmuchError(ret) -msg = message.StandaloneMessage(self, msg_pp[0], db=self) +msg = message.Message(self, msg_pp[0], db=self) if sync_flags: msg.tags.from_maildir_flags() return self.AddedMessage( @@ -468,7 +468,7 @@ class Database(base.NotmuchObject): msg_p = msg_pp[0] if msg_p == capi.ffi.NULL: raise LookupError -msg = message.StandaloneMessage(self, msg_p, db=self) +msg = message.Message(self, msg_p, db=self) return msg def get(self, filename): @@ -501,7 +501,7 @@ class Database(base.NotmuchObject): msg_p = msg_pp[0] if msg_p == capi.ffi.NULL: raise LookupError -msg = message.StandaloneMessage(self, msg_p, db=self) +msg = message.Message(self, msg_p, db=self) return msg @property diff --git a/bindings/python-cffi/notmuch2/_message.py b/bindings/python-cffi/notmuch2/_message.py index 416ce7ca..02de50ad 100644 --- a/bindings/python-cffi/notmuch2/_message.py +++ b/bindings/python-cffi/notmuch2/_message.py @@ -47,9 +47,7 @@ class Message(base.NotmuchObject): :type db: Database :param msg_p: The C pointer to the ``notmuch_message_t``. :type msg_p: - :param dup: Whether the message was a duplicate on insertion. - :type dup: None or bool """ _msg_p = base.MemoryPointer() @@ -61,10 +59,22 @@ class Message(base.NotmuchObject): @property def alive(self): -return self._parent.alive +if not self._parent.alive: +return False +try: +self._msg_p +except errors.ObjectDestroyedError: +return False +else: +return True + +def __del__(self): +self._destroy() def _destroy(self): -pass +if self.alive: +capi.lib.notmuch_message_destroy(self._msg_p) +self._msg_p = None @property def messageid(self): @@ -363,30 +373,26 @@ class Message(base.NotmuchObject): if isinstance(other, self.__class__): return self.messageid == other.messageid -class StandaloneMessage(Message): -"""An email message stored in the notmuch database. -This subclass of Message is used for messages that are retrieved from the -database directly and are not owned by a query. +class OwnedMessage(Message): +"""An email message owned by parent thread object. + +This subclass of Message is used for messages that are retrieved +from the notmuch database via a parent :class:`notmuch2.Thread` +object, which "owns" this message. This means that when this +message object is destroyed, by calling :func:`del` or +:meth:`_destroy` directly or indirectly, the message is not freed +in the notmuch API and the parent :class:`notmuch2.Thread` object +can return the same object again when needed. """ + @property def alive(self): -if not self._parent.alive: -return False -try: -self._msg_p -except errors.ObjectDestroyedError: -return False -else: -return True - -def __del__(self): -self._destroy() +return self._parent.alive def _destroy(self): -if self.alive: -capi.lib.notmuch_message_destroy(self._msg_p) -self._msg_p = None +pass + class FilenamesIter(base.NotmuchIter): """Iterator for binary filenames objects.""" @@ -690,8 +696,9 @@ collections.abc.ValuesView.register(PropertiesValuesView) class MessageIter(base.NotmuchIter): -def __init__(self, parent, msgs_p, *, db): +def __init__(self, parent, msgs_p, *, db, msg_cls=Message): self._db = db +self._msg_cls = msg_cls super().__init__(parent, msgs_p, fn_destroy=capi.lib.notmuch_messages_destroy, fn_valid=capi.lib.notmuch_messages_valid, @@ -700,4 +707,4 @@ class
python: Continuing message re-use fix
Hi, This builds on the patch by Anton Khirnov to fix the message re-use that is possible when accessing messages from a thread. I started with just addressing my own comments on this patch, but evolved it into switching the logic around and leave the normal Message object untouched. Instead I created a new OwnedMessage which is used by the Thread which does not free itself on __del__(). I think this is preferable because the other iterators, mainly Database.messages(), do not allow retrieving messages more than once since the query object is hidden from the API. I've left the original commit in this patch series to not alter any contributions. Cheers, Floris ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
[PATCH 1/2] python/notmuch2: add bindings for the database config strings
From: Anton Khirnov --- bindings/python-cffi/notmuch2/_build.py| 17 + bindings/python-cffi/notmuch2/_config.py | 84 ++ bindings/python-cffi/notmuch2/_database.py | 23 ++ 3 files changed, 124 insertions(+) create mode 100644 bindings/python-cffi/notmuch2/_config.py diff --git a/bindings/python-cffi/notmuch2/_build.py b/bindings/python-cffi/notmuch2/_build.py index 5e1fcac1..f269f2a1 100644 --- a/bindings/python-cffi/notmuch2/_build.py +++ b/bindings/python-cffi/notmuch2/_build.py @@ -314,6 +314,23 @@ ffibuilder.cdef( notmuch_indexopts_get_decrypt_policy (const notmuch_indexopts_t *indexopts); void notmuch_indexopts_destroy (notmuch_indexopts_t *options); + +notmuch_status_t +notmuch_database_set_config (notmuch_database_t *db, const char *key, const char *value); +notmuch_status_t +notmuch_database_get_config (notmuch_database_t *db, const char *key, char **value); +notmuch_status_t +notmuch_database_get_config_list (notmuch_database_t *db, const char *prefix, notmuch_config_list_t **out); +notmuch_bool_t +notmuch_config_list_valid (notmuch_config_list_t *config_list); +const char * +notmuch_config_list_key (notmuch_config_list_t *config_list); +const char * +notmuch_config_list_value (notmuch_config_list_t *config_list); +void +notmuch_config_list_move_to_next (notmuch_config_list_t *config_list); +void +notmuch_config_list_destroy (notmuch_config_list_t *config_list); """ ) diff --git a/bindings/python-cffi/notmuch2/_config.py b/bindings/python-cffi/notmuch2/_config.py new file mode 100644 index ..58383c16 --- /dev/null +++ b/bindings/python-cffi/notmuch2/_config.py @@ -0,0 +1,84 @@ +import collections.abc + +import notmuch2._base as base +import notmuch2._capi as capi +import notmuch2._errors as errors + +__all__ = ['ConfigMapping'] + +class ConfigIter(base.NotmuchIter): +def __init__(self, parent, iter_p): +super().__init__( +parent, iter_p, +fn_destroy=capi.lib.notmuch_config_list_destroy, +fn_valid=capi.lib.notmuch_config_list_valid, +fn_get=capi.lib.notmuch_config_list_key, +fn_next=capi.lib.notmuch_config_list_move_to_next) + +def __next__(self): +item = super().__next__() +return base.BinString.from_cffi(item) + +class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping): +"""The config key/value pairs stored in the database. + +The entries are exposed as a :class:`collections.abc.MutableMapping` object. +Note that setting a value to an empty string is the same as deleting it. + +:param parent: the parent object +:param ptr_name: the name of the attribute on the parent which will + return the memory pointer. This allows this object to + access the pointer via the parent's descriptor and thus + trigger :class:`MemoryPointer`'s memory safety. +""" + +def __init__(self, parent, ptr_name): +self._parent = parent +self._ptr = lambda: getattr(parent, ptr_name) + +@property +def alive(self): +return self._parent.alive + +def _destroy(self): +pass + +def __getitem__(self, key): +if isinstance(key, str): +key = key.encode('utf-8') +val_pp = capi.ffi.new('char**') +ret = capi.lib.notmuch_database_get_config(self._ptr(), key, val_pp) +if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: +raise errors.NotmuchError(ret) +if val_pp[0] == "": +capi.lib.free(val_pp[0]) +raise KeyError +val = base.BinString.from_cffi(val_pp[0]) +capi.lib.free(val_pp[0]) +return val + +def __setitem__(self, key, val): +if isinstance(key, str): +key = key.encode('utf-8') +if isinstance(val, str): +val = val.encode('utf-8') +ret = capi.lib.notmuch_database_set_config(self._ptr(), key, val) +if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: +raise errors.NotmuchError(ret) + +def __delitem__(self, key): +self[key] = "" + +def __iter__(self): +"""Return an iterator over the config items. + +:raises NullPointerError: If the iterator can not be created. +""" +configlist_pp = capi.ffi.new('notmuch_config_list_t**') +ret = capi.lib.notmuch_database_get_config_list(self._ptr(), b'', configlist_pp) +if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: +raise errors.NotmuchError(ret) +return ConfigIter(self._parent, configlist_pp[0]) + +def __len__(self): +return sum(1 for t in self) diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index 95f59ca0..3c06402d 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -7,6 +7,7 @@ import pathlib import weakref
[PATCH 2/2] python config access: fix style and KeyError bug
This fixes some minor style/pep8 things and adds tests for the new config support. Also fixes a bug where KeyError was never raised on a missing key. --- bindings/python-cffi/notmuch2/_config.py | 9 ++-- bindings/python-cffi/tests/test_config.py | 56 +++ 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 bindings/python-cffi/tests/test_config.py diff --git a/bindings/python-cffi/notmuch2/_config.py b/bindings/python-cffi/notmuch2/_config.py index 58383c16..29de6495 100644 --- a/bindings/python-cffi/notmuch2/_config.py +++ b/bindings/python-cffi/notmuch2/_config.py @@ -4,9 +4,12 @@ import notmuch2._base as base import notmuch2._capi as capi import notmuch2._errors as errors + __all__ = ['ConfigMapping'] + class ConfigIter(base.NotmuchIter): + def __init__(self, parent, iter_p): super().__init__( parent, iter_p, @@ -19,6 +22,7 @@ class ConfigIter(base.NotmuchIter): item = super().__next__() return base.BinString.from_cffi(item) + class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping): """The config key/value pairs stored in the database. @@ -50,11 +54,10 @@ class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping): ret = capi.lib.notmuch_database_get_config(self._ptr(), key, val_pp) if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: raise errors.NotmuchError(ret) -if val_pp[0] == "": -capi.lib.free(val_pp[0]) -raise KeyError val = base.BinString.from_cffi(val_pp[0]) capi.lib.free(val_pp[0]) +if val == '': +raise KeyError return val def __setitem__(self, key, val): diff --git a/bindings/python-cffi/tests/test_config.py b/bindings/python-cffi/tests/test_config.py new file mode 100644 index ..1b2695f5 --- /dev/null +++ b/bindings/python-cffi/tests/test_config.py @@ -0,0 +1,56 @@ +import collections.abc + +import pytest + +import notmuch2._database as dbmod + +import notmuch2._config as config + + +class TestIter: + +@pytest.fixture +def db(self, maildir): +with dbmod.Database.create(maildir.path) as db: +yield db + +def test_type(self, db): +assert isinstance(db.config, collections.abc.MutableMapping) +assert isinstance(db.config, config.ConfigMapping) + +def test_alive(self, db): +assert db.config.alive + +def test_set_get(self, maildir): +# Ensure get-set works from different db objects +with dbmod.Database.create(maildir.path) as db0: +db0.config['spam'] = 'ham' +with dbmod.Database(maildir.path) as db1: +assert db1.config['spam'] == 'ham' + +def test_get_keyerror(self, db): +with pytest.raises(KeyError): +val = db.config['not-a-key'] +print(repr(val)) + +def test_iter(self, db): +assert list(db.config) == [] +db.config['spam'] = 'ham' +db.config['eggs'] = 'bacon' +assert set(db.config) == {'spam', 'eggs'} +assert set(db.config.keys()) == {'spam', 'eggs'} +assert set(db.config.values()) == {'ham', 'bacon'} +assert set(db.config.items()) == {('spam', 'ham'), ('eggs', 'bacon')} + +def test_len(self, db): +assert len(db.config) == 0 +db.config['spam'] = 'ham' +assert len(db.config) == 1 +db.config['eggs'] = 'bacon' +assert len(db.config) == 2 + +def test_del(self, db): +db.config['spam'] = 'ham' +assert db.config.get('spam') == 'ham' +del db.config['spam'] +assert db.config.get('spam') is None -- 2.27.0 ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
python: config API
This is a followup on the patch from Anton Khirnov adding config API support to the python bindings. I can not help myself but point out that I did not spot the bug until not only I had written tests, but until I looked at the test coverage to see what was not yet executed and added more tests. Tests and test coverage is good! ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH] Update tox.ini for python3.8 and fix pypy3.6
On Mon 15 Jun 2020 at 07:06 -0300, David Bremner wrote: > Floris Bruynooghe writes: > >> [testenv] >> deps = >> @@ -14,3 +14,6 @@ commands = pytest --cov={envsitepackagesdir}/notmuch2 >> {posargs} >> >> [testenv:pypy35] >> basepython = pypy3.5 >> + >> +[testenv:pypy36] >> +basepython = pypy3.6 > > I'm not an expert, but should python 3.7 and python 3.8 have similar > clauses? Or do they work by default? They do work by default. But to be honest these two pypy ones are not fully standardised and rely on me manually ensuring I have both a pypy3.5 and pypy3.6 binary on my path. The default thing that works is just a "pypy3" which doesn't give you any guarantees over the version you get (other than not pypy2). ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
[PATCH 1/2] python/notmuch2: do not destroy messages owned by a query
From: Anton Khirnov Any messages retrieved from a query - either directly via search_messages() or indirectly via thread objects - are owned by that query. Retrieving the same message (i.e. corresponding to the same message ID / database object) several times will always yield the same C object. The caller is allowed to destroy message objects owned by a query before the query itself - which can save memory for long-lived queries. However, that message must then never be retrieved again from that query. The python-notmuch2 bindings will currently destroy every message object in Message._destroy(), which will lead to an invalid free if the same message is then retrieved again. E.g. the following python program leads to libtalloc abort()ing: import notmuch2 db = notmuch2.Database(mode = notmuch2.Database.MODE.READ_ONLY) t= next(db.threads('*')) msgs = list(zip(t.toplevel(), t.toplevel())) msgs = list(zip(t.toplevel(), t.toplevel())) Fix this issue by creating a subclass of Message, which is used for "standalone" message which have to be freed by the caller. Message class is then used only for messages descended from a query, which do not need to be freed by the caller. --- bindings/python-cffi/notmuch2/_database.py | 6 ++-- bindings/python-cffi/notmuch2/_message.py | 42 ++ 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index 95f59ca0..f14eac78 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -399,7 +399,7 @@ class Database(base.NotmuchObject): capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID] if ret not in ok: raise errors.NotmuchError(ret) -msg = message.Message(self, msg_pp[0], db=self) +msg = message.StandaloneMessage(self, msg_pp[0], db=self) if sync_flags: msg.tags.from_maildir_flags() return self.AddedMessage( @@ -468,7 +468,7 @@ class Database(base.NotmuchObject): msg_p = msg_pp[0] if msg_p == capi.ffi.NULL: raise LookupError -msg = message.Message(self, msg_p, db=self) +msg = message.StandaloneMessage(self, msg_p, db=self) return msg def get(self, filename): @@ -501,7 +501,7 @@ class Database(base.NotmuchObject): msg_p = msg_pp[0] if msg_p == capi.ffi.NULL: raise LookupError -msg = message.Message(self, msg_p, db=self) +msg = message.StandaloneMessage(self, msg_p, db=self) return msg @property diff --git a/bindings/python-cffi/notmuch2/_message.py b/bindings/python-cffi/notmuch2/_message.py index c5fdbf6d..416ce7ca 100644 --- a/bindings/python-cffi/notmuch2/_message.py +++ b/bindings/python-cffi/notmuch2/_message.py @@ -14,7 +14,7 @@ __all__ = ['Message'] class Message(base.NotmuchObject): -"""An email message stored in the notmuch database. +"""An email message stored in the notmuch database retrieved via a query. This should not be directly created, instead it will be returned by calling methods on :class:`Database`. A message keeps a @@ -61,22 +61,10 @@ class Message(base.NotmuchObject): @property def alive(self): -if not self._parent.alive: -return False -try: -self._msg_p -except errors.ObjectDestroyedError: -return False -else: -return True - -def __del__(self): -self._destroy() +return self._parent.alive def _destroy(self): -if self.alive: -capi.lib.notmuch_message_destroy(self._msg_p) -self._msg_p = None +pass @property def messageid(self): @@ -375,6 +363,30 @@ class Message(base.NotmuchObject): if isinstance(other, self.__class__): return self.messageid == other.messageid +class StandaloneMessage(Message): +"""An email message stored in the notmuch database. + +This subclass of Message is used for messages that are retrieved from the +database directly and are not owned by a query. +""" +@property +def alive(self): +if not self._parent.alive: +return False +try: +self._msg_p +except errors.ObjectDestroyedError: +return False +else: +return True + +def __del__(self): +self._destroy() + +def _destroy(self): +if self.alive: +capi.lib.notmuch_message_destroy(self._msg_p) +self._msg_p = None class FilenamesIter(base.NotmuchIter): """Iterator for binary filenames objects.""" -- 2.27.0 ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
Re: difficulties with notmuch2 python bindings for alot
On Sun 14 Jun 2020 at 19:44 -0300, David Bremner wrote: > 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). Thanks for confirming, so that should be fine. Cheers, Floris ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH] Support aborting the atomic context
On Mon 15 Jun 2020 at 07:35 -0300, David Bremner wrote: > Floris Bruynooghe writes: > >> 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. I realise that this is perhaps not a very helpful comment. I'll see if I can narrow this down into a proper bug report. >> 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. > > There is a Xapian method WritableDatabase::cancel_transaction(), but it > is not currently exposed by notmuch. I think it could be added, but > getting the testing working right is not something I want to tackle at > this point in the release cycle. I guess this should be upwardly > compatible, as all code correct for your current implementation should > still work if we replace notmuch_database_close with > notmuch_cancel_atomic. Thoughts? I agree with your reasoning here that such a change would be upwardly compatible. Though I can think of some really convoluted scenarios where this could be false, e.g. after aborting a transaction code could rely on handling NOTMUCH_STATUS_XAPIAN_EXCEPTION it gets from a the closed database. This seems so much a corner case though, that I'd be willing to ignore it. ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
Re: python: config API
Floris Bruynooghe writes: > This is a followup on the patch from Anton Khirnov adding config > API support to the python bindings. > > I can not help myself but point out that I did not spot the bug > until not only I had written tests, but until I looked at the > test coverage to see what was not yet executed and added more > tests. Tests and test coverage is good! > I've merged the revised series to release and master. ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH] Support aborting the atomic context
Floris Bruynooghe writes: > 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. There is a Xapian method WritableDatabase::cancel_transaction(), but it is not currently exposed by notmuch. I think it could be added, but getting the testing working right is not something I want to tackle at this point in the release cycle. I guess this should be upwardly compatible, as all code correct for your current implementation should still work if we replace notmuch_database_close with notmuch_cancel_atomic. Thoughts? d ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH] Update tox.ini for python3.8 and fix pypy3.6
Floris Bruynooghe writes: > [testenv] > deps = > @@ -14,3 +14,6 @@ commands = pytest --cov={envsitepackagesdir}/notmuch2 > {posargs} > > [testenv:pypy35] > basepython = pypy3.5 > + > +[testenv:pypy36] > +basepython = pypy3.6 I'm not an expert, but should python 3.7 and python 3.8 have similar clauses? Or do they work by default? d ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH] Update tox.ini for python3.8 and fix pypy3.6
On Mon, Jun 15 2020, David Bremner wrote: > Floris Bruynooghe writes: > >> [testenv] >> deps = >> @@ -14,3 +14,6 @@ commands = pytest --cov={envsitepackagesdir}/notmuch2 >> {posargs} >> >> [testenv:pypy35] >> basepython = pypy3.5 >> + >> +[testenv:pypy36] >> +basepython = pypy3.6 > > I'm not an expert, but should python 3.7 and python 3.8 have similar > clauses? Or do they work by default? "Python 3.6 compatible PyPy3.6 v7.3.1" in https://www.pypy.org/download.html hints there are not (yet) pypy3.7 nor pypy3.8 > > d Tomi ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH] Update tox.ini for python3.8 and fix pypy3.6
Tomi Ollila writes: > On Mon, Jun 15 2020, David Bremner wrote: > >> Floris Bruynooghe writes: >> >>> [testenv] >>> deps = >>> @@ -14,3 +14,6 @@ commands = pytest --cov={envsitepackagesdir}/notmuch2 >>> {posargs} >>> >>> [testenv:pypy35] >>> basepython = pypy3.5 >>> + >>> +[testenv:pypy36] >>> +basepython = pypy3.6 >> >> I'm not an expert, but should python 3.7 and python 3.8 have similar >> clauses? Or do they work by default? > > "Python 3.6 compatible PyPy3.6 v7.3.1" in https://www.pypy.org/download.html > hints there are not (yet) pypy3.7 nor pypy3.8 Oh sorry. I really should read better. I even knew that about pypy. OK, no objections to the patch as is. d ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH] Update tox.ini for python3.8 and fix pypy3.6
Floris Bruynooghe writes: > Python 3.8 has been released for a while now, make sure we keep > supporting it correctly. > > PyPy 3.6 wasn not configured correctly. merged to origin and master d ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH] Add missing set methods to tagsets
Floris Bruynooghe writes: > 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. merged to release and master. d ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch