From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp12.migadu.com ([2001:41d0:8:6d80::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms0.migadu.com with LMTPS id iNy7OYW+DmL93gAAgWs5BA (envelope-from ) for ; Thu, 17 Feb 2022 22:30:45 +0100 Received: from aspmx1.migadu.com ([2001:41d0:8:6d80::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp12.migadu.com with LMTPS id QGy4NoW+DmJjNAEAauVa8A (envelope-from ) for ; Thu, 17 Feb 2022 22:30:45 +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 359C238CEF for ; Thu, 17 Feb 2022 22:30:45 +0100 (CET) Received: from yantan.tethera.net (localhost [127.0.0.1]) by mail.notmuchmail.org (Postfix) with ESMTP id 114885F70B; Thu, 17 Feb 2022 21:30:42 +0000 (UTC) Received: from mail-ed1-x52c.google.com (mail-ed1-x52c.google.com [IPv6:2a00:1450:4864:20::52c]) by mail.notmuchmail.org (Postfix) with ESMTPS id 09A015F6C2 for ; Thu, 17 Feb 2022 21:30:39 +0000 (UTC) Received: by mail-ed1-x52c.google.com with SMTP id b13so12100860edn.0 for ; Thu, 17 Feb 2022 13:30:38 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=sender:from:to:subject:in-reply-to:references:date:message-id :mime-version; bh=NzsyCQoT6friFMRUfNbz4X+4s9wO0UJIjzvl/W9kO2c=; b=F4jjz6lKxi9oTgogKAZH01iCQcPzclGML4GzeeYOk4Re3SG9m9CvbQ5UPsVLMCOKPy 8pIS8X206+Q3NzfC8pmXYvR/aY8qkWS0R9pQzRoqZFz3rx854/vEzS+NcFCWGEXhAzTG Hdfv6KyBZBf8lqrV2aN4uzZFiBWu1QQDj9kRlU/JeM8PE6TuNwpNmhDlfoQD3srK+eRr 1GkVQqTAKql3P0+G/gjtA1Inw2O2ftt73tkp099Xhyvlh3vqcrC6N8tH0jeFYzy64SAa D8+e3Cp5lio6l5M8jb9olnZxYA1endxH+Cdo59BDYDBStLwhk5IFb7cMNIi8scOWt8ST P38Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:sender:from:to:subject:in-reply-to:references :date:message-id:mime-version; bh=NzsyCQoT6friFMRUfNbz4X+4s9wO0UJIjzvl/W9kO2c=; b=c1uvGWm9alQ0vWjTwY4Qm2KjKqoHqyMLl+b8F5lPL9u0IUx6OASUowB1JR7fWAPGlP BStO5rkWOIcn3dxLDHOAV59oNmrVOxVZoA3ytf3YuLofFjkZE3i3+FSnY7AEGMraQNsS dviuMsJ53NkU+5fMQubPV3akch2LOqkG3TSn721JGkI6XOweKcKy4h+DgSqrvuKaez4p oLf/i6TNI4zJdPCBqHb54Y/NXekruH1uMFagiSruViTyW+65mwWeWruIfqGe9EmY2QmY +TOxrlgHfrIKGL2RlefzbFgETEYwcF445C10apIYRo5I1AkptinPJAs9uBD8IyGcuzhT fyHA== X-Gm-Message-State: AOAM531gtdnapPUbQZsQMEHO6R8DnnuWNNkr153tUiXBwn80ZllfvlD2 qhpmCp8z+haychz+yMPKdIE= X-Google-Smtp-Source: ABdhPJwz54venMJmLZzd0LSXmLB1YmcX6QSMxc+LOvqvr/um7xSpHmOQcQDblyL4nyc2HDxKSo3hfQ== X-Received: by 2002:a05:6402:1e88:b0:410:ebb4:c329 with SMTP id f8-20020a0564021e8800b00410ebb4c329mr4809430edf.300.1645133438112; Thu, 17 Feb 2022 13:30:38 -0800 (PST) Received: from powell.devork.be (62-178-226-148.cable.dynamic.surfer.at. [62.178.226.148]) by smtp.gmail.com with ESMTPSA id j11sm2433597eda.106.2022.02.17.13.30.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 17 Feb 2022 13:30:37 -0800 (PST) Sender: Floris Bruynooghe Received: (nullmailer pid 261113 invoked by uid 1000); Thu, 17 Feb 2022 21:30:36 -0000 From: Floris Bruynooghe To: David Bremner , David Bremner , notmuch@notmuchmail.org Subject: Re: [PATCH v2 2/2] python-cffi: use config_pairs API in ConfigIterator In-Reply-To: <20220217020835.180940-3-david@tethera.net> References: <8735kipm9j.fsf@powell.devork.be> <20220217020835.180940-1-david@tethera.net> <20220217020835.180940-3-david@tethera.net> Date: Thu, 17 Feb 2022 22:30:36 +0100 Message-ID: <87wnhtnptv.fsf@powell.devork.be> MIME-Version: 1.0 Message-ID-Hash: NW3YX37LP7F4FDILRPDCPAR7CME6R4ZJ X-Message-ID-Hash: NW3YX37LP7F4FDILRPDCPAR7CME6R4ZJ X-MailFrom: floris.bruynooghe@gmail.com 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=1645133445; h=from:from:sender:sender: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:dkim-signature; bh=2PL7bNn+Ul4XsXJXnod4EdKsHjWgP6mlAl24PGKKIqI=; b=IAZ9kIGy27OpVlzkLkjP5+Rm90wwZiNsN2H17n65jOs2F0jq2dLIKEpX0VsRY2B07T0rmP Vr3RvRBYTBLs3X+ipt7ICdxQwNa/CaoergrDEoh5ZksOcnLHhubWRN0bz8rq3OobvbfK6+ KlW8AerFf1uCSgKYE9dwV+18nxE0Q7k2b8pLjkUXSnudJ3jZ+ea6vFO01eNn8eXHxSxMgT PIljG423pB48+uRiPlvLSDbEcEx5btB04lBZwQ/Sssw7yalJW5UpXzCpYZSBfZoSvxsntx +nvC0gJfiL8CVEkJjtEg3F4hlj7ayNSEo+GAbZl/ZkM8F/EDGGLKf/alJl1kcQ== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1645133445; a=rsa-sha256; cv=none; b=jbWQd6NGKxHmoC5tHHMVTJzdXz+2Ls6YggfQBT/djFym3qLUr+H5MMj27+nrglzewmzTlA Q9Lvdk5nfRcWB+1YyeMV3QsRkQuAQ6SM5pcKk7+mU/hQ6U4l59VLHoWtop9ShnF4j32NjF ulAuJdcQ5friX2ZD1FxLa2tpT16LrPGYcJ2ppRWGMVqFoUOZK5Wd/FokbT06PQG9nX/ESJ NyjU9jzEX/vSuNsfaNwMYTF7CAs/CYaFcu44AXm2AXTpCeo8bFBuEULtVFbDt5Ero8FVD6 H3qXGgnGnjSkIq+wpwuOmdBC9AQwlOtCHdQCVr2xmkdn/ygCztROZCoADuXa1w== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=gmail.com header.s=20210112 header.b=F4jjz6lK; 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=fail ("body hash did not verify") header.d=gmail.com header.s=20210112 header.b=F4jjz6lK; 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: 359C238CEF X-Spam-Score: -1.35 X-Migadu-Scanner: scn1.migadu.com X-TUID: HWkFjYUhFud4 lgtm i think :) On Wed 16 Feb 2022 at 22:08 -0400, David Bremner wrote: > 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)