]> git.notmuchmail.org Git - notmuch/blobdiff - bindings/python/notmuch/database.py
python: harmonize the sphinx keyword for exceptions
[notmuch] / bindings / python / notmuch / database.py
index 36b65ecb7ba767af2d079ac39a7a1b91a60a2192..42a4442fa2f0a3d041041eb362e6164c6e358f8a 100644 (file)
@@ -20,14 +20,24 @@ Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
 import os
 import codecs
 from ctypes import c_char_p, c_void_p, c_uint, c_long, byref, POINTER
 import os
 import codecs
 from ctypes import c_char_p, c_void_p, c_uint, c_long, byref, POINTER
-from notmuch.globals import (nmlib, STATUS, NotmuchError, NotInitializedError,
-     NullPointerError, Enum, _str,
-     NotmuchDatabaseP, NotmuchDirectoryP, NotmuchMessageP, NotmuchTagsP,
-     NotmuchQueryP, NotmuchMessagesP, NotmuchThreadsP, NotmuchFilenamesP)
-from notmuch.thread import Threads
-from notmuch.message import Messages, Message
+from notmuch.globals import (
+    nmlib,
+    STATUS,
+    FileError,
+    NotmuchError,
+    NullPointerError,
+    NotInitializedError,
+    Enum,
+    _str,
+    NotmuchDatabaseP,
+    NotmuchDirectoryP,
+    NotmuchMessageP,
+    NotmuchTagsP,
+    NotmuchFilenamesP
+)
+from notmuch.message import Message
 from notmuch.tag import Tags
 from notmuch.tag import Tags
-
+from .query import Query
 
 class Database(object):
     """The :class:`Database` is the highest-level object that notmuch
 
 class Database(object):
     """The :class:`Database` is the highest-level object that notmuch
@@ -41,6 +51,10 @@ class Database(object):
     :exc:`XapianError` as the underlying database has been
     modified. Close and reopen the database to continue working with it.
 
     :exc:`XapianError` as the underlying database has been
     modified. Close and reopen the database to continue working with it.
 
+    :class:`Database` objects implement the context manager protocol
+    so you can use the :keyword:`with` statement to ensure that the
+    database is properly closed.
+
     .. note::
 
         Any function in this class can and will throw an
     .. note::
 
         Any function in this class can and will throw an
@@ -126,7 +140,7 @@ 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
@@ -142,9 +156,12 @@ class Database(object):
         else:
             self.create(path)
 
         else:
             self.create(path)
 
+    def __del__(self):
+        self.close()
+
     def _assert_db_is_initialized(self):
         """Raises :exc:`NotInitializedError` if self._db is `None`"""
     def _assert_db_is_initialized(self):
         """Raises :exc:`NotInitializedError` if self._db is `None`"""
-        if self._db is None:
+        if not self._db:
             raise NotInitializedError()
 
     def create(self, path):
             raise NotInitializedError()
 
     def create(self, path):
@@ -160,7 +177,7 @@ class Database(object):
         :param path: A directory in which we should create the database.
         :type path: str
         :returns: Nothing
         :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:
@@ -184,7 +201,7 @@ class Database(object):
         :param status: Open the database in read-only or read-write mode
         :type status:  :attr:`MODE`
         :returns: Nothing
         :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)
@@ -193,6 +210,28 @@ class Database(object):
             raise NotmuchError(message="Could not open the specified database")
         self._db = res
 
             raise NotmuchError(message="Could not open the specified database")
         self._db = res
 
+    _close = nmlib.notmuch_database_close
+    _close.argtypes = [NotmuchDatabaseP]
+    _close.restype = None
+
+    def close(self):
+        """Close and free the notmuch database if needed"""
+        if self._db is not None:
+            self._close(self._db)
+            self._db = None
+
+    def __enter__(self):
+        '''
+        Implements the context manager protocol.
+        '''
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        '''
+        Implements the context manager protocol.
+        '''
+        self.close()
+
     def get_path(self):
         """Returns the file path of an open database"""
         self._assert_db_is_initialized()
     def get_path(self):
         """Returns the file path of an open database"""
         self._assert_db_is_initialized()
