2 from ctypes import c_int, c_char_p, c_void_p, c_uint, c_uint64, c_bool
3 from cnotmuch.globals import nmlib, STATUS, NotmuchError, Enum
5 from datetime import date
7 class Database(object):
8 """Represents a notmuch database (wraps notmuch_database_t)
10 .. note:: Do remember that as soon as we tear down this object,
11 all underlying derived objects such as queries, threads,
12 messages, tags etc will be freed by the underlying library
13 as well. Accessing these objects will lead to segfaults and
14 other unexpected behavior. See above for more details.
17 """Class attribute to cache user's default database"""
19 MODE = Enum(['READ_ONLY','READ_WRITE'])
20 """Constants: Mode in which to open the database"""
22 """notmuch_database_get_path (notmuch_database_t *database)"""
23 _get_path = nmlib.notmuch_database_get_path
24 _get_path.restype = c_char_p
26 """notmuch_database_get_version"""
27 _get_version = nmlib.notmuch_database_get_version
28 _get_version.restype = c_uint
30 """notmuch_database_open (const char *path, notmuch_database_mode_t mode)"""
31 _open = nmlib.notmuch_database_open
32 _open.restype = c_void_p
34 """ notmuch_database_find_message """
35 _find_message = nmlib.notmuch_database_find_message
36 _find_message.restype = c_void_p
38 """notmuch_database_get_all_tags (notmuch_database_t *database)"""
39 _get_all_tags = nmlib.notmuch_database_get_all_tags
40 _get_all_tags.restype = c_void_p
42 """ notmuch_database_create(const char *path):"""
43 _create = nmlib.notmuch_database_create
44 _create.restype = c_void_p
46 def __init__(self, path=None, create=False, mode= 0):
47 """If *path* is *None*, we will try to read a users notmuch
48 configuration and use his configured database. The location of the
49 configuration file can be specified through the environment variable
50 *NOTMUCH_CONFIG*, falling back to the default `~/.notmuch-config`.
52 If *create* is `True`, the database will always be created in
53 :attr:`MODE`.READ_WRITE mode. Default mode for opening is READ_ONLY.
55 :param path: Directory to open/create the database in (see
56 above for behavior if `None`)
57 :type path: `str` or `None`
58 :param create: Pass `False` to open an existing, `True` to create a new
61 :param mode: Mode to open a database in. Is always
62 :attr:`MODE`.READ_WRITE when creating a new one.
63 :type mode: :attr:`MODE`
65 :exception: :exc:`NotmuchError` in case of failure.
69 # no path specified. use a user's default database
70 if Database._std_db_path is None:
71 #the following line throws a NotmuchError if it fails
72 Database._std_db_path = self._get_user_default_db()
73 path = Database._std_db_path
80 def create(self, path):
81 """Creates a new notmuch database
83 This function is used by __init__() and usually does not need
84 to be called directly. It wraps the underlying
85 *notmuch_database_create* function and creates a new notmuch
86 database at *path*. It will always return a database in
87 :attr:`MODE`.READ_WRITE mode as creating an empty database for
88 reading only does not make a great deal of sense.
90 :param path: A directory in which we should create the database.
93 :exception: :exc:`NotmuchError` in case of any failure
94 (after printing an error message on stderr).
96 if self._db is not None:
98 message="Cannot create db, this Database() already has an open one.")
100 res = Database._create(path, Database.MODE.READ_WRITE)
104 message="Could not create the specified database")
107 def open(self, path, mode= 0):
108 """Opens an existing database
110 This function is used by __init__() and usually does not need
111 to be called directly. It wraps the underlying
112 *notmuch_database_open* function.
114 :param status: Open the database in read-only or read-write mode
115 :type status: :attr:`MODE`
117 :exception: Raises :exc:`NotmuchError` in case
118 of any failure (after printing an error message on stderr).
121 res = Database._open(path, mode)
125 message="Could not open the specified database")
129 """Returns the file path of an open database
131 Wraps notmuch_database_get_path"""
132 return Database._get_path(self._db)
134 def get_version(self):
135 """Returns the database format version
137 :returns: The database version as positive integer
138 :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
139 the database was not intitialized.
142 raise NotmuchError(STATUS.NOT_INITIALIZED)
144 return Database._get_version (self._db)
146 def needs_upgrade(self):
147 """Does this database need to be upgraded before writing to it?
149 If this function returns True then no functions that modify the
150 database (:meth:`add_message`, :meth:`add_tag`,
151 :meth:`Directory.set_mtime`, etc.) will work unless :meth:`upgrade`
152 is called successfully first.
154 :returns: `True` or `False`
155 :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
156 the database was not intitialized.
159 raise NotmuchError(STATUS.NOT_INITIALIZED)
161 return notmuch_database_needs_upgrade(self.db)
163 def find_message(self, msgid):
164 """Returns a :class:`Message` as identified by its message ID
166 Wraps the underlying *notmuch_database_find_message* function.
168 :param msgid: The message ID
170 :returns: :class:`Message` or `None` if no message is found or if an
171 out-of-memory situation occurs.
172 :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
173 the database was not intitialized.
176 raise NotmuchError(STATUS.NOT_INITIALIZED)
177 msg_p = Database._find_message(self._db, msgid)
180 return Message(msg_p, self)
182 def get_all_tags(self):
183 """Returns :class:`Tags` with a list of all tags found in the database
185 :returns: :class:`Tags`
186 :execption: :exc:`NotmuchError` with STATUS.NULL_POINTER on error
189 raise NotmuchError(STATUS.NOT_INITIALIZED)
191 tags_p = Database._get_all_tags (self._db)
193 raise NotmuchError(STATUS.NULL_POINTER)
194 return Tags(tags_p, self)
197 return "'Notmuch DB " + self.get_path() + "'"
200 """Close and free the notmuch database if needed"""
201 if self._db is not None:
202 logging.debug("Freeing the database now")
203 nmlib.notmuch_database_close(self._db)
205 def _get_user_default_db(self):
206 """ Reads a user's notmuch config and returns his db location
208 Throws a NotmuchError if it cannot find it"""
209 from ConfigParser import SafeConfigParser
210 config = SafeConfigParser()
211 conf_f = os.getenv('NOTMUCH_CONFIG',
212 os.path.expanduser('~/.notmuch-config'))
214 if not config.has_option('database','path'):
215 raise NotmuchError(message=
216 "No DB path specified and no user default found")
217 return config.get('database','path')
221 """Property returning a pointer to the notmuch_database_t or `None`
223 This should normally not be needed by a user."""
226 #------------------------------------------------------------------------------
228 """ Represents a search query on an opened :class:`Database`.
230 A query selects and filters a subset of messages from the notmuch
231 database we derive from.
233 Technically, it wraps the underlying *notmuch_query_t* struct.
235 .. note:: Do remember that as soon as we tear down this object,
236 all underlying derived objects such as threads,
237 messages, tags etc will be freed by the underlying library
238 as well. Accessing these objects will lead to segfaults and
239 other unexpected behavior. See above for more details.
242 SORT = Enum(['OLDEST_FIRST','NEWEST_FIRST','MESSAGE_ID'])
243 """Constants: Sort order in which to return results"""
245 """notmuch_query_create"""
246 _create = nmlib.notmuch_query_create
247 _create.restype = c_void_p
249 """notmuch_query_search_messages"""
250 _search_messages = nmlib.notmuch_query_search_messages
251 _search_messages.restype = c_void_p
254 """notmuch_query_count_messages"""
255 _count_messages = nmlib.notmuch_query_count_messages
256 _count_messages.restype = c_uint
258 def __init__(self, db, querystr):
260 :param db: An open database which we derive the Query from.
261 :type db: :class:`Database`
262 :param querystr: The query string for the message.
267 self.create(db, querystr)
269 def create(self, db, querystr):
270 """Creates a new query derived from a Database.
272 This function is utilized by __init__() and usually does not need to
275 :param db: Database to create the query from.
276 :type db: :class:`Database`
277 :param querystr: The query string
280 :exception: :exc:`NotmuchError`
282 * STATUS.NOT_INITIALIZED if db is not inited
283 * STATUS.NULL_POINTER if the query creation failed
287 raise NotmuchError(STATUS.NOT_INITIALIZED)
288 # create reference to parent db to keep it alive
291 # create query, return None if too little mem available
292 query_p = Query._create(db.db_p, querystr)
294 NotmuchError(STATUS.NULL_POINTER)
295 self._query = query_p
297 def set_sort(self, sort):
298 """Set the sort order future results will be delivered in
300 Wraps the underlying *notmuch_query_set_sort* function.
302 :param sort: Sort order (see :attr:`Query.SORT`)
304 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if query has not
307 if self._query is None:
308 raise NotmuchError(STATUS.NOT_INITIALIZED)
310 nmlib.notmuch_query_set_sort(self._query, sort)
312 def search_messages(self):
313 """Filter messages according to the query and return
314 :class:`Messages` in the defined sort order
316 Technically, it wraps the underlying
317 *notmuch_query_search_messages* function.
319 :returns: :class:`Messages`
320 :exception: :exc:`NotmuchError`
322 * STATUS.NOT_INITIALIZED if query is not inited
323 * STATUS.NULL_POINTER if search_messages failed
325 if self._query is None:
326 raise NotmuchError(STATUS.NOT_INITIALIZED)
328 msgs_p = Query._search_messages(self._query)
331 NotmuchError(STATUS.NULL_POINTER)
333 return Messages(msgs_p,self)
335 def count_messages(self):
336 """Estimate the number of messages matching the query
338 This function performs a search and returns Xapian's best
339 guess as to the number of matching messages. It is much faster
340 than performing :meth:`search_messages` and counting the
341 result with `len()` (although it always returned the same
342 result in my tests). Technically, it wraps the underlying
343 *notmuch_query_count_messages* function.
345 :returns: :class:`Messages`
346 :exception: :exc:`NotmuchError`
348 * STATUS.NOT_INITIALIZED if query is not inited
350 if self._query is None:
351 raise NotmuchError(STATUS.NOT_INITIALIZED)
353 return Query._count_messages(self._query)
356 """Close and free the Query"""
357 if self._query is not None:
358 logging.debug("Freeing the Query now")
359 nmlib.notmuch_query_destroy (self._query)
361 #------------------------------------------------------------------------------
363 """Represents a list of notmuch tags
365 This object provides an iterator over a list of notmuch tags. Do
366 note that the underlying library only provides a one-time iterator
367 (it cannot reset the iterator to the start). Thus iterating over
368 the function will "exhaust" the list of tags, and a subsequent
369 iteration attempt will raise a :exc:`NotmuchError`
370 STATUS.NOT_INITIALIZED. Also note, that any function that uses
371 iteration (nearly all) will also exhaust the tags. So both::
373 for tag in tags: print tag
377 number_of_tags = len(tags)
381 #str() iterates over all tags to construct a space separated list
384 will "exhaust" the Tags. If you need to re-iterate over a list of
385 tags you will need to retrieve a new :class:`Tags` object.
389 _get = nmlib.notmuch_tags_get
390 _get.restype = c_char_p
392 def __init__(self, tags_p, parent=None):
394 :param tags_p: A pointer to an underlying *notmuch_tags_t*
395 structure. These are not publically exposed, so a user
396 will almost never instantiate a :class:`Tags` object
397 herself. They are usually handed back as a result,
398 e.g. in :meth:`Database.get_all_tags`. *tags_p* must be
399 valid, we will raise an :exc:`NotmuchError`
400 (STATUS.NULL_POINTER) if it is `None`.
401 :type tags_p: :class:`ctypes.c_void_p`
402 :param parent: The parent object (ie :class:`Database` or
403 :class:`Message` these tags are derived from, and saves a
404 reference to it, so we can automatically delete the db object
405 once all derived objects are dead.
406 :TODO: Make the iterator optionally work more than once by
407 cache the tags in the Python object(?)
410 NotmuchError(STATUS.NULL_POINTER)
413 #save reference to parent object so we keep it alive
414 self._parent = parent
415 logging.debug("Inited Tags derived from %s" %(repr(parent)))
418 """ Make Tags an iterator """
422 if self._tags is None:
423 raise NotmuchError(STATUS.NOT_INITIALIZED)
425 if not nmlib.notmuch_tags_valid(self._tags):
429 tag = Tags._get (self._tags)
430 nmlib.notmuch_tags_move_to_next(self._tags)
434 """len(:class:`Tags`) returns the number of contained tags
436 .. note:: As this iterates over the tags, we will not be able
437 to iterate over them again (as in retrieve them)! If
438 the tags have been exhausted already, this will raise a
439 :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
442 if self._tags is None:
443 raise NotmuchError(STATUS.NOT_INITIALIZED)
446 while nmlib.notmuch_tags_valid(self._msgs):
447 nmlib.notmuch_tags_move_to_next(self._msgs)
453 """The str() representation of Tags() is a space separated list of tags
455 .. note:: As this iterates over the tags, we will not be able
456 to iterate over them again (as in retrieve them)! If
457 the tags have been exhausted already, this will raise a
458 :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
461 return " ".join(self)
464 """Close and free the notmuch tags"""
465 if self._tags is not None:
466 logging.debug("Freeing the Tags now")
467 nmlib.notmuch_tags_destroy (self._tags)
470 #------------------------------------------------------------------------------
471 class Messages(object):
472 """Represents a list of notmuch messages
474 This object provides an iterator over a list of notmuch messages
475 (Technically, it provides a wrapper for the underlying
476 *notmuch_messages_t* structure). Do note that the underlying
477 library only provides a one-time iterator (it cannot reset the
478 iterator to the start). Thus iterating over the function will
479 "exhaust" the list of messages, and a subsequent iteration attempt
480 will raise a :exc:`NotmuchError` STATUS.NOT_INITIALIZED. Also
481 note, that any function that uses iteration will also
482 exhaust the messages. So both::
484 for msg in msgs: print msg
488 number_of_msgs = len(msgs)
490 will "exhaust" the Messages. If you need to re-iterate over a list of
491 messages you will need to retrieve a new :class:`Messages` object.
493 Things are not as bad as it seems though, you can store and reuse
494 the single Message objects as often as you want as long as you
495 keep the parent Messages object around. (Recall that due to
496 hierarchical memory allocation, all derived Message objects will
497 be invalid when we delete the parent Messages() object, even if it
498 was already "exhausted".) So this works::
501 msgs = Query(db,'').search_messages() #get a Messages() object
506 # msgs is "exhausted" now and even len(msgs) will raise an exception.
507 # However it will be kept around until all retrieved Message() objects are
508 # also deleted. If you did e.g. an explicit del(msgs) here, the
509 # following lines would fail.
511 # You can reiterate over *msglist* however as often as you want.
512 # It is simply a list with Message objects.
514 print (msglist[0].get_filename())
515 print (msglist[1].get_filename())
516 print (msglist[0].get_message_id())
520 _get = nmlib.notmuch_messages_get
521 _get.restype = c_void_p
523 _collect_tags = nmlib.notmuch_messages_collect_tags
524 _collect_tags.restype = c_void_p
526 def __init__(self, msgs_p, parent=None):
528 :param msgs_p: A pointer to an underlying *notmuch_messages_t*
529 structure. These are not publically exposed, so a user
530 will almost never instantiate a :class:`Messages` object
531 herself. They are usually handed back as a result,
532 e.g. in :meth:`Query.search_messages`. *msgs_p* must be
533 valid, we will raise an :exc:`NotmuchError`
534 (STATUS.NULL_POINTER) if it is `None`.
535 :type msgs_p: :class:`ctypes.c_void_p`
536 :param parent: The parent object
537 (ie :class:`Query`) these tags are derived from. It saves
538 a reference to it, so we can automatically delete the db
539 object once all derived objects are dead.
540 :TODO: Make the iterator work more than once and cache the tags in
541 the Python object.(?)
544 NotmuchError(STATUS.NULL_POINTER)
547 #store parent, so we keep them alive as long as self is alive
548 self._parent = parent
549 logging.debug("Inited Messages derived from %s" %(str(parent)))
551 def collect_tags(self):
552 """Return the unique :class:`Tags` in the contained messages
554 :returns: :class:`Tags`
555 :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not inited
557 .. note:: :meth:`collect_tags` will iterate over the messages and
558 therefore will not allow further iterations.
560 if self._msgs is None:
561 raise NotmuchError(STATUS.NOT_INITIALIZED)
563 # collect all tags (returns NULL on error)
564 tags_p = Messages._collect_tags (self._msgs)
565 #reset _msgs as we iterated over it and can do so only once
569 raise NotmuchError(STATUS.NULL_POINTER)
570 return Tags(tags_p, self)
573 """ Make Messages an iterator """
577 if self._msgs is None:
578 raise NotmuchError(STATUS.NOT_INITIALIZED)
580 if not nmlib.notmuch_messages_valid(self._msgs):
584 msg = Message(Messages._get (self._msgs), self)
585 nmlib.notmuch_messages_move_to_next(self._msgs)
589 """len(:class:`Messages`) returns the number of contained messages
591 .. note:: As this iterates over the messages, we will not be able to
592 iterate over them again (as in retrieve them)!
594 if self._msgs is None:
595 raise NotmuchError(STATUS.NOT_INITIALIZED)
598 while nmlib.notmuch_messages_valid(self._msgs):
599 nmlib.notmuch_messages_move_to_next(self._msgs)
607 """Close and free the notmuch Messages"""
608 if self._msgs is not None:
609 logging.debug("Freeing the Messages now")
610 nmlib.notmuch_messages_destroy (self._msgs)
613 #------------------------------------------------------------------------------
614 class Message(object):
615 """Represents a single Email message
617 Technically, this wraps the underlying *notmuch_message_t* structure.
620 """notmuch_message_get_filename (notmuch_message_t *message)"""
621 _get_filename = nmlib.notmuch_message_get_filename
622 _get_filename.restype = c_char_p
624 """notmuch_message_get_message_id (notmuch_message_t *message)"""
625 _get_message_id = nmlib.notmuch_message_get_message_id
626 _get_message_id.restype = c_char_p
628 """notmuch_message_get_thread_id"""
629 _get_thread_id = nmlib.notmuch_message_get_thread_id
630 _get_thread_id.restype = c_char_p
632 """notmuch_message_get_tags (notmuch_message_t *message)"""
633 _get_tags = nmlib.notmuch_message_get_tags
634 _get_tags.restype = c_void_p
636 _get_date = nmlib.notmuch_message_get_date
637 _get_date.restype = c_uint64
639 _get_header = nmlib.notmuch_message_get_header
640 _get_header.restype = c_char_p
642 def __init__(self, msg_p, parent=None):
644 :param msg_p: A pointer to an internal notmuch_message_t
645 Structure. If it is `None`, we will raise an :exc:`NotmuchError`
647 :param parent: A 'parent' object is passed which this message is
648 derived from. We save a reference to it, so we can
649 automatically delete the parent object once all derived
653 NotmuchError(STATUS.NULL_POINTER)
655 #keep reference to parent, so we keep it alive
656 self._parent = parent
657 logging.debug("Inited Message derived from %s" %(str(parent)))
660 def get_message_id(self):
661 """Returns the message ID
663 :returns: String with a message ID
664 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
667 if self._msg is None:
668 raise NotmuchError(STATUS.NOT_INITIALIZED)
669 return Message._get_message_id(self._msg)
671 def get_thread_id(self):
672 """Returns the thread ID
674 The returned string belongs to 'message' will only be valid for as
675 long as the message is valid.
677 This function will not return None since Notmuch ensures that every
678 message belongs to a single thread.
680 :returns: String with a thread ID
681 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
684 if self._msg is None:
685 raise NotmuchError(STATUS.NOT_INITIALIZED)
687 return Message._get_thread_id (self._msg);
690 """Returns time_t of the message date
692 For the original textual representation of the Date header from the
693 message call notmuch_message_get_header() with a header value of
696 :returns: a time_t timestamp
698 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
701 if self._msg is None:
702 raise NotmuchError(STATUS.NOT_INITIALIZED)
703 return Message._get_date(self._msg)
705 def get_header(self, header):
706 """Returns a message header
708 This returns any message header that is stored in the notmuch database.
709 This is only a selected subset of headers, which is currently:
711 TODO: add stored headers
713 :param header: The name of the header to be retrieved.
714 It is not case-sensitive (TODO: confirm).
716 :returns: The header value as string
717 :exception: :exc:`NotmuchError`
719 * STATUS.NOT_INITIALIZED if the message
721 * STATUS.NULL_POINTER, if no header was found
723 if self._msg is None:
724 raise NotmuchError(STATUS.NOT_INITIALIZED)
726 #Returns NULL if any error occurs.
727 header = Message._get_header (self._msg, header)
729 raise NotmuchError(STATUS.NULL_POINTER)
732 def get_filename(self):
733 """Return the file path of the message file
735 :returns: Absolute file path & name of the message file
736 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
739 if self._msg is None:
740 raise NotmuchError(STATUS.NOT_INITIALIZED)
741 return Message._get_filename(self._msg)
744 """ Return the message tags
746 :returns: Message tags
747 :rtype: :class:`Tags`
748 :exception: :exc:`NotmuchError`
750 * STATUS.NOT_INITIALIZED if the message
752 * STATUS.NULL_POINTER, on error
754 if self._msg is None:
755 raise NotmuchError(STATUS.NOT_INITIALIZED)
757 tags_p = Message._get_tags(self._msg)
759 raise NotmuchError(STATUS.NULL_POINTER)
760 return Tags(tags_p, self)
762 def add_tag(self, tag):
763 """Add a tag to the given message
765 Adds a tag to the current message. The maximal tag length is defined in
766 the notmuch library and is currently 200 bytes.
768 :param tag: String with a 'tag' to be added.
769 :returns: STATUS.SUCCESS if the tag was successfully added.
770 Raises an exception otherwise.
771 :exception: :exc:`NotmuchError`. They have the following meaning:
774 The 'tag' argument is NULL
776 The length of 'tag' is too long
777 (exceeds Message.NOTMUCH_TAG_MAX)
778 STATUS.READ_ONLY_DATABASE
779 Database was opened in read-only mode so message cannot be
781 STATUS.NOT_INITIALIZED
782 The message has not been initialized.
784 if self._msg is None:
785 raise NotmuchError(STATUS.NOT_INITIALIZED)
787 status = nmlib.notmuch_message_add_tag (self._msg, tag)
789 if STATUS.SUCCESS == status:
793 raise NotmuchError(status)
795 def remove_tag(self, tag):
796 """Removes a tag from the given message
798 If the message has no such tag, this is a non-operation and
799 will report success anyway.
801 :param tag: String with a 'tag' to be removed.
802 :returns: STATUS.SUCCESS if the tag was successfully removed or if
803 the message had no such tag.
804 Raises an exception otherwise.
805 :exception: :exc:`NotmuchError`. They have the following meaning:
808 The 'tag' argument is NULL
810 The length of 'tag' is too long
811 (exceeds NOTMUCH_TAG_MAX)
812 STATUS.READ_ONLY_DATABASE
813 Database was opened in read-only mode so message cannot
815 STATUS.NOT_INITIALIZED
816 The message has not been initialized.
818 if self._msg is None:
819 raise NotmuchError(STATUS.NOT_INITIALIZED)
821 status = nmlib.notmuch_message_remove_tag(self._msg, tag)
823 if STATUS.SUCCESS == status:
827 raise NotmuchError(status)
829 def remove_all_tags(self):
830 """Removes all tags from the given message.
832 See :meth:`freeze` for an example showing how to safely
835 :returns: STATUS.SUCCESS if the tags were successfully removed.
836 Raises an exception otherwise.
837 :exception: :exc:`NotmuchError`. They have the following meaning:
839 STATUS.READ_ONLY_DATABASE
840 Database was opened in read-only mode so message cannot
842 STATUS.NOT_INITIALIZED
843 The message has not been initialized.
845 if self._msg is None:
846 raise NotmuchError(STATUS.NOT_INITIALIZED)
848 status = nmlib.notmuch_message_remove_all_tags(self._msg)
850 if STATUS.SUCCESS == status:
854 raise NotmuchError(status)
857 """Freezes the current state of 'message' within the database
859 This means that changes to the message state, (via :meth:`add_tag`,
860 :meth:`remove_tag`, and :meth:`remove_all_tags`), will not be
861 committed to the database until the message is :meth:`thaw`ed.
863 Multiple calls to freeze/thaw are valid and these calls will
864 "stack". That is there must be as many calls to thaw as to freeze
865 before a message is actually thawed.
867 The ability to do freeze/thaw allows for safe transactions to
868 change tag values. For example, explicitly setting a message to
869 have a given set of tags might look like this::
872 msg.remove_all_tags()
877 With freeze/thaw used like this, the message in the database is
878 guaranteed to have either the full set of original tag values, or
879 the full set of new tag values, but nothing in between.
881 Imagine the example above without freeze/thaw and the operation
882 somehow getting interrupted. This could result in the message being
883 left with no tags if the interruption happened after
884 :meth:`remove_all_tags` but before :meth:`add_tag`.
886 :returns: STATUS.SUCCESS if the message was successfully frozen.
887 Raises an exception otherwise.
888 :exception: :exc:`NotmuchError`. They have the following meaning:
890 STATUS.READ_ONLY_DATABASE
891 Database was opened in read-only mode so message cannot
893 STATUS.NOT_INITIALIZED
894 The message has not been initialized.
896 if self._msg is None:
897 raise NotmuchError(STATUS.NOT_INITIALIZED)
899 status = nmlib.notmuch_message_freeze(self._msg)
901 if STATUS.SUCCESS == status:
905 raise NotmuchError(status)
908 """Thaws the current 'message'
910 Thaw the current 'message', synchronizing any changes that may have
911 occurred while 'message' was frozen into the notmuch database.
913 See :meth:`freeze` for an example of how to use this
914 function to safely provide tag changes.
916 Multiple calls to freeze/thaw are valid and these calls with
917 "stack". That is there must be as many calls to thaw as to freeze
918 before a message is actually thawed.
920 :returns: STATUS.SUCCESS if the message was successfully frozen.
921 Raises an exception otherwise.
922 :exception: :exc:`NotmuchError`. They have the following meaning:
924 STATUS.UNBALANCED_FREEZE_THAW
925 An attempt was made to thaw an unfrozen message.
926 That is, there have been an unbalanced number of calls
927 to :meth:`freeze` and :meth:`thaw`.
928 STATUS.NOT_INITIALIZED
929 The message has not been initialized.
931 if self._msg is None:
932 raise NotmuchError(STATUS.NOT_INITIALIZED)
934 status = nmlib.notmuch_message_thaw(self._msg)
936 if STATUS.SUCCESS == status:
940 raise NotmuchError(status)
944 """A message() is represented by a 1-line summary"""
946 msg['from'] = self.get_header('from')
947 msg['tags'] = str(self.get_tags())
948 msg['date'] = date.fromtimestamp(self.get_date())
949 return "%(from)s (%(date)s) (%(tags)s)" % (msg)
951 def format_as_text(self):
952 """Output like notmuch show (Not implemented)"""
956 """Close and free the notmuch Message"""
957 if self._msg is not None:
958 logging.debug("Freeing the Message now")
959 nmlib.notmuch_message_destroy (self._msg)