]> git.notmuchmail.org Git - notmuch/blob - bindings/python/notmuch/query.py
bd8e769587fb37e080b6789f9cd99b07db6fbd03
[notmuch] / bindings / python / notmuch / query.py
1 """
2 This file is part of notmuch.
3
4 Notmuch is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License as published by the
6 Free Software Foundation, either version 3 of the License, or (at your
7 option) any later version.
8
9 Notmuch is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
16
17 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
18 """
19
20 from ctypes import c_char_p, c_uint, POINTER, byref
21 from .globals import (
22     nmlib,
23     Enum,
24     _str,
25     NotmuchQueryP,
26     NotmuchThreadsP,
27     NotmuchDatabaseP,
28     NotmuchMessagesP,
29 )
30 from .errors import (
31     NotmuchError,
32     NullPointerError,
33     NotInitializedError,
34 )
35 from .threads import Threads
36 from .messages import Messages
37
38
39 class Query(object):
40     """Represents a search query on an opened :class:`Database`.
41
42     A query selects and filters a subset of messages from the notmuch
43     database we derive from.
44
45     :class:`Query` provides an instance attribute :attr:`sort`, which
46     contains the sort order (if specified via :meth:`set_sort`) or
47     `None`.
48
49     Any function in this class may throw an :exc:`NotInitializedError`
50     in case the underlying query object was not set up correctly.
51
52     .. note:: Do remember that as soon as we tear down this object,
53            all underlying derived objects such as threads,
54            messages, tags etc will be freed by the underlying library
55            as well. Accessing these objects will lead to segfaults and
56            other unexpected behavior. See above for more details.
57     """
58     # constants
59     SORT = Enum(['OLDEST_FIRST', 'NEWEST_FIRST', 'MESSAGE_ID', 'UNSORTED'])
60     """Constants: Sort order in which to return results"""
61
62     def __init__(self, db, querystr):
63         """
64         :param db: An open database which we derive the Query from.
65         :type db: :class:`Database`
66         :param querystr: The query string for the message.
67         :type querystr: utf-8 encoded str or unicode
68         """
69         self._db = None
70         self._query = None
71         self.sort = None
72         self.create(db, querystr)
73
74     def _assert_query_is_initialized(self):
75         """Raises :exc:`NotInitializedError` if self._query is `None`"""
76         if not self._query:
77             raise NotInitializedError()
78
79     """notmuch_query_create"""
80     _create = nmlib.notmuch_query_create
81     _create.argtypes = [NotmuchDatabaseP, c_char_p]
82     _create.restype = NotmuchQueryP
83
84     def create(self, db, querystr):
85         """Creates a new query derived from a Database
86
87         This function is utilized by __init__() and usually does not need to
88         be called directly.
89
90         :param db: Database to create the query from.
91         :type db: :class:`Database`
92         :param querystr: The query string
93         :type querystr: utf-8 encoded str or unicode
94         :raises:
95             :exc:`NullPointerError` if the query creation failed
96                 (e.g. too little memory).
97             :exc:`NotInitializedError` if the underlying db was not
98                 intitialized.
99         """
100         db._assert_db_is_initialized()
101         # create reference to parent db to keep it alive
102         self._db = db
103         # create query, return None if too little mem available
104         query_p = Query._create(db._db, _str(querystr))
105         if not query_p:
106             raise NullPointerError
107         self._query = query_p
108
109     _set_sort = nmlib.notmuch_query_set_sort
110     _set_sort.argtypes = [NotmuchQueryP, c_uint]
111     _set_sort.argtypes = None
112
113     def set_sort(self, sort):
114         """Set the sort order future results will be delivered in
115
116         :param sort: Sort order (see :attr:`Query.SORT`)
117         """
118         self._assert_query_is_initialized()
119         self.sort = sort
120         self._set_sort(self._query, sort)
121
122     _exclude_tag = nmlib.notmuch_query_add_tag_exclude
123     _exclude_tag.argtypes = [NotmuchQueryP, c_char_p]
124     _exclude_tag.resttype = None
125
126     def exclude_tag(self, tagname):
127         """Add a tag that will be excluded from the query results by default.
128
129         This exclusion will be overridden if this tag appears explicitly in the
130         query.
131
132         :param tagname: Name of the tag to be excluded
133         """
134         self._assert_query_is_initialized()
135         self._exclude_tag(self._query, _str(tagname))
136
137     """notmuch_query_search_threads"""
138     _search_threads = nmlib.notmuch_query_search_threads
139     _search_threads.argtypes = [NotmuchQueryP]
140     _search_threads.restype = NotmuchThreadsP
141
142     def search_threads(self):
143         """Execute a query for threads
144
145         Execute a query for threads, returning a :class:`Threads` iterator.
146         The returned threads are owned by the query and as such, will only be
147         valid until the Query is deleted.
148
149         The method sets :attr:`Message.FLAG`\.MATCH for those messages that
150         match the query. The method :meth:`Message.get_flag` allows us
151         to get the value of this flag.
152
153         :returns: :class:`Threads`
154         :raises: :exc:`NullPointerError` if search_threads failed
155         """
156         self._assert_query_is_initialized()
157         threads_p = Query._search_threads(self._query)
158
159         if not threads_p:
160             raise NullPointerError
161         return Threads(threads_p, self)
162
163     """notmuch_query_search_messages"""
164     _search_messages = nmlib.notmuch_query_search_messages
165     _search_messages.argtypes = [NotmuchQueryP]
166     _search_messages.restype = NotmuchMessagesP
167
168     def search_messages(self):
169         """Filter messages according to the query and return
170         :class:`Messages` in the defined sort order
171
172         :returns: :class:`Messages`
173         :raises: :exc:`NullPointerError` if search_messages failed
174         """
175         self._assert_query_is_initialized()
176         msgs_p = Query._search_messages(self._query)
177
178         if not msgs_p:
179             raise NullPointerError
180         return Messages(msgs_p, self)
181
182     _count_messages = nmlib.notmuch_query_count_messages_st
183     _count_messages.argtypes = [NotmuchQueryP, POINTER(c_uint)]
184     _count_messages.restype = c_uint
185
186     def count_messages(self):
187         '''
188         This function performs a search and returns Xapian's best
189         guess as to the number of matching messages.
190
191         :returns: the estimated number of messages matching this query
192         :rtype:   int
193         '''
194         self._assert_query_is_initialized()
195         count = c_uint(0)
196         status = Query._count_messages(self._query, byref(count))
197         if status != 0:
198             raise NotmuchError(status)
199         return count.value
200
201     _count_threads = nmlib.notmuch_query_count_threads_st
202     _count_threads.argtypes = [NotmuchQueryP, POINTER(c_uint)]
203     _count_threads.restype = c_uint
204
205     def count_threads(self):
206         '''
207         This function performs a search and returns the number of
208         unique thread IDs in the matching messages. This is the same
209         as number of threads matching a search.
210
211         Note that this is a significantly heavier operation than
212         meth:`Query.count_messages`.
213
214         :returns: the number of threads returned by this query
215         :rtype:   int
216         '''
217         self._assert_query_is_initialized()
218         count = c_uint(0)
219         status = Query._count_threads(self._query, byref(count))
220         if status != 0:
221             raise NotmuchError(status)
222         return count.value
223
224     _destroy = nmlib.notmuch_query_destroy
225     _destroy.argtypes = [NotmuchQueryP]
226     _destroy.restype = None
227
228     def __del__(self):
229         """Close and free the Query"""
230         if self._query:
231             self._destroy(self._query)