From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp2 ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms11 with LMTPS id cFggJik6tl5SSAAA0tVLHw (envelope-from ) for ; Sat, 09 May 2020 05:05:45 +0000 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp2 with LMTPS id sNd8ITY6tl6rNQAAB5/wlQ (envelope-from ) for ; Sat, 09 May 2020 05:05:58 +0000 Received: from arlo.cworth.org (arlo.cworth.org [50.126.95.6]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 339F1940D66 for ; Sat, 9 May 2020 05:05:56 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by arlo.cworth.org (Postfix) with ESMTP id B867A6DE13B9; Fri, 8 May 2020 22:05:53 -0700 (PDT) X-Virus-Scanned: Debian amavisd-new at cworth.org Received: from arlo.cworth.org ([127.0.0.1]) by localhost (arlo.cworth.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id w85hPnZ9VBGO; Fri, 8 May 2020 22:05:53 -0700 (PDT) Received: from arlo.cworth.org (localhost [IPv6:::1]) by arlo.cworth.org (Postfix) with ESMTP id 596576DE0F60; Fri, 8 May 2020 22:05:52 -0700 (PDT) Received: from localhost (localhost [127.0.0.1]) by arlo.cworth.org (Postfix) with ESMTP id 105606DE0F60 for ; Fri, 8 May 2020 22:05:50 -0700 (PDT) X-Virus-Scanned: Debian amavisd-new at cworth.org Received: from arlo.cworth.org ([127.0.0.1]) by localhost (arlo.cworth.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id Yj7Gl8p3cUbB for ; Fri, 8 May 2020 22:05:49 -0700 (PDT) Received: from mail.red.khirnov.net (red.khirnov.net [176.97.15.12]) by arlo.cworth.org (Postfix) with ESMTPS id B986A6DE0F68 for ; Fri, 8 May 2020 22:05:44 -0700 (PDT) Received: from localhost (localhost [IPv6:::1]) by mail.red.khirnov.net (Postfix) with ESMTP id 262D2289EF6 for ; Sat, 9 May 2020 07:05:43 +0200 (CEST) Received: from mail.red.khirnov.net ([IPv6:::1]) by localhost (mail.red.khirnov.net [IPv6:::1]) (amavisd-new, port 10024) with ESMTP id jxfKmRmaOYsr for ; Sat, 9 May 2020 07:05:42 +0200 (CEST) Received: from quelana.khirnov.net (unknown [IPv6:2a00:c500:61:23b::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) client-signature RSA-PSS (2048 bits)) (Client CN "quelana.khirnov.net", Issuer "smtp.khirnov.net SMTP CA" (verified OK)) by mail.red.khirnov.net (Postfix) with ESMTPS id 916E1289E5F for ; Sat, 9 May 2020 07:05:42 +0200 (CEST) Received: from localhost (quelana.khirnov.net [IPv6:::1]) by quelana.khirnov.net (Postfix) with ESMTP id 7226C2027C for ; Sat, 9 May 2020 07:05:42 +0200 (CEST) Received: from quelana.khirnov.net ([IPv6:::1]) by localhost (quelana.khirnov.net [IPv6:::1]) (amavisd-new, port 10024) with ESMTP id 4kumREVRVdWU for ; Sat, 9 May 2020 07:05:41 +0200 (CEST) Received: from dev.daenerys.khirnov.net (dev.daenerys.khirnov.net [IPv6:2a00:c500:561:201::6]) by quelana.khirnov.net (Postfix) with ESMTP id 123EA20228 for ; Sat, 9 May 2020 07:05:41 +0200 (CEST) Received: by dev.daenerys.khirnov.net (Postfix, from userid 1000) id 05ED15C1A05; Sat, 9 May 2020 07:05:40 +0200 (CEST) From: Anton Khirnov To: notmuch@notmuchmail.org Subject: [PATCH 2/2] python/notmuch2: add bindings for the database config strings Date: Sat, 9 May 2020 07:05:26 +0200 Message-Id: <20200509050526.23148-2-anton@khirnov.net> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200509050526.23148-1-anton@khirnov.net> References: <20200509050526.23148-1-anton@khirnov.net> MIME-Version: 1.0 X-BeenThere: notmuch@notmuchmail.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Use and development of the notmuch mail system." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: notmuch-bounces@notmuchmail.org Sender: "notmuch" X-Scanner: scn0 X-Spam-Score: -0.01 Authentication-Results: aspmx1.migadu.com; dkim=none; dmarc=none; spf=pass (aspmx1.migadu.com: domain of notmuch-bounces@notmuchmail.org designates 50.126.95.6 as permitted sender) smtp.mailfrom=notmuch-bounces@notmuchmail.org X-Scan-Result: default: False [-0.01 / 13.00]; GENERIC_REPUTATION(0.00)[-0.45458922361247]; DWL_DNSWL_BLOCKED(0.00)[50.126.95.6:from]; R_SPF_ALLOW(-0.20)[+a]; IP_REPUTATION_HAM(0.00)[asn: 27017(-0.18), country: US(-0.00), ip: 50.126.95.6(-0.45)]; TO_DN_NONE(0.00)[]; RCVD_COUNT_TWELVE(0.00)[13]; MX_GOOD(-0.50)[cached: notmuchmail.org]; MAILLIST(-0.20)[mailman]; RCVD_IN_DNSWL_FAIL(0.00)[50.126.95.6:server fail]; RCVD_TLS_LAST(0.00)[]; R_DKIM_NA(0.00)[]; ASN(0.00)[asn:27017, ipnet:50.126.64.0/18, country:US]; MIME_TRACE(0.00)[0:+]; FROM_NEQ_ENVFROM(0.00)[anton@khirnov.net,notmuch-bounces@notmuchmail.org]; ARC_NA(0.00)[]; URIBL_BLOCKED(0.00)[notmuchmail.org:email,collections.abc:url]; FROM_HAS_DN(0.00)[]; SPF_REPUTATION_HAM(0.00)[-0.43554041226053]; MIME_GOOD(-0.10)[text/plain]; PREVIOUSLY_DELIVERED(0.00)[notmuch@notmuchmail.org]; DMARC_NA(0.00)[khirnov.net]; HAS_LIST_UNSUB(-0.01)[]; RCPT_COUNT_ONE(0.00)[1]; MID_CONTAINS_FROM(1.00)[]; FORGED_SENDER_MAILLIST(0.00)[] X-TUID: 6vnNLW+QmoYO --- bindings/python-cffi/notmuch2/_build.py | 17 +++++ bindings/python-cffi/notmuch2/_config.py | 84 ++++++++++++++++++++++ bindings/python-cffi/notmuch2/_database.py | 23 ++++++ 3 files changed, 124 insertions(+) create mode 100644 bindings/python-cffi/notmuch2/_config.py diff --git a/bindings/python-cffi/notmuch2/_build.py b/bindings/python-cffi/notmuch2/_build.py index 5e1fcac1..f269f2a1 100644 --- a/bindings/python-cffi/notmuch2/_build.py +++ b/bindings/python-cffi/notmuch2/_build.py @@ -314,6 +314,23 @@ ffibuilder.cdef( notmuch_indexopts_get_decrypt_policy (const notmuch_indexopts_t *indexopts); void notmuch_indexopts_destroy (notmuch_indexopts_t *options); + + notmuch_status_t + 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_bool_t + notmuch_config_list_valid (notmuch_config_list_t *config_list); + const char * + notmuch_config_list_key (notmuch_config_list_t *config_list); + const char * + notmuch_config_list_value (notmuch_config_list_t *config_list); + void + notmuch_config_list_move_to_next (notmuch_config_list_t *config_list); + void + notmuch_config_list_destroy (notmuch_config_list_t *config_list); """ ) diff --git a/bindings/python-cffi/notmuch2/_config.py b/bindings/python-cffi/notmuch2/_config.py new file mode 100644 index 00000000..58383c16 --- /dev/null +++ b/bindings/python-cffi/notmuch2/_config.py @@ -0,0 +1,84 @@ +import collections.abc + +import notmuch2._base as base +import notmuch2._capi as capi +import notmuch2._errors as errors + +__all__ = ['ConfigMapping'] + +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) + + def __next__(self): + item = super().__next__() + return base.BinString.from_cffi(item) + +class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping): + """The config key/value pairs stored in the database. + + 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. + + :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): + self._parent = parent + self._ptr = lambda: getattr(parent, ptr_name) + + @property + def alive(self): + return self._parent.alive + + def _destroy(self): + pass + + def __getitem__(self, key): + if isinstance(key, str): + key = key.encode('utf-8') + val_pp = capi.ffi.new('char**') + ret = capi.lib.notmuch_database_get_config(self._ptr(), key, val_pp) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + if val_pp[0] == "": + capi.lib.free(val_pp[0]) + raise KeyError + val = base.BinString.from_cffi(val_pp[0]) + capi.lib.free(val_pp[0]) + return val + + def __setitem__(self, key, val): + if isinstance(key, str): + key = key.encode('utf-8') + if isinstance(val, str): + val = val.encode('utf-8') + ret = capi.lib.notmuch_database_set_config(self._ptr(), key, val) + if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: + raise errors.NotmuchError(ret) + + def __delitem__(self, key): + self[key] = "" + + def __iter__(self): + """Return an iterator over the config items. + + :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]) + + def __len__(self): + return sum(1 for t in self) diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index f14eac78..fc55fea8 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -7,6 +7,7 @@ import pathlib import weakref import notmuch2._base as base +import notmuch2._config as config import notmuch2._capi as capi import notmuch2._errors as errors import notmuch2._message as message @@ -536,6 +537,28 @@ class Database(base.NotmuchObject): self._cached_tagset = weakref.ref(tagset) return tagset + @property + def config(self): + """Return a mutable mapping with the settings stored in this database. + + This returns an mutable dict-like object implementing the + collections.abc.MutableMapping Abstract Base Class. + + :rtype: Config + + :raises ObjectDestroyedError: if used after destroyed. + """ + try: + ref = self._cached_config + except AttributeError: + config_mapping = None + else: + config_mapping = ref() + if config_mapping is None: + config_mapping = config.ConfigMapping(self, '_db_p') + self._cached_config = weakref.ref(config_mapping) + return config_mapping + def _create_query(self, query, *, omit_excluded=EXCLUDE.TRUE, sort=SORT.UNSORTED, # Check this default -- 2.20.1