From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp11.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms0.migadu.com with LMTPS id cOblHTKuDWKZYAAAgWs5BA (envelope-from ) for ; Thu, 17 Feb 2022 03:08:50 +0100 Received: from aspmx1.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp11.migadu.com with LMTPS id MC86GzKuDWLYtwAA9RJhRA (envelope-from ) for ; Thu, 17 Feb 2022 03:08:50 +0100 Received: from mail.notmuchmail.org (yantan.tethera.net [IPv6:2a01:4f9:c011:7a79::1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id C0BB14CE26 for ; Thu, 17 Feb 2022 03:08:49 +0100 (CET) Received: from yantan.tethera.net (localhost [127.0.0.1]) by mail.notmuchmail.org (Postfix) with ESMTP id 8DCBD5F709; Thu, 17 Feb 2022 02:08:47 +0000 (UTC) Received: from fethera.tethera.net (fethera.tethera.net [198.245.60.197]) by mail.notmuchmail.org (Postfix) with ESMTP id B8F9A5F5E3 for ; Thu, 17 Feb 2022 02:08:44 +0000 (UTC) Received: by fethera.tethera.net (Postfix, from userid 1001) id 7D3225FBD4; Wed, 16 Feb 2022 21:08:43 -0500 (EST) Received: (nullmailer pid 181064 invoked by uid 1000); Thu, 17 Feb 2022 02:08:39 -0000 From: David Bremner To: Floris Bruynooghe , David Bremner , notmuch@notmuchmail.org Subject: [PATCH v2 2/2] python-cffi: use config_pairs API in ConfigIterator Date: Wed, 16 Feb 2022 22:08:35 -0400 Message-Id: <20220217020835.180940-3-david@tethera.net> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220217020835.180940-1-david@tethera.net> References: <8735kipm9j.fsf@powell.devork.be> <20220217020835.180940-1-david@tethera.net> MIME-Version: 1.0 Message-ID-Hash: YEHMKHHF4LSUYR5C2Y5PEHYXEQBIGNVL X-Message-ID-Hash: YEHMKHHF4LSUYR5C2Y5PEHYXEQBIGNVL X-MailFrom: bremner@tethera.net X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-notmuch.notmuchmail.org-0; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.3 Precedence: list List-Id: "Use and development of the notmuch mail system." List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit X-Migadu-Flow: FLOW_IN X-Migadu-Country: DE ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1645063730; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-owner:list-unsubscribe:list-subscribe:list-post; bh=w0IzmalulTpcjk/2fkfmMS7IhTWbDnWB8cYmhE72D0g=; b=SNlxGCd5Ck5iUIflTtUy/uY0alYX2UFH4WFAlQeL0w/fay88zKwdzxDlnfIOk7fCkhTX/F Qx9qk+DnBfm/ebWl0mX3XwbatGg6IiBoNOzbQeyjiuCXtwrRUp+BnRJL0HCQCW2p6p4gQP vBDrBnuJbIbXZZcuXGZONT00EmQ+fK7V+mc3XolXaa8E2UyLloqbZe0/nVPj4D0ufpG/vW 1+c+443fWf8xYV1Lw3rDSfMVij+R32QL31mHPU3uMOKARGy+u9Zc7zScCNtfLORQnXaDn0 nZFE8fkf6vK+YzOPbTDE6dyZ8evQF3iffdatm7B5JUYmEMnL8zN9JUJGIp1Sjw== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1645063730; a=rsa-sha256; cv=none; b=sIO0oYKhbVBHW4//K0ZonXIBvg22WdbAordxbeJq8m7vcd4BafapzVxCWk06E6viFJNgBV kfJ/b1tH8VqhCDclur7wiImObYZdfzM+28kHNmhFWaXMbnyhoyfGctR7yAhGz2i7nQNvQr 5F3IJdceXXQJszj0WRsN8VFUDXQnQntEoEcAX3AXMTi56D6CrSHAP1wBz6+XFkwgyD5Ox2 K7UzDJO7+0rmepkN/tFegFZ4WOH3LWqYPN+2u6epdvedYFaHKW4ff2qkEzcL1TOZZSgYf6 LEU/W3q2cxTfW+NHBBvBMqRMwuphlnJv1SpcFNUUHOSZ5h+IsUFZXREqSkmGBg== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=none; dmarc=none; spf=pass (aspmx1.migadu.com: domain of notmuch-bounces@notmuchmail.org designates 2a01:4f9:c011:7a79::1 as permitted sender) smtp.mailfrom=notmuch-bounces@notmuchmail.org X-Migadu-Spam-Score: -1.35 Authentication-Results: aspmx1.migadu.com; dkim=none; dmarc=none; spf=pass (aspmx1.migadu.com: domain of notmuch-bounces@notmuchmail.org designates 2a01:4f9:c011:7a79::1 as permitted sender) smtp.mailfrom=notmuch-bounces@notmuchmail.org X-Migadu-Queue-Id: C0BB14CE26 X-Spam-Score: -1.35 X-Migadu-Scanner: scn1.migadu.com X-TUID: J5S/dJtuaV3Y This returns all of the config keys with non-empty values, not just those that happen to be stored in the database. --- bindings/python-cffi/notmuch2/_build.py | 16 ++++----- bindings/python-cffi/notmuch2/_config.py | 40 +++++++++++++++-------- bindings/python-cffi/tests/test_config.py | 24 ++++++++------ test/T055-path-config.sh | 1 - 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/bindings/python-cffi/notmuch2/_build.py b/bindings/python-cffi/notmuch2/_build.py index a55b484f..349bb79d 100644 --- a/bindings/python-cffi/notmuch2/_build.py +++ b/bindings/python-cffi/notmuch2/_build.py @@ -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); """ ) diff --git a/bindings/python-cffi/notmuch2/_config.py b/bindings/python-cffi/notmuch2/_config.py index 29de6495..603fdcbf 100644 --- a/bindings/python-cffi/notmuch2/_config.py +++ b/bindings/python-cffi/notmuch2/_config.py @@ -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) diff --git a/bindings/python-cffi/tests/test_config.py b/bindings/python-cffi/tests/test_config.py index 67b0dea4..2a7f42f0 100644 --- a/bindings/python-cffi/tests/test_config.py +++ b/bindings/python-cffi/tests/test_config.py @@ -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' diff --git a/test/T055-path-config.sh b/test/T055-path-config.sh index 71823039..4897c814 100755 --- a/test/T055-path-config.sh +++ b/test/T055-path-config.sh @@ -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 < OUTPUT from notmuch2 import Database db=Database(config=Database.CONFIG.SEARCH) -- 2.34.1