From ba95980cf1a5e2b32104611ccdf2e9c43bf3305a Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 22 Feb 2012 21:55:59 +0100 Subject: [PATCH] python: refactor the python bindings Move the Directory class into its own file, merge the two Filenames classes into one, deprecate Filenames.as_iterator, update the documentation accordingly. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/docs/source/index.rst | 20 ++- bindings/python/notmuch/__init__.py | 15 +- bindings/python/notmuch/database.py | 239 +------------------------- bindings/python/notmuch/directory.py | 183 ++++++++++++++++++++ bindings/python/notmuch/filename.py | 62 +++++-- 5 files changed, 256 insertions(+), 263 deletions(-) create mode 100644 bindings/python/notmuch/directory.py diff --git a/bindings/python/docs/source/index.rst b/bindings/python/docs/source/index.rst index 25eb146c..e87a8659 100644 --- a/bindings/python/docs/source/index.rst +++ b/bindings/python/docs/source/index.rst @@ -254,26 +254,28 @@ More information on specific topics can be found on the following pages: :class:`Filenames` -- An iterator over filenames ------------------------------------------------ -.. autoclass:: notmuch.database.Filenames +.. autoclass:: notmuch.Filenames - .. automethod:: notmuch.database.Filenames.__len__ + .. automethod:: notmuch.Filenames.__len__ + + .. automethod:: notmuch.Filenames.as_generator :class:`notmuch.database.Directoy` -- A directory entry in the database ------------------------------------------------------------------------ -.. autoclass:: notmuch.database.Directory +.. autoclass:: notmuch.Directory - .. automethod:: notmuch.database.Directory.get_child_files + .. automethod:: notmuch.Directory.get_child_files - .. automethod:: notmuch.database.Directory.get_child_directories + .. automethod:: notmuch.Directory.get_child_directories - .. automethod:: notmuch.database.Directory.get_mtime + .. automethod:: notmuch.Directory.get_mtime - .. automethod:: notmuch.database.Directory.set_mtime + .. automethod:: notmuch.Directory.set_mtime - .. autoattribute:: notmuch.database.Directory.mtime + .. autoattribute:: notmuch.Directory.mtime - .. autoattribute:: notmuch.database.Directory.path + .. autoattribute:: notmuch.Directory.path The `next page `_ contains information on possible Status and Error values. diff --git a/bindings/python/notmuch/__init__.py b/bindings/python/notmuch/__init__.py index f3ff9874..8de73d58 100644 --- a/bindings/python/notmuch/__init__.py +++ b/bindings/python/notmuch/__init__.py @@ -51,11 +51,14 @@ along with notmuch. If not, see . Copyright 2010-2011 Sebastian Spaeth """ -from notmuch.database import Database, Query -from notmuch.message import Messages, Message -from notmuch.thread import Threads, Thread -from notmuch.tag import Tags -from notmuch.globals import ( +from .database import Database +from .directory import Directory +from .filename import Filenames +from .message import Messages, Message +from .query import Query +from .tag import Tags +from .thread import Threads, Thread +from .globals import ( nmlib, STATUS, NotmuchError, @@ -71,6 +74,6 @@ from notmuch.globals import ( UnbalancedAtomicError, NotInitializedError, ) -from notmuch.version import __VERSION__ +from .version import __VERSION__ __LICENSE__ = "GPL v3+" __AUTHOR__ = 'Sebastian Spaeth ' diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index a054be76..800264b7 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -19,7 +19,7 @@ Copyright 2010 Sebastian Spaeth ' import os import codecs -from ctypes import c_char_p, c_void_p, c_uint, c_long, byref, POINTER +from ctypes import c_char_p, c_void_p, c_uint, byref, POINTER from notmuch.globals import ( nmlib, STATUS, @@ -34,11 +34,11 @@ from notmuch.globals import ( NotmuchDirectoryP, NotmuchMessageP, NotmuchTagsP, - NotmuchFilenamesP ) from notmuch.message import Message from notmuch.tag import Tags from .query import Query +from .directory import Directory class Database(object): """The :class:`Database` is the highest-level object that notmuch @@ -603,238 +603,3 @@ class Database(object): guaranteed to remain stable in future versions). """ return self._db - - -class Directory(object): - """Represents a directory entry in the notmuch directory - - Modifying attributes of this object will modify the - database, not the real directory attributes. - - The Directory object is usually derived from another object - e.g. via :meth:`Database.get_directory`, and will automatically be - become invalid whenever that parent is deleted. You should - therefore initialized this object handing it a reference to the - parent, preventing the parent from automatically being garbage - collected. - """ - - """notmuch_directory_get_mtime""" - _get_mtime = nmlib.notmuch_directory_get_mtime - _get_mtime.argtypes = [NotmuchDirectoryP] - _get_mtime.restype = c_long - - """notmuch_directory_set_mtime""" - _set_mtime = nmlib.notmuch_directory_set_mtime - _set_mtime.argtypes = [NotmuchDirectoryP, c_long] - _set_mtime.restype = c_uint - - """notmuch_directory_get_child_files""" - _get_child_files = nmlib.notmuch_directory_get_child_files - _get_child_files.argtypes = [NotmuchDirectoryP] - _get_child_files.restype = NotmuchFilenamesP - - """notmuch_directory_get_child_directories""" - _get_child_directories = nmlib.notmuch_directory_get_child_directories - _get_child_directories.argtypes = [NotmuchDirectoryP] - _get_child_directories.restype = NotmuchFilenamesP - - def _assert_dir_is_initialized(self): - """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) - if dir_p is None""" - if not self._dir_p: - raise NotInitializedError() - - def __init__(self, path, dir_p, parent): - """ - :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 - this, but store a reference to it as long as - this Directory object lives. This keeps the - parent object alive. - """ - self._path = path - self._dir_p = dir_p - self._parent = parent - - def set_mtime(self, mtime): - """Sets the mtime value of this directory in the database - - The intention is for the caller to use the mtime to allow efficient - identification of new messages to be added to the database. The - recommended usage is as follows: - - * Read the mtime of a directory from the filesystem - - * Call :meth:`Database.add_message` for all mail files in - the directory - - * Call notmuch_directory_set_mtime with the mtime read from the - filesystem. Then, when wanting to check for updates to the - directory in the future, the client can call :meth:`get_mtime` - and know that it only needs to add files if the mtime of the - directory and files are newer than the stored timestamp. - - .. note:: - - :meth:`get_mtime` function does not allow the caller to - distinguish a timestamp of 0 from a non-existent timestamp. So - don't store a timestamp of 0 unless you are comfortable with - that. - - :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() - status = Directory._set_mtime(self._dir_p, mtime) - - if status != STATUS.SUCCESS: - raise NotmuchError(status) - - def get_mtime(self): - """Gets the mtime value of this directory in the database - - Retrieves a previously stored mtime for this directory. - - :param mtime: A (time_t) timestamp - :raises: :exc:`NotmuchError`: - - :attr:`STATUS`.NOT_INITIALIZED - The directory has not been initialized - """ - self._assert_dir_is_initialized() - return Directory._get_mtime(self._dir_p) - - # Make mtime attribute a property of Directory() - mtime = property(get_mtime, set_mtime, doc="""Property that allows getting - and setting of the Directory *mtime* (read-write) - - See :meth:`get_mtime` and :meth:`set_mtime` for usage and - possible exceptions.""") - - def get_child_files(self): - """Gets a Filenames iterator listing all the filenames of - messages in the database within the given directory. - - The returned filenames will be the basename-entries only (not - complete paths. - """ - self._assert_dir_is_initialized() - files_p = Directory._get_child_files(self._dir_p) - return Filenames(files_p, self) - - def get_child_directories(self): - """Gets a :class:`Filenames` iterator listing all the filenames of - sub-directories in the database within the given directory - - The returned filenames will be the basename-entries only (not - complete paths. - """ - self._assert_dir_is_initialized() - files_p = Directory._get_child_directories(self._dir_p) - return Filenames(files_p, self) - - @property - def path(self): - """Returns the absolute path of this Directory (read-only)""" - return self._path - - def __repr__(self): - """Object representation""" - return "" % self._path - - _destroy = nmlib.notmuch_directory_destroy - _destroy.argtypes = [NotmuchDirectoryP] - _destroy.argtypes = None - - def __del__(self): - """Close and free the Directory""" - if self._dir_p is not None: - self._destroy(self._dir_p) - - -class Filenames(object): - """An iterator over File- or Directory names stored in the database""" - - #notmuch_filenames_get - _get = nmlib.notmuch_filenames_get - _get.argtypes = [NotmuchFilenamesP] - _get.restype = c_char_p - - def __init__(self, files_p, parent): - """ - :param files_p: The pointer to an internal notmuch_filenames_t object. - :param parent: The object this Directory is derived from - (usually a Directory()). We do not directly use - this, but store a reference to it as long as - this Directory object lives. This keeps the - parent object alive. - """ - self._files_p = files_p - self._parent = parent - - def __iter__(self): - """ Make Filenames an iterator """ - return self - - _valid = nmlib.notmuch_filenames_valid - _valid.argtypes = [NotmuchFilenamesP] - _valid.restype = bool - - _move_to_next = nmlib.notmuch_filenames_move_to_next - _move_to_next.argtypes = [NotmuchFilenamesP] - _move_to_next.restype = None - - def __next__(self): - if not self._files_p: - raise NotInitializedError() - - if not self._valid(self._files_p): - self._files_p = None - raise StopIteration - - file_ = Filenames._get(self._files_p) - self._move_to_next(self._files_p) - return file_.decode('utf-8', 'ignore') - next = __next__ # python2.x iterator protocol compatibility - - def __len__(self): - """len(:class:`Filenames`) returns the number of contained files - - .. note:: - - As this iterates over the files, we will not be able to - iterate over them again! So this will fail:: - - #THIS FAILS - files = Database().get_directory('').get_child_files() - if len(files) > 0: # this 'exhausts' msgs - # next line raises - # NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) - for file in files: print file - """ - if not self._files_p: - raise NotInitializedError() - - i = 0 - while self._valid(self._files_p): - self._move_to_next(self._files_p) - i += 1 - self._files_p = None - return i - - _destroy = nmlib.notmuch_filenames_destroy - _destroy.argtypes = [NotmuchFilenamesP] - _destroy.restype = None - - def __del__(self): - """Close and free Filenames""" - if self._files_p is not None: - self._destroy(self._files_p) diff --git a/bindings/python/notmuch/directory.py b/bindings/python/notmuch/directory.py new file mode 100644 index 00000000..3e0763f2 --- /dev/null +++ b/bindings/python/notmuch/directory.py @@ -0,0 +1,183 @@ +""" +This file is part of notmuch. + +Notmuch is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +Notmuch is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with notmuch. If not, see . + +Copyright 2010 Sebastian Spaeth ' +""" + +from ctypes import c_uint, c_long +from notmuch.globals import ( + nmlib, + STATUS, + NotmuchError, + NotInitializedError, + NotmuchDirectoryP, + NotmuchFilenamesP +) +from .filename import Filenames + +class Directory(object): + """Represents a directory entry in the notmuch directory + + Modifying attributes of this object will modify the + database, not the real directory attributes. + + The Directory object is usually derived from another object + e.g. via :meth:`Database.get_directory`, and will automatically be + become invalid whenever that parent is deleted. You should + therefore initialized this object handing it a reference to the + parent, preventing the parent from automatically being garbage + collected. + """ + + """notmuch_directory_get_mtime""" + _get_mtime = nmlib.notmuch_directory_get_mtime + _get_mtime.argtypes = [NotmuchDirectoryP] + _get_mtime.restype = c_long + + """notmuch_directory_set_mtime""" + _set_mtime = nmlib.notmuch_directory_set_mtime + _set_mtime.argtypes = [NotmuchDirectoryP, c_long] + _set_mtime.restype = c_uint + + """notmuch_directory_get_child_files""" + _get_child_files = nmlib.notmuch_directory_get_child_files + _get_child_files.argtypes = [NotmuchDirectoryP] + _get_child_files.restype = NotmuchFilenamesP + + """notmuch_directory_get_child_directories""" + _get_child_directories = nmlib.notmuch_directory_get_child_directories + _get_child_directories.argtypes = [NotmuchDirectoryP] + _get_child_directories.restype = NotmuchFilenamesP + + def _assert_dir_is_initialized(self): + """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) + if dir_p is None""" + if not self._dir_p: + raise NotInitializedError() + + def __init__(self, path, dir_p, parent): + """ + :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 + this, but store a reference to it as long as + this Directory object lives. This keeps the + parent object alive. + """ + self._path = path + self._dir_p = dir_p + self._parent = parent + + def set_mtime(self, mtime): + """Sets the mtime value of this directory in the database + + The intention is for the caller to use the mtime to allow efficient + identification of new messages to be added to the database. The + recommended usage is as follows: + + * Read the mtime of a directory from the filesystem + + * Call :meth:`Database.add_message` for all mail files in + the directory + + * Call notmuch_directory_set_mtime with the mtime read from the + filesystem. Then, when wanting to check for updates to the + directory in the future, the client can call :meth:`get_mtime` + and know that it only needs to add files if the mtime of the + directory and files are newer than the stored timestamp. + + .. note:: + + :meth:`get_mtime` function does not allow the caller to + distinguish a timestamp of 0 from a non-existent timestamp. So + don't store a timestamp of 0 unless you are comfortable with + that. + + :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() + status = Directory._set_mtime(self._dir_p, mtime) + + if status != STATUS.SUCCESS: + raise NotmuchError(status) + + def get_mtime(self): + """Gets the mtime value of this directory in the database + + Retrieves a previously stored mtime for this directory. + + :param mtime: A (time_t) timestamp + :raises: :exc:`NotmuchError`: + + :attr:`STATUS`.NOT_INITIALIZED + The directory has not been initialized + """ + self._assert_dir_is_initialized() + return Directory._get_mtime(self._dir_p) + + # Make mtime attribute a property of Directory() + mtime = property(get_mtime, set_mtime, doc="""Property that allows getting + and setting of the Directory *mtime* (read-write) + + See :meth:`get_mtime` and :meth:`set_mtime` for usage and + possible exceptions.""") + + def get_child_files(self): + """Gets a Filenames iterator listing all the filenames of + messages in the database within the given directory. + + The returned filenames will be the basename-entries only (not + complete paths. + """ + self._assert_dir_is_initialized() + files_p = Directory._get_child_files(self._dir_p) + return Filenames(files_p, self) + + def get_child_directories(self): + """Gets a :class:`Filenames` iterator listing all the filenames of + sub-directories in the database within the given directory + + The returned filenames will be the basename-entries only (not + complete paths. + """ + self._assert_dir_is_initialized() + files_p = Directory._get_child_directories(self._dir_p) + return Filenames(files_p, self) + + @property + def path(self): + """Returns the absolute path of this Directory (read-only)""" + return self._path + + def __repr__(self): + """Object representation""" + return "" % self._path + + _destroy = nmlib.notmuch_directory_destroy + _destroy.argtypes = [NotmuchDirectoryP] + _destroy.argtypes = None + + def __del__(self): + """Close and free the Directory""" + if self._dir_p is not None: + self._destroy(self._dir_p) diff --git a/bindings/python/notmuch/filename.py b/bindings/python/notmuch/filename.py index 353eb76e..232a9eda 100644 --- a/bindings/python/notmuch/filename.py +++ b/bindings/python/notmuch/filename.py @@ -78,10 +78,14 @@ class Filenames(Python3StringMixIn): if not files_p: raise NullPointerError() - self._files = files_p + self._files_p = files_p #save reference to parent object so we keep it alive self._parent = parent + def __iter__(self): + """ Make Filenames an iterator """ + return self + _valid = nmlib.notmuch_filenames_valid _valid.argtypes = [NotmuchFilenamesP] _valid.restype = bool @@ -90,19 +94,30 @@ class Filenames(Python3StringMixIn): _move_to_next.argtypes = [NotmuchFilenamesP] _move_to_next.restype = None + def __next__(self): + if not self._files_p: + raise NotInitializedError() + + if not self._valid(self._files_p): + self._files_p = None + raise StopIteration + + file_ = Filenames._get(self._files_p) + self._move_to_next(self._files_p) + return file_.decode('utf-8', 'ignore') + next = __next__ # python2.x iterator protocol compatibility + def as_generator(self): """Return generator of Filenames This is the main function that will usually be used by the - user.""" - if not self._files: - raise NotInitializedError() + user. - while self._valid(self._files): - yield Filenames._get(self._files).decode('utf-8', 'ignore') - self._move_to_next(self._files) - - self._files = None + .. deprecated:: 0.12 + :class:`Filenames` objects implement the + iterator protocol. + """ + return self def __unicode__(self): """Represent Filenames() as newline-separated list of full paths @@ -123,5 +138,30 @@ class Filenames(Python3StringMixIn): def __del__(self): """Close and free the notmuch filenames""" - if self._files is not None: - self._destroy(self._files) + if self._files_p is not None: + self._destroy(self._files_p) + + def __len__(self): + """len(:class:`Filenames`) returns the number of contained files + + .. note:: + + As this iterates over the files, we will not be able to + iterate over them again! So this will fail:: + + #THIS FAILS + files = Database().get_directory('').get_child_files() + if len(files) > 0: # this 'exhausts' msgs + # next line raises + # NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) + for file in files: print file + """ + if not self._files_p: + raise NotInitializedError() + + i = 0 + while self._valid(self._files_p): + self._move_to_next(self._files_p) + i += 1 + self._files_p = None + return i