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