Bump to version 3.6
[rss2email.git] / rss2email / error.py
1 # Copyright (C) 2012-2013 W. Trevor King <wking@tremily.us>
2 #
3 # This file is part of rss2email.
4 #
5 # rss2email is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 2 of the License, or (at your option) version 3 of
8 # the License.
9 #
10 # rss2email is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along with
15 # rss2email.  If not, see <http://www.gnu.org/licenses/>.
16
17 """rss2email-specific errors
18 """
19
20 import sys as _sys
21
22 from . import LOG as _LOG
23 from . import __version__, __url__, __email__
24
25 import pprint as _pprint
26
27 import feedparser as _feedparser
28 import html2text as _html2text
29
30
31 class RSS2EmailError (Exception):
32     def __init__(self, message):
33         super(RSS2EmailError, self).__init__(message)
34
35     def log(self):
36         _LOG.error(str(self))
37         if self.__cause__ is not None:
38             _LOG.error('cause: {}'.format(self.__cause__))
39
40
41 class TimeoutError (RSS2EmailError):
42     def __init__(self, time_limited_function, message=None):
43         if message is None:
44             if time_limited_function.error is not None:
45                 message = (
46                     'error while running time limited function: {}'.format(
47                         time_limited_function.error[1]))
48             else:
49                 message = '{} second timeout exceeded'.format(
50                     time_limited_function.timeout)
51         super(TimeoutError, self).__init__(message=message)
52         self.time_limited_function = time_limited_function
53
54
55 class NoValidEncodingError (RSS2EmailError, ValueError):
56     def __init__(self, string, encodings):
57         message = 'no valid encoding for {} in {}'.format(string, encodings)
58         super(NoValidEncodingError, self).__init__(message=message)
59         self.string = string
60         self.encodings = encodings
61
62
63 class SMTPConnectionError (ValueError, RSS2EmailError):
64     def __init__(self, server, message=None):
65         if message is None:
66             message = 'could not connect to mail server {}'.format(server)
67         super(SMTPConnectionError, self).__init__(message=message)
68         self.server = server
69
70     def log(self):
71         super(SMTPConnectionError, self).log()
72         _LOG.warning(
73             'check your config file to confirm that smtp-server and other '
74             'mail server settings are configured properly')
75         if hasattr(self.__cause__, 'reason'):
76             _LOG.error('reason: {}'.format(self.__cause__.reason))
77
78
79 class SMTPAuthenticationError (SMTPConnectionError):
80     def __init__(self, server, username):
81         message = (
82             'could not authenticate with mail server {} as user {}'.format(
83                 server, username))
84         super(SMTPAuthenticationError, self).__init__(
85             server=server, message=message)
86         self.username = username
87
88
89 class IMAPConnectionError (ValueError, RSS2EmailError):
90     def __init__(self, server, port, message=None):
91         if message is None:
92             message = 'could not connect to mail server {}:{}'.format(
93                 server, port)
94         super(IMAPConnectionError, self).__init__(message=message)
95         self.server = server
96         self.port = port
97
98     def log(self):
99         super(IMAPConnectionError, self).log()
100         _LOG.warning(
101             'check your config file to confirm that imap-server and other '
102             'mail server settings are configured properly')
103         if hasattr(self.__cause__, 'reason'):
104             _LOG.error('reason: {}'.format(self.__cause__.reason))
105
106
107 class IMAPAuthenticationError (IMAPConnectionError):
108     def __init__(self, server, port, username):
109         message = (
110             'could not authenticate with mail server {}:{} as user {}'.format(
111                 server, port, username))
112         super(IMAPAuthenticationError, self).__init__(
113             server=server, port=port, message=message)
114         self.username = username
115
116
117 class SendmailError (RSS2EmailError):
118     def __init__(self, status=None, stdout=None, stderr=None):
119         if status:
120             message = 'sendmail exited with code {}'.format(status)
121         else:
122             message = ''
123         super(SendmailError, self).__init__(message=message)
124         self.status = status
125         self.stdout = stdout
126         self.stderr = stderr
127
128     def log(self):
129         super(SendmailError, self).log()
130         _LOG.warning((
131                 'Error attempting to send email via sendmail. You may need '
132                 'to configure rss2email to use an SMTP server. Please refer '
133                 'to the rss2email documentation or website ({}) for complete '
134                 'documentation.').format(__url__))
135
136
137 class FeedError (RSS2EmailError):
138     def __init__(self, feed, message=None):
139         if message is None:
140             message = 'error with feed {}'.format(feed.name)
141         super(FeedError, self).__init__(message=message)
142         self.feed = feed
143
144
145 class InvalidFeedConfig (FeedError):
146     def __init__(self, setting, feed, message=None, **kwargs):
147         if not message:
148             message = "invalid feed configuration {}".format(
149                 {setting: getattr(feed, setting)})
150         super(InvalidFeedConfig, self).__init__(
151             feed=feed, message=message, **kwargs)
152         self.setting = setting
153
154
155 class InvalidFeedName (InvalidFeedConfig):
156     def __init__(self, name, **kwargs):
157         message = "invalid feed name '{}'".format(name)
158         super(InvalidFeedName, self).__init__(
159             setting='name', message=message, **kwargs)
160
161
162 class ProcessingError (FeedError):
163     def __init__(self, parsed, feed, message=None, **kwargs):
164         if message is None:
165             message = 'error processing feed {}'.format(feed)
166         super(ProcessingError, self).__init__(feed=feed, message=message)
167         self.parsed = parsed
168
169     def log(self):
170         super(ProcessingError, self).log()
171         if type(self) == ProcessingError:  # not a more specific subclass
172             _LOG.warning(
173                 '=== rss2email encountered a problem with this feed ===')
174             _LOG.warning(
175                 '=== See the rss2email FAQ at {} for assistance ==='.format(
176                     __url__))
177             _LOG.warning(
178                 '=== If this occurs repeatedly, send this to {} ==='.format(
179                     __email__))
180             _LOG.warning(
181                 'error: {} {}'.format(
182                     self.parsed.get('bozo_exception', "can't process"),
183                     self.feed.url))
184             _LOG.warning(_pprint.pformat(self.parsed))
185             _LOG.warning('rss2email {}'.format(__version__))
186             _LOG.warning('feedparser {}'.format(_feedparser.__version__))
187             _LOG.warning('html2text {}'.format(_html2text.__version__))
188             _LOG.warning('Python {}'.format(_sys.version))
189             _LOG.warning('=== END HERE ===')
190
191
192 class HTTPError (FeedError):
193     def __init__(self, status, feed, **kwargs):
194         message = 'HTTP status {} fetching feed {}'.format(status, feed)
195         super(HTTPError, self).__init__(feed=feed, message=message)
196         self.status = status
197
198
199 class FeedsError (RSS2EmailError):
200     def __init__(self, feeds=None, message=None, **kwargs):
201         if message is None:
202             message = 'error with feeds'
203         super(FeedsError, self).__init__(message=message, **kwargs)
204         self.feeds = feeds
205
206
207 class DataFileError (FeedsError):
208     def __init__(self, feeds, message=None):
209         if message is None:
210             message = 'problem with the feed data file {}'.format(
211                 feeds.datafile)
212         super(DataFileError, self).__init__(feeds=feeds, message=message)
213
214
215 class NoDataFile (DataFileError):
216     def __init__(self, feeds):
217         message = 'feed data file {} does not exist'.format(feeds.datafile)
218         super(NoDataFile, self).__init__(feeds=feeds, message=message)
219
220     def log(self):
221         super(NoDataFile, self).log()
222         _LOG.warning(
223             "if you're using r2e for the first time, you have to run "
224             "'r2e new' first.")
225
226
227 class NoToEmailAddress (InvalidFeedConfig, FeedsError):
228     def __init__(self, feed, **kwargs):
229         message = 'no target email address has been defined'
230         super(NoToEmailAddress, self).__init__(
231             setting='to', feed=feed, message=message, **kwargs)
232
233     def log(self):
234         super(NoToEmailAddress, self).log()
235         _LOG.warning(
236             "please run 'r2e email emailaddress' or "
237             "'r2e add name url emailaddress'.")
238
239
240 class FeedIndexError (FeedsError, IndexError):
241     def __init__(self, index, message=None, **kwargs):
242         if message is None:
243             message = 'feed {!r} not found'.format(index)
244         super(FeedIndexError, self).__init__(
245             message=message, **kwargs)
246         self.index = index
247
248
249 class OPMLReadError (RSS2EmailError):
250     def __init__(self, **kwargs):
251         message = 'error reading OPML'
252         super(OPMLReadError, self).__init__(message=message, **kwargs)