]> git.notmuchmail.org Git - notmuch/blobdiff - bindings/python/notmuch/database.py
python: help function Query._assert_query_is_initialized
[notmuch] / bindings / python / notmuch / database.py
index 644e2e5fa68a2615f0084158fcb52de68683adc6..f4bc53e02c6d17574595e25972979af99ad6aa9d 100644 (file)
@@ -19,19 +19,34 @@ Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
 
 import os
 from ctypes import c_int, c_char_p, c_void_p, c_uint, c_long, byref
-from notmuch.globals import nmlib, STATUS, NotmuchError, Enum, _str
+from notmuch.globals import (nmlib, STATUS, NotmuchError, NotInitializedError,
+     NullPointerError, OutOfMemoryError, XapianError, Enum, _str)
 from notmuch.thread import Threads
 from notmuch.message import Messages, Message
 from notmuch.tag import Tags
 
 class Database(object):
-    """Represents a notmuch database (wraps notmuch_database_t)
-
-    .. note:: Do remember 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. See above for more details.
+    """The :class:`Database` is the highest-level object that notmuch
+    provides. It references a notmuch database, and can be opened in
+    read-only or read-write mode. A :class:`Query` can be derived from
+    or be applied to a specific database to find messages. Also adding
+    and removing messages to the database happens via this
+    object. Modifications to the database are not atmic by default (see
+    :meth:`begin_atomic`) and once a database has been modified, all
+    other database objects pointing to the same data-base will throw an
+    :exc:`XapianError` as the underlying database has been
+    modified. Close and reopen the database to continue working with it.
+
+    .. note:: Any function in this class can and will throw an
+           :exc:`NotInitializedError` if the database was not
+           intitialized properly.
+
+    .. note:: Do remember that as soon as we tear down (e.g. via `del
+           db`) 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. See above for
+           more details.
     """
     _std_db_path = None
     """Class attribute to cache user's default database"""
@@ -61,11 +76,9 @@ class Database(object):
 
     """ notmuch_database_find_message"""
     _find_message = nmlib.notmuch_database_find_message
-    _find_message.restype = c_void_p
 
     """notmuch_database_find_message_by_filename"""
     _find_message_by_filename = nmlib.notmuch_database_find_message_by_filename
-    _find_message_by_filename.restype = c_void_p
 
     """notmuch_database_get_all_tags"""
     _get_all_tags = nmlib.notmuch_database_get_all_tags
@@ -93,8 +106,8 @@ class Database(object):
         :param mode:   Mode to open a database in. Is always
                        :attr:`MODE`.READ_WRITE when creating a new one.
         :type mode:    :attr:`MODE`
-        :returns:      Nothing
-        :exception:    :exc:`NotmuchError` in case of failure.
+        :exception: :exc:`NotmuchError` or derived exception in case of
+            failure.
         """
         self._db = None
         if path is None:
@@ -110,9 +123,9 @@ class Database(object):
             self.create(path)
 
     def _assert_db_is_initialized(self):
-        """Raises a NotmuchError in case self._db is still None"""
+        """Raises :exc:`NotInitializedError` if self._db is `None`"""
         if self._db is None:
-            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
+            raise NotInitializedError()
 
     def create(self, path):
         """Creates a new notmuch database
@@ -128,7 +141,7 @@ class Database(object):
         :type path: str
         :returns: Nothing
         :exception: :exc:`NotmuchError` in case of any failure
-                    (after printing an error message on stderr).
+                    (possibly after printing an error message on stderr).
         """
         if self._db is not None:
             raise NotmuchError(message="Cannot create db, this Database() "
@@ -152,7 +165,7 @@ class Database(object):
         :type status:  :attr:`MODE`
         :returns: Nothing
         :exception: Raises :exc:`NotmuchError` in case
-                    of any failure (after printing an error message on stderr).
+            of any failure (possibly after printing an error message on stderr).
         """
         res = Database._open(_str(path), mode)
 
@@ -161,9 +174,7 @@ class Database(object):
         self._db = res
 
     def get_path(self):
-        """Returns the file path of an open database
-
-        .. ..:: Wraps underlying `notmuch_database_get_path`"""
+        """Returns the file path of an open database"""
         self._assert_db_is_initialized()
         return Database._get_path(self._db).decode('utf-8')
 
