PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/twisted/web/error.py

https://bitbucket.org/pitrou/t3k
Python | 382 lines | 352 code | 7 blank | 23 comment | 13 complexity | d4b4f83e15d0856028860dfe8116ddb9 MD5 | raw file
  1. # -*- test-case-name: twisted.web.test.test_error -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Exception definitions for L{twisted.web}.
  6. """
  7. import operator
  8. from twisted.web import http
  9. import collections
  10. class Error(Exception):
  11. """
  12. A basic HTTP error.
  13. @type status: C{str}
  14. @ivar status: Refers to an HTTP status code, for example L{http.NOT_FOUND}.
  15. @type message: C{str}
  16. @param message: A short error message, for example "NOT FOUND".
  17. @type response: C{str}
  18. @ivar response: A complete HTML document for an error page.
  19. """
  20. def __init__(self, code, message=None, response=None):
  21. """
  22. Initializes a basic exception.
  23. @type code: C{str}
  24. @param code: Refers to an HTTP status code, for example
  25. L{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to a
  26. descriptive string that is used instead.
  27. @type message: C{str}
  28. @param message: A short error message, for example "NOT FOUND".
  29. @type response: C{str}
  30. @param response: A complete HTML document for an error page.
  31. """
  32. if not message:
  33. try:
  34. message = http.responses.get(int(code))
  35. except ValueError:
  36. # If code wasn't a stringified int, can't map the
  37. # status code to a descriptive string so keep message
  38. # unchanged.
  39. pass
  40. Exception.__init__(self, code, message, response)
  41. self.status = code
  42. self.message = message
  43. self.response = response
  44. def __str__(self):
  45. return '%s %s' % (self[0], self[1])
  46. class PageRedirect(Error):
  47. """
  48. A request resulted in an HTTP redirect.
  49. @type location: C{str}
  50. @ivar location: The location of the redirect which was not followed.
  51. """
  52. def __init__(self, code, message=None, response=None, location=None):
  53. """
  54. Initializes a page redirect exception.
  55. @type code: C{str}
  56. @param code: Refers to an HTTP status code, for example
  57. L{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to a
  58. descriptive string that is used instead.
  59. @type message: C{str}
  60. @param message: A short error message, for example "NOT FOUND".
  61. @type response: C{str}
  62. @param response: A complete HTML document for an error page.
  63. @type location: C{str}
  64. @param location: The location response-header field value. It is an
  65. absolute URI used to redirect the receiver to a location other than
  66. the Request-URI so the request can be completed.
  67. """
  68. if not message:
  69. try:
  70. message = http.responses.get(int(code))
  71. except ValueError:
  72. # If code wasn't a stringified int, can't map the
  73. # status code to a descriptive string so keep message
  74. # unchanged.
  75. pass
  76. if location and message:
  77. message = "%s to %s" % (message, location)
  78. Error.__init__(self, code, message, response)
  79. self.location = location
  80. class InfiniteRedirection(Error):
  81. """
  82. HTTP redirection is occurring endlessly.
  83. @type location: C{str}
  84. @ivar location: The first URL in the series of redirections which was
  85. not followed.
  86. """
  87. def __init__(self, code, message=None, response=None, location=None):
  88. """
  89. Initializes an infinite redirection exception.
  90. @type code: C{str}
  91. @param code: Refers to an HTTP status code, for example
  92. L{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to a
  93. descriptive string that is used instead.
  94. @type message: C{str}
  95. @param message: A short error message, for example "NOT FOUND".
  96. @type response: C{str}
  97. @param response: A complete HTML document for an error page.
  98. @type location: C{str}
  99. @param location: The location response-header field value. It is an
  100. absolute URI used to redirect the receiver to a location other than
  101. the Request-URI so the request can be completed.
  102. """
  103. if not message:
  104. try:
  105. message = http.responses.get(int(code))
  106. except ValueError:
  107. # If code wasn't a stringified int, can't map the
  108. # status code to a descriptive string so keep message
  109. # unchanged.
  110. pass
  111. if location and message:
  112. message = "%s to %s" % (message, location)
  113. Error.__init__(self, code, message, response)
  114. self.location = location
  115. class RedirectWithNoLocation(Error):
  116. """
  117. Exception passed to L{ResponseFailed} if we got a redirect without a
  118. C{Location} header field.
  119. @since: 11.1
  120. """
  121. def __init__(self, code, message, uri):
  122. """
  123. Initializes a page redirect exception when no location is given.
  124. @type code: C{str}
  125. @param code: Refers to an HTTP status code, for example
  126. L{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to
  127. a descriptive string that is used instead.
  128. @type message: C{str}
  129. @param message: A short error message.
  130. @type uri: C{str}
  131. @param uri: The URI which failed to give a proper location header
  132. field.
  133. """
  134. message = "%s to %s" % (message, uri)
  135. Error.__init__(self, code, message)
  136. self.uri = uri
  137. class UnsupportedMethod(Exception):
  138. """
  139. Raised by a resource when faced with a strange request method.
  140. RFC 2616 (HTTP 1.1) gives us two choices when faced with this situtation:
  141. If the type of request is known to us, but not allowed for the requested
  142. resource, respond with NOT_ALLOWED. Otherwise, if the request is something
  143. we don't know how to deal with in any case, respond with NOT_IMPLEMENTED.
  144. When this exception is raised by a Resource's render method, the server
  145. will make the appropriate response.
  146. This exception's first argument MUST be a sequence of the methods the
  147. resource *does* support.
  148. """
  149. allowedMethods = ()
  150. def __init__(self, allowedMethods, *args):
  151. Exception.__init__(self, allowedMethods, *args)
  152. self.allowedMethods = allowedMethods
  153. if not isinstance(allowedMethods, collections.Sequence):
  154. why = "but my first argument is not a sequence."
  155. s = ("First argument must be a sequence of"
  156. " supported methods, %s" % (why,))
  157. raise TypeError(s)
  158. class SchemeNotSupported(Exception):
  159. """
  160. The scheme of a URI was not one of the supported values.
  161. """
  162. class RenderError(Exception):
  163. """
  164. Base exception class for all errors which can occur during template
  165. rendering.
  166. """
  167. class MissingRenderMethod(RenderError):
  168. """
  169. Tried to use a render method which does not exist.
  170. @ivar element: The element which did not have the render method.
  171. @ivar renderName: The name of the renderer which could not be found.
  172. """
  173. def __init__(self, element, renderName):
  174. RenderError.__init__(self, element, renderName)
  175. self.element = element
  176. self.renderName = renderName
  177. def __repr__(self):
  178. return '%r: %r had no render method named %r' % (
  179. self.__class__.__name__, self.element, self.renderName)
  180. class MissingTemplateLoader(RenderError):
  181. """
  182. L{MissingTemplateLoader} is raised when trying to render an Element without
  183. a template loader, i.e. a C{loader} attribute.
  184. @ivar element: The Element which did not have a document factory.
  185. """
  186. def __init__(self, element):
  187. RenderError.__init__(self, element)
  188. self.element = element
  189. def __repr__(self):
  190. return '%r: %r had no loader' % (self.__class__.__name__,
  191. self.element)
  192. class UnexposedMethodError(Exception):
  193. """
  194. Raised on any attempt to get a method which has not been exposed.
  195. """
  196. class UnfilledSlot(Exception):
  197. """
  198. During flattening, a slot with no associated data was encountered.
  199. """
  200. class UnsupportedType(Exception):
  201. """
  202. During flattening, an object of a type which cannot be flattened was
  203. encountered.
  204. """
  205. class FlattenerError(Exception):
  206. """
  207. An error occurred while flattening an object.
  208. @ivar _roots: A list of the objects on the flattener's stack at the time
  209. the unflattenable object was encountered. The first element is least
  210. deeply nested object and the last element is the most deeply nested.
  211. """
  212. def __init__(self, exception, roots, traceback):
  213. self._exception = exception
  214. self._roots = roots
  215. self._traceback = traceback
  216. Exception.__init__(self, exception, roots, traceback)
  217. def _formatRoot(self, obj):
  218. """
  219. Convert an object from C{self._roots} to a string suitable for
  220. inclusion in a render-traceback (like a normal Python traceback, but
  221. can include "frame" source locations which are not in Python source
  222. files).
  223. @param obj: Any object which can be a render step I{root}.
  224. Typically, L{Tag}s, strings, and other simple Python types.
  225. @return: A string representation of C{obj}.
  226. @rtype: L{str}
  227. """
  228. # There's a circular dependency between this class and 'Tag', although
  229. # only for an isinstance() check.
  230. from twisted.web.template import Tag
  231. if isinstance(obj, str):
  232. # It's somewhat unlikely that there will ever be a str in the roots
  233. # list. However, something like a MemoryError during a str.replace
  234. # call (eg, replacing " with ") could possibly cause this.
  235. # Likewise, UTF-8 encoding a unicode string to a byte string might
  236. # fail like this.
  237. if len(obj) > 40:
  238. if isinstance(obj, str):
  239. prefix = 1
  240. else:
  241. prefix = 2
  242. return repr(obj[:20])[:-1] + '<...>' + repr(obj[-20:])[prefix:]
  243. else:
  244. return repr(obj)
  245. elif isinstance(obj, Tag):
  246. if obj.filename is None:
  247. return 'Tag <' + obj.tagName + '>'
  248. else:
  249. return "File \"%s\", line %d, column %d, in \"%s\"" % (
  250. obj.filename, obj.lineNumber,
  251. obj.columnNumber, obj.tagName)
  252. else:
  253. return repr(obj)
  254. def __repr__(self):
  255. """
  256. Present a string representation which includes a template traceback, so
  257. we can tell where this error occurred in the template, as well as in
  258. Python.
  259. """
  260. # Avoid importing things unnecessarily until we actually need them;
  261. # since this is an 'error' module we should be extra paranoid about
  262. # that.
  263. from traceback import format_list
  264. if self._roots:
  265. roots = ' ' + '\n '.join([
  266. self._formatRoot(r) for r in self._roots]) + '\n'
  267. else:
  268. roots = ''
  269. if self._traceback:
  270. traceback = '\n'.join([
  271. line
  272. for entry in format_list(self._traceback)
  273. for line in entry.splitlines()]) + '\n'
  274. else:
  275. traceback = ''
  276. return (
  277. 'Exception while flattening:\n' +
  278. roots + traceback +
  279. self._exception.__class__.__name__ + ': ' +
  280. str(self._exception) + '\n')
  281. def __str__(self):
  282. return repr(self)
  283. __all__ = [
  284. 'Error', 'PageRedirect', 'InfiniteRedirection', 'RenderError',
  285. 'MissingRenderMethod', 'MissingTemplateLoader', 'UnexposedMethodError',
  286. 'UnfilledSlot', 'UnsupportedType', 'FlattenerError',
  287. 'RedirectWithNoLocation'
  288. ]