Imported Upstream version 12.1.0
[contrib/python-twisted.git] / twisted / web / error.py
1 # -*- test-case-name: twisted.web.test.test_error -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 """
6 Exception definitions for L{twisted.web}.
7 """
8
9 import operator, warnings
10
11 from twisted.web import http
12
13
14 class Error(Exception):
15     """
16     A basic HTTP error.
17
18     @type status: C{str}
19     @ivar status: Refers to an HTTP status code, for example L{http.NOT_FOUND}.
20
21     @type message: C{str}
22     @param message: A short error message, for example "NOT FOUND".
23
24     @type response: C{str}
25     @ivar response: A complete HTML document for an error page.
26     """
27     def __init__(self, code, message=None, response=None):
28         """
29         Initializes a basic exception.
30
31         @type code: C{str}
32         @param code: Refers to an HTTP status code, for example
33             L{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to a
34             descriptive string that is used instead.
35
36         @type message: C{str}
37         @param message: A short error message, for example "NOT FOUND".
38
39         @type response: C{str}
40         @param response: A complete HTML document for an error page.
41         """
42         if not message:
43             try:
44                 message = http.responses.get(int(code))
45             except ValueError:
46                 # If code wasn't a stringified int, can't map the
47                 # status code to a descriptive string so keep message
48                 # unchanged.
49                 pass
50
51         Exception.__init__(self, code, message, response)
52         self.status = code
53         self.message = message
54         self.response = response
55
56
57     def __str__(self):
58         return '%s %s' % (self[0], self[1])
59
60
61
62 class PageRedirect(Error):
63     """
64     A request resulted in an HTTP redirect.
65
66     @type location: C{str}
67     @ivar location: The location of the redirect which was not followed.
68     """
69     def __init__(self, code, message=None, response=None, location=None):
70         """
71         Initializes a page redirect exception.
72
73         @type code: C{str}
74         @param code: Refers to an HTTP status code, for example
75             L{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to a
76             descriptive string that is used instead.
77
78         @type message: C{str}
79         @param message: A short error message, for example "NOT FOUND".
80
81         @type response: C{str}
82         @param response: A complete HTML document for an error page.
83
84         @type location: C{str}
85         @param location: The location response-header field value. It is an
86             absolute URI used to redirect the receiver to a location other than
87             the Request-URI so the request can be completed.
88         """
89         if not message:
90             try:
91                 message = http.responses.get(int(code))
92             except ValueError:
93                 # If code wasn't a stringified int, can't map the
94                 # status code to a descriptive string so keep message
95                 # unchanged.
96                 pass
97
98         if location and message:
99             message = "%s to %s" % (message, location)
100
101         Error.__init__(self, code, message, response)
102         self.location = location
103
104
105
106 class InfiniteRedirection(Error):
107     """
108     HTTP redirection is occurring endlessly.
109
110     @type location: C{str}
111     @ivar location: The first URL in the series of redirections which was
112         not followed.
113     """
114     def __init__(self, code, message=None, response=None, location=None):
115         """
116         Initializes an infinite redirection exception.
117
118         @type code: C{str}
119         @param code: Refers to an HTTP status code, for example
120             L{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to a
121             descriptive string that is used instead.
122
123         @type message: C{str}
124         @param message: A short error message, for example "NOT FOUND".
125
126         @type response: C{str}
127         @param response: A complete HTML document for an error page.
128
129         @type location: C{str}
130         @param location: The location response-header field value. It is an
131             absolute URI used to redirect the receiver to a location other than
132             the Request-URI so the request can be completed.
133         """
134         if not message:
135             try:
136                 message = http.responses.get(int(code))
137             except ValueError:
138                 # If code wasn't a stringified int, can't map the
139                 # status code to a descriptive string so keep message
140                 # unchanged.
141                 pass
142
143         if location and message:
144             message = "%s to %s" % (message, location)
145
146         Error.__init__(self, code, message, response)
147         self.location = location
148
149
150
151 class RedirectWithNoLocation(Error):
152     """
153     Exception passed to L{ResponseFailed} if we got a redirect without a
154     C{Location} header field.
155
156     @since: 11.1
157     """
158
159     def __init__(self, code, message, uri):
160         """
161         Initializes a page redirect exception when no location is given.
162
163         @type code: C{str}
164         @param code: Refers to an HTTP status code, for example
165             L{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to
166             a descriptive string that is used instead.
167
168         @type message: C{str}
169         @param message: A short error message.
170
171         @type uri: C{str}
172         @param uri: The URI which failed to give a proper location header
173             field.
174         """
175         message = "%s to %s" % (message, uri)
176
177         Error.__init__(self, code, message)
178         self.uri = uri
179
180
181
182 class UnsupportedMethod(Exception):
183     """
184     Raised by a resource when faced with a strange request method.
185
186     RFC 2616 (HTTP 1.1) gives us two choices when faced with this situtation:
187     If the type of request is known to us, but not allowed for the requested
188     resource, respond with NOT_ALLOWED.  Otherwise, if the request is something
189     we don't know how to deal with in any case, respond with NOT_IMPLEMENTED.
190
191     When this exception is raised by a Resource's render method, the server
192     will make the appropriate response.
193
194     This exception's first argument MUST be a sequence of the methods the
195     resource *does* support.
196     """
197
198     allowedMethods = ()
199
200     def __init__(self, allowedMethods, *args):
201         Exception.__init__(self, allowedMethods, *args)
202         self.allowedMethods = allowedMethods
203
204         if not operator.isSequenceType(allowedMethods):
205             why = "but my first argument is not a sequence."
206             s = ("First argument must be a sequence of"
207                  " supported methods, %s" % (why,))
208             raise TypeError, s
209
210
211
212 class SchemeNotSupported(Exception):
213     """
214     The scheme of a URI was not one of the supported values.
215     """
216
217
218
219 from twisted.web import resource as _resource
220
221 class ErrorPage(_resource.ErrorPage):
222     """
223     Deprecated alias for L{twisted.web.resource.ErrorPage}.
224     """
225     def __init__(self, *args, **kwargs):
226         warnings.warn(
227             "twisted.web.error.ErrorPage is deprecated since Twisted 9.0.  "
228             "See twisted.web.resource.ErrorPage.", DeprecationWarning,
229             stacklevel=2)
230         _resource.ErrorPage.__init__(self, *args, **kwargs)
231
232
233
234 class NoResource(_resource.NoResource):
235     """
236     Deprecated alias for L{twisted.web.resource.NoResource}.
237     """
238     def __init__(self, *args, **kwargs):
239         warnings.warn(
240             "twisted.web.error.NoResource is deprecated since Twisted 9.0.  "
241             "See twisted.web.resource.NoResource.", DeprecationWarning,
242             stacklevel=2)
243         _resource.NoResource.__init__(self, *args, **kwargs)
244
245
246
247 class ForbiddenResource(_resource.ForbiddenResource):
248     """
249     Deprecated alias for L{twisted.web.resource.ForbiddenResource}.
250     """
251     def __init__(self, *args, **kwargs):
252         warnings.warn(
253             "twisted.web.error.ForbiddenResource is deprecated since Twisted "
254             "9.0.  See twisted.web.resource.ForbiddenResource.",
255             DeprecationWarning, stacklevel=2)
256         _resource.ForbiddenResource.__init__(self, *args, **kwargs)
257
258
259
260 class RenderError(Exception):
261     """
262     Base exception class for all errors which can occur during template
263     rendering.
264     """
265
266
267
268 class MissingRenderMethod(RenderError):
269     """
270     Tried to use a render method which does not exist.
271
272     @ivar element: The element which did not have the render method.
273     @ivar renderName: The name of the renderer which could not be found.
274     """
275     def __init__(self, element, renderName):
276         RenderError.__init__(self, element, renderName)
277         self.element = element
278         self.renderName = renderName
279
280
281     def __repr__(self):
282         return '%r: %r had no render method named %r' % (
283             self.__class__.__name__, self.element, self.renderName)
284
285
286
287 class MissingTemplateLoader(RenderError):
288     """
289     L{MissingTemplateLoader} is raised when trying to render an Element without
290     a template loader, i.e. a C{loader} attribute.
291
292     @ivar element: The Element which did not have a document factory.
293     """
294     def __init__(self, element):
295         RenderError.__init__(self, element)
296         self.element = element
297
298
299     def __repr__(self):
300         return '%r: %r had no loader' % (self.__class__.__name__,
301                                          self.element)
302
303
304
305 class UnexposedMethodError(Exception):
306     """
307     Raised on any attempt to get a method which has not been exposed.
308     """
309
310
311
312 class UnfilledSlot(Exception):
313     """
314     During flattening, a slot with no associated data was encountered.
315     """
316
317
318
319 class UnsupportedType(Exception):
320     """
321     During flattening, an object of a type which cannot be flattened was
322     encountered.
323     """
324
325
326
327 class FlattenerError(Exception):
328     """
329     An error occurred while flattening an object.
330
331     @ivar _roots: A list of the objects on the flattener's stack at the time
332         the unflattenable object was encountered.  The first element is least
333         deeply nested object and the last element is the most deeply nested.
334     """
335     def __init__(self, exception, roots, traceback):
336         self._exception = exception
337         self._roots = roots
338         self._traceback = traceback
339         Exception.__init__(self, exception, roots, traceback)
340
341
342     def _formatRoot(self, obj):
343         """
344         Convert an object from C{self._roots} to a string suitable for
345         inclusion in a render-traceback (like a normal Python traceback, but
346         can include "frame" source locations which are not in Python source
347         files).
348
349         @param obj: Any object which can be a render step I{root}.
350             Typically, L{Tag}s, strings, and other simple Python types.
351
352         @return: A string representation of C{obj}.
353         @rtype: L{str}
354         """
355         # There's a circular dependency between this class and 'Tag', although
356         # only for an isinstance() check.
357         from twisted.web.template import Tag
358         if isinstance(obj, (str, unicode)):
359             # It's somewhat unlikely that there will ever be a str in the roots
360             # list.  However, something like a MemoryError during a str.replace
361             # call (eg, replacing " with ") could possibly cause this.
362             # Likewise, UTF-8 encoding a unicode string to a byte string might
363             # fail like this.
364             if len(obj) > 40:
365                 if isinstance(obj, str):
366                     prefix = 1
367                 else:
368                     prefix = 2
369                 return repr(obj[:20])[:-1] + '<...>' + repr(obj[-20:])[prefix:]
370             else:
371                 return repr(obj)
372         elif isinstance(obj, Tag):
373             if obj.filename is None:
374                 return 'Tag <' + obj.tagName + '>'
375             else:
376                 return "File \"%s\", line %d, column %d, in \"%s\"" % (
377                     obj.filename, obj.lineNumber,
378                     obj.columnNumber, obj.tagName)
379         else:
380             return repr(obj)
381
382
383     def __repr__(self):
384         """
385         Present a string representation which includes a template traceback, so
386         we can tell where this error occurred in the template, as well as in
387         Python.
388         """
389         # Avoid importing things unnecessarily until we actually need them;
390         # since this is an 'error' module we should be extra paranoid about
391         # that.
392         from traceback import format_list
393         if self._roots:
394             roots = '  ' + '\n  '.join([
395                     self._formatRoot(r) for r in self._roots]) + '\n'
396         else:
397             roots = ''
398         if self._traceback:
399             traceback = '\n'.join([
400                     line
401                     for entry in format_list(self._traceback)
402                     for line in entry.splitlines()]) + '\n'
403         else:
404             traceback = ''
405         return (
406             'Exception while flattening:\n' +
407             roots + traceback +
408             self._exception.__class__.__name__ + ': ' +
409             str(self._exception) + '\n')
410
411
412     def __str__(self):
413         return repr(self)
414
415
416
417 __all__ = [
418     'Error', 'PageRedirect', 'InfiniteRedirection', 'ErrorPage', 'NoResource',
419     'ForbiddenResource', 'RenderError', 'MissingRenderMethod',
420     'MissingTemplateLoader', 'UnexposedMethodError', 'UnfilledSlot',
421     'UnsupportedType', 'FlattenerError', 'RedirectWithNoLocation'
422 ]