@@ -171,8 +182,6 @@ class Database(object):
         """Returns the database format version
 
         :returns: The database version as positive integer
-        :exception: :exc:`NotmuchError` with :attr:`STATUS`.NOT_INITIALIZED if
-                    the database was not intitialized.
         """
         self._assert_db_is_initialized()
         return Database._get_version(self._db)
@@ -186,8 +195,6 @@ class Database(object):
         etc.) will work unless :meth:`upgrade` is called successfully first.
 
         :returns: `True` or `False`
-        :exception: :exc:`NotmuchError` with :attr:`STATUS`.NOT_INITIALIZED if
-                    the database was not intitialized.
         """
         self._assert_db_is_initialized()
         return nmlib.notmuch_database_needs_upgrade(self._db)
@@ -231,7 +238,7 @@ class Database(object):
         self._assert_db_is_initialized()
         status = nmlib.notmuch_database_begin_atomic(self._db)
         if status != STATUS.SUCCESS:
-            raise NotmuchError.get_subclass_exc(status)
+            raise NotmuchError(status)
         return status
 
     def end_atomic(self):
@@ -253,7 +260,7 @@ class Database(object):
         self._assert_db_is_initialized()
         status = nmlib.notmuch_database_end_atomic(self._db)
         if status != STATUS.SUCCESS:
-            raise NotmuchError.get_subclass_exc(status)
+            raise NotmuchError(status)
         return status
 
     def get_directory(self, path):
@@ -268,15 +275,10 @@ class Database(object):
               of database (see :meth:`get_path`), or else should be an absolute path
               with initial components that match the path of 'database'.
         :returns: :class:`Directory` or raises an exception.
-        :exception: :exc:`NotmuchError`
-
-                  :attr:`STATUS`.NOT_INITIALIZED
-                    If the database was not intitialized.
-
-                  :attr:`STATUS`.FILE_ERROR
+        :exception:
+            :exc:`NotmuchError` with :attr:`STATUS`.FILE_ERROR
                     If path is not relative database or absolute with initial
                     components same as database.
-
         """
         self._assert_db_is_initialized()
         # sanity checking if path is valid, and make path absolute
@@ -284,7 +286,7 @@ class Database(object):
             # we got an absolute path
             if not path.startswith(self.get_path()):
                 # but its initial components are not equal to the db path
