4 from notmuch2 import _capi as capi
5 from notmuch2 import _errors as errors
8 __all__ = ['NotmuchObject', 'BinString']
11 class NotmuchObject(metaclass=abc.ABCMeta):
12 """Base notmuch object syntax.
14 This base class exists to define the memory management handling
15 required to use the notmuch library. It is meant as an interface
16 definition rather than a base class, though you can use it as a
17 base class to ensure you don't forget part of the interface. It
18 only concerns you if you are implementing this package itself
21 libnotmuch uses a hierarchical memory allocator, where freeing the
22 memory of a parent object also frees the memory of all child
23 objects. To make this work seamlessly in Python this package
24 keeps references to parent objects which makes them stay alive
25 correctly under normal circumstances. When an object finally gets
26 deleted the :meth:`__del__` method will be called to free the
29 However during some peculiar situations, e.g. interpreter
30 shutdown, it is possible for the :meth:`__del__` method to have
31 been called, whele there are still references to an object. This
32 could result in child objects asking their memory to be freed
33 after the parent has already freed the memory, making things
34 rather unhappy as double frees are not taken lightly in C. To
35 handle this case all objects need to follow the same protocol to
36 destroy themselves, see :meth:`destroy`.
38 Once an object has been destroyed trying to use it should raise
39 the :exc:`ObjectDestroyedError` exception. For this see also the
40 convenience :class:`MemoryPointer` descriptor in this module which
41 can be used as a pointer to libnotmuch memory.
45 def __init__(self, parent, *args, **kwargs):
46 """Create a new object.
48 Other then for the toplevel :class:`Database` object
49 constructors are only ever called by internal code and not by
50 the user. Per convention their signature always takes the
51 parent object as first argument. Feel free to make the rest
52 of the signature match the object's requirement. The object
53 needs to keep a reference to the parent, so it can check the
54 parent is still alive.
60 """Whether the object is still alive.
62 This indicates whether the object is still alive. The first
63 thing this needs to check is whether the parent object is
64 still alive, if it is not then this object can not be alive
65 either. If the parent is alive then it depends on whether the
66 memory for this object has been freed yet or not.
74 """Destroy the object, freeing all memory.
76 This method needs to destroy the object on the
77 libnotmuch-level. It must ensure it's not been destroyed by
78 it's parent object yet before doing so. It also must be
84 """Data Descriptor to handle accessing libnotmuch pointers.
86 Most :class:`NotmuchObject` instances will have one or more CFFI
87 pointers to C-objects. Once an object is destroyed this pointer
88 should no longer be used and a :exc:`ObjectDestroyedError`
89 exception should be raised on trying to access it. This
90 descriptor simplifies implementing this, allowing the creation of
91 an attribute which can be assigned to, but when accessed when the
92 stored value is *None* it will raise the
93 :exc:`ObjectDestroyedError` exception::
96 _ptr = MemoryPointer()
98 def __init__(self, ptr):
102 somehow_free(self._ptr)
105 def do_something(self):
106 return some_libnotmuch_call(self._ptr)
109 def __get__(self, instance, owner):
111 val = getattr(instance, self.attr_name, None)
112 except AttributeError:
113 # We're not on 3.6+ and self.attr_name does not exist
114 self.__set_name__(instance, 'dummy')
115 val = getattr(instance, self.attr_name, None)
117 raise errors.ObjectDestroyedError()
120 def __set__(self, instance, value):
122 setattr(instance, self.attr_name, value)
123 except AttributeError:
124 # We're not on 3.6+ and self.attr_name does not exist
125 self.__set_name__(instance, 'dummy')
126 setattr(instance, self.attr_name, value)
128 def __set_name__(self, instance, name):
129 self.attr_name = '_memptr_{}_{:x}'.format(name, id(instance))
132 class BinString(str):
133 """A str subclass with binary data.
135 Most data in libnotmuch should be valid ASCII or valid UTF-8.
136 However since it is a C library these are represented as
137 bytestrings instead which means on an API level we can not
138 guarantee that decoding this to UTF-8 will both succeed and be
139 lossless. This string type converts bytes to unicode in a lossy
140 way, but also makes the raw bytes available.
142 This object is a normal unicode string for most intents and
143 purposes, but you can get the original bytestring back by calling
147 def __new__(cls, data, encoding='utf-8', errors='ignore'):
148 if not isinstance(data, bytes):
149 data = bytes(data, encoding=encoding)
150 strdata = str(data, encoding=encoding, errors=errors)
151 inst = super().__new__(cls, strdata)
156 def from_cffi(cls, cdata):
157 """Create a new string from a CFFI cdata pointer."""
158 return cls(capi.ffi.string(cdata))
164 class NotmuchIter(NotmuchObject, collections.abc.Iterator):
165 """An iterator for libnotmuch iterators.
167 It is tempting to use a generator function instead, but this would
168 not correctly respect the :class:`NotmuchObject` memory handling
169 protocol and in some unsuspecting cornercases cause memory
170 trouble. You probably want to sublcass this in order to wrap the
171 value returned by :meth:`__next__`.
173 :param parent: The parent object.
174 :type parent: NotmuchObject
175 :param iter_p: The CFFI pointer to the C iterator.
176 :type iter_p: cffi.cdata
177 :param fn_destory: The CFFI notmuch_*_destroy function.
178 :param fn_valid: The CFFI notmuch_*_valid function.
179 :param fn_get: The CFFI notmuch_*_get function.
180 :param fn_next: The CFFI notmuch_*_move_to_next function.
182 _iter_p = MemoryPointer()
184 def __init__(self, parent, iter_p,
185 *, fn_destroy, fn_valid, fn_get, fn_next):
186 self._parent = parent
187 self._iter_p = iter_p
188 self._fn_destroy = fn_destroy
189 self._fn_valid = fn_valid
190 self._fn_get = fn_get
191 self._fn_next = fn_next
198 if not self._parent.alive:
202 except errors.ObjectDestroyedError:
210 self._fn_destroy(self._iter_p)
211 except errors.ObjectDestroyedError:
216 """Return the iterator itself.
218 Note that as this is an iterator and not a container this will
219 not return a new iterator. Thus any elements already consumed
220 will not be yielded by the :meth:`__next__` method anymore.
225 if not self._fn_valid(self._iter_p):
227 raise StopIteration()
228 obj_p = self._fn_get(self._iter_p)
229 self._fn_next(self._iter_p)
235 except errors.ObjectDestroyedError:
236 return '<NotmuchIter (exhausted)>'
238 return '<NotmuchIter>'