X-Git-Url: https://git.notmuchmail.org/git?a=blobdiff_plain;f=bindings%2Fpython%2Fnotmuch%2Fdatabase.py;h=f4bc53e02c6d17574595e25972979af99ad6aa9d;hb=5e75398c80ab3008ca0ecd60544897dc5e546e47;hp=644e2e5fa68a2615f0084158fcb52de68683adc6;hpb=b6a01735d238733ef78f941a8b7c4bad59db2734;p=notmuch diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 644e2e5f..f4bc53e0 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -19,19 +19,34 @@ Copyright 2010 Sebastian Spaeth ' 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):