+++ /dev/null
-import abc
-import collections.abc
-
-from notdb import _capi as capi
-from notdb import _errors as errors
-
-
-__all__ = ['NotmuchObject', 'BinString']
-
-
-class NotmuchObject(metaclass=abc.ABCMeta):
- """Base notmuch object syntax.
-
- This base class exists to define the memory management handling
- required to use the notmuch library. It is meant as an interface
- definition rather than a base class, though you can use it as a
- base class to ensure you don't forget part of the interface. It
- only concerns you if you are implementing this package itself
- rather then using it.
-
- libnotmuch uses a hierarchical memory allocator, where freeing the
- memory of a parent object also frees the memory of all child
- objects. To make this work seamlessly in Python this package
- keeps references to parent objects which makes them stay alive
- correctly under normal circumstances. When an object finally gets
- deleted the :meth:`__del__` method will be called to free the
- memory.
-
- However during some peculiar situations, e.g. interpreter
- shutdown, it is possible for the :meth:`__del__` method to have
- been called, whele there are still references to an object. This
- could result in child objects asking their memeory to be freed
- after the parent has already freed the memory, making things
- rather unhappy as double frees are not taken lightly in C. To
- handle this case all objects need to follow the same protocol to
- destroy themselves, see :meth:`destroy`.
-
- Once an object has been destroyed trying to use it should raise
- the :exc:`ObjectDestroyedError` exception. For this see also the
- convenience :class:`MemoryPointer` descriptor in this module which
- can be used as a pointer to libnotmuch memory.
- """
-
- @abc.abstractmethod
- def __init__(self, parent, *args, **kwargs):
- """Create a new object.
-
- Other then for the toplevel :class:`Database` object
- constructors are only ever called by internal code and not by
- the user. Per convention their signature always takes the
- parent object as first argument. Feel free to make the rest
- of the signature match the object's requirement. The object
- needs to keep a reference to the parent, so it can check the
- parent is still alive.
- """
-
- @property
- @abc.abstractmethod
- def alive(self):
- """Whether the object is still alive.
-
- This indicates whether the object is still alive. The first
- thing this needs to check is whether the parent object is
- still alive, if it is not then this object can not be alive
- either. If the parent is alive then it depends on whether the
- memory for this object has been freed yet or not.
- """
-
- def __del__(self):
- self._destroy()
-
- @abc.abstractmethod
- def _destroy(self):
- """Destroy the object, freeing all memory.
-
- This method needs to destory the object on the
- libnotmuch-level. It must ensure it's not been destroyed by
- it's parent object yet before doing so. It also must be
- idempotent.
- """
-
-
-class MemoryPointer:
- """Data Descriptor to handle accessing libnotmuch pointers.
-
- Most :class:`NotmuchObject` instances will have one or more CFFI
- pointers to C-objects. Once an object is destroyed this pointer
- should no longer be used and a :exc:`ObjectDestroyedError`
- exception should be raised on trying to access it. This
- descriptor simplifies implementing this, allowing the creation of
- an attribute which can be assigned to, but when accessed when the
- stored value is *None* it will raise the
- :exc:`ObjectDestroyedError` exception::
-
- class SomeOjb:
- _ptr = MemoryPointer()
-
- def __init__(self, ptr):
- self._ptr = ptr
-
- def destroy(self):
- somehow_free(self._ptr)
- self._ptr = None
-
- def do_something(self):
- return some_libnotmuch_call(self._ptr)
- """
-
- def __get__(self, instance, owner):
- try:
- val = getattr(instance, self.attr_name, None)
- except AttributeError:
- # We're not on 3.6+ and self.attr_name does not exist
- self.__set_name__(instance, 'dummy')
- val = getattr(instance, self.attr_name, None)
- if val is None:
- raise errors.ObjectDestroyedError()
- return val
-
- def __set__(self, instance, value):
- try:
- setattr(instance, self.attr_name, value)
- except AttributeError:
- # We're not on 3.6+ and self.attr_name does not exist
- self.__set_name__(instance, 'dummy')
- setattr(instance, self.attr_name, value)
-
- def __set_name__(self, instance, name):
- self.attr_name = '_memptr_{}_{:x}'.format(name, id(instance))
-
-
-class BinString(str):
- """A str subclass with binary data.
-
- Most data in libnotmuch should be valid ASCII or valid UTF-8.
- However since it is a C library these are represented as
- bytestrings intead which means on an API level we can not
- guarantee that decoding this to UTF-8 will both succeed and be
- lossless. This string type converts bytes to unicode in a lossy
- way, but also makes the raw bytes available.
-
- This object is a normal unicode string for most intents and
- purposes, but you can get the original bytestring back by calling
- ``bytes()`` on it.
- """
-
- def __new__(cls, data, encoding='utf-8', errors='ignore'):
- if not isinstance(data, bytes):
- data = bytes(data, encoding=encoding)
- strdata = str(data, encoding=encoding, errors=errors)
- inst = super().__new__(cls, strdata)
- inst._bindata = data
- return inst
-
- @classmethod
- def from_cffi(cls, cdata):
- """Create a new string from a CFFI cdata pointer."""
- return cls(capi.ffi.string(cdata))
-
- def __bytes__(self):
- return self._bindata
-
-
-class NotmuchIter(NotmuchObject, collections.abc.Iterator):
- """An iterator for libnotmuch iterators.
-
- It is tempting to use a generator function instead, but this would
- not correctly respect the :class:`NotmuchObject` memory handling
- protocol and in some unsuspecting cornercases cause memory
- trouble. You probably want to sublcass this in order to wrap the
- value returned by :meth:`__next__`.
-
- :param parent: The parent object.
- :type parent: NotmuchObject
- :param iter_p: The CFFI pointer to the C iterator.
- :type iter_p: cffi.cdata
- :param fn_destory: The CFFI notmuch_*_destroy function.
- :param fn_valid: The CFFI notmuch_*_valid function.
- :param fn_get: The CFFI notmuch_*_get function.
- :param fn_next: The CFFI notmuch_*_move_to_next function.
- """
- _iter_p = MemoryPointer()
-
- def __init__(self, parent, iter_p,
- *, fn_destroy, fn_valid, fn_get, fn_next):
- self._parent = parent
- self._iter_p = iter_p
- self._fn_destroy = fn_destroy
- self._fn_valid = fn_valid
- self._fn_get = fn_get
- self._fn_next = fn_next
-
- def __del__(self):
- self._destroy()
-
- @property
- def alive(self):
- if not self._parent.alive:
- return False
- try:
- self._iter_p
- except errors.ObjectDestroyedError:
- return False
- else:
- return True
-
- def _destroy(self):
- if self.alive:
- try:
- self._fn_destroy(self._iter_p)
- except errors.ObjectDestroyedError:
- pass
- self._iter_p = None
-
- def __iter__(self):
- """Return the iterator itself.
-
- Note that as this is an iterator and not a container this will
- not return a new iterator. Thus any elements already consumed
- will not be yielded by the :meth:`__next__` method anymore.
- """
- return self
-
- def __next__(self):
- if not self._fn_valid(self._iter_p):
- self._destroy()
- raise StopIteration()
- obj_p = self._fn_get(self._iter_p)
- self._fn_next(self._iter_p)
- return obj_p
-
- def __repr__(self):
- try:
- self._iter_p
- except errors.ObjectDestroyedError:
- return '<NotmuchIter (exhausted)>'
- else:
- return '<NotmuchIter>'