python: move the exception classes into error.py
[notmuch] / bindings / python / notmuch / errors.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_int
21
22 from .globals import (
23     nmlib,
24     Enum,
25     Python3StringMixIn,
26 )
27
28 class Status(Enum):
29     """Enum with a string representation of a notmuch_status_t value."""
30     _status2str = nmlib.notmuch_status_to_string
31     _status2str.restype = c_char_p
32     _status2str.argtypes = [c_int]
33
34     def __init__(self, statuslist):
35         """It is initialized with a list of strings that are available as
36         Status().string1 - Status().stringn attributes.
37         """
38         super(Status, self).__init__(statuslist)
39
40     @classmethod
41     def status2str(self, status):
42         """Get a (unicode) string representation of a notmuch_status_t value."""
43         # define strings for custom error messages
44         if status == STATUS.NOT_INITIALIZED:
45             return "Operation on uninitialized object impossible."
46         return unicode(Status._status2str(status))
47
48 STATUS = Status(['SUCCESS',
49   'OUT_OF_MEMORY',
50   'READ_ONLY_DATABASE',
51   'XAPIAN_EXCEPTION',
52   'FILE_ERROR',
53   'FILE_NOT_EMAIL',
54   'DUPLICATE_MESSAGE_ID',
55   'NULL_POINTER',
56   'TAG_TOO_LONG',
57   'UNBALANCED_FREEZE_THAW',
58   'UNBALANCED_ATOMIC',
59   'NOT_INITIALIZED'])
60 """STATUS is a class, whose attributes provide constants that serve as return
61 indicators for notmuch functions. Currently the following ones are defined. For
62 possible return values and specific meaning for each method, see the method
63 description.
64
65   * SUCCESS
66   * OUT_OF_MEMORY
67   * READ_ONLY_DATABASE
68   * XAPIAN_EXCEPTION
69   * FILE_ERROR
70   * FILE_NOT_EMAIL
71   * DUPLICATE_MESSAGE_ID
72   * NULL_POINTER
73   * TAG_TOO_LONG
74   * UNBALANCED_FREEZE_THAW
75   * UNBALANCED_ATOMIC
76   * NOT_INITIALIZED
77
78 Invoke the class method `notmuch.STATUS.status2str` with a status value as
79 argument to receive a human readable string"""
80 STATUS.__name__ = 'STATUS'
81
82
83 class NotmuchError(Exception, Python3StringMixIn):
84     """Is initiated with a (notmuch.STATUS[, message=None]). It will not
85     return an instance of the class NotmuchError, but a derived instance
86     of a more specific Error Message, e.g. OutOfMemoryError. Each status
87     but SUCCESS has a corresponding subclassed Exception."""
88
89     @classmethod
90     def get_exc_subclass(cls, status):
91         """Returns a fine grained Exception() type,
92         detailing the error status"""
93         subclasses = {
94             STATUS.OUT_OF_MEMORY: OutOfMemoryError,
95             STATUS.READ_ONLY_DATABASE: ReadOnlyDatabaseError,
96             STATUS.XAPIAN_EXCEPTION: XapianError,
97             STATUS.FILE_ERROR: FileError,
98             STATUS.FILE_NOT_EMAIL: FileNotEmailError,
99             STATUS.DUPLICATE_MESSAGE_ID: DuplicateMessageIdError,
100             STATUS.NULL_POINTER: NullPointerError,
101             STATUS.TAG_TOO_LONG: TagTooLongError,
102             STATUS.UNBALANCED_FREEZE_THAW: UnbalancedFreezeThawError,
103             STATUS.UNBALANCED_ATOMIC: UnbalancedAtomicError,
104             STATUS.NOT_INITIALIZED: NotInitializedError,
105         }
106         assert 0 < status <= len(subclasses)
107         return subclasses[status]
108
109     def __new__(cls, *args, **kwargs):
110         """Return a correct subclass of NotmuchError if needed
111
112         We return a NotmuchError instance if status is None (or 0) and a
113         subclass that inherits from NotmuchError depending on the
114         'status' parameter otherwise."""
115         # get 'status'. Passed in as arg or kwarg?
116         status = args[0] if len(args) else kwargs.get('status', None)
117         # no 'status' or cls is subclass already, return 'cls' instance
118         if not status or cls != NotmuchError:
119             return super(NotmuchError, cls).__new__(cls)
120         subclass = cls.get_exc_subclass(status)  # which class to use?
121         return subclass.__new__(subclass, *args, **kwargs)
122
123     def __init__(self, status=None, message=None):
124         self.status = status
125         self.message = message
126
127     def __unicode__(self):
128         if self.message is not None:
129             return self.message
130         elif self.status is not None:
131             return STATUS.status2str(self.status)
132         else:
133             return 'Unknown error'
134
135
136 # List of Subclassed exceptions that correspond to STATUS values and are
137 # subclasses of NotmuchError.
138 class OutOfMemoryError(NotmuchError):
139     status = STATUS.OUT_OF_MEMORY
140
141
142 class ReadOnlyDatabaseError(NotmuchError):
143     status = STATUS.READ_ONLY_DATABASE
144
145
146 class XapianError(NotmuchError):
147     status = STATUS.XAPIAN_EXCEPTION
148
149
150 class FileError(NotmuchError):
151     status = STATUS.FILE_ERROR
152
153
154 class FileNotEmailError(NotmuchError):
155     status = STATUS.FILE_NOT_EMAIL
156
157
158 class DuplicateMessageIdError(NotmuchError):
159     status = STATUS.DUPLICATE_MESSAGE_ID
160
161
162 class NullPointerError(NotmuchError):
163     status = STATUS.NULL_POINTER
164
165
166 class TagTooLongError(NotmuchError):
167     status = STATUS.TAG_TOO_LONG
168
169
170 class UnbalancedFreezeThawError(NotmuchError):
171     status = STATUS.UNBALANCED_FREEZE_THAW
172
173
174 class UnbalancedAtomicError(NotmuchError):
175     status = STATUS.UNBALANCED_ATOMIC
176
177
178 class NotInitializedError(NotmuchError):
179     """Derived from NotmuchError, this occurs if the underlying data
180     structure (e.g. database is not initialized (yet) or an iterator has
181     been exhausted. You can test for NotmuchError with .status =
182     STATUS.NOT_INITIALIZED"""
183     status = STATUS.NOT_INITIALIZED