-                raise NotmuchError.get_subclass_exc(STATUS.FILE_ERROR,
+                raise NotmuchError(STATUS.FILE_ERROR,
                                    message="Database().get_directory() called "
                                            "with a wrong absolute path.")
             abs_dirpath = path
@@ -345,8 +347,6 @@ class Database(object):
               :attr:`STATUS`.READ_ONLY_DATABASE
                       Database was opened in read-only mode so no message can
                       be added.
-              :attr:`STATUS`.NOT_INITIALIZED
-                      The database has not been initialized.
         """
         self._assert_db_is_initialized()
         msg_p = c_void_p()
@@ -355,7 +355,7 @@ class Database(object):
                                                   byref(msg_p))
 
         if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
-            raise NotmuchError.get_subclass_exc(status)
+            raise NotmuchError(status)
 
         #construct Message() and return
         msg = Message(msg_p, self)
@@ -390,8 +390,6 @@ class Database(object):
              :attr:`STATUS`.READ_ONLY_DATABASE
                Database was opened in read-only mode so no message can be
                removed.
-             :attr:`STATUS`.NOT_INITIALIZED
-               The database has not been initialized.
         """
         self._assert_db_is_initialized()
         return nmlib.notmuch_database_remove_message(self._db,
@@ -403,20 +401,25 @@ class Database(object):
         Wraps the underlying *notmuch_database_find_message* function.
 
         :param msgid: The message ID
-        :type msgid: string
-        :returns: :class:`Message` or `None` if no message is found or
-                  if any xapian exception or out-of-memory situation
-                  occurs. Do note that Xapian Exceptions include
-                  "Database modified" situations, e.g. when the
-                  notmuch database has been modified by
-                  another program in the meantime. A return value of
-                  `None` is therefore no guarantee that the message
-                  does not exist.
-        :exception: :exc:`NotmuchError` with :attr:`STATUS`.NOT_INITIALIZED if
-                  the database was not intitialized.
+        :type msgid: unicode or str
+        :returns: :class:`Message` or `None` if no message is found.
+        :exception:
+            :exc:`OutOfMemoryError`
+                  If an Out-of-memory occured while constructing the message.
+            :exc:`XapianError`
+                  In case of a Xapian Exception. These exceptions
+                  include "Database modified" situations, e.g. when the
+                  notmuch database has been modified by another program
+                  in the meantime. In this case, you should close and
+                  reopen the database and retry.
+            :exc:`NotInitializedError` if
+                    the database was not intitialized.
         """
         self._assert_db_is_initialized()
-        msg_p = Database._find_message(self._db, _str(msgid))
+        msg_p = c_void_p()
+        status = Database._find_message(self._db, _str(msgid), byref(msg_p))
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
         return msg_p and Message(msg_p, self) or None
 
     def find_message_by_filename(self, filename):
@@ -429,15 +432,28 @@ class Database(object):
 
         :returns: If the database contains a message with the given
             filename, then a class:`Message:` is returned.  This
-            function returns None in the following situations:
+            function returns None if no message is found with the given
+            filename.
 
-                * No message is found with the given filename
-                * An out-of-memory situation occurs
-                * A Xapian exception occurs
+        :exception:
+            :exc:`OutOfMemoryError`
+                  If an Out-of-memory occured while constructing the message.
+            :exc:`XapianError`
+                  In case of a Xapian Exception. These exceptions
+                  include "Database modified" situations, e.g. when the
+                  notmuch database has been modified by another program
+                  in the meantime. In this case, you should close and
+                  reopen the database and retry.
+            :exc:`NotInitializedError` if
+                    the database was not intitialized.
 
         *Added in notmuch 0.9*"""
         self._assert_db_is_initialized()
-        msg_p = Database._find_message_by_filename(self._db, _str(filename))
+        msg_p = c_void_p()
+        status = Database._find_message_by_filename(self._db, _str(filename),
+                                                    byref(msg_p))
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
         return msg_p and Message(msg_p, self) or None
 
     def get_all_tags(self):
@@ -449,7 +465,7 @@ class Database(object):
         self._assert_db_is_initialized()
         tags_p = Database._get_all_tags(self._db)
         if tags_p == None:
-            raise NotmuchError.get_subclass_exc(STATUS.NULL_POINTER)
+            raise NotmuchError(STATUS.NULL_POINTER)
         return Tags(tags_p, self)
 
     def create_query(self, querystring):
@@ -470,7 +486,6 @@ class Database(object):
 
         This function is a python extension and not in the underlying C API.
         """
-        self._assert_db_is_initialized()
         return Query(self, querystring)
 
     def __repr__(self):
@@ -511,11 +526,12 @@ class Query(object):
     A query selects and filters a subset of messages from the notmuch
     database we derive from.
 
-    Query() provides an instance attribute :attr:`sort`, which
+    :class:`Query` provides an instance attribute :attr:`sort`, which
     contains the sort order (if specified via :meth:`set_sort`) or
     `None`.
 
-    Technically, it wraps the underlying *notmuch_query_t* struct.
+    Any function in this class may throw an :exc:`NotInitializedError`
+    in case the underlying query object was not set up correctly.
 
     .. note:: Do remember that as soon as we tear down this object,
            all underlying derived objects such as threads,
@@ -555,6 +571,11 @@ class Query(object):
         self.sort = None
         self.create(db, querystr)
 
+    def _assert_query_is_initialized(self):
+        """Raises :exc:`NotInitializedError` if self._query is `None`"""
+        if self._query is None:
+            raise NotInitializedError()
+
     def create(self, db, querystr):
         """Creates a new query derived from a Database
 
@@ -566,35 +587,27 @@ class Query(object):
         :param querystr: The query string
         :type querystr: utf-8 encoded str or unicode
         :returns: Nothing
-        :exception: :exc:`NotmuchError`
-
-                      * :attr:`STATUS`.NOT_INITIALIZED if db is not inited
-                      * :attr:`STATUS`.NULL_POINTER if the query creation failed
-                        (too little memory)
+        :exception:
+            :exc:`NullPointerError` if the query creation failed
+                (e.g. too little memory).
+            :exc:`NotInitializedError` if the underlying db was not
+                intitialized.
         """
-        if db.db_p is None:
-            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
+        db._assert_db_is_initialized()
         # create reference to parent db to keep it alive
         self._db = db
         # create query, return None if too little mem available
         query_p = Query._create(db.db_p, _str(querystr))
         if query_p is None:
-            raise NotmuchError.get_subclass_exc(STATUS.NULL_POINTER)
+            raise NullPointerError
         self._query = query_p
 
     def set_sort(self, sort):
         """Set the sort order future results will be delivered in
 
-        Wraps the underlying *notmuch_query_set_sort* function.
-
         :param sort: Sort order (see :attr:`Query.SORT`)
-        :returns: Nothing
-        :exception: :exc:`NotmuchError` :attr:`STATUS`.NOT_INITIALIZED if query has not
-                    been initialized.
         """
-        if self._query is None:
-            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
-
+        self._assert_query_is_initialized()
         self.sort = sort
         nmlib.notmuch_query_set_sort(self._query, sort)
 
@@ -609,46 +622,28 @@ class Query(object):
         match the query. The method :meth:`Message.get_flag` allows us
         to get the value of this flag.
 
-        Technically, it wraps the underlying
-        *notmuch_query_search_threads* function.
-
         :returns: :class:`Threads`
-        :exception: :exc:`NotmuchError`
-
-                      * :attr:`STATUS`.NOT_INITIALIZED if query is not inited
-                      * :attr:`STATUS`.NULL_POINTER if search_threads failed
+        :exception: :exc:`NullPointerError` if search_threads failed
         """
-        if self._query is None:
-            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
-
+        self._assert_query_is_initialized()
         threads_p = Query._search_threads(self._query)
 
         if threads_p is None:
-            raise NotmuchError(STATUS.NULL_POINTER)
-
+            raise NullPointerError
         return Threads(threads_p, self)
 
     def search_messages(self):
         """Filter messages according to the query and return
         :class:`Messages` in the defined sort order
 
-        Technically, it wraps the underlying
-        *notmuch_query_search_messages* function.
-
         :returns: :class:`Messages`
-        :exception: :exc:`NotmuchError`
-
-                      * :attr:`STATUS`.NOT_INITIALIZED if query is not inited
-                      * :attr:`STATUS`.NULL_POINTER if search_messages failed
+        :exception: :exc:`NullPointerError` if search_messages failed
         """
-        if self._query is None:
-            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
-
+        self._assert_query_is_initialized()
         msgs_p = Query._search_messages(self._query)
 
         if msgs_p is None:
-            raise NotmuchError.get_subclass_exc(STATUS.NULL_POINTER)
-
+            raise NullPointerError
         return Messages(msgs_p, self)
 
     def count_messages(self):
@@ -662,13 +657,8 @@ class Query(object):
         *notmuch_query_count_messages* function.
 
         :returns: :class:`Messages`
-        :exception: :exc:`NotmuchError`
-
-                      * :attr:`STATUS`.NOT_INITIALIZED if query is not inited
         """
-        if self._query is None:
-            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
-
+        self._assert_query_is_initialized()
         return Query._count_messages(self._query)
 
     def __del__(self):
@@ -710,7 +700,7 @@ class Directory(object):
     def _assert_dir_is_initialized(self):
         """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) if dir_p is None"""
         if self._dir_p is None:
-            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
+            raise NotmuchError(STATUS.NOT_INITIALIZED)
 
     def __init__(self, path, dir_p, parent):
         """
@@ -770,7 +760,7 @@ class Directory(object):
         if status == STATUS.SUCCESS:
             return
         #fail with Exception otherwise
-        raise NotmuchError.get_subclass_exc(status)
+        raise NotmuchError(status)
 
     def get_mtime(self):
         """Gets the mtime value of this directory in the database
@@ -856,7 +846,7 @@ class Filenames(object):
 
     def next(self):
         if self._files_p is None:
-            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
+            raise NotmuchError(STATUS.NOT_INITIALIZED)
 
         if not nmlib.notmuch_filenames_valid(self._files_p):
             self._files_p = None
@@ -879,7 +869,7 @@ class Filenames(object):
                      for file in files: print file
         """
         if self._files_p is None:
-            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
+            raise NotmuchError(STATUS.NOT_INITIALIZED)
 
         i = 0
         while nmlib.notmuch_filenames_valid(self._files_p):