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