]> git.notmuchmail.org Git - notmuch/blobdiff - cnotmuch/database.py
Improve source documentation
[notmuch] / cnotmuch / database.py
index 09af0df964d15822e2606d76a64a68454fd2c9ba..eb54626dccfa8964a98f09202fa5f4768efdaa92 100644 (file)
@@ -1,20 +1,43 @@
 import ctypes
 import ctypes
-from ctypes import c_int, c_char_p, c_void_p
-from cnotmuch.globals import nmlib, STATUS, NotmuchError
+from ctypes import c_int, c_char_p, c_void_p, c_uint64
+from cnotmuch.globals import nmlib, STATUS, NotmuchError, Enum
 import logging
 import logging
+from datetime import date
 
 class Database(object):
 
 class Database(object):
-    """ Wrapper around a notmuch_database_t
+    """Represents a notmuch database (wraps notmuch_database_t)
 
 
-    Do note that as soon as we tear down this object, all derived queries,
-    threads, and messages will be freed as well.
+    .. note:: Do note that as soon as we tear down this object, all underlying 
+           derived objects such as queries, threads, messages, tags etc will 
+           be freed by the underlying library as well. Accessing these objects
+           will lead to segfaults and other unexpected behavior.
+
+           We implement reference counting, so that parent objects can be 
+           automatically freed when they are not needed anymore, for example::
+
+            db = Database('path',create=True)
+            msgs = Query(db,'from:myself').search_messages()
+
+           This returns a :class:`Messages` which internally contains
+           a reference to the parent :class:`Query` object. Otherwise
+           the Query() would be immediately freed, taking our *msgs*
+           down with it.
+
+           In this case, the above Query() object will be
+           automatically freed whenever we delete all derived objects,
+           ie in our case: `del (msgs)` would also delete the parent
+           Query (but not the parent Database() as that is still
+           referenced from the variable *db* in which it is stored.
+
+           Pretty much the same is valid for all other objects in the hierarchy,
+           such as :class:`Query`, :class:`Messages`, :class:`Message`,
+           and :class:`Tags`.
     """
     """
-    #constants
-    MODE_READ_ONLY = 0
-    MODE_READ_WRITE = 1
+    MODE = Enum(['READ_ONLY','READ_WRITE'])
+    """Constants: Mode in which to open the database"""
 
     _std_db_path = None
 
     _std_db_path = None
-    """Class attribute of users default database"""
+    """Class attribute to cache user's default database"""
 
     """notmuch_database_get_path (notmuch_database_t *database)"""
     _get_path = nmlib.notmuch_database_get_path
 
     """notmuch_database_get_path (notmuch_database_t *database)"""
     _get_path = nmlib.notmuch_database_get_path
@@ -36,12 +59,24 @@ class Database(object):
     _create = nmlib.notmuch_database_create
     _create.restype = c_void_p
 
     _create = nmlib.notmuch_database_create
     _create.restype = c_void_p
 
-    def __init__(self, path=None, create=False, status= MODE_READ_ONLY):
-        """ Open or create a notmuch database
-
-        If path is None, we will try to read a users notmuch configuration and
-        use his default database.
-        Throws a NotmuchError in case of failure.
+    def __init__(self, path=None, create=False, mode= MODE.READ_ONLY):
+        """If *path* is *None*, we will try to read a users notmuch
+        configuration and use his default database. If *create* is `True`,
+        the database will always be created in
+        :attr:`MODE.READ_WRITE` mode as creating an empty
+        database for reading only does not make a great deal of sense.
+
+        :param path:   Directory to open/create the database in (see
+                       above for behavior if `None`)
+        :type path:    `str` or `None`
+        :param create: False to open an existing, True to create a new
+                       database.  
+        :type create:  bool
+        :param mdoe:   Mode to open a database in. Always 
+                       :attr:`MODE`.READ_WRITE when creating a new one.
+        :type mode:    :attr:`MODE`
+        :returns:      Nothing
+        :exception:    :exc:`NotmuchError` in case of failure.
         """
         self._db = None
         if path is None:
         """
         self._db = None
         if path is None:
@@ -54,27 +89,40 @@ class Database(object):
         if create == False:
             self.open(path, status)
         else:
         if create == False:
             self.open(path, status)
         else:
-            self.create(path, status)
+            self.create(path)
 
 
-    def create(self, path, status=MODE_READ_ONLY):
-        """ notmuch_database_create(const char *path)
+    def create(self, path):
+        """Creates a new notmuch database
 
 
-        :returns: Raises :exc:`notmuch.NotmuchError` in case
-                  of any failure (after printing an error message on stderr).
+        This function wraps *notmuch_database_create(...)* and creates
+        a new notmuch database at *path*. It will always return a database in
+        :attr:`MODE`.READ_WRITE mode as creating an empty database 
+        for reading only does not make a great deal of sense.
+
+        :param path: A directory in which we should create the database.
+        :type path: str
+        :returns: Nothing
+        :exception: :exc:`NotmuchError` in case of any failure
+                    (after printing an error message on stderr).
         """
         """
-        res = Database._create(path, status)
+        if self._db is not None:
+            raise NotmuchError(
+            message="Cannot create db, this Database() already has an open one.")
+
+        res = Database._create(path, MODE.READ_WRITE)
 
         if res is None:
             raise NotmuchError(
                 message="Could not create the specified database")
         self._db = res
 
 
         if res is None:
             raise NotmuchError(
                 message="Could not create the specified database")
         self._db = res
 
-    def open(self, path, status= MODE_READ_ONLY): 
+    def open(self, path, status= MODE.READ_ONLY): 
         """calls notmuch_database_open
 
         :returns: Raises :exc:`notmuch.NotmuchError` in case
                   of any failure (after printing an error message on stderr).
         """
         """calls notmuch_database_open
 
         :returns: Raises :exc:`notmuch.NotmuchError` in case
                   of any failure (after printing an error message on stderr).
         """
+
         res = Database._open(path, status)
 
         if res is None:
         res = Database._open(path, status)
 
         if res is None:
@@ -88,11 +136,13 @@ class Database(object):
 
     def find_message(self, msgid):
         """notmuch_database_find_message
 
     def find_message(self, msgid):
         """notmuch_database_find_message
-        :param msgid: The message id
-        :ptype msgid: string
 
 
-        :returns: Message() or None if no message is found or if an
+        :param msgid: The message id
+        :type msgid: string
+        :returns: :class:`Message` or `None` if no message is found or if an
                   out-of-memory situation occurs.
                   out-of-memory situation occurs.
+        :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
+                  the database was not intitialized.
         """
         if self._db is None:
             raise NotmuchError(STATUS.NOT_INITIALIZED)
         """
         if self._db is None:
             raise NotmuchError(STATUS.NOT_INITIALIZED)
@@ -102,9 +152,9 @@ class Database(object):
         return Message(msg_p, self)
 
     def get_all_tags(self):
         return Message(msg_p, self)
 
     def get_all_tags(self):
-        """Return a Tags() object (list of all tags found in the database)
+        """Returns :class:`Tags` with a list of all tags found in the database
 
 
-        :returns: Tags() object or raises :exc:`NotmuchError` with 
+        :returns: :class:`Tags` object or raises :exc:`NotmuchError` with 
                   STATUS.NULL_POINTER on error
         """
         if self._db is None:
                   STATUS.NULL_POINTER on error
         """
         if self._db is None:
@@ -139,7 +189,9 @@ class Database(object):
 
     @property
     def db_p(self):
 
     @property
     def db_p(self):
-        """Returns a pointer to the current notmuch_database_t or None"""
+        """Property returning a pointer to the notmuch_database_t or `None`.
+
+        This should normally not be needed by a user."""
         return self._db
 
 #------------------------------------------------------------------------------
         return self._db
 
 #------------------------------------------------------------------------------
@@ -225,12 +277,12 @@ class Tags(object):
     _get = nmlib.notmuch_tags_get
     _get.restype = c_char_p
 
     _get = nmlib.notmuch_tags_get
     _get.restype = c_char_p
 
-    def __init__(self, tags_p, db=None):
+    def __init__(self, tags_p, parent=None):
         """
         msg_p is a pointer to an notmuch_message_t Structure. If it is None,
         we will raise an NotmuchError(STATUS.NULL_POINTER).
 
         """
         msg_p is a pointer to an notmuch_message_t Structure. If it is None,
         we will raise an NotmuchError(STATUS.NULL_POINTER).
 
-        Is passed the db these tags are derived from, and saves a
+        Is passed the parent these tags are derived from, and saves a
         reference to it, so we can automatically delete the db object
         once all derived objects are dead.
 
         reference to it, so we can automatically delete the db object
         once all derived objects are dead.
 
@@ -244,8 +296,9 @@ class Tags(object):
             NotmuchError(STATUS.NULL_POINTER)
 
         self._tags = tags_p
             NotmuchError(STATUS.NULL_POINTER)
 
         self._tags = tags_p
-        self._db = db
-        logging.debug("Inited Tags derived from %s" %(str(db)))
+        #save reference to parent object so we keep it alive
+        self._parent = parent
+        logging.debug("Inited Tags derived from %s" %(repr(parent)))
     
     def __iter__(self):
         """ Make Tags an iterator """
     
     def __iter__(self):
         """ Make Tags an iterator """
@@ -386,6 +439,12 @@ class Message(object):
     _get_tags = nmlib.notmuch_message_get_tags
     _get_tags.restype = c_void_p
 
     _get_tags = nmlib.notmuch_message_get_tags
     _get_tags.restype = c_void_p
 
+    _get_date = nmlib.notmuch_message_get_date
+    _get_date.restype = c_uint64
+
+    _get_header = nmlib.notmuch_message_get_header
+    _get_header.restype = c_char_p
+
     def __init__(self, msg_p, parent=None):
         """
         msg_p is a pointer to an notmuch_message_t Structure. If it is None,
     def __init__(self, msg_p, parent=None):
         """
         msg_p is a pointer to an notmuch_message_t Structure. If it is None,
@@ -412,6 +471,28 @@ class Message(object):
             raise NotmuchError(STATUS.NOT_INITIALIZED)
         return Message._get_message_id(self._msg)
 
             raise NotmuchError(STATUS.NOT_INITIALIZED)
         return Message._get_message_id(self._msg)
 
+    def get_date(self):
+        """returns time_t of the message date
+
+        For the original textual representation of the Date header from the
+        message call notmuch_message_get_header() with a header value of
+        "date".
+        Raises NotmuchError(STATUS.NOT_INITIALIZED) if not inited
+        """
+        if self._msg is None:
+            raise NotmuchError(STATUS.NOT_INITIALIZED)
+        return Message._get_date(self._msg)
+
+    def get_header(self, header):
+        """ TODO document me"""
+        if self._msg is None:
+            raise NotmuchError(STATUS.NOT_INITIALIZED)
+
+        #Returns NULL if any error occurs.
+        header = Message._get_header (self._msg, header)
+        if header == None:
+            raise NotmuchError(STATUS.NULL_POINTER)
+        return header
 
     def get_filename(self):
         """ return the msg filename
 
     def get_filename(self):
         """ return the msg filename
@@ -436,6 +517,18 @@ class Message(object):
             raise NotmuchError(STATUS.NULL_POINTER)
         return Tags(tags_p, self)
 
             raise NotmuchError(STATUS.NULL_POINTER)
         return Tags(tags_p, self)
 
+    def __str__(self):
+        """A message() is represented by a 1-line summary"""
+        msg = {}
+        msg['from'] = self.get_header('from')
+        msg['tags'] = str(self.get_tags())
+        msg['date'] = date.fromtimestamp(self.get_date())
+        return "%(from)s (%(date)s) (%(tags)s)" % (msg)
+
+    def format_as_text(self):
+        """ Output like notmuch show """
+        return str(self)
+
     def __del__(self):
         """Close and free the notmuch Message"""
         if self._msg is not None:
     def __del__(self):
         """Close and free the notmuch Message"""
         if self._msg is not None: