1import abc 2import collections.abc 3 4from notmuch2 import _capi as capi 5from notmuch2 import _errors as errors 6 7 8__all__ = ['NotmuchObject', 'BinString'] 9 10 11class NotmuchObject(metaclass=abc.ABCMeta): 12 """Base notmuch object syntax. 13 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 19 rather then using it. 20 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 27 memory. 28 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`. 37 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. 42 """ 43 44 @abc.abstractmethod 45 def __init__(self, parent, *args, **kwargs): 46 """Create a new object. 47 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. 55 """ 56 57 @property 58 @abc.abstractmethod 59 def alive(self): 60 """Whether the object is still alive. 61 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. 67 """ 68 69 def __del__(self): 70 self._destroy() 71 72 @abc.abstractmethod 73 def _destroy(self): 74 """Destroy the object, freeing all memory. 75 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 79 idempotent. 80 """ 81 82 83class MemoryPointer: 84 """Data Descriptor to handle accessing libnotmuch pointers. 85 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:: 94 95 class SomeOjb: 96 _ptr = MemoryPointer() 97 98 def __init__(self, ptr): 99 self._ptr = ptr 100 101 def destroy(self): 102 somehow_free(self._ptr) 103 self._ptr = None 104 105 def do_something(self): 106 return some_libnotmuch_call(self._ptr) 107 """ 108 109 def __get__(self, instance, owner): 110 try: 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) 116 if val is None: 117 raise errors.ObjectDestroyedError() 118 return val 119 120 def __set__(self, instance, value): 121 try: 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) 127 128 def __set_name__(self, instance, name): 129 self.attr_name = '_memptr_{}_{:x}'.format(name, id(instance)) 130 131 132class BinString(str): 133 """A str subclass with binary data. 134 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. 141 142 This object is a normal unicode string for most intents and 143 purposes, but you can get the original bytestring back by calling 144 ``bytes()`` on it. 145 """ 146 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) 152 inst._bindata = data 153 return inst 154 155 @classmethod 156 def from_cffi(cls, cdata): 157 """Create a new string from a CFFI cdata pointer.""" 158 return cls(capi.ffi.string(cdata)) 159 160 def __bytes__(self): 161 return self._bindata 162 163 164class NotmuchIter(NotmuchObject, collections.abc.Iterator): 165 """An iterator for libnotmuch iterators. 166 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__`. 172 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. 181 """ 182 _iter_p = MemoryPointer() 183 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 192 193 def __del__(self): 194 self._destroy() 195 196 @property 197 def alive(self): 198 if not self._parent.alive: 199 return False 200 try: 201 self._iter_p 202 except errors.ObjectDestroyedError: 203 return False 204 else: 205 return True 206 207 def _destroy(self): 208 if self.alive: 209 try: 210 self._fn_destroy(self._iter_p) 211 except errors.ObjectDestroyedError: 212 pass 213 self._iter_p = None 214 215 def __iter__(self): 216 """Return the iterator itself. 217 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. 221 """ 222 return self 223 224 def __next__(self): 225 if not self._fn_valid(self._iter_p): 226 self._destroy() 227 raise StopIteration() 228 obj_p = self._fn_get(self._iter_p) 229 self._fn_next(self._iter_p) 230 return obj_p 231 232 def __repr__(self): 233 try: 234 self._iter_p 235 except errors.ObjectDestroyedError: 236 return '<NotmuchIter (exhausted)>' 237 else: 238 return '<NotmuchIter>' 239