From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp10.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms0.migadu.com with LMTPS id OB4GHPpeBmIRFwAAgWs5BA (envelope-from ) for ; Fri, 11 Feb 2022 14:04:58 +0100 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp10.migadu.com with LMTPS id 8LiwFPpeBmKGGgAAG6o9tA (envelope-from ) for ; Fri, 11 Feb 2022 14:04:58 +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 1A0841736F for ; Fri, 11 Feb 2022 14:04:56 +0100 (CET) Received: from yantan.tethera.net (localhost [127.0.0.1]) by mail.notmuchmail.org (Postfix) with ESMTP id D92B55F721; Fri, 11 Feb 2022 13:04:48 +0000 (UTC) Received: from fethera.tethera.net (fethera.tethera.net [198.245.60.197]) by mail.notmuchmail.org (Postfix) with ESMTP id 72DAA5F719 for ; Fri, 11 Feb 2022 13:04:45 +0000 (UTC) Received: by fethera.tethera.net (Postfix, from userid 1001) id F04DE5FBD4; Fri, 11 Feb 2022 08:04:43 -0500 (EST) Received: (nullmailer pid 1417031 invoked by uid 1000); Fri, 11 Feb 2022 13:04:18 -0000 From: David Bremner To: notmuch@notmuchmail.org Subject: [PATCH 2/2] python-cffi: use config_pairs API in ConfigIterator Date: Fri, 11 Feb 2022 09:04:12 -0400 Message-Id: <20220211130412.1416990-3-david@tethera.net> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220211130412.1416990-1-david@tethera.net> References: <20220211130412.1416990-1-david@tethera.net> MIME-Version: 1.0 Message-ID-Hash: XALXZLOUCX4BQ3VJO42M24RJCI6M3TZI X-Message-ID-Hash: XALXZLOUCX4BQ3VJO42M24RJCI6M3TZI 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=1644584696; 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=qM64IM9ckIifz8i3iRiDKyC9NJSD6rVPO2NAlvlz5Gg=; b=hAEd8qxvcC0KQX6FH/HyLNApi1LWXmv+HjtZp90SADY3f48RrOQRujMIyqABGqDiDsUtoo Vrsyyc1DrkLCkOx5g06GVKQr0/EXJv4FFHJtXc3IzZ5TAej2Yt6A63skKUi4dCf/Ku5T8A p2CUm6XjmfBStZTOOSBpm7mjO/xNQAkHI7IqB0XdZW0l3J8r2LjmCZsRM58KNlJ22sDE0q /YWWZLgG7QoRV82WfnBm/bGuYo0L9jqtNaph5zwtJt+uWGjN2jiGp/pQv8R9+hB2wKvE+S 3I/1Fq0z8mlz1VFpd1rYKkVILoc+Hs8BC09kSNT72yBJ46jwLViOp2mdWqftqQ== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1644584696; a=rsa-sha256; cv=none; b=gmyqN8+vRoOyBvpvovZEdJX1lgUgfUV10ZBB0OuwAskYdDFrhFEcZH0sgDT3dfNGHoZom/ 3Mzn561RJF5HHBzNNejAs7rhM3Ok65hignR+Ctsi5uqsw/TSmXJg/h57R+z6ecvPaK4VjP sjpaLoRG6vl33b5ph2ThUAzHe6BKWWuObuUb6NlWiZ1BsKOHxqA0VdcQh6mJyVzm3B0B1P IIcaelKLGhSiFzpafm8y51B9iJRcKZaFlXWfO3ZyrCki2PwREd4zuThcX1WA73ymhPjBPS EBLHwqoQ8EJc9aGGV1okzofkJvHOPj3DrmSAqbsx1quS21puxwOG4P7b0fGO9Q== 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.33 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: 1A0841736F X-Spam-Score: -1.33 X-Migadu-Scanner: scn0.migadu.com X-TUID: qUU7y8jV+h4/ 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 | 37 +++++++++++++++-------- bindings/python-cffi/tests/test_config.py | 22 ++++++++------ test/T055-path-config.sh | 1 - 4 files changed, 44 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..d73539c5 100644 --- a/bindings/python-cffi/notmuch2/_config.py +++ b/bindings/python-cffi/notmuch2/_config.py @@ -13,27 +13,39 @@ 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) + 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 +89,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..c62aeb98 100644 --- a/bindings/python-cffi/tests/test_config.py +++ b/bindings/python-cffi/tests/test_config.py @@ -34,20 +34,22 @@ 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')} + import re + prefix = re.compile(r"^TEST[.]") + assert [ x for x in list(db.config) if prefix.match(x) ] == [] + db.config['TEST.spam'] = 'TEST.ham' + db.config['TEST.eggs'] = 'TEST.bacon' + assert { x for x in set(db.config) if prefix.match(x) } == {'TEST.spam', 'TEST.eggs'} + assert { x for x in set(db.config.keys()) if prefix.match (x) } == {'TEST.spam', 'TEST.eggs'} + assert { x for x in set(db.config.values()) if prefix.match (x) } == {'TEST.ham', 'TEST.bacon'} + assert { (x, y) for (x,y) in set(db.config.items()) if prefix.match(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