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
253 def __init__(self, db, querystr):
255 :param db: An open database which we derive the Query from.
256 :type db: :class:`Database`
257 :param querystr: The query string for the message.
262 self.create(db, querystr)
264 def create(self, db, querystr):
265 """Creates a new query derived from a Database.
267 This function is utilized by __init__() and usually does not need to
270 :param db: Database to create the query from.
271 :type db: :class:`Database`
272 :param querystr: The query string
275 :exception: :exc:`NotmuchError`
277 * STATUS.NOT_INITIALIZED if db is not inited
278 * STATUS.NULL_POINTER if the query creation failed
282 raise NotmuchError(STATUS.NOT_INITIALIZED)
283 # create reference to parent db to keep it alive
286 # create query, return None if too little mem available
287 query_p = Query._create(db.db_p, querystr)
289 NotmuchError(STATUS.NULL_POINTER)
290 self._query = query_p
292 def set_sort(self, sort):
293 """Set the sort order future results will be delivered in
295 Wraps the underlying *notmuch_query_set_sort* function.
297 :param sort: Sort order (see :attr:`Query.SORT`)
299 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if query has not
302 if self._query is None:
303 raise NotmuchError(STATUS.NOT_INITIALIZED)
305 nmlib.notmuch_query_set_sort(self._query, sort)
307 def search_messages(self):
308 """Filter messages according to the query and return
309 :class:`Messages` in the defined sort order
311 Technically, it wraps the underlying
312 *notmuch_query_search_messages* function.
314 :returns: :class:`Messages`
315 :exception: :exc:`NotmuchError`
317 * STATUS.NOT_INITIALIZED if query is not inited
318 * STATUS.NULL_POINTER if search_messages failed
320 if self._query is None:
321 raise NotmuchError(STATUS.NOT_INITIALIZED)
323 msgs_p = Query._search_messages(self._query)
326 NotmuchError(STATUS.NULL_POINTER)
328 return Messages(msgs_p,self)
332 """Close and free the Query"""
333 if self._query is not None:
334 logging.debug("Freeing the Query now")
335 nmlib.notmuch_query_destroy (self._query)
337 #------------------------------------------------------------------------------
339 """Represents a list of notmuch tags
341 This object provides an iterator over a list of notmuch tags. Do
342 note that the underlying library only provides a one-time iterator
343 (it cannot reset the iterator to the start). Thus iterating over
344 the function will "exhaust" the list of tags, and a subsequent
345 iteration attempt will raise a :exc:`NotmuchError`
346 STATUS.NOT_INITIALIZED. Also note, that any function that uses
347 iteration (nearly all) will also exhaust the tags. So both::
349 for tag in tags: print tag
353 number_of_tags = len(tags)
357 #str() iterates over all tags to construct a space separated list
360 will "exhaust" the Tags. If you need to re-iterate over a list of
361 tags you will need to retrieve a new :class:`Tags` object.
365 _get = nmlib.notmuch_tags_get
366 _get.restype = c_char_p
368 def __init__(self, tags_p, parent=None):
370 :param tags_p: A pointer to an underlying *notmuch_tags_t*
371 structure. These are not publically exposed, so a user
372 will almost never instantiate a :class:`Tags` object
373 herself. They are usually handed back as a result,
374 e.g. in :meth:`Database.get_all_tags`. *tags_p* must be
375 valid, we will raise an :exc:`NotmuchError`
376 (STATUS.NULL_POINTER) if it is `None`.
377 :type tags_p: :class:`ctypes.c_void_p`
378 :param parent: The parent object (ie :class:`Database` or
379 :class:`Message` these tags are derived from, and saves a
380 reference to it, so we can automatically delete the db object
381 once all derived objects are dead.
382 :TODO: Make the iterator optionally work more than once by
383 cache the tags in the Python object(?)
386 NotmuchError(STATUS.NULL_POINTER)
389 #save reference to parent object so we keep it alive
390 self._parent = parent
391 logging.debug("Inited Tags derived from %s" %(repr(parent)))
394 """ Make Tags an iterator """
398 if self._tags is None:
399 raise NotmuchError(STATUS.NOT_INITIALIZED)
401 if not nmlib.notmuch_tags_valid(self._tags):
405 tag = Tags._get (self._tags)
406 nmlib.notmuch_tags_move_to_next(self._tags)
410 """len(:class:`Tags`) returns the number of contained tags
412 .. note:: As this iterates over the tags, we will not be able
413 to iterate over them again (as in retrieve them)! If
414 the tags have been exhausted already, this will raise a
415 :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
418 if self._tags is None:
419 raise NotmuchError(STATUS.NOT_INITIALIZED)
422 while nmlib.notmuch_tags_valid(self._msgs):
423 nmlib.notmuch_tags_move_to_next(self._msgs)
429 """The str() representation of Tags() is a space separated list of tags
431 .. note:: As this iterates over the tags, we will not be able
432 to iterate over them again (as in retrieve them)! If
433 the tags have been exhausted already, this will raise a
434 :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
437 return " ".join(self)
440 """Close and free the notmuch tags"""
441 if self._tags is not None:
442 logging.debug("Freeing the Tags now")
443 nmlib.notmuch_tags_destroy (self._tags)
446 #------------------------------------------------------------------------------
447 class Messages(object):
448 """Represents a list of notmuch messages
450 This object provides an iterator over a list of notmuch messages
451 (Technically, it provides a wrapper for the underlying
452 *notmuch_messages_t* structure). Do note that the underlying
453 library only provides a one-time iterator (it cannot reset the
454 iterator to the start). Thus iterating over the function will
455 "exhaust" the list of messages, and a subsequent iteration attempt
456 will raise a :exc:`NotmuchError` STATUS.NOT_INITIALIZED. Also
457 note, that any function that uses iteration will also
458 exhaust the messages. So both::
460 for msg in msgs: print msg
464 number_of_msgs = len(msgs)
466 will "exhaust" the Messages. If you need to re-iterate over a list of
467 messages you will need to retrieve a new :class:`Messages` object.
469 Things are not as bad as it seems though, you can store and reuse
470 the single Message objects as often as you want as long as you
471 keep the parent Messages object around. (Recall that due to
472 hierarchical memory allocation, all derived Message objects will
473 be invalid when we delete the parent Messages() object, even if it
474 was already "exhausted".) So this works::
477 msgs = Query(db,'').search_messages() #get a Messages() object
482 # msgs is "exhausted" now and even len(msgs) will raise an exception.
483 # However it will be kept around until all retrieved Message() objects are
484 # also deleted. If you did e.g. an explicit del(msgs) here, the
485 # following lines would fail.
487 # You can reiterate over *msglist* however as often as you want.
488 # It is simply a list with Message objects.
490 print (msglist[0].get_filename())
491 print (msglist[1].get_filename())
492 print (msglist[0].get_message_id())
496 _get = nmlib.notmuch_messages_get
497 _get.restype = c_void_p
499 _collect_tags = nmlib.notmuch_messages_collect_tags
500 _collect_tags.restype = c_void_p
502 def __init__(self, msgs_p, parent=None):
504 :param msgs_p: A pointer to an underlying *notmuch_messages_t*
505 structure. These are not publically exposed, so a user
506 will almost never instantiate a :class:`Messages` object
507 herself. They are usually handed back as a result,
508 e.g. in :meth:`Query.search_messages`. *msgs_p* must be
509 valid, we will raise an :exc:`NotmuchError`
510 (STATUS.NULL_POINTER) if it is `None`.
511 :type msgs_p: :class:`ctypes.c_void_p`
512 :param parent: The parent object
513 (ie :class:`Query`) these tags are derived from. It saves
514 a reference to it, so we can automatically delete the db
515 object once all derived objects are dead.
516 :TODO: Make the iterator work more than once and cache the tags in
517 the Python object.(?)
520 NotmuchError(STATUS.NULL_POINTER)
523 #store parent, so we keep them alive as long as self is alive
524 self._parent = parent
525 logging.debug("Inited Messages derived from %s" %(str(parent)))
527 def collect_tags(self):
528 """Return the unique :class:`Tags` in the contained messages
530 :returns: :class:`Tags`
531 :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not inited
533 .. note:: :meth:`collect_tags` will iterate over the messages and
534 therefore will not allow further iterations.
536 if self._msgs is None:
537 raise NotmuchError(STATUS.NOT_INITIALIZED)
539 # collect all tags (returns NULL on error)
540 tags_p = Messages._collect_tags (self._msgs)
541 #reset _msgs as we iterated over it and can do so only once
545 raise NotmuchError(STATUS.NULL_POINTER)
546 return Tags(tags_p, self)
549 """ Make Messages an iterator """
553 if self._msgs is None:
554 raise NotmuchError(STATUS.NOT_INITIALIZED)
556 if not nmlib.notmuch_messages_valid(self._msgs):
560 msg = Message(Messages._get (self._msgs), self)
561 nmlib.notmuch_messages_move_to_next(self._msgs)
565 """len(:class:`Messages`) returns the number of contained messages
567 .. note:: As this iterates over the messages, we will not be able to
568 iterate over them again (as in retrieve them)!
570 if self._msgs is None:
571 raise NotmuchError(STATUS.NOT_INITIALIZED)
574 while nmlib.notmuch_messages_valid(self._msgs):
575 nmlib.notmuch_messages_move_to_next(self._msgs)
583 """Close and free the notmuch Messages"""
584 if self._msgs is not None:
585 logging.debug("Freeing the Messages now")
586 nmlib.notmuch_messages_destroy (self._msgs)
589 #------------------------------------------------------------------------------
590 class Message(object):
591 """Represents a single Email message
593 Technically, this wraps the underlying *notmuch_message_t* structure.
596 """notmuch_message_get_filename (notmuch_message_t *message)"""
597 _get_filename = nmlib.notmuch_message_get_filename
598 _get_filename.restype = c_char_p
599 """notmuch_message_get_message_id (notmuch_message_t *message)"""
600 _get_message_id = nmlib.notmuch_message_get_message_id
601 _get_message_id.restype = c_char_p
603 """notmuch_message_get_tags (notmuch_message_t *message)"""
604 _get_tags = nmlib.notmuch_message_get_tags
605 _get_tags.restype = c_void_p
607 _get_date = nmlib.notmuch_message_get_date
608 _get_date.restype = c_uint64
610 _get_header = nmlib.notmuch_message_get_header
611 _get_header.restype = c_char_p
613 def __init__(self, msg_p, parent=None):
615 :param msg_p: A pointer to an internal notmuch_message_t
616 Structure. If it is `None`, we will raise an :exc:`NotmuchError`
618 :param parent: A 'parent' object is passed which this message is
619 derived from. We save a reference to it, so we can
620 automatically delete the parent object once all derived
624 NotmuchError(STATUS.NULL_POINTER)
626 #keep reference to parent, so we keep it alive
627 self._parent = parent
628 logging.debug("Inited Message derived from %s" %(str(parent)))
631 def get_message_id(self):
632 """Return the message ID
634 :returns: String with a message ID
635 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
638 if self._msg is None:
639 raise NotmuchError(STATUS.NOT_INITIALIZED)
640 return Message._get_message_id(self._msg)
643 """Returns time_t of the message date
645 For the original textual representation of the Date header from the
646 message call notmuch_message_get_header() with a header value of
649 :returns: a time_t timestamp
651 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
654 if self._msg is None:
655 raise NotmuchError(STATUS.NOT_INITIALIZED)
656 return Message._get_date(self._msg)
658 def get_header(self, header):
659 """Returns a message header
661 This returns any message header that is stored in the notmuch database.
662 This is only a selected subset of headers, which is currently:
664 TODO: add stored headers
666 :param header: The name of the header to be retrieved.
667 It is not case-sensitive (TODO: confirm).
669 :returns: The header value as string
670 :exception: :exc:`NotmuchError`
672 * STATUS.NOT_INITIALIZED if the message
674 * STATUS.NULL_POINTER, if no header was found
676 if self._msg is None:
677 raise NotmuchError(STATUS.NOT_INITIALIZED)
679 #Returns NULL if any error occurs.
680 header = Message._get_header (self._msg, header)
682 raise NotmuchError(STATUS.NULL_POINTER)
685 def get_filename(self):
686 """Return the file path of the message file
688 :returns: Absolute file path & name of the message file
689 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
692 if self._msg is None:
693 raise NotmuchError(STATUS.NOT_INITIALIZED)
694 return Message._get_filename(self._msg)
697 """ Return the message tags
699 :returns: Message tags
700 :rtype: :class:`Tags`
701 :exception: :exc:`NotmuchError`
703 * STATUS.NOT_INITIALIZED if the message
705 * STATUS.NULL_POINTER, on error
707 if self._msg is None:
708 raise NotmuchError(STATUS.NOT_INITIALIZED)
710 tags_p = Message._get_tags(self._msg)
712 raise NotmuchError(STATUS.NULL_POINTER)
713 return Tags(tags_p, self)
715 def add_tag(self, tag):
716 """Add a tag to the given message
718 Adds a tag to the current message. The maximal tag length is defined in
719 the notmuch library and is currently 200 bytes.
721 :param tag: String with a 'tag' to be added.
722 :returns: STATUS.SUCCESS if the tag was successfully added.
723 Raises an exception otherwise.
724 :exception: :exc:`NotmuchError`. They have the following meaning:
727 The 'tag' argument is NULL
729 The length of 'tag' is too long
730 (exceeds Message.NOTMUCH_TAG_MAX)
731 STATUS.READ_ONLY_DATABASE
732 Database was opened in read-only mode so message cannot be
734 STATUS.NOT_INITIALIZED
735 The message has not been initialized.
737 if self._msg is None:
738 raise NotmuchError(STATUS.NOT_INITIALIZED)
740 status = nmlib.notmuch_message_add_tag (self._msg, tag)
742 if STATUS.SUCCESS == status:
746 raise NotmuchError(status)
748 def remove_tag(self, tag):
749 """Removes a tag from the given message
751 If the message has no such tag, this is a non-operation and
752 will report success anyway.
754 :param tag: String with a 'tag' to be removed.
755 :returns: STATUS.SUCCESS if the tag was successfully removed or if
756 the message had no such tag.
757 Raises an exception otherwise.
758 :exception: :exc:`NotmuchError`. They have the following meaning:
761 The 'tag' argument is NULL
763 The length of 'tag' is too long
764 (exceeds NOTMUCH_TAG_MAX)
765 STATUS.READ_ONLY_DATABASE
766 Database was opened in read-only mode so message cannot
768 STATUS.NOT_INITIALIZED
769 The message has not been initialized.
771 if self._msg is None:
772 raise NotmuchError(STATUS.NOT_INITIALIZED)
774 status = nmlib.notmuch_message_remove_tag(self._msg, tag)
776 if STATUS.SUCCESS == status:
780 raise NotmuchError(status)
782 def remove_all_tags(self):
783 """Removes all tags from the given message.
785 See :meth:`freeze` for an example showing how to safely
788 :returns: STATUS.SUCCESS if the tags were successfully removed.
789 Raises an exception otherwise.
790 :exception: :exc:`NotmuchError`. They have the following meaning:
792 STATUS.READ_ONLY_DATABASE
793 Database was opened in read-only mode so message cannot
795 STATUS.NOT_INITIALIZED
796 The message has not been initialized.
798 if self._msg is None:
799 raise NotmuchError(STATUS.NOT_INITIALIZED)
801 status = nmlib.notmuch_message_remove_all_tags(self._msg)
803 if STATUS.SUCCESS == status:
807 raise NotmuchError(status)
810 """Freezes the current state of 'message' within the database
812 This means that changes to the message state, (via :meth:`add_tag`,
813 :meth:`remove_tag`, and :meth:`remove_all_tags`), will not be
814 committed to the database until the message is :meth:`thaw`ed.
816 Multiple calls to freeze/thaw are valid and these calls will
817 "stack". That is there must be as many calls to thaw as to freeze
818 before a message is actually thawed.
820 The ability to do freeze/thaw allows for safe transactions to
821 change tag values. For example, explicitly setting a message to
822 have a given set of tags might look like this::
825 msg.remove_all_tags()
830 With freeze/thaw used like this, the message in the database is
831 guaranteed to have either the full set of original tag values, or
832 the full set of new tag values, but nothing in between.
834 Imagine the example above without freeze/thaw and the operation
835 somehow getting interrupted. This could result in the message being
836 left with no tags if the interruption happened after
837 :meth:`remove_all_tags` but before :meth:`add_tag`.
839 :returns: STATUS.SUCCESS if the message was successfully frozen.
840 Raises an exception otherwise.
841 :exception: :exc:`NotmuchError`. They have the following meaning:
843 STATUS.READ_ONLY_DATABASE
844 Database was opened in read-only mode so message cannot
846 STATUS.NOT_INITIALIZED
847 The message has not been initialized.
849 if self._msg is None:
850 raise NotmuchError(STATUS.NOT_INITIALIZED)
852 status = nmlib.notmuch_message_freeze(self._msg)
854 if STATUS.SUCCESS == status:
858 raise NotmuchError(status)
861 """Thaws the current 'message'
863 Thaw the current 'message', synchronizing any changes that may have
864 occurred while 'message' was frozen into the notmuch database.
866 See :meth:`freeze` for an example of how to use this
867 function to safely provide tag changes.
869 Multiple calls to freeze/thaw are valid and these calls with
870 "stack". That is there must be as many calls to thaw as to freeze
871 before a message is actually thawed.
873 :returns: STATUS.SUCCESS if the message was successfully frozen.
874 Raises an exception otherwise.
875 :exception: :exc:`NotmuchError`. They have the following meaning:
877 STATUS.UNBALANCED_FREEZE_THAW
878 An attempt was made to thaw an unfrozen message.
879 That is, there have been an unbalanced number of calls
880 to :meth:`freeze` and :meth:`thaw`.
881 STATUS.NOT_INITIALIZED
882 The message has not been initialized.
884 if self._msg is None:
885 raise NotmuchError(STATUS.NOT_INITIALIZED)
887 status = nmlib.notmuch_message_thaw(self._msg)
889 if STATUS.SUCCESS == status:
893 raise NotmuchError(status)
897 """A message() is represented by a 1-line summary"""
899 msg['from'] = self.get_header('from')
900 msg['tags'] = str(self.get_tags())
901 msg['date'] = date.fromtimestamp(self.get_date())
902 return "%(from)s (%(date)s) (%(tags)s)" % (msg)
904 def format_as_text(self):
905 """Output like notmuch show (Not implemented)"""
909 """Close and free the notmuch Message"""
910 if self._msg is not None:
911 logging.debug("Freeing the Message now")
912 nmlib.notmuch_message_destroy (self._msg)