2 This file is part of notmuch.
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.
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
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/>.
17 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
23 from ctypes import c_char_p, c_void_p, c_uint, byref, POINTER
24 from .compat import SafeConfigParser
25 from .globals import (
43 from .message import Message
45 from .query import Query
46 from .directory import Directory
48 class Database(object):
49 """The :class:`Database` is the highest-level object that notmuch
50 provides. It references a notmuch database, and can be opened in
51 read-only or read-write mode. A :class:`Query` can be derived from
52 or be applied to a specific database to find messages. Also adding
53 and removing messages to the database happens via this
54 object. Modifications to the database are not atmic by default (see
55 :meth:`begin_atomic`) and once a database has been modified, all
56 other database objects pointing to the same data-base will throw an
57 :exc:`XapianError` as the underlying database has been
58 modified. Close and reopen the database to continue working with it.
60 :class:`Database` objects implement the context manager protocol
61 so you can use the :keyword:`with` statement to ensure that the
62 database is properly closed. See :meth:`close` for more
67 Any function in this class can and will throw an
68 :exc:`NotInitializedError` if the database was not intitialized
72 """Class attribute to cache user's default database"""
74 MODE = Enum(['READ_ONLY', 'READ_WRITE'])
75 """Constants: Mode in which to open the database"""
77 DECRYPTION_POLICY = Enum(['FALSE', 'TRUE', 'AUTO', 'NOSTASH'])
78 """Constants: policies for decrypting messages during indexing"""
80 """notmuch_database_get_directory"""
81 _get_directory = nmlib.notmuch_database_get_directory
82 _get_directory.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(NotmuchDirectoryP)]
83 _get_directory.restype = c_uint
85 """notmuch_database_get_path"""
86 _get_path = nmlib.notmuch_database_get_path
87 _get_path.argtypes = [NotmuchDatabaseP]
88 _get_path.restype = c_char_p
90 """notmuch_database_get_version"""
91 _get_version = nmlib.notmuch_database_get_version
92 _get_version.argtypes = [NotmuchDatabaseP]
93 _get_version.restype = c_uint
95 """notmuch_database_get_revision"""
96 _get_revision = nmlib.notmuch_database_get_revision
97 _get_revision.argtypes = [NotmuchDatabaseP, POINTER(c_char_p)]
98 _get_revision.restype = c_uint
100 """notmuch_database_open"""
101 _open = nmlib.notmuch_database_open
102 _open.argtypes = [c_char_p, c_uint, POINTER(NotmuchDatabaseP)]
103 _open.restype = c_uint
105 """notmuch_database_upgrade"""
106 _upgrade = nmlib.notmuch_database_upgrade
107 _upgrade.argtypes = [NotmuchDatabaseP, c_void_p, c_void_p]
108 _upgrade.restype = c_uint
110 """ notmuch_database_find_message"""
111 _find_message = nmlib.notmuch_database_find_message
112 _find_message.argtypes = [NotmuchDatabaseP, c_char_p,
113 POINTER(NotmuchMessageP)]
114 _find_message.restype = c_uint
116 """notmuch_database_find_message_by_filename"""
117 _find_message_by_filename = nmlib.notmuch_database_find_message_by_filename
118 _find_message_by_filename.argtypes = [NotmuchDatabaseP, c_char_p,
119 POINTER(NotmuchMessageP)]
120 _find_message_by_filename.restype = c_uint
122 """notmuch_database_get_all_tags"""
123 _get_all_tags = nmlib.notmuch_database_get_all_tags
124 _get_all_tags.argtypes = [NotmuchDatabaseP]
125 _get_all_tags.restype = NotmuchTagsP
127 """notmuch_database_create"""
128 _create = nmlib.notmuch_database_create
129 _create.argtypes = [c_char_p, POINTER(NotmuchDatabaseP)]
130 _create.restype = c_uint
132 def __init__(self, path = None, create = False,
133 mode = MODE.READ_ONLY):
134 """If *path* is `None`, we will try to read a users notmuch
135 configuration and use his configured database. The location of the
136 configuration file can be specified through the environment variable
137 *NOTMUCH_CONFIG*, falling back to the default `~/.notmuch-config`.
139 If *create* is `True`, the database will always be created in
140 :attr:`MODE`.READ_WRITE mode. Default mode for opening is READ_ONLY.
142 :param path: Directory to open/create the database in (see
143 above for behavior if `None`)
144 :type path: `str` or `None`
145 :param create: Pass `False` to open an existing, `True` to create a new
148 :param mode: Mode to open a database in. Is always
149 :attr:`MODE`.READ_WRITE when creating a new one.
150 :type mode: :attr:`MODE`
151 :raises: :exc:`NotmuchError` or derived exception in case of
157 # no path specified. use a user's default database
158 if Database._std_db_path is None:
159 #the following line throws a NotmuchError if it fails
160 Database._std_db_path = self._get_user_default_db()
161 path = Database._std_db_path
164 self.open(path, mode)
168 _destroy = nmlib.notmuch_database_destroy
169 _destroy.argtypes = [NotmuchDatabaseP]
170 _destroy.restype = c_uint
174 status = self._destroy(self._db)
175 if status != STATUS.SUCCESS:
176 raise NotmuchError(status)
178 def _assert_db_is_initialized(self):
179 """Raises :exc:`NotInitializedError` if self._db is `None`"""
181 raise NotInitializedError()
183 def create(self, path):
184 """Creates a new notmuch database
186 This function is used by __init__() and usually does not need
187 to be called directly. It wraps the underlying
188 *notmuch_database_create* function and creates a new notmuch
189 database at *path*. It will always return a database in :attr:`MODE`
190 .READ_WRITE mode as creating an empty database for
191 reading only does not make a great deal of sense.
193 :param path: A directory in which we should create the database.
195 :raises: :exc:`NotmuchError` in case of any failure
196 (possibly after printing an error message on stderr).
199 raise NotmuchError(message="Cannot create db, this Database() "
200 "already has an open one.")
202 db = NotmuchDatabaseP()
203 status = Database._create(_str(path), byref(db))
205 if status != STATUS.SUCCESS:
206 raise NotmuchError(status)
210 def open(self, path, mode=0):
211 """Opens an existing database
213 This function is used by __init__() and usually does not need
214 to be called directly. It wraps the underlying
215 *notmuch_database_open* function.
217 :param status: Open the database in read-only or read-write mode
218 :type status: :attr:`MODE`
219 :raises: Raises :exc:`NotmuchError` in case of any failure
220 (possibly after printing an error message on stderr).
222 db = NotmuchDatabaseP()
223 status = Database._open(_str(path), mode, byref(db))
225 if status != STATUS.SUCCESS:
226 raise NotmuchError(status)
230 _close = nmlib.notmuch_database_close
231 _close.argtypes = [NotmuchDatabaseP]
232 _close.restype = c_uint
236 Closes the notmuch database.
240 This function closes the notmuch database. From that point
241 on every method invoked on any object ever derived from
242 the closed database may cease to function and raise a
246 status = self._close(self._db)
247 if status != STATUS.SUCCESS:
248 raise NotmuchError(status)
252 Implements the context manager protocol.
256 def __exit__(self, exc_type, exc_value, traceback):
258 Implements the context manager protocol.
263 """Returns the file path of an open database"""
264 self._assert_db_is_initialized()
265 return Database._get_path(self._db).decode('utf-8')
267 def get_version(self):
268 """Returns the database format version
270 :returns: The database version as positive integer
272 self._assert_db_is_initialized()
273 return Database._get_version(self._db)
275 def get_revision (self):
276 """Returns the committed database revison and UUID
278 :returns: (revison, uuid) The database revision as a positive integer
279 and the UUID of the database.
281 self._assert_db_is_initialized()
283 revision = Database._get_revision(self._db, byref (uuid))
284 return (revision, uuid.value.decode ('utf-8'))
286 _needs_upgrade = nmlib.notmuch_database_needs_upgrade
287 _needs_upgrade.argtypes = [NotmuchDatabaseP]
288 _needs_upgrade.restype = bool
290 def needs_upgrade(self):
291 """Does this database need to be upgraded before writing to it?
293 If this function returns `True` then no functions that modify the
294 database (:meth:`index_file`,
295 :meth:`Message.add_tag`, :meth:`Directory.set_mtime`,
296 etc.) will work unless :meth:`upgrade` is called successfully first.
298 :returns: `True` or `False`
300 self._assert_db_is_initialized()
301 return self._needs_upgrade(self._db)
304 """Upgrades the current database
306 After opening a database in read-write mode, the client should
307 check if an upgrade is needed (notmuch_database_needs_upgrade) and
308 if so, upgrade with this function before making any modifications.
310 NOT IMPLEMENTED: The optional progress_notify callback can be
311 used by the caller to provide progress indication to the
312 user. If non-NULL it will be called periodically with
313 'progress' as a floating-point value in the range of [0.0..1.0]
314 indicating the progress made so far in the upgrade process.
316 :TODO: catch exceptions, document return values and etc...
318 self._assert_db_is_initialized()
319 status = Database._upgrade(self._db, None, None)
320 # TODO: catch exceptions, document return values and etc
323 _begin_atomic = nmlib.notmuch_database_begin_atomic
324 _begin_atomic.argtypes = [NotmuchDatabaseP]
325 _begin_atomic.restype = c_uint
327 def begin_atomic(self):
328 """Begin an atomic database operation
330 Any modifications performed between a successful
331 :meth:`begin_atomic` and a :meth:`end_atomic` will be applied to
332 the database atomically. Note that, unlike a typical database
333 transaction, this only ensures atomicity, not durability;
334 neither begin nor end necessarily flush modifications to disk.
336 :returns: :attr:`STATUS`.SUCCESS or raises
337 :raises: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION
338 Xapian exception occurred; atomic section not entered.
340 *Added in notmuch 0.9*"""
341 self._assert_db_is_initialized()
342 status = self._begin_atomic(self._db)
343 if status != STATUS.SUCCESS:
344 raise NotmuchError(status)
347 _end_atomic = nmlib.notmuch_database_end_atomic
348 _end_atomic.argtypes = [NotmuchDatabaseP]
349 _end_atomic.restype = c_uint
351 def end_atomic(self):
352 """Indicate the end of an atomic database operation
354 See :meth:`begin_atomic` for details.
356 :returns: :attr:`STATUS`.SUCCESS or raises
360 :attr:`STATUS`.XAPIAN_EXCEPTION
361 A Xapian exception occurred; atomic section not
363 :attr:`STATUS`.UNBALANCED_ATOMIC:
364 end_atomic has been called more times than begin_atomic.
366 *Added in notmuch 0.9*"""
367 self._assert_db_is_initialized()
368 status = self._end_atomic(self._db)
369 if status != STATUS.SUCCESS:
370 raise NotmuchError(status)
373 def get_directory(self, path):
374 """Returns a :class:`Directory` of path,
376 :param path: An unicode string containing the path relative to the path
377 of database (see :meth:`get_path`), or else should be an absolute
378 path with initial components that match the path of 'database'.
379 :returns: :class:`Directory` or raises an exception.
380 :raises: :exc:`FileError` if path is not relative database or absolute
381 with initial components same as database.
383 self._assert_db_is_initialized()
385 # sanity checking if path is valid, and make path absolute
386 if path and path[0] == os.sep:
387 # we got an absolute path
388 if not path.startswith(self.get_path()):
389 # but its initial components are not equal to the db path
390 raise FileError('Database().get_directory() called '
391 'with a wrong absolute path')
394 #we got a relative path, make it absolute
395 abs_dirpath = os.path.abspath(os.path.join(self.get_path(), path))
397 dir_p = NotmuchDirectoryP()
398 status = Database._get_directory(self._db, _str(path), byref(dir_p))
400 if status != STATUS.SUCCESS:
401 raise NotmuchError(status)
405 # return the Directory, init it with the absolute path
406 return Directory(abs_dirpath, dir_p, self)
408 _get_default_indexopts = nmlib.notmuch_database_get_default_indexopts
409 _get_default_indexopts.argtypes = [NotmuchDatabaseP]
410 _get_default_indexopts.restype = NotmuchIndexoptsP
412 _indexopts_set_decrypt_policy = nmlib.notmuch_indexopts_set_decrypt_policy
413 _indexopts_set_decrypt_policy.argtypes = [NotmuchIndexoptsP, c_uint]
414 _indexopts_set_decrypt_policy.restype = None
416 _indexopts_destroy = nmlib.notmuch_indexopts_destroy
417 _indexopts_destroy.argtypes = [NotmuchIndexoptsP]
418 _indexopts_destroy.restype = None
420 _index_file = nmlib.notmuch_database_index_file
421 _index_file.argtypes = [NotmuchDatabaseP, c_char_p,
423 POINTER(NotmuchMessageP)]
424 _index_file.restype = c_uint
426 def index_file(self, filename, sync_maildir_flags=False, decrypt_policy=None):
427 """Adds a new message to the database
429 :param filename: should be a path relative to the path of the
430 open database (see :meth:`get_path`), or else should be an
431 absolute filename with initial components that match the
432 path of the database.
434 The file should be a single mail message (not a
435 multi-message mbox) that is expected to remain at its
436 current location, since the notmuch database will reference
437 the filename, and will not copy the entire contents of the
440 :param sync_maildir_flags: If the message contains Maildir
441 flags, we will -depending on the notmuch configuration- sync
442 those tags to initial notmuch tags, if set to `True`. It is
443 `False` by default to remain consistent with the libnotmuch
444 API. You might want to look into the underlying method
445 :meth:`Message.maildir_flags_to_tags`.
447 :param decrypt_policy: If the message contains any encrypted
448 parts, and decrypt_policy is set to
449 :attr:`DECRYPTION_POLICY`.TRUE, notmuch will try to
450 decrypt the message and index the cleartext, stashing any
451 discovered session keys. If it is set to
452 :attr:`DECRYPTION_POLICY`.FALSE, it will never try to
453 decrypt during indexing. If it is set to
454 :attr:`DECRYPTION_POLICY`.AUTO, then it will try to use
455 any stashed session keys it knows about, but will not try
456 to access the user's secret keys.
457 :attr:`DECRYPTION_POLICY`.NOSTASH behaves the same as
458 :attr:`DECRYPTION_POLICY`.TRUE except that no session keys
459 are stashed in the database. If decrypt_policy is set to
460 None (the default), then the database itself will decide
461 whether to decrypt, based on the `index.decrypt`
462 configuration setting (see notmuch-config(1)).
464 :returns: On success, we return
466 1) a :class:`Message` object that can be used for things
467 such as adding tags to the just-added message.
468 2) one of the following :attr:`STATUS` values:
470 :attr:`STATUS`.SUCCESS
471 Message successfully added to database.
472 :attr:`STATUS`.DUPLICATE_MESSAGE_ID
473 Message has the same message ID as another message already
474 in the database. The new filename was successfully added
475 to the list of the filenames for the existing message.
477 :rtype: 2-tuple(:class:`Message`, :attr:`STATUS`)
479 :raises: Raises a :exc:`NotmuchError` with the following meaning.
480 If such an exception occurs, nothing was added to the database.
482 :attr:`STATUS`.FILE_ERROR
483 An error occurred trying to open the file, (such as
484 permission denied, or file not found, etc.).
485 :attr:`STATUS`.FILE_NOT_EMAIL
486 The contents of filename don't look like an email
488 :attr:`STATUS`.READ_ONLY_DATABASE
489 Database was opened in read-only mode so no message can
492 self._assert_db_is_initialized()
493 msg_p = NotmuchMessageP()
494 indexopts = c_void_p(None)
495 if decrypt_policy is not None:
496 indexopts = self._get_default_indexopts(self._db)
497 self._indexopts_set_decrypt_policy(indexopts, decrypt_policy)
499 status = self._index_file(self._db, _str(filename), indexopts, byref(msg_p))
502 self._indexopts_destroy(indexopts)
504 if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
505 raise NotmuchError(status)
507 #construct Message() and return
508 msg = Message(msg_p, self)
509 #automatic sync initial tags from Maildir flags
510 if sync_maildir_flags:
511 msg.maildir_flags_to_tags()
514 def add_message(self, filename, sync_maildir_flags=False):
515 """Deprecated alias for :meth:`index_file`
518 "This function is deprecated and will be removed in the future, use index_file.", DeprecationWarning)
520 return self.index_file(filename, sync_maildir_flags=sync_maildir_flags)
522 _remove_message = nmlib.notmuch_database_remove_message
523 _remove_message.argtypes = [NotmuchDatabaseP, c_char_p]
524 _remove_message.restype = c_uint
526 def remove_message(self, filename):
527 """Removes a message (filename) from the given notmuch database
529 Note that only this particular filename association is removed from
530 the database. If the same message (as determined by the message ID)
531 is still available via other filenames, then the message will
532 persist in the database for those filenames. When the last filename
533 is removed for a particular message, the database content for that
534 message will be entirely removed.
536 :returns: A :attr:`STATUS` value with the following meaning:
538 :attr:`STATUS`.SUCCESS
539 The last filename was removed and the message was removed
541 :attr:`STATUS`.DUPLICATE_MESSAGE_ID
542 This filename was removed but the message persists in the
543 database with at least one other filename.
545 :raises: Raises a :exc:`NotmuchError` with the following meaning.
546 If such an exception occurs, nothing was removed from the
549 :attr:`STATUS`.READ_ONLY_DATABASE
550 Database was opened in read-only mode so no message can be
553 self._assert_db_is_initialized()
554 status = self._remove_message(self._db, _str(filename))
555 if status not in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
556 raise NotmuchError(status)
559 def find_message(self, msgid):
560 """Returns a :class:`Message` as identified by its message ID
562 Wraps the underlying *notmuch_database_find_message* function.
564 :param msgid: The message ID
565 :type msgid: unicode or str
566 :returns: :class:`Message` or `None` if no message is found.
568 :exc:`OutOfMemoryError`
569 If an Out-of-memory occurred while constructing the message.
571 In case of a Xapian Exception. These exceptions
572 include "Database modified" situations, e.g. when the
573 notmuch database has been modified by another program
574 in the meantime. In this case, you should close and
575 reopen the database and retry.
576 :exc:`NotInitializedError` if
577 the database was not intitialized.
579 self._assert_db_is_initialized()
580 msg_p = NotmuchMessageP()
581 status = Database._find_message(self._db, _str(msgid), byref(msg_p))
582 if status != STATUS.SUCCESS:
583 raise NotmuchError(status)
584 return msg_p and Message(msg_p, self) or None
586 def find_message_by_filename(self, filename):
587 """Find a message with the given filename
589 :returns: If the database contains a message with the given
590 filename, then a class:`Message:` is returned. This
591 function returns None if no message is found with the given
594 :raises: :exc:`OutOfMemoryError` if an Out-of-memory occurred while
595 constructing the message.
596 :raises: :exc:`XapianError` in case of a Xapian Exception.
597 These exceptions include "Database modified"
598 situations, e.g. when the notmuch database has been
599 modified by another program in the meantime. In this
600 case, you should close and reopen the database and
602 :raises: :exc:`NotInitializedError` if the database was not
605 *Added in notmuch 0.9*"""
606 self._assert_db_is_initialized()
608 msg_p = NotmuchMessageP()
609 status = Database._find_message_by_filename(self._db, _str(filename),
611 if status != STATUS.SUCCESS:
612 raise NotmuchError(status)
613 return msg_p and Message(msg_p, self) or None
615 def get_all_tags(self):
616 """Returns :class:`Tags` with a list of all tags found in the database
618 :returns: :class:`Tags`
619 :execption: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER
622 self._assert_db_is_initialized()
623 tags_p = Database._get_all_tags(self._db)
625 raise NullPointerError()
626 return Tags(tags_p, self)
628 def create_query(self, querystring):
629 """Returns a :class:`Query` derived from this database
631 This is a shorthand method for doing::
634 # Automatically frees the Database() when 'q' is deleted
636 q = Database(dbpath).create_query('from:"Biene Maja"')
638 # long version, which is functionally equivalent but will keep the
639 # Database in the 'db' variable around after we delete 'q':
641 db = Database(dbpath)
642 q = Query(db,'from:"Biene Maja"')
644 This function is a python extension and not in the underlying C API.
646 return Query(self, querystring)
648 """notmuch_database_status_string"""
649 _status_string = nmlib.notmuch_database_status_string
650 _status_string.argtypes = [NotmuchDatabaseP]
651 _status_string.restype = c_char_p
653 def status_string(self):
654 """Returns the status string of the database
656 This is sometimes used for additional error reporting
658 self._assert_db_is_initialized()
659 s = Database._status_string(self._db)
661 return s.decode('utf-8', 'ignore')
665 return "'Notmuch DB " + self.get_path() + "'"
667 def _get_user_default_db(self):
668 """ Reads a user's notmuch config and returns his db location
670 Throws a NotmuchError if it cannot find it"""
671 config = SafeConfigParser()
672 conf_f = os.getenv('NOTMUCH_CONFIG',
673 os.path.expanduser('~/.notmuch-config'))
674 config.readfp(codecs.open(conf_f, 'r', 'utf-8'))
675 if not config.has_option('database', 'path'):
676 raise NotmuchError(message="No DB path specified"
677 " and no user default found")
678 return config.get('database', 'path')
680 """notmuch_database_get_config"""
681 _get_config = nmlib.notmuch_database_get_config
682 _get_config.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(c_char_p)]
683 _get_config.restype = c_uint
685 def get_config(self, key):
686 """Return the value of the given config key.
688 Note that only config values that are stored in the database are
689 searched and returned. The config file is not read.
691 :param key: the config key under which a value should be looked up, it
692 should probably be in the form "section.key"
694 :returns: the config value or the empty string if no value is present
697 :raises: :exc:`NotmuchError` in case of failure.
700 self._assert_db_is_initialized()
701 return_string = c_char_p()
702 status = self._get_config(self._db, _str(key), byref(return_string))
703 if status != STATUS.SUCCESS:
704 raise NotmuchError(status)
705 return return_string.value.decode('utf-8')
707 """notmuch_database_get_config_list"""
708 _get_config_list = nmlib.notmuch_database_get_config_list
709 _get_config_list.argtypes = [NotmuchDatabaseP, c_char_p,
710 POINTER(NotmuchConfigListP)]
711 _get_config_list.restype = c_uint
713 _config_list_valid = nmlib.notmuch_config_list_valid
714 _config_list_valid.argtypes = [NotmuchConfigListP]
715 _config_list_valid.restype = bool
717 _config_list_key = nmlib.notmuch_config_list_key
718 _config_list_key.argtypes = [NotmuchConfigListP]
719 _config_list_key.restype = c_char_p
721 _config_list_value = nmlib.notmuch_config_list_value
722 _config_list_value.argtypes = [NotmuchConfigListP]
723 _config_list_value.restype = c_char_p
725 _config_list_move_to_next = nmlib.notmuch_config_list_move_to_next
726 _config_list_move_to_next.argtypes = [NotmuchConfigListP]
727 _config_list_move_to_next.restype = None
729 _config_list_destroy = nmlib.notmuch_config_list_destroy
730 _config_list_destroy.argtypes = [NotmuchConfigListP]
731 _config_list_destroy.restype = None
733 def get_configs(self, prefix=''):
734 """Return a generator of key, value pairs where the start of key
735 matches the given prefix
737 Note that only config values that are stored in the database are
738 searched and returned. The config file is not read. If no `prefix` is
739 given all config values are returned.
741 This could be used to get all named queries into a dict for example::
743 queries = {k[6:]: v for k, v in db.get_configs('query.')}
745 :param prefix: a string by which the keys should be selected
747 :yields: all key-value pairs where `prefix` matches the beginning
750 :raises: :exc:`NotmuchError` in case of failure.
753 self._assert_db_is_initialized()
754 config_list_p = NotmuchConfigListP()
755 status = self._get_config_list(self._db, _str(prefix),
756 byref(config_list_p))
757 if status != STATUS.SUCCESS:
758 raise NotmuchError(status)
759 while self._config_list_valid(config_list_p):
760 key = self._config_list_key(config_list_p).decode('utf-8')
761 value = self._config_list_value(config_list_p).decode('utf-8')
763 self._config_list_move_to_next(config_list_p)
765 """notmuch_database_set_config"""
766 _set_config = nmlib.notmuch_database_set_config
767 _set_config.argtypes = [NotmuchDatabaseP, c_char_p, c_char_p]
768 _set_config.restype = c_uint
770 def set_config(self, key, value):
771 """Set a config value in the notmuch database.
773 If an empty string is provided as `value` the `key` is unset!
775 :param key: the key to set
777 :param value: the value to store under `key`
780 :raises: :exc:`NotmuchError` in case of failure.
783 self._assert_db_is_initialized()
784 status = self._set_config(self._db, _str(key), _str(value))
785 if status != STATUS.SUCCESS:
786 raise NotmuchError(status)