python-cffi: use config_pairs API in ConfigIterator

This returns all of the config keys with non-empty values, not just
those that happen to be stored in the database.
This commit is contained in:
David Bremner 2022-02-09 08:28:54 -04:00
parent e221a4531f
commit 9ddd13f758
4 changed files with 49 additions and 32 deletions

View file

@ -97,7 +97,7 @@ ffibuilder.cdef(
typedef struct _notmuch_string_map_iterator notmuch_message_properties_t; typedef struct _notmuch_string_map_iterator notmuch_message_properties_t;
typedef struct _notmuch_directory notmuch_directory_t; typedef struct _notmuch_directory notmuch_directory_t;
typedef struct _notmuch_filenames notmuch_filenames_t; typedef struct _notmuch_filenames notmuch_filenames_t;
typedef struct _notmuch_config_list notmuch_config_list_t; typedef struct _notmuch_config_pairs notmuch_config_pairs_t;
typedef struct _notmuch_indexopts notmuch_indexopts_t; typedef struct _notmuch_indexopts notmuch_indexopts_t;
const char * const char *
@ -325,18 +325,18 @@ ffibuilder.cdef(
notmuch_database_set_config (notmuch_database_t *db, const char *key, const char *value); notmuch_database_set_config (notmuch_database_t *db, const char *key, const char *value);
notmuch_status_t notmuch_status_t
notmuch_database_get_config (notmuch_database_t *db, const char *key, char **value); notmuch_database_get_config (notmuch_database_t *db, const char *key, char **value);
notmuch_status_t notmuch_config_pairs_t *
notmuch_database_get_config_list (notmuch_database_t *db, const char *prefix, notmuch_config_list_t **out); notmuch_config_get_pairs (notmuch_database_t *db, const char *prefix);
notmuch_bool_t notmuch_bool_t
notmuch_config_list_valid (notmuch_config_list_t *config_list); notmuch_config_pairs_valid (notmuch_config_pairs_t *config_list);
const char * const char *
notmuch_config_list_key (notmuch_config_list_t *config_list); notmuch_config_pairs_key (notmuch_config_pairs_t *config_list);
const char * const char *
notmuch_config_list_value (notmuch_config_list_t *config_list); notmuch_config_pairs_value (notmuch_config_pairs_t *config_list);
void void
notmuch_config_list_move_to_next (notmuch_config_list_t *config_list); notmuch_config_pairs_move_to_next (notmuch_config_pairs_t *config_list);
void void
notmuch_config_list_destroy (notmuch_config_list_t *config_list); notmuch_config_pairs_destroy (notmuch_config_pairs_t *config_list);
""" """
) )

View file