@@ -257,7 +296,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*"""
@@ -278,7 +317,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
@@ -307,7 +346,7 @@ 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.
               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:
+        :raises:
             :exc:`NotmuchError` with :attr:`STATUS`.FILE_ERROR
                     If path is not relative database or absolute with initial
                     components same as database.
             :exc:`NotmuchError` with :attr:`STATUS`.FILE_ERROR
                     If path is not relative database or absolute with initial
                     components same as database.
@@ -318,9 +357,8 @@ 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
             # 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
@@ -372,7 +410,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
@@ -422,7 +460,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.
 
@@ -441,7 +479,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`
@@ -474,7 +512,7 @@ class Database(object):
             function returns None if no message is found with the given
             filename.
 
             function returns None if no message is found with the given
             filename.
 
-        :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`
@@ -505,7 +543,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):
@@ -531,15 +569,6 @@ class Database(object):
     def __repr__(self):
         return "'Notmuch DB " + self.get_path() + "'"
 
     def __repr__(self):
         return "'Notmuch DB " + self.get_path() + "'"
 
-    _close = nmlib.notmuch_database_close
-    _close.argtypes = [NotmuchDatabaseP]
-    _close.restype = None
-
-    def __del__(self):
-        """Close and free the notmuch database if needed"""
-        if self._db is not None:
-            self._close(self._db)
-
     def _get_user_default_db(self):
         """ Reads a user's notmuch config and returns his db location
 
     def _get_user_default_db(self):
         """ Reads a user's notmuch config and returns his db location
 
@@ -570,165 +599,6 @@ class Database(object):
         return self._db
 
 
         return self._db
 
 
-class Query(object):
-    """Represents a search query on an opened :class:`Database`.
-
-    A query selects and filters a subset of messages from the notmuch
-    database we derive from.
-
-    :class:`Query` provides an instance attribute :attr:`sort`, which
-    contains the sort order (if specified via :meth:`set_sort`) or
-    `None`.
-
-    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,
-           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.
-    """
-    # constants
-    SORT = Enum(['OLDEST_FIRST', 'NEWEST_FIRST', 'MESSAGE_ID', 'UNSORTED'])
-    """Constants: Sort order in which to return results"""
-
-    """notmuch_query_create"""
-    _create = nmlib.notmuch_query_create
-    _create.argtypes = [NotmuchDatabaseP, c_char_p]
-    _create.restype = NotmuchQueryP
-
-    """notmuch_query_search_threads"""
-    _search_threads = nmlib.notmuch_query_search_threads
-    _search_threads.argtypes = [NotmuchQueryP]
-    _search_threads.restype = NotmuchThreadsP
-
-    """notmuch_query_search_messages"""
-    _search_messages = nmlib.notmuch_query_search_messages
-    _search_messages.argtypes = [NotmuchQueryP]
-    _search_messages.restype = NotmuchMessagesP
-
-    """notmuch_query_count_messages"""
-    _count_messages = nmlib.notmuch_query_count_messages
-    _count_messages.argtypes = [NotmuchQueryP]
-    _count_messages.restype = c_uint
-
-    def __init__(self, db, querystr):
-        """
-        :param db: An open database which we derive the Query from.
-        :type db: :class:`Database`
-        :param querystr: The query string for the message.
-        :type querystr: utf-8 encoded str or unicode
-        """
-        self._db = None
-        self._query = None
-        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
-
-        This function is utilized by __init__() and usually does not need to
-        be called directly.
-
-        :param db: Database to create the query from.
-        :type db: :class:`Database`
-        :param querystr: The query string
-        :type querystr: utf-8 encoded str or unicode
-        :returns: Nothing
-        :exception:
-            :exc:`NullPointerError` if the query creation failed
-                (e.g. too little memory).
-            :exc:`NotInitializedError` if the underlying db was not
-                intitialized.
-        """
-        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 not query_p:
-            raise NullPointerError
-        self._query = query_p
-
-    _set_sort = nmlib.notmuch_query_set_sort
-    _set_sort.argtypes = [NotmuchQueryP, c_uint]
-    _set_sort.argtypes = None
-
-    def set_sort(self, sort):
-        """Set the sort order future results will be delivered in
-
-        :param sort: Sort order (see :attr:`Query.SORT`)
-        """
-        self._assert_query_is_initialized()
-        self.sort = sort
-        self._set_sort(self._query, sort)
-
-    def search_threads(self):
-        """Execute a query for threads
-
-        Execute a query for threads, returning a :class:`Threads` iterator.
-        The returned threads are owned by the query and as such, will only be
-        valid until the Query is deleted.
-
-        The method sets :attr:`Message.FLAG`\.MATCH for those messages that
-        match the query. The method :meth:`Message.get_flag` allows us
-        to get the value of this flag.
-
-        :returns: :class:`Threads`
-        :exception: :exc:`NullPointerError` if search_threads failed
-        """
-        self._assert_query_is_initialized()
-        threads_p = Query._search_threads(self._query)
-
-        if not threads_p:
-            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
-
-        :returns: :class:`Messages`
-        :exception: :exc:`NullPointerError` if search_messages failed
-        """
-        self._assert_query_is_initialized()
-        msgs_p = Query._search_messages(self._query)
-
-        if not msgs_p:
-            raise NullPointerError
-        return Messages(msgs_p, self)
-
-    def count_messages(self):
-        """Estimate the number of messages matching the query
-
-        This function performs a search and returns Xapian's best
-        guess as to the number of matching messages. It is much faster
-        than performing :meth:`search_messages` and counting the
-        result with `len()` (although it always returned the same
-        result in my tests). Technically, it wraps the underlying
-        *notmuch_query_count_messages* function.
-
-        :returns: :class:`Messages`
-        """
-        self._assert_query_is_initialized()
-        return Query._count_messages(self._query)
-
-    _destroy = nmlib.notmuch_query_destroy
-    _destroy.argtypes = [NotmuchQueryP]
-    _destroy.restype = None
-
-    def __del__(self):
-        """Close and free the Query"""
-        if self._query is not None:
-            self._destroy(self._query)
-
-
 class Directory(object):
     """Represents a directory entry in the notmuch directory
 
 class Directory(object):
     """Represents a directory entry in the notmuch directory
 
@@ -767,7 +637,7 @@ 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):
         """
@@ -811,7 +681,7 @@ class Directory(object):
 
           :param mtime: A (time_t) timestamp
           :returns: Nothing on success, raising an exception on failure.
 
           :param mtime: A (time_t) timestamp
           :returns: Nothing on success, raising an exception on failure.
-          :exception: :exc:`NotmuchError`:
+          :raises: :exc:`NotmuchError`:
 
                         :attr:`STATUS`.XAPIAN_EXCEPTION
                           A Xapian exception occurred, mtime not stored.
 
                         :attr:`STATUS`.XAPIAN_EXCEPTION
                           A Xapian exception occurred, mtime not stored.
@@ -838,7 +708,7 @@ class Directory(object):
 
         :param mtime: A (time_t) timestamp
         :returns: Nothing on success, raising an exception on failure.
 
         :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
@@ -928,7 +798,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
@@ -955,7 +825,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):