]> git.notmuchmail.org Git - notmuch/blobdiff - bindings/python/notmuch/database.py
python: work around libnotmuch calling exit(3) in Database.find_message_by_filename
[notmuch] / bindings / python / notmuch / database.py
index 6edb18b69d845514dbbe5ad9e7a960e96ad4b184..a054be76e1ba4a72ee8d678b3950fa659508cb01 100644 (file)
@@ -23,8 +23,11 @@ from ctypes import c_char_p, c_void_p, c_uint, c_long, byref, POINTER
 from notmuch.globals import (
     nmlib,
     STATUS,
 from notmuch.globals import (
     nmlib,
     STATUS,
+    FileError,
     NotmuchError,
     NotmuchError,
+    NullPointerError,
     NotInitializedError,
     NotInitializedError,
+    ReadOnlyDatabaseError,
     Enum,
     _str,
     NotmuchDatabaseP,
     Enum,
     _str,
     NotmuchDatabaseP,
@@ -120,7 +123,8 @@ class Database(object):
     _create.argtypes = [c_char_p]
     _create.restype = NotmuchDatabaseP
 
     _create.argtypes = [c_char_p]
     _create.restype = NotmuchDatabaseP
 
-    def __init__(self, path=None, create=False, mode=0):
+    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 configured database. The location of the
         configuration file can be specified through the environment variable
         """If *path* is `None`, we will try to read a users notmuch
         configuration and use his configured database. The location of the
         configuration file can be specified through the environment variable
@@ -138,10 +142,11 @@ 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`
         :param mode:   Mode to open a database in. Is always
                        :attr:`MODE`.READ_WRITE when creating a new one.
         :type mode:    :attr:`MODE`
-        :exception: :exc:`NotmuchError` or derived exception in case of
+        :raises: :exc:`NotmuchError` or derived exception in case of
             failure.
         """
         self._db = None
             failure.
         """
         self._db = None
+        self.mode = mode
         if path is None:
             # no path specified. use a user's default database
             if Database._std_db_path is None:
         if path is None:
             # no path specified. use a user's default database
             if Database._std_db_path is None:
@@ -174,8 +179,7 @@ class Database(object):
 
         :param path: A directory in which we should create the database.
         :type path: str
 
         :param path: A directory in which we should create the database.
         :type path: str
-        :returns: Nothing
-        :exception: :exc:`NotmuchError` in case of any failure
+        :raises: :exc:`NotmuchError` in case of any failure
                     (possibly after printing an error message on stderr).
         """
         if self._db is not None:
                     (possibly after printing an error message on stderr).
         """
         if self._db is not None:
@@ -198,8 +202,7 @@ class Database(object):
 
         :param status: Open the database in read-only or read-write mode
         :type status:  :attr:`MODE`
 
         :param status: Open the database in read-only or read-write mode
         :type status:  :attr:`MODE`
-        :returns: Nothing
-        :exception: Raises :exc:`NotmuchError` in case of any failure
+        :raises: Raises :exc:`NotmuchError` in case of any failure
                     (possibly after printing an error message on stderr).
         """
         res = Database._open(_str(path), mode)
                     (possibly after printing an error message on stderr).
         """
         res = Database._open(_str(path), mode)
@@ -294,7 +297,7 @@ class Database(object):
         neither begin nor end necessarily flush modifications to disk.
 
         :returns: :attr:`STATUS`.SUCCESS or raises
         neither begin nor end necessarily flush modifications to disk.
 
         :returns: :attr:`STATUS`.SUCCESS or raises
-        :exception: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION
+        :raises: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION
                     Xapian exception occurred; atomic section not entered.
 
         *Added in notmuch 0.9*"""
                     Xapian exception occurred; atomic section not entered.
 
         *Added in notmuch 0.9*"""
@@ -315,7 +318,7 @@ class Database(object):
 
         :returns: :attr:`STATUS`.SUCCESS or raises
 
 
         :returns: :attr:`STATUS`.SUCCESS or raises
 
-        :exception:
+        :raises:
             :exc:`NotmuchError`:
                 :attr:`STATUS`.XAPIAN_EXCEPTION
                     A Xapian exception occurred; atomic section not
             :exc:`NotmuchError`:
                 :attr:`STATUS`.XAPIAN_EXCEPTION
                     A Xapian exception occurred; atomic section not
@@ -334,30 +337,31 @@ class Database(object):
         """Returns a :class:`Directory` of path,
         (creating it if it does not exist(?))
 
         """Returns a :class:`Directory` of path,
         (creating it if it does not exist(?))
 
-        .. warning::
-
-            This call needs a writeable database in
-            :attr:`Database.MODE`.READ_WRITE mode. The underlying library will
-            exit the program if this method is used on a read-only database!
-
         :param path: An unicode string containing the path relative to the path
               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.
         :param path: An unicode string containing the path relative to the path
               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` with :attr:`STATUS`.FILE_ERROR
-                    If path is not relative database or absolute with initial
-                    components same as database.
+        :raises: :exc:`FileError` if path is not relative database or absolute
+                 with initial components same as database.
+        :raises: :exc:`ReadOnlyDatabaseError` if the database has not been
+                 opened in read-write mode
         """
         self._assert_db_is_initialized()
         """
         self._assert_db_is_initialized()
+
+        # work around libnotmuch calling exit(3), see
+        # id:20120221002921.8534.57091@thinkbox.jade-hamburg.de
+        # TODO: remove once this issue is resolved
+        if self.mode != Database.MODE.READ_WRITE:
+            raise ReadOnlyDatabaseError('The database has to be opened in '
+                                        'read-write mode for get_directory')
+
         # sanity checking if path is valid, and make path absolute
         # sanity checking if path is valid, and make path absolute
-        if path[0] == os.sep:
+        if path and path[0] == os.sep:
             # we got an absolute path
             if not path.startswith(self.get_path()):
                 # but its initial components are not equal to the db path
             # 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(STATUS.FILE_ERROR,
-                                   message="Database().get_directory() called "
-                                           "with a wrong absolute path.")
+                raise FileError('Database().get_directory() called '
+                                'with a wrong absolute path')
             abs_dirpath = path
         else:
             #we got a relative path, make it absolute
             abs_dirpath = path
         else:
             #we got a relative path, make it absolute
@@ -366,7 +370,7 @@ class Database(object):
         dir_p = Database._get_directory(self._db, _str(path))
 
         # return the Directory, init it with the absolute path
         dir_p = Database._get_directory(self._db, _str(path))
 
         # return the Directory, init it with the absolute path
-        return Directory(_str(abs_dirpath), dir_p, self)
+        return Directory(abs_dirpath, dir_p, self)
 
     _add_message = nmlib.notmuch_database_add_message
     _add_message.argtypes = [NotmuchDatabaseP, c_char_p,
 
     _add_message = nmlib.notmuch_database_add_message
     _add_message.argtypes = [NotmuchDatabaseP, c_char_p,
@@ -409,7 +413,7 @@ class Database(object):
 
         :rtype:   2-tuple(:class:`Message`, :attr:`STATUS`)
 
 
         :rtype:   2-tuple(:class:`Message`, :attr:`STATUS`)
 
-        :exception: Raises a :exc:`NotmuchError` with the following meaning.
+        :raises: Raises a :exc:`NotmuchError` with the following meaning.
               If such an exception occurs, nothing was added to the database.
 
               :attr:`STATUS`.FILE_ERROR
               If such an exception occurs, nothing was added to the database.
 
               :attr:`STATUS`.FILE_ERROR
@@ -459,7 +463,7 @@ class Database(object):
                This filename was removed but the message persists in the
                database with at least one other filename.
 
                This filename was removed but the message persists in the
                database with at least one other filename.
 
-        :exception: Raises a :exc:`NotmuchError` with the following meaning.
+        :raises: Raises a :exc:`NotmuchError` with the following meaning.
              If such an exception occurs, nothing was removed from the
              database.
 
              If such an exception occurs, nothing was removed from the
              database.
 
@@ -478,7 +482,7 @@ class Database(object):
         :param msgid: The message ID
         :type msgid: unicode or str
         :returns: :class:`Message` or `None` if no message is found.
         :param msgid: The message ID
         :type msgid: unicode or str
         :returns: :class:`Message` or `None` if no message is found.
-        :exception:
+        :raises:
             :exc:`OutOfMemoryError`
                   If an Out-of-memory occured while constructing the message.
             :exc:`XapianError`
             :exc:`OutOfMemoryError`
                   If an Out-of-memory occured while constructing the message.
             :exc:`XapianError`
@@ -500,31 +504,34 @@ class Database(object):
     def find_message_by_filename(self, filename):
         """Find a message with the given filename
 
     def find_message_by_filename(self, filename):
         """Find a message with the given filename
 
-        .. warning::
-
-            This call needs a writeable database in
-            :attr:`Database.MODE`.READ_WRITE mode. The underlying library will
-            exit the program if this method is used on a read-only database!
-
         :returns: If the database contains a message with the given
             filename, then a class:`Message:` is returned.  This
             function returns None if no message is found with the given
             filename.
 
         :returns: If the database contains a message with the given
             filename, then a class:`Message:` is returned.  This
             function returns None if no message is found with the given
             filename.
 
-        :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.
+        :raises: :exc:`OutOfMemoryError` if an Out-of-memory occured while
+                 constructing the message.
+        :raises: :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.
+        :raises: :exc:`NotInitializedError` if the database was not
+                 intitialized.
+        :raises: :exc:`ReadOnlyDatabaseError` if the database has not been
+                 opened in read-write mode
 
         *Added in notmuch 0.9*"""
         self._assert_db_is_initialized()
 
         *Added in notmuch 0.9*"""
         self._assert_db_is_initialized()
+
+        # work around libnotmuch calling exit(3), see
+        # id:20120221002921.8534.57091@thinkbox.jade-hamburg.de
+        # TODO: remove once this issue is resolved
+        if self.mode != Database.MODE.READ_WRITE:
+            raise ReadOnlyDatabaseError('The database has to be opened in '
+                                        'read-write mode for get_directory')
+
         msg_p = NotmuchMessageP()
         status = Database._find_message_by_filename(self._db, _str(filename),
                                                     byref(msg_p))
         msg_p = NotmuchMessageP()
         status = Database._find_message_by_filename(self._db, _str(filename),
                                                     byref(msg_p))
@@ -542,7 +549,7 @@ class Database(object):
         self._assert_db_is_initialized()
         tags_p = Database._get_all_tags(self._db)
         if tags_p == None:
         self._assert_db_is_initialized()
         tags_p = Database._get_all_tags(self._db)
         if tags_p == None:
-            raise NotmuchError(STATUS.NULL_POINTER)
+            raise NullPointerError()
         return Tags(tags_p, self)
 
     def create_query(self, querystring):
         return Tags(tags_p, self)
 
     def create_query(self, querystring):
@@ -636,11 +643,11 @@ class Directory(object):
         """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED)
         if dir_p is None"""
         if not self._dir_p:
         """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED)
         if dir_p is None"""
         if not self._dir_p:
-            raise NotmuchError(STATUS.NOT_INITIALIZED)
+            raise NotInitializedError()
 
     def __init__(self, path, dir_p, parent):
         """
 
     def __init__(self, path, dir_p, parent):
         """
-        :param path:   The absolute path of the directory object as unicode.
+        :param path:   The absolute path of the directory object.
         :param dir_p:  The pointer to an internal notmuch_directory_t object.
         :param parent: The object this Directory is derived from
                        (usually a :class:`Database`). We do not directly use
         :param dir_p:  The pointer to an internal notmuch_directory_t object.
         :param parent: The object this Directory is derived from
                        (usually a :class:`Database`). We do not directly use
@@ -648,7 +655,6 @@ class Directory(object):
                        this Directory object lives. This keeps the
                        parent object alive.
         """
                        this Directory object lives. This keeps the
                        parent object alive.
         """
-        assert isinstance(path, unicode), "Path needs to be an UNICODE object"
         self._path = path
         self._dir_p = dir_p
         self._parent = parent
         self._path = path
         self._dir_p = dir_p
         self._parent = parent
@@ -678,27 +684,19 @@ class Directory(object):
                 don't store a timestamp of 0 unless you are comfortable with
                 that.
 
                 don't store a timestamp of 0 unless you are comfortable with
                 that.
 
-          :param mtime: A (time_t) timestamp
-          :returns: Nothing on success, raising an exception on failure.
-          :exception: :exc:`NotmuchError`:
-
-                        :attr:`STATUS`.XAPIAN_EXCEPTION
-                          A Xapian exception occurred, mtime not stored.
-                        :attr:`STATUS`.READ_ONLY_DATABASE
-                          Database was opened in read-only mode so directory
-                          mtime cannot be modified.
-                        :attr:`STATUS`.NOT_INITIALIZED
-                          The directory has not been initialized
+        :param mtime: A (time_t) timestamp
+        :raises: :exc:`XapianError` a Xapian exception occurred, mtime
+                 not stored
+        :raises: :exc:`ReadOnlyDatabaseError` the database was opened
+                 in read-only mode so directory mtime cannot be modified
+        :raises: :exc:`NotInitializedError` the directory object has not
+                 been initialized
         """
         self._assert_dir_is_initialized()
         """
         self._assert_dir_is_initialized()
-        #TODO: make sure, we convert the mtime parameter to a 'c_long'
         status = Directory._set_mtime(self._dir_p, mtime)
 
         status = Directory._set_mtime(self._dir_p, mtime)
 
-        #return on success
-        if status == STATUS.SUCCESS:
-            return
-        #fail with Exception otherwise
-        raise NotmuchError(status)
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
 
     def get_mtime(self):
         """Gets the mtime value of this directory in the database
 
     def get_mtime(self):
         """Gets the mtime value of this directory in the database
@@ -706,8 +704,7 @@ class Directory(object):
         Retrieves a previously stored mtime for this directory.
 
         :param mtime: A (time_t) timestamp
         Retrieves a previously stored mtime for this directory.
 
         :param mtime: A (time_t) timestamp
-        :returns: Nothing on success, raising an exception on failure.
-        :exception: :exc:`NotmuchError`:
+        :raises: :exc:`NotmuchError`:
 
                         :attr:`STATUS`.NOT_INITIALIZED
                           The directory has not been initialized
 
                         :attr:`STATUS`.NOT_INITIALIZED
                           The directory has not been initialized
@@ -797,7 +794,7 @@ class Filenames(object):
 
     def __next__(self):
         if not self._files_p:
 
     def __next__(self):
         if not self._files_p:
-            raise NotmuchError(STATUS.NOT_INITIALIZED)
+            raise NotInitializedError()
 
         if not self._valid(self._files_p):
             self._files_p = None
 
         if not self._valid(self._files_p):
             self._files_p = None
@@ -824,7 +821,7 @@ class Filenames(object):
                      for file in files: print file
         """
         if not self._files_p:
                      for file in files: print file
         """
         if not self._files_p:
-            raise NotmuchError(STATUS.NOT_INITIALIZED)
+            raise NotInitializedError()
 
         i = 0
         while self._valid(self._files_p):
 
         i = 0
         while self._valid(self._files_p):