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_directory notmuch_directory_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;
const char *
@ -325,18 +325,18 @@ ffibuilder.cdef(
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_config_pairs_t *
notmuch_config_get_pairs (notmuch_database_t *db, const char *prefix);
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 *
notmuch_config_list_key (notmuch_config_list_t *config_list);
notmuch_config_pairs_key (notmuch_config_pairs_t *config_list);
const char *
notmuch_config_list_value (notmuch_config_list_t *config_list);
notmuch_config_pairs_value (notmuch_config_pairs_t *config_list);
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
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):
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)
fn_destroy=capi.lib.notmuch_config_pairs_destroy,
fn_valid=capi.lib.notmuch_config_pairs_valid,
fn_get=capi.lib.notmuch_config_pairs_key,
fn_next=capi.lib.notmuch_config_pairs_move_to_next)
def __next__(self):
item = super().__next__()
return base.BinString.from_cffi(item)
# skip pairs whose value is NULL
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):
"""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.
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 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):
@ -77,11 +92,10 @@ class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping):
: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])
config_pairs_p = capi.lib.notmuch_config_get_pairs(self._ptr(), b'')
if config_pairs_p == capi.ffi.NULL:
raise KeyError
return ConfigIter(self._parent, config_pairs_p)
def __len__(self):
return sum(1 for t in self)

View file

@ -34,20 +34,24 @@ class TestIter:
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 has_prefix(x):
return x.startswith('TEST.')
assert [ x for x in db.config if has_prefix(x) ] == []
db.config['TEST.spam'] = 'TEST.ham'
db.config['TEST.eggs'] = 'TEST.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):
assert len(db.config) == 0
defaults = len(db.config)
db.config['spam'] = 'ham'
assert len(db.config) == 1
assert len(db.config) == defaults + 1
db.config['eggs'] = 'bacon'
assert len(db.config) == 2
assert len(db.config) == defaults + 2
def test_del(self, db):
db.config['spam'] = 'ham'

View file

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