]> git.notmuchmail.org Git - notmuch/blob - cnotmuch/database.py
79f57ea2275f2a12ea7e57f50491068899cbb3a4
[notmuch] / cnotmuch / database.py
1 import ctypes
2 from ctypes import c_int, c_char_p, c_void_p, c_uint64
3 from cnotmuch.globals import nmlib, STATUS, NotmuchError, Enum
4 import logging
5 from datetime import date
6
7 class Database(object):
8     """Represents a notmuch database (wraps notmuch_database_t)
9
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.
15     """
16     MODE = Enum(['READ_ONLY','READ_WRITE'])
17     """Constants: Mode in which to open the database"""
18
19     _std_db_path = None
20     """Class attribute to cache user's default database"""
21
22     """notmuch_database_get_path (notmuch_database_t *database)"""
23     _get_path = nmlib.notmuch_database_get_path
24     _get_path.restype = c_char_p
25
26     """notmuch_database_open (const char *path, notmuch_database_mode_t mode)"""
27     _open = nmlib.notmuch_database_open 
28     _open.restype = c_void_p
29
30     """ notmuch_database_find_message """
31     _find_message = nmlib.notmuch_database_find_message
32     _find_message.restype = c_void_p
33
34     """notmuch_database_get_all_tags (notmuch_database_t *database)"""
35     _get_all_tags = nmlib.notmuch_database_get_all_tags
36     _get_all_tags.restype = c_void_p
37
38     """ notmuch_database_create(const char *path):"""
39     _create = nmlib.notmuch_database_create
40     _create.restype = c_void_p
41
42     def __init__(self, path=None, create=False, mode= MODE.READ_ONLY):
43         """If *path* is *None*, we will try to read a users notmuch
44         configuration and use his default database. If *create* is `True`,
45         the database will always be created in
46         :attr:`MODE`.READ_WRITE mode.
47
48         :param path:   Directory to open/create the database in (see
49                        above for behavior if `None`)
50         :type path:    `str` or `None`
51         :param create: Pass `False` to open an existing, `True` to create a new
52                        database.  
53         :type create:  bool
54         :param mode:   Mode to open a database in. Is always 
55                        :attr:`MODE`.READ_WRITE when creating a new one.
56         :type mode:    :attr:`MODE`
57         :returns:      Nothing
58         :exception:    :exc:`NotmuchError` in case of failure.
59         """
60         self._db = None
61         if path is None:
62             # no path specified. use a user's default database
63             if Database._std_db_path is None:
64                 #the following line throws a NotmuchError if it fails
65                 Database._std_db_path = self._get_user_default_db()
66             path = Database._std_db_path
67
68         if create == False:
69             self.open(path, mode)
70         else:
71             self.create(path)
72
73     def create(self, path):
74         """Creates a new notmuch database
75
76         This function is used by __init__() and usually does not need
77         to be called directly. It wraps the underlying
78         *notmuch_database_create* function and creates a new notmuch
79         database at *path*. It will always return a database in
80         :attr:`MODE`.READ_WRITE mode as creating an empty database for
81         reading only does not make a great deal of sense.
82
83         :param path: A directory in which we should create the database.
84         :type path: str
85         :returns: Nothing
86         :exception: :exc:`NotmuchError` in case of any failure
87                     (after printing an error message on stderr).
88         """
89         if self._db is not None:
90             raise NotmuchError(
91             message="Cannot create db, this Database() already has an open one.")
92
93         res = Database._create(path, MODE.READ_WRITE)
94
95         if res is None:
96             raise NotmuchError(
97                 message="Could not create the specified database")
98         self._db = res
99
100     def open(self, path, mode= MODE.READ_ONLY): 
101         """Opens an existing database
102
103         This function is used by __init__() and usually does not need
104         to be called directly. It wraps the underlying
105         *notmuch_database_open* function.
106
107         :param status: Open the database in read-only or read-write mode
108         :type status:  :attr:`MODE` 
109         :returns: Nothing
110         :exception: Raises :exc:`NotmuchError` in case
111                     of any failure (after printing an error message on stderr).
112         """
113
114         res = Database._open(path, mode)
115
116         if res is None:
117             raise NotmuchError(
118                 message="Could not open the specified database")
119         self._db = res
120
121     def get_path(self):
122         """Returns the file path of an open database
123
124         Wraps notmuch_database_get_path"""
125         return Database._get_path(self._db)
126
127     def find_message(self, msgid):
128         """Returns a :class:`Message` as identified by its message ID
129
130         Wraps the underlying *notmuch_database_find_message* function.
131
132         :param msgid: The message ID
133         :type msgid: string
134         :returns: :class:`Message` or `None` if no message is found or if an
135                   out-of-memory situation occurs.
136         :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
137                   the database was not intitialized.
138         """
139         if self._db is None:
140             raise NotmuchError(STATUS.NOT_INITIALIZED)
141         msg_p = Database._find_message(self._db, msgid)
142         if msg_p is None:
143             return None
144         return Message(msg_p, self)
145
146     def get_all_tags(self):
147         """Returns :class:`Tags` with a list of all tags found in the database
148
149         :returns: :class:`Tags`
150         :execption: :exc:`NotmuchError` with STATUS.NULL_POINTER on error
151         """
152         if self._db is None:
153             raise NotmuchError(STATUS.NOT_INITIALIZED)
154
155         tags_p = Database._get_all_tags (self._db)
156         if tags_p == None:
157             raise NotmuchError(STATUS.NULL_POINTER)
158         return Tags(tags_p, self)
159
160     def __repr__(self):
161         return "'Notmuch DB " + self.get_path() + "'"
162
163     def __del__(self):
164         """Close and free the notmuch database if needed"""
165         if self._db is not None:
166             logging.debug("Freeing the database now")
167             nmlib.notmuch_database_close(self._db)
168
169     def _get_user_default_db(self):
170         """ Reads a user's notmuch config and returns his db location
171
172         Throws a NotmuchError if it cannot find it"""
173         from ConfigParser import SafeConfigParser
174         import os.path
175         config = SafeConfigParser()
176         config.read(os.path.expanduser('~/.notmuch-config'))
177         if not config.has_option('database','path'):
178             raise NotmuchError(message=
179                                "No DB path specified and no user default found")
180         return config.get('database','path')
181
182     @property
183     def db_p(self):
184         """Property returning a pointer to the notmuch_database_t or `None`
185
186         This should normally not be needed by a user."""
187         return self._db
188
189 #------------------------------------------------------------------------------
190 class Query(object):
191     """ Represents a search query on an opened :class:`Database`.
192
193     A query selects and filters a subset of messages from the notmuch
194     database we derive from.
195
196     Technically, it wraps the underlying *notmuch_query_t* struct.
197
198     .. note:: Do remember that as soon as we tear down this object,
199            all underlying derived objects such as threads,
200            messages, tags etc will be freed by the underlying library
201            as well. Accessing these objects will lead to segfaults and
202            other unexpected behavior. See above for more details.
203     """
204     # constants
205     SORT = Enum(['OLDEST_FIRST','NEWEST_FIRST','MESSAGE_ID'])
206     """Constants: Sort order in which to return results"""
207
208     """notmuch_query_create"""
209     _create = nmlib.notmuch_query_create
210     _create.restype = c_void_p
211
212     """notmuch_query_search_messages"""
213     _search_messages = nmlib.notmuch_query_search_messages
214     _search_messages.restype = c_void_p
215
216     def __init__(self, db, querystr):
217         """
218         :param db: An open database which we derive the Query from.
219         :type db: :class:`Database`
220         :param querystr: The query string for the message.
221         :type querystr: str
222         """
223         self._db = None
224         self._query = None
225         self.create(db, querystr)
226
227     def create(self, db, querystr):
228         """Creates a new query derived from a Database.
229
230         This function is utilized by __init__() and usually does not need to 
231         be called directly.
232
233         :param db: Database to create the query from.
234         :type db: :class:`Database`
235         :param querystr: The query string
236         :type querystr: str
237         :returns: Nothing
238         :exception: :exc:`NotmuchError`
239
240                       * STATUS.NOT_INITIALIZED if db is not inited
241                       * STATUS.NULL_POINTER if the query creation failed 
242                         (too little memory)
243         """
244         if db.db_p is None:
245             raise NotmuchError(STATUS.NOT_INITIALIZED)            
246         # create reference to parent db to keep it alive
247         self._db = db
248         
249         # create query, return None if too little mem available
250         query_p = Query._create(db.db_p, querystr)
251         if query_p is None:
252             NotmuchError(STATUS.NULL_POINTER)
253         self._query = query_p
254
255     def set_sort(self, sort):
256         """Set the sort order future results will be delivered in
257
258         Wraps the underlying *notmuch_query_set_sort* function.
259
260         :param sort: Sort order (see :attr:`Query.SORT`)
261         :returns: Nothing
262         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if query has not 
263                     been initialized.
264         """
265         if self._query is None:
266             raise NotmuchError(STATUS.NOT_INITIALIZED)
267
268         nmlib.notmuch_query_set_sort(self._query, sort)
269
270     def search_messages(self):
271         """Filter messages according to the query and return
272         :class:`Messages` in the defined sort order
273
274         Technically, it wraps the underlying
275         *notmuch_query_search_messages* function.
276
277         :returns: :class:`Messages`
278         :exception: :exc:`NotmuchError`
279
280                       * STATUS.NOT_INITIALIZED if query is not inited
281                       * STATUS.NULL_POINTER if search_messages failed 
282         """
283         if self._query is None:
284             raise NotmuchError(STATUS.NOT_INITIALIZED)            
285
286         msgs_p = Query._search_messages(self._query)
287
288         if msgs_p is None:
289             NotmuchError(STATUS.NULL_POINTER)
290
291         return Messages(msgs_p,self)
292
293
294     def __del__(self):
295         """Close and free the Query"""
296         if self._query is not None:
297             logging.debug("Freeing the Query now")
298             nmlib.notmuch_query_destroy (self._query)
299
300 #------------------------------------------------------------------------------
301 class Tags(object):
302     """Represents a list of notmuch tags
303
304     This object provides an iterator over a list of notmuch tags. Do
305     note that the underlying library only provides a one-time iterator
306     (it cannot reset the iterator to the start). Thus iterating over
307     the function will "exhaust" the list of tags, and a subsequent
308     iteration attempt will raise a :exc:`NotmuchError`
309     STATUS.NOT_INITIALIZED. Also note, that any function that uses
310     iteration (nearly all) will also exhaust the tags. So both::
311
312       for tag in tags: print tag 
313
314     as well as::
315
316        number_of_tags = len(tags)
317
318     and even a simple::
319
320        #str() iterates over all tags to construct a space separated list
321        print(str(tags))
322
323     will "exhaust" the Tags. If you need to re-iterate over a list of
324     tags you will need to retrieve a new :class:`Tags` object.
325     """
326
327     #notmuch_tags_get
328     _get = nmlib.notmuch_tags_get
329     _get.restype = c_char_p
330
331     def __init__(self, tags_p, parent=None):
332         """
333         :param tags_p: A pointer to an underlying *notmuch_tags_t*
334              structure. These are not publically exposed, so a user
335              will almost never instantiate a :class:`Tags` object
336              herself. They are usually handed back as a result,
337              e.g. in :meth:`Database.get_all_tags`.  *tags_p* must be
338              valid, we will raise an :exc:`NotmuchError`
339              (STATUS.NULL_POINTER) if it is `None`.
340         :type tags_p: :class:`ctypes.c_void_p`
341         :param parent: The parent object (ie :class:`Database` or 
342              :class:`Message` these tags are derived from, and saves a
343              reference to it, so we can automatically delete the db object
344              once all derived objects are dead.
345         :TODO: Make the iterator optionally work more than once by
346                cache the tags in the Python object(?)
347         """
348         if tags_p is None:
349             NotmuchError(STATUS.NULL_POINTER)
350
351         self._tags = tags_p
352         #save reference to parent object so we keep it alive
353         self._parent = parent
354         logging.debug("Inited Tags derived from %s" %(repr(parent)))
355     
356     def __iter__(self):
357         """ Make Tags an iterator """
358         return self
359
360     def next(self):
361         if self._tags is None:
362             raise NotmuchError(STATUS.NOT_INITIALIZED)
363
364         if not nmlib.notmuch_tags_valid(self._tags):
365             self._tags = None
366             raise StopIteration
367
368         tag = Tags._get (self._tags)
369         nmlib.notmuch_tags_move_to_next(self._tags)
370         return tag
371
372     def __len__(self):
373         """len(:class:`Tags`) returns the number of contained tags
374
375         .. note:: As this iterates over the tags, we will not be able
376                to iterate over them again (as in retrieve them)! If
377                the tags have been exhausted already, this will raise a
378                :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
379                subsequent attempts.
380         """
381         if self._tags is None:
382             raise NotmuchError(STATUS.NOT_INITIALIZED)
383
384         i=0
385         while nmlib.notmuch_tags_valid(self._msgs):
386             nmlib.notmuch_tags_move_to_next(self._msgs)
387             i += 1
388         self._tags = None
389         return i
390
391     def __str__(self):
392         """The str() representation of Tags() is a space separated list of tags
393
394         .. note:: As this iterates over the tags, we will not be able
395                to iterate over them again (as in retrieve them)! If
396                the tags have been exhausted already, this will raise a
397                :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
398                subsequent attempts.
399         """
400         return " ".join(self)
401
402     def __del__(self):
403         """Close and free the notmuch tags"""
404         if self._tags is not None:
405             logging.debug("Freeing the Tags now")
406             nmlib.notmuch_tags_destroy (self._tags)
407
408
409 #------------------------------------------------------------------------------
410 class Messages(object):
411     """Represents a list of notmuch messages
412
413     This object provides an iterator over a list of notmuch messages
414     (Technically, it provides a wrapper for the underlying
415     *notmuch_messages_t* structure). Do note that the underlying
416     library only provides a one-time iterator (it cannot reset the
417     iterator to the start). Thus iterating over the function will
418     "exhaust" the list of messages, and a subsequent iteration attempt
419     will raise a :exc:`NotmuchError` STATUS.NOT_INITIALIZED. Also
420     note, that any function that uses iteration will also
421     exhaust the messages. So both::
422
423       for msg in msgs: print msg 
424
425     as well as::
426
427        number_of_msgs = len(msgs)
428
429     will "exhaust" the Messages. If you need to re-iterate over a list of
430     messages you will need to retrieve a new :class:`Messages` object.
431
432     Things are not as bad as it seems though, you can store and reuse
433     the single Message objects as often as you want as long as you
434     keep the parent Messages object around. (Recall that due to
435     hierarchical memory allocation, all derived Message objects will
436     be invalid when we delete the parent Messages() object, even if it
437     was already "exhausted".) So this works::
438
439       db   = Database()
440       msgs = Query(db,'').search_messages() #get a Messages() object
441       msglist = []
442       for m in msgs:
443          msglist.append(m)
444
445       # msgs is "exhausted" now and even len(msgs) will raise an exception.
446       # However it will be kept around until all retrieved Message() objects are
447       # also deleted. If you did e.g. an explicit del(msgs) here, the 
448       # following lines would fail.
449       
450       # You can reiterate over *msglist* however as often as you want. 
451       # It is simply a list with Message objects.
452
453       print (msglist[0].get_filename())
454       print (msglist[1].get_filename())
455       print (msglist[0].get_message_id())
456     """
457
458     #notmuch_tags_get
459     _get = nmlib.notmuch_messages_get
460     _get.restype = c_void_p
461
462     _collect_tags = nmlib.notmuch_messages_collect_tags
463     _collect_tags.restype = c_void_p
464
465     def __init__(self, msgs_p, parent=None):
466         """
467         :param msgs_p:  A pointer to an underlying *notmuch_messages_t*
468              structure. These are not publically exposed, so a user
469              will almost never instantiate a :class:`Messages` object
470              herself. They are usually handed back as a result,
471              e.g. in :meth:`Query.search_messages`.  *msgs_p* must be
472              valid, we will raise an :exc:`NotmuchError`
473              (STATUS.NULL_POINTER) if it is `None`.
474         :type msgs_p: :class:`ctypes.c_void_p`
475         :param parent: The parent object
476              (ie :class:`Query`) these tags are derived from. It saves
477              a reference to it, so we can automatically delete the db
478              object once all derived objects are dead.
479         :TODO: Make the iterator work more than once and cache the tags in 
480                the Python object.(?)
481         """
482         if msgs_p is None:
483             NotmuchError(STATUS.NULL_POINTER)
484
485         self._msgs = msgs_p
486         #store parent, so we keep them alive as long as self  is alive
487         self._parent = parent
488         logging.debug("Inited Messages derived from %s" %(str(parent)))
489
490     def collect_tags(self):
491         """Return the unique :class:`Tags` in the contained messages
492
493         :returns: :class:`Tags`
494         :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not inited
495
496         .. note:: :meth:`collect_tags` will iterate over the messages and
497           therefore will not allow further iterations.
498         """
499         if self._msgs is None:
500             raise NotmuchError(STATUS.NOT_INITIALIZED)
501
502         # collect all tags (returns NULL on error)
503         tags_p = Messages._collect_tags (self._msgs)
504         #reset _msgs as we iterated over it and can do so only once
505         self._msgs = None
506
507         if tags_p == None:
508             raise NotmuchError(STATUS.NULL_POINTER)
509         return Tags(tags_p, self)
510
511     def __iter__(self):
512         """ Make Messages an iterator """
513         return self
514
515     def next(self):
516         if self._msgs is None:
517             raise NotmuchError(STATUS.NOT_INITIALIZED)
518
519         if not nmlib.notmuch_messages_valid(self._msgs):
520             self._msgs = None
521             raise StopIteration
522
523         msg = Message(Messages._get (self._msgs), self)
524         nmlib.notmuch_messages_move_to_next(self._msgs)
525         return msg
526
527     def __len__(self):
528         """len(:class:`Messages`) returns the number of contained messages
529
530         .. note:: As this iterates over the messages, we will not be able to 
531                iterate over them again (as in retrieve them)!
532         """
533         if self._msgs is None:
534             raise NotmuchError(STATUS.NOT_INITIALIZED)
535
536         i=0
537         while nmlib.notmuch_messages_valid(self._msgs):
538             nmlib.notmuch_messages_move_to_next(self._msgs)
539             i += 1
540         self._msgs = None
541         return i
542
543
544
545     def __del__(self):
546         """Close and free the notmuch Messages"""
547         if self._msgs is not None:
548             logging.debug("Freeing the Messages now")
549             nmlib.notmuch_messages_destroy (self._msgs)
550
551
552 #------------------------------------------------------------------------------
553 class Message(object):
554     """Represents a single Email message
555
556     Technically, this wraps the underlying *notmuch_message_t* structure.
557     """
558
559     """notmuch_message_get_filename (notmuch_message_t *message)"""
560     _get_filename = nmlib.notmuch_message_get_filename
561     _get_filename.restype = c_char_p 
562     """notmuch_message_get_message_id (notmuch_message_t *message)"""
563     _get_message_id = nmlib.notmuch_message_get_message_id
564     _get_message_id.restype = c_char_p 
565
566     """notmuch_message_get_tags (notmuch_message_t *message)"""
567     _get_tags = nmlib.notmuch_message_get_tags
568     _get_tags.restype = c_void_p
569
570     _get_date = nmlib.notmuch_message_get_date
571     _get_date.restype = c_uint64
572
573     _get_header = nmlib.notmuch_message_get_header
574     _get_header.restype = c_char_p
575
576     def __init__(self, msg_p, parent=None):
577         """
578         :param msg_p: A pointer to an internal notmuch_message_t
579             Structure.  If it is `None`, we will raise an :exc:`NotmuchError`
580             STATUS.NULL_POINTER.
581         :param parent: A 'parent' object is passed which this message is
582               derived from. We save a reference to it, so we can
583               automatically delete the parent object once all derived
584               objects are dead.
585         """
586         if msg_p is None:
587             NotmuchError(STATUS.NULL_POINTER)
588         self._msg = msg_p
589         #keep reference to parent, so we keep it alive
590         self._parent = parent
591         logging.debug("Inited Message derived from %s" %(str(parent)))
592
593
594     def get_message_id(self):
595         """Return the message ID
596         
597         :returns: String with a message ID
598         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message 
599                     is not initialized.
600         """
601         if self._msg is None:
602             raise NotmuchError(STATUS.NOT_INITIALIZED)
603         return Message._get_message_id(self._msg)
604
605     def get_date(self):
606         """Returns time_t of the message date
607
608         For the original textual representation of the Date header from the
609         message call notmuch_message_get_header() with a header value of
610         "date".
611
612         :returns: a time_t timestamp
613         :rtype: c_unit64
614         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message 
615                     is not initialized.
616         """
617         if self._msg is None:
618             raise NotmuchError(STATUS.NOT_INITIALIZED)
619         return Message._get_date(self._msg)
620
621     def get_header(self, header):
622         """Returns a message header
623         
624         This returns any message header that is stored in the notmuch database.
625         This is only a selected subset of headers, which is currently:
626
627           TODO: add stored headers
628
629         :param header: The name of the header to be retrieved.
630                        It is not case-sensitive (TODO: confirm).
631         :type header: str
632         :returns: The header value as string
633         :exception: :exc:`NotmuchError`
634
635                     * STATUS.NOT_INITIALIZED if the message 
636                       is not initialized.
637                     * STATUS.NULL_POINTER, if no header was found
638         """
639         if self._msg is None:
640             raise NotmuchError(STATUS.NOT_INITIALIZED)
641
642         #Returns NULL if any error occurs.
643         header = Message._get_header (self._msg, header)
644         if header == None:
645             raise NotmuchError(STATUS.NULL_POINTER)
646         return header
647
648     def get_filename(self):
649         """Return the file path of the message file
650
651         :returns: Absolute file path & name of the message file
652         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message 
653               is not initialized.
654         """
655         if self._msg is None:
656             raise NotmuchError(STATUS.NOT_INITIALIZED)
657         return Message._get_filename(self._msg)
658
659     def get_tags(self):
660         """ Return the message tags
661
662         :returns: Message tags
663         :rtype: :class:`Tags`
664         :exception: :exc:`NotmuchError`
665
666                       * STATUS.NOT_INITIALIZED if the message 
667                         is not initialized.
668                       * STATUS.NULL_POINTER, on error
669         """
670         if self._msg is None:
671             raise NotmuchError(STATUS.NOT_INITIALIZED)
672
673         tags_p = Message._get_tags(self._msg)
674         if tags_p == None:
675             raise NotmuchError(STATUS.NULL_POINTER)
676         return Tags(tags_p, self)
677
678     def add_tag(self, tag):
679         """Add a tag to the given message
680
681         Adds a tag to the current message. The maximal tag length is defined in
682         the notmuch library and is currently 200 bytes.
683
684         :param tag: String with a 'tag' to be added.
685         :returns: STATUS.SUCCESS if the tag was successfully added.
686                   Raises an exception otherwise.
687         :exception: :exc:`NotmuchError`. They have the following meaning:
688
689                   STATUS.NULL_POINTER
690                     The 'tag' argument is NULL
691
692                   STATUS.TAG_TOO_LONG
693                     The length of 'tag' is too long 
694                     (exceeds Message.NOTMUCH_TAG_MAX)
695
696                   STATUS.READ_ONLY_DATABASE
697                     Database was opened in read-only mode so message cannot be 
698                     modified.
699
700                   STATUS.NOT_INITIALIZED
701                      The message has not been initialized.
702        """
703         if self._msg is None:
704             raise NotmuchError(STATUS.NOT_INITIALIZED)
705
706         status = nmlib.notmuch_message_add_tag (self._msg, tag)
707
708         if STATUS.SUCCESS == status:
709             # return on success
710             return status
711
712         raise NotmuchError(status)
713
714     def remove_tag(self, tag):
715         """Removes a tag from the given message
716
717         :param tag: String with a 'tag' to be removed.
718         :returns: STATUS.SUCCESS if the tag was successfully removed.
719                   Raises an exception otherwise.
720         :exception: :exc:`NotmuchError`. They have the following meaning:
721
722                    STATUS.NULL_POINTER
723                      The 'tag' argument is NULL
724                    NOTMUCH_STATUS_TAG_TOO_LONG
725                      The length of 'tag' is too long
726                      (exceeds NOTMUCH_TAG_MAX)
727                    NOTMUCH_STATUS_READ_ONLY_DATABASE
728                      Database was opened in read-only mode so message cannot 
729                      be modified.
730                    STATUS.NOT_INITIALIZED
731                      The message has not been initialized.
732
733         """
734         if self._msg is None:
735             raise NotmuchError(STATUS.NOT_INITIALIZED)
736
737         status = nmlib.notmuch_message_remove_tag(self._msg, tag)
738
739         if STATUS.SUCCESS == status:
740             # return on success
741             return status
742
743         raise NotmuchError(status)
744
745     def __str__(self):
746         """A message() is represented by a 1-line summary"""
747         msg = {}
748         msg['from'] = self.get_header('from')
749         msg['tags'] = str(self.get_tags())
750         msg['date'] = date.fromtimestamp(self.get_date())
751         return "%(from)s (%(date)s) (%(tags)s)" % (msg)
752
753     def format_as_text(self):
754         """Output like notmuch show (Not implemented)"""
755         return str(self)
756
757     def __del__(self):
758         """Close and free the notmuch Message"""
759         if self._msg is not None:
760             logging.debug("Freeing the Message now")
761             nmlib.notmuch_message_destroy (self._msg)