python: move the exception classes into error.py
[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 )
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_p, _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     """notmuch_query_search_threads"""
122     _search_threads = nmlib.notmuch_query_search_threads
123     _search_threads.argtypes = [NotmuchQueryP]
124     _search_threads.restype = NotmuchThreadsP
125
126     def search_threads(self):
127         """Execute a query for threads
128
129         Execute a query for threads, returning a :class:`Threads` iterator.
130         The returned threads are owned by the query and as such, will only be
131         valid until the Query is deleted.
132
133         The method sets :attr:`Message.FLAG`\.MATCH for those messages that
134         match the query. The method :meth:`Message.get_flag` allows us
135         to get the value of this flag.
136
137         :returns: :class:`Threads`
138         :raises: :exc:`NullPointerError` if search_threads failed
139         """
140         self._assert_query_is_initialized()
141         threads_p = Query._search_threads(self._query)
142
143         if not threads_p:
144             raise NullPointerError
145         return Threads(threads_p, self)
146
147     """notmuch_query_search_messages"""
148     _search_messages = nmlib.notmuch_query_search_messages
149     _search_messages.argtypes = [NotmuchQueryP]
150     _search_messages.restype = NotmuchMessagesP
151
152     def search_messages(self):
153         """Filter messages according to the query and return
154         :class:`Messages` in the defined sort order
155
156         :returns: :class:`Messages`
157         :raises: :exc:`NullPointerError` if search_messages failed
158         """
159         self._assert_query_is_initialized()
160         msgs_p = Query._search_messages(self._query)
161
162         if not msgs_p:
163             raise NullPointerError
164         return Messages(msgs_p, self)
165
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         '''
172         This function performs a search and returns Xapian's best
173         guess as to the number of matching messages.
174
175         :returns: the estimated number of messages matching this query
176         :rtype:   int
177         '''
178         self._assert_query_is_initialized()
179         return Query._count_messages(self._query)
180
181     _count_threads = nmlib.notmuch_query_count_threads
182     _count_threads.argtypes = [NotmuchQueryP]
183     _count_threads.restype = c_uint
184
185     def count_threads(self):
186         '''
187         This function performs a search and returns the number of
188         unique thread IDs in the matching messages. This is the same
189         as number of threads matching a search.
190
191         Note that this is a significantly heavier operation than
192         meth:`Query.count_messages`.
193
194         :returns: the number of threads returned by this query
195         :rtype:   int
196         '''
197         self._assert_query_is_initialized()
198         return Query._count_threads(self._query)
199
200     _destroy = nmlib.notmuch_query_destroy
201     _destroy.argtypes = [NotmuchQueryP]
202     _destroy.restype = None
203
204     def __del__(self):
205         """Close and free the Query"""
206         if self._query is not None:
207             self._destroy(self._query)