@ -13,27 +13,42 @@ class ConfigIter(base.NotmuchIter):
def __init__(self, parent, iter_p): def __init__(self, parent, iter_p):
super().__init__( super().__init__(
parent, iter_p, parent, iter_p,
fn_destroy=capi.lib.notmuch_config_list_destroy, fn_destroy=capi.lib.notmuch_config_pairs_destroy,
fn_valid=capi.lib.notmuch_config_list_valid, fn_valid=capi.lib.notmuch_config_pairs_valid,
fn_get=capi.lib.notmuch_config_list_key, fn_get=capi.lib.notmuch_config_pairs_key,
fn_next=capi.lib.notmuch_config_list_move_to_next) fn_next=capi.lib.notmuch_config_pairs_move_to_next)
def __next__(self): def __next__(self):
item = super().__next__() # skip pairs whose value is NULL
return base.BinString.from_cffi(item) while capi.lib.notmuch_config_pairs_valid(super()._iter_p):
val_p = capi.lib.notmuch_config_pairs_value(super()._iter_p)
key_p = capi.lib.notmuch_config_pairs_key(super()._iter_p)
if key_p == capi.ffi.NULL:
# this should never happen
raise errors.NullPointerError
key = base.BinString.from_cffi(key_p)
capi.lib.notmuch_config_pairs_move_to_next(super()._iter_p)
if val_p != capi.ffi.NULL and base.BinString.from_cffi(val_p) != "":
return key
self._destroy()
raise StopIteration
class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping): class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping):
"""The config key/value pairs stored in the database. """The config key/value pairs loaded from the database, config file,
and and/or defaults.
The entries are exposed as a :class:`collections.abc.MutableMapping` object. 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. Note that setting a value to an empty string is the same as deleting it.
Mutating (deleting or updating values) in the map persists only in
the database, which can be shadowed by config files.
:param parent: the parent object :param parent: the parent object
:param ptr_name: the name of the attribute on the parent which will :param ptr_name: the name of the attribute on the parent which will
return the memory pointer. This allows this object to return the memory pointer. This allows this object to
access the pointer via the parent's descriptor and thus access the pointer via the parent's descriptor and thus
trigger :class:`MemoryPointer`'s memory safety. trigger :class:`MemoryPointer`'s memory safety.
""" """
def __init__(self, parent, ptr_name): def __init__(self, parent, ptr_name):
@ -77,11 +92,10 @@ class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping):
:raises NullPointerError: If the iterator can not be created. :raises NullPointerError: If the iterator can not be created.
""" """
configlist_pp = capi.ffi.new('notmuch_config_list_t**') config_pairs_p = capi.lib.notmuch_config_get_pairs(self._ptr(), b'')
ret = capi.lib.notmuch_database_get_config_list(self._ptr(), b'', configlist_pp) if config_pairs_p == capi.ffi.NULL:
if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: raise KeyError
raise errors.NotmuchError(ret) return ConfigIter(self._parent, config_pairs_p)
return ConfigIter(self._parent, configlist_pp[0])
def __len__(self): def __len__(self):
return sum(1 for t in self) return sum(1 for t in self)

View file

@ -34,20 +34,24 @@ class TestIter:
print(repr(val)) print(repr(val))
def test_iter(self, db): def test_iter(self, db):
assert list(db.config) == [] def has_prefix(x):
db.config['spam'] = 'ham' return x.startswith('TEST.')
db.config['eggs'] = 'bacon'
assert set(db.config) == {'spam', 'eggs'} assert [ x for x in db.config if has_prefix(x) ] == []
assert set(db.config.keys()) == {'spam', 'eggs'} db.config['TEST.spam'] = 'TEST.ham'
assert set(db.config.values()) == {'ham', 'bacon'} db.config['TEST.eggs'] = 'TEST.bacon'
assert set(db.config.items()) == {('spam', 'ham'), ('eggs', 'bacon')} assert { x for x in db.config if has_prefix(x) } == {'TEST.spam', 'TEST.eggs'}
assert { x for x in db.config.keys() if has_prefix(x) } == {'TEST.spam', 'TEST.eggs'}
assert { x for x in db.config.values() if has_prefix(x) } == {'TEST.ham', 'TEST.bacon'}
assert { (x, y) for (x,y) in db.config.items() if has_prefix(x) } == \
{('TEST.spam', 'TEST.ham'), ('TEST.eggs', 'TEST.bacon')}
def test_len(self, db): def test_len(self, db):
assert len(db.config) == 0 defaults = len(db.config)
db.config['spam'] = 'ham' db.config['spam'] = 'ham'
assert len(db.config) == 1 assert len(db.config) == defaults + 1
db.config['eggs'] = 'bacon' db.config['eggs'] = 'bacon'
assert len(db.config) == 2 assert len(db.config) == defaults + 2
def test_del(self, db): def test_del(self, db):
db.config['spam'] = 'ham' db.config['spam'] = 'ham'

View file

@ -294,7 +294,6 @@ EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Config list from python ($config)" test_begin_subtest "Config list from python ($config)"
test_subtest_known_broken
test_python <<EOF > OUTPUT test_python <<EOF > OUTPUT
from notmuch2 import Database from notmuch2 import Database
db=Database(config=Database.CONFIG.SEARCH) db=Database(config=Database.CONFIG.SEARCH)