]> git.notmuchmail.org Git - notmuch/blob - bindings/python/notmuch/database.py
python: Rename get_config_list to get_configs
[notmuch] / bindings / python / notmuch / database.py
1 """
2 This file is part of notmuch.
3
4 Notmuch is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License as published by the
6 Free Software Foundation, either version 3 of the License, or (at your
7 option) any later version.
8
9 Notmuch is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
16
17 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
18 """
19
20 import os
21 import codecs
22 import warnings
23 from ctypes import c_char_p, c_void_p, c_uint, byref, POINTER
24 from .compat import SafeConfigParser
25 from .globals import (
26     nmlib,
27     Enum,
28     _str,
29     NotmuchConfigListP,
30     NotmuchDatabaseP,
31     NotmuchDirectoryP,
32     NotmuchMessageP,
33     NotmuchTagsP,
34 )
35 from .errors import (
36     STATUS,
37     FileError,
38     NotmuchError,
39     NullPointerError,
40     NotInitializedError,
41 )
42 from .message import Message
43 from .tag import Tags
44 from .query import Query
45 from .directory import Directory
46
47 class Database(object):
48     """The :class:`Database` is the highest-level object that notmuch
49     provides. It references a notmuch database, and can be opened in
50     read-only or read-write mode. A :class:`Query` can be derived from
51     or be applied to a specific database to find messages. Also adding
52     and removing messages to the database happens via this
53     object. Modifications to the database are not atmic by default (see
54     :meth:`begin_atomic`) and once a database has been modified, all
55     other database objects pointing to the same data-base will throw an
56     :exc:`XapianError` as the underlying database has been
57     modified. Close and reopen the database to continue working with it.
58
59     :class:`Database` objects implement the context manager protocol
60     so you can use the :keyword:`with` statement to ensure that the
61     database is properly closed. See :meth:`close` for more
62     information.
63
64     .. note::
65
66         Any function in this class can and will throw an
67         :exc:`NotInitializedError` if the database was not intitialized
68         properly.
69     """
70     _std_db_path = None
71     """Class attribute to cache user's default database"""
72
73     MODE = Enum(['READ_ONLY', 'READ_WRITE'])
74     """Constants: Mode in which to open the database"""
75
76     """notmuch_database_get_directory"""
77     _get_directory = nmlib.notmuch_database_get_directory
78     _get_directory.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(NotmuchDirectoryP)]
79     _get_directory.restype = c_uint
80
81     """notmuch_database_get_path"""
82     _get_path = nmlib.notmuch_database_get_path
83     _get_path.argtypes = [NotmuchDatabaseP]
84     _get_path.restype = c_char_p
85
86     """notmuch_database_get_version"""
87     _get_version = nmlib.notmuch_database_get_version
88     _get_version.argtypes = [NotmuchDatabaseP]
89     _get_version.restype = c_uint
90
91     """notmuch_database_get_revision"""
92     _get_revision = nmlib.notmuch_database_get_revision
93     _get_revision.argtypes = [NotmuchDatabaseP, POINTER(c_char_p)]
94     _get_revision.restype = c_uint
95
96     """notmuch_database_open"""
97     _open = nmlib.notmuch_database_open
98     _open.argtypes = [c_char_p, c_uint, POINTER(NotmuchDatabaseP)]
99     _open.restype = c_uint
100
101     """notmuch_database_upgrade"""
102     _upgrade = nmlib.notmuch_database_upgrade
103     _upgrade.argtypes = [NotmuchDatabaseP, c_void_p, c_void_p]
104     _upgrade.restype = c_uint
105
106     """ notmuch_database_find_message"""
107     _find_message = nmlib.notmuch_database_find_message
108     _find_message.argtypes = [NotmuchDatabaseP, c_char_p,
109                               POINTER(NotmuchMessageP)]
110     _find_message.restype = c_uint
111
112     """notmuch_database_find_message_by_filename"""
113     _find_message_by_filename = nmlib.notmuch_database_find_message_by_filename
114     _find_message_by_filename.argtypes = [NotmuchDatabaseP, c_char_p,
115                                           POINTER(NotmuchMessageP)]
116     _find_message_by_filename.restype = c_uint
117
118     """notmuch_database_get_all_tags"""
119     _get_all_tags = nmlib.notmuch_database_get_all_tags
120     _get_all_tags.argtypes = [NotmuchDatabaseP]
121     _get_all_tags.restype = NotmuchTagsP
122
123     """notmuch_database_create"""
124     _create = nmlib.notmuch_database_create
125     _create.argtypes = [c_char_p, POINTER(NotmuchDatabaseP)]
126     _create.restype = c_uint
127
128     def __init__(self, path = None, create = False,
129                  mode = MODE.READ_ONLY):
130         """If *path* is `None`, we will try to read a users notmuch
131         configuration and use his configured database. The location of the
132         configuration file can be specified through the environment variable
133         *NOTMUCH_CONFIG*, falling back to the default `~/.notmuch-config`.
134
135         If *create* is `True`, the database will always be created in
136         :attr:`MODE`.READ_WRITE mode. Default mode for opening is READ_ONLY.
137
138         :param path:   Directory to open/create the database in (see
139                        above for behavior if `None`)
140         :type path:    `str` or `None`
141         :param create: Pass `False` to open an existing, `True` to create a new
142                        database.
143         :type create:  bool
144         :param mode:   Mode to open a database in. Is always
145                        :attr:`MODE`.READ_WRITE when creating a new one.
146         :type mode:    :attr:`MODE`
147         :raises: :exc:`NotmuchError` or derived exception in case of
148             failure.
149         """
150         self._db = None
151         self.mode = mode
152         if path is None:
153             # no path specified. use a user's default database
154             if Database._std_db_path is None:
155                 #the following line throws a NotmuchError if it fails
156                 Database._std_db_path = self._get_user_default_db()
157             path = Database._std_db_path
158
159         if create == False:
160             self.open(path, mode)
161         else:
162             self.create(path)
163
164     _destroy = nmlib.notmuch_database_destroy
165     _destroy.argtypes = [NotmuchDatabaseP]
166     _destroy.restype = c_uint
167
168     def __del__(self):
169         if self._db:
170             status = self._destroy(self._db)
171             if status != STATUS.SUCCESS:
172                 raise NotmuchError(status)
173
174     def _assert_db_is_initialized(self):
175         """Raises :exc:`NotInitializedError` if self._db is `None`"""
176         if not self._db:
177             raise NotInitializedError()
178
179     def create(self, path):
180         """Creates a new notmuch database
181
182         This function is used by __init__() and usually does not need
183         to be called directly. It wraps the underlying
184         *notmuch_database_create* function and creates a new notmuch
185         database at *path*. It will always return a database in :attr:`MODE`
186         .READ_WRITE mode as creating an empty database for
187         reading only does not make a great deal of sense.
188
189         :param path: A directory in which we should create the database.
190         :type path: str
191         :raises: :exc:`NotmuchError` in case of any failure
192                     (possibly after printing an error message on stderr).
193         """
194         if self._db:
195             raise NotmuchError(message="Cannot create db, this Database() "
196                                        "already has an open one.")
197
198         db = NotmuchDatabaseP()
199         status = Database._create(_str(path), byref(db))
200
201         if status != STATUS.SUCCESS:
202             raise NotmuchError(status)
203         self._db = db
204         return status
205
206     def open(self, path, mode=0):
207         """Opens an existing database
208
209         This function is used by __init__() and usually does not need
210         to be called directly. It wraps the underlying
211         *notmuch_database_open* function.
212
213         :param status: Open the database in read-only or read-write mode
214         :type status:  :attr:`MODE`
215         :raises: Raises :exc:`NotmuchError` in case of any failure
216                     (possibly after printing an error message on stderr).
217         """
218         db = NotmuchDatabaseP()
219         status = Database._open(_str(path), mode, byref(db))
220
221         if status != STATUS.SUCCESS:
222             raise NotmuchError(status)
223         self._db = db
224         return status
225
226     _close = nmlib.notmuch_database_close
227     _close.argtypes = [NotmuchDatabaseP]
228     _close.restype = c_uint
229
230     def close(self):
231         '''
232         Closes the notmuch database.
233
234         .. warning::
235
236             This function closes the notmuch database. From that point
237             on every method invoked on any object ever derived from
238             the closed database may cease to function and raise a
239             NotmuchError.
240         '''
241         if self._db:
242             status = self._close(self._db)
243             if status != STATUS.SUCCESS:
244                 raise NotmuchError(status)
245
246     def __enter__(self):
247         '''
248         Implements the context manager protocol.
249         '''
250         return self
251
252     def __exit__(self, exc_type, exc_value, traceback):
253         '''
254         Implements the context manager protocol.
255         '''
256         self.close()
257
258     def get_path(self):
259         """Returns the file path of an open database"""
260         self._assert_db_is_initialized()
261         return Database._get_path(self._db).decode('utf-8')
262
263     def get_version(self):
264         """Returns the database format version
265
266         :returns: The database version as positive integer
267         """
268         self._assert_db_is_initialized()
269         return Database._get_version(self._db)
270
271     def get_revision (self):
272         """Returns the committed database revison and UUID
273
274         :returns: (revison, uuid) The database revision as a positive integer
275         and the UUID of the database.
276         """
277         self._assert_db_is_initialized()
278         uuid = c_char_p ()
279         revision = Database._get_revision(self._db, byref (uuid))
280         return (revision, uuid.value.decode ('utf-8'))
281
282     _needs_upgrade = nmlib.notmuch_database_needs_upgrade
283     _needs_upgrade.argtypes = [NotmuchDatabaseP]
284     _needs_upgrade.restype = bool
285
286     def needs_upgrade(self):
287         """Does this database need to be upgraded before writing to it?
288
289         If this function returns `True` then no functions that modify the
290         database (:meth:`index_file`,
291         :meth:`Message.add_tag`, :meth:`Directory.set_mtime`,
292         etc.) will work unless :meth:`upgrade` is called successfully first.
293
294         :returns: `True` or `False`
295         """
296         self._assert_db_is_initialized()
297         return self._needs_upgrade(self._db)
298
299     def upgrade(self):
300         """Upgrades the current database
301
302         After opening a database in read-write mode, the client should
303         check if an upgrade is needed (notmuch_database_needs_upgrade) and
304         if so, upgrade with this function before making any modifications.
305
306         NOT IMPLEMENTED: The optional progress_notify callback can be
307         used by the caller to provide progress indication to the
308         user. If non-NULL it will be called periodically with
309         'progress' as a floating-point value in the range of [0.0..1.0]
310         indicating the progress made so far in the upgrade process.
311
312         :TODO: catch exceptions, document return values and etc...
313         """
314         self._assert_db_is_initialized()
315         status = Database._upgrade(self._db, None, None)
316         # TODO: catch exceptions, document return values and etc
317         return status
318
319     _begin_atomic = nmlib.notmuch_database_begin_atomic
320     _begin_atomic.argtypes = [NotmuchDatabaseP]
321     _begin_atomic.restype = c_uint
322
323     def begin_atomic(self):
324         """Begin an atomic database operation
325
326         Any modifications performed between a successful
327         :meth:`begin_atomic` and a :meth:`end_atomic` will be applied to
328         the database atomically.  Note that, unlike a typical database
329         transaction, this only ensures atomicity, not durability;
330         neither begin nor end necessarily flush modifications to disk.
331
332         :returns: :attr:`STATUS`.SUCCESS or raises
333         :raises: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION
334                     Xapian exception occurred; atomic section not entered.
335
336         *Added in notmuch 0.9*"""
337         self._assert_db_is_initialized()
338         status = self._begin_atomic(self._db)
339         if status != STATUS.SUCCESS:
340             raise NotmuchError(status)
341         return status
342
343     _end_atomic = nmlib.notmuch_database_end_atomic
344     _end_atomic.argtypes = [NotmuchDatabaseP]
345     _end_atomic.restype = c_uint
346
347     def end_atomic(self):
348         """Indicate the end of an atomic database operation
349
350         See :meth:`begin_atomic` for details.
351
352         :returns: :attr:`STATUS`.SUCCESS or raises
353
354         :raises:
355             :exc:`NotmuchError`:
356                 :attr:`STATUS`.XAPIAN_EXCEPTION
357                     A Xapian exception occurred; atomic section not
358                     ended.
359                 :attr:`STATUS`.UNBALANCED_ATOMIC:
360                     end_atomic has been called more times than begin_atomic.
361
362         *Added in notmuch 0.9*"""
363         self._assert_db_is_initialized()
364         status = self._end_atomic(self._db)
365         if status != STATUS.SUCCESS:
366             raise NotmuchError(status)
367         return status
368
369     def get_directory(self, path):
370         """Returns a :class:`Directory` of path,
371
372         :param path: An unicode string containing the path relative to the path
373               of database (see :meth:`get_path`), or else should be an absolute
374               path with initial components that match the path of 'database'.
375         :returns: :class:`Directory` or raises an exception.
376         :raises: :exc:`FileError` if path is not relative database or absolute
377                  with initial components same as database.
378         """
379         self._assert_db_is_initialized()
380
381         # sanity checking if path is valid, and make path absolute
382         if path and path[0] == os.sep:
383             # we got an absolute path
384             if not path.startswith(self.get_path()):
385                 # but its initial components are not equal to the db path
386                 raise FileError('Database().get_directory() called '
387                                 'with a wrong absolute path')
388             abs_dirpath = path
389         else:
390             #we got a relative path, make it absolute
391             abs_dirpath = os.path.abspath(os.path.join(self.get_path(), path))
392
393         dir_p = NotmuchDirectoryP()
394         status = Database._get_directory(self._db, _str(path), byref(dir_p))
395
396         if status != STATUS.SUCCESS:
397             raise NotmuchError(status)
398         if not dir_p:
399             return None
400
401         # return the Directory, init it with the absolute path
402         return Directory(abs_dirpath, dir_p, self)
403
404     _index_file = nmlib.notmuch_database_index_file
405     _index_file.argtypes = [NotmuchDatabaseP, c_char_p,
406                              c_void_p,
407                              POINTER(NotmuchMessageP)]
408     _index_file.restype = c_uint
409
410     def index_file(self, filename, sync_maildir_flags=False):
411         """Adds a new message to the database
412
413         :param filename: should be a path relative to the path of the
414             open database (see :meth:`get_path`), or else should be an
415             absolute filename with initial components that match the
416             path of the database.
417
418             The file should be a single mail message (not a
419             multi-message mbox) that is expected to remain at its
420             current location, since the notmuch database will reference
421             the filename, and will not copy the entire contents of the
422             file.
423
424         :param sync_maildir_flags: If the message contains Maildir
425             flags, we will -depending on the notmuch configuration- sync
426             those tags to initial notmuch tags, if set to `True`. It is
427             `False` by default to remain consistent with the libnotmuch
428             API. You might want to look into the underlying method
429             :meth:`Message.maildir_flags_to_tags`.
430
431         :returns: On success, we return
432
433            1) a :class:`Message` object that can be used for things
434               such as adding tags to the just-added message.
435            2) one of the following :attr:`STATUS` values:
436
437               :attr:`STATUS`.SUCCESS
438                   Message successfully added to database.
439               :attr:`STATUS`.DUPLICATE_MESSAGE_ID
440                   Message has the same message ID as another message already
441                   in the database. The new filename was successfully added
442                   to the list of the filenames for the existing message.
443
444         :rtype:   2-tuple(:class:`Message`, :attr:`STATUS`)
445
446         :raises: Raises a :exc:`NotmuchError` with the following meaning.
447               If such an exception occurs, nothing was added to the database.
448
449               :attr:`STATUS`.FILE_ERROR
450                       An error occurred trying to open the file, (such as
451                       permission denied, or file not found, etc.).
452               :attr:`STATUS`.FILE_NOT_EMAIL
453                       The contents of filename don't look like an email
454                       message.
455               :attr:`STATUS`.READ_ONLY_DATABASE
456                       Database was opened in read-only mode so no message can
457                       be added.
458         """
459         self._assert_db_is_initialized()
460         msg_p = NotmuchMessageP()
461         status = self._index_file(self._db, _str(filename), c_void_p(None), byref(msg_p))
462
463         if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
464             raise NotmuchError(status)
465
466         #construct Message() and return
467         msg = Message(msg_p, self)
468         #automatic sync initial tags from Maildir flags
469         if sync_maildir_flags:
470             msg.maildir_flags_to_tags()
471         return (msg, status)
472
473     def add_message(self, filename, sync_maildir_flags=False):
474         """Deprecated alias for :meth:`index_file`
475         """
476         warnings.warn(
477                 "This function is deprecated and will be removed in the future, use index_file.", DeprecationWarning)
478
479         return self.index_file(filename, sync_maildir_flags=sync_maildir_flags)
480
481     _remove_message = nmlib.notmuch_database_remove_message
482     _remove_message.argtypes = [NotmuchDatabaseP, c_char_p]
483     _remove_message.restype = c_uint
484
485     def remove_message(self, filename):
486         """Removes a message (filename) from the given notmuch database
487
488         Note that only this particular filename association is removed from
489         the database. If the same message (as determined by the message ID)
490         is still available via other filenames, then the message will
491         persist in the database for those filenames. When the last filename
492         is removed for a particular message, the database content for that
493         message will be entirely removed.
494
495         :returns: A :attr:`STATUS` value with the following meaning:
496
497              :attr:`STATUS`.SUCCESS
498                The last filename was removed and the message was removed
499                from the database.
500              :attr:`STATUS`.DUPLICATE_MESSAGE_ID
501                This filename was removed but the message persists in the
502                database with at least one other filename.
503
504         :raises: Raises a :exc:`NotmuchError` with the following meaning.
505              If such an exception occurs, nothing was removed from the
506              database.
507
508              :attr:`STATUS`.READ_ONLY_DATABASE
509                Database was opened in read-only mode so no message can be
510                removed.
511         """
512         self._assert_db_is_initialized()
513         status = self._remove_message(self._db, _str(filename))
514         if status not in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
515             raise NotmuchError(status)
516         return status
517
518     def find_message(self, msgid):
519         """Returns a :class:`Message` as identified by its message ID
520
521         Wraps the underlying *notmuch_database_find_message* function.
522
523         :param msgid: The message ID
524         :type msgid: unicode or str
525         :returns: :class:`Message` or `None` if no message is found.
526         :raises:
527             :exc:`OutOfMemoryError`
528                   If an Out-of-memory occured while constructing the message.
529             :exc:`XapianError`
530                   In case of a Xapian Exception. These exceptions
531                   include "Database modified" situations, e.g. when the
532                   notmuch database has been modified by another program
533                   in the meantime. In this case, you should close and
534                   reopen the database and retry.
535             :exc:`NotInitializedError` if
536                     the database was not intitialized.
537         """
538         self._assert_db_is_initialized()
539         msg_p = NotmuchMessageP()
540         status = Database._find_message(self._db, _str(msgid), byref(msg_p))
541         if status != STATUS.SUCCESS:
542             raise NotmuchError(status)
543         return msg_p and Message(msg_p, self) or None
544
545     def find_message_by_filename(self, filename):
546         """Find a message with the given filename
547
548         :returns: If the database contains a message with the given
549             filename, then a class:`Message:` is returned.  This
550             function returns None if no message is found with the given
551             filename.
552
553         :raises: :exc:`OutOfMemoryError` if an Out-of-memory occured while
554                  constructing the message.
555         :raises: :exc:`XapianError` in case of a Xapian Exception.
556                  These exceptions include "Database modified"
557                  situations, e.g. when the notmuch database has been
558                  modified by another program in the meantime. In this
559                  case, you should close and reopen the database and
560                  retry.
561         :raises: :exc:`NotInitializedError` if the database was not
562                  intitialized.
563
564         *Added in notmuch 0.9*"""
565         self._assert_db_is_initialized()
566
567         msg_p = NotmuchMessageP()
568         status = Database._find_message_by_filename(self._db, _str(filename),
569                                                     byref(msg_p))
570         if status != STATUS.SUCCESS:
571             raise NotmuchError(status)
572         return msg_p and Message(msg_p, self) or None
573
574     def get_all_tags(self):
575         """Returns :class:`Tags` with a list of all tags found in the database
576
577         :returns: :class:`Tags`
578         :execption: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER
579                     on error
580         """
581         self._assert_db_is_initialized()
582         tags_p = Database._get_all_tags(self._db)
583         if not tags_p:
584             raise NullPointerError()
585         return Tags(tags_p, self)
586
587     def create_query(self, querystring):
588         """Returns a :class:`Query` derived from this database
589
590         This is a shorthand method for doing::
591
592           # short version
593           # Automatically frees the Database() when 'q' is deleted
594
595           q  = Database(dbpath).create_query('from:"Biene Maja"')
596
597           # long version, which is functionally equivalent but will keep the
598           # Database in the 'db' variable around after we delete 'q':
599
600           db = Database(dbpath)
601           q  = Query(db,'from:"Biene Maja"')
602
603         This function is a python extension and not in the underlying C API.
604         """
605         return Query(self, querystring)
606
607     """notmuch_database_status_string"""
608     _status_string = nmlib.notmuch_database_status_string
609     _status_string.argtypes = [NotmuchDatabaseP]
610     _status_string.restype = c_char_p
611
612     def status_string(self):
613         """Returns the status string of the database
614
615         This is sometimes used for additional error reporting
616         """
617         self._assert_db_is_initialized()
618         s = Database._status_string(self._db)
619         if s:
620             return s.decode('utf-8', 'ignore')
621         return s
622
623     def __repr__(self):
624         return "'Notmuch DB " + self.get_path() + "'"
625
626     def _get_user_default_db(self):
627         """ Reads a user's notmuch config and returns his db location
628
629         Throws a NotmuchError if it cannot find it"""
630         config = SafeConfigParser()
631         conf_f = os.getenv('NOTMUCH_CONFIG',
632                            os.path.expanduser('~/.notmuch-config'))
633         config.readfp(codecs.open(conf_f, 'r', 'utf-8'))
634         if not config.has_option('database', 'path'):
635             raise NotmuchError(message="No DB path specified"
636                                        " and no user default found")
637         return config.get('database', 'path')
638
639     """notmuch_database_get_config"""
640     _get_config = nmlib.notmuch_database_get_config
641     _get_config.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(c_char_p)]
642     _get_config.restype = c_uint
643
644     def get_config(self, key):
645         """Return the value of the given config key.
646
647         Note that only config values that are stored in the database are
648         searched and returned.  The config file is not read.
649
650         :param key: the config key under which a value should be looked up, it
651                     should probably be in the form "section.key"
652         :type key:  str
653         :returns:   the config value or the empty string if no value is present
654                     for that key
655         :rtype:     str
656         :raises:    :exc:`NotmuchError` in case of failure.
657
658         """
659         self._assert_db_is_initialized()
660         return_string = c_char_p()
661         status = self._get_config(self._db, _str(key), byref(return_string))
662         if status != STATUS.SUCCESS:
663             raise NotmuchError(status)
664         return return_string.value.decode('utf-8')
665
666     """notmuch_database_get_config_list"""
667     _get_config_list = nmlib.notmuch_database_get_config_list
668     _get_config_list.argtypes = [NotmuchDatabaseP, c_char_p,
669                                  POINTER(NotmuchConfigListP)]
670     _get_config_list.restype = c_uint
671
672     _config_list_valid = nmlib.notmuch_config_list_valid
673     _config_list_valid.argtypes = [NotmuchConfigListP]
674     _config_list_valid.restype = bool
675
676     _config_list_key = nmlib.notmuch_config_list_key
677     _config_list_key.argtypes = [NotmuchConfigListP]
678     _config_list_key.restype = c_char_p
679
680     _config_list_value = nmlib.notmuch_config_list_value
681     _config_list_value.argtypes = [NotmuchConfigListP]
682     _config_list_value.restype = c_char_p
683
684     _config_list_move_to_next = nmlib.notmuch_config_list_move_to_next
685     _config_list_move_to_next.argtypes = [NotmuchConfigListP]
686     _config_list_move_to_next.restype = None
687
688     _config_list_destroy = nmlib.notmuch_config_list_destroy
689     _config_list_destroy.argtypes = [NotmuchConfigListP]
690     _config_list_destroy.restype = None
691
692     def get_configs(self, prefix=''):
693         """Return a generator of key, value pairs where the start of key
694         matches the given prefix
695
696         Note that only config values that are stored in the database are
697         searched and returned.  The config file is not read.  If no `prefix` is
698         given all config values are returned.
699
700         This could be used to get all named queries into a dict for example::
701
702             queries = {k[6:]: v for k, v in db.get_configs('query.')}
703
704         :param prefix: a string by which the keys should be selected
705         :type prefix:  str
706         :yields:       all key-value pairs where `prefix` matches the beginning
707                        of the key
708         :ytype:        pairs of str
709         :raises:      :exc:`NotmuchError` in case of failure.
710
711         """
712         self._assert_db_is_initialized()
713         config_list_p = NotmuchConfigListP()
714         status = self._get_config_list(self._db, _str(prefix),
715                                        byref(config_list_p))
716         if status != STATUS.SUCCESS:
717             raise NotmuchError(status)
718         while self._config_list_valid(config_list_p):
719             key = self._config_list_key(config_list_p).decode('utf-8')
720             value = self._config_list_value(config_list_p).decode('utf-8')
721             yield key, value
722             self._config_list_move_to_next(config_list_p)
723
724     """notmuch_database_set_config"""
725     _set_config = nmlib.notmuch_database_set_config
726     _set_config.argtypes = [NotmuchDatabaseP, c_char_p, c_char_p]
727     _set_config.restype = c_uint
728
729     def set_config(self, key, value):
730         """Set a config value in the notmuch database.
731
732         If an empty string is provided as `value` the `key` is unset!
733
734         :param key:   the key to set
735         :type key:    str
736         :param value: the value to store under `key`
737         :type value:  str
738         :returns:     None
739         :raises:      :exc:`NotmuchError` in case of failure.
740
741         """
742         self._assert_db_is_initialized()
743         status = self._set_config(self._db, _str(key), _str(value))
744         if status != STATUS.SUCCESS:
745             raise NotmuchError(status)