web_exceptions.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. import warnings
  2. from typing import Any, Dict, Iterable, List, Optional, Set # noqa
  3. from yarl import URL
  4. from .typedefs import LooseHeaders, StrOrURL
  5. from .web_response import Response
  6. __all__ = (
  7. "HTTPException",
  8. "HTTPError",
  9. "HTTPRedirection",
  10. "HTTPSuccessful",
  11. "HTTPOk",
  12. "HTTPCreated",
  13. "HTTPAccepted",
  14. "HTTPNonAuthoritativeInformation",
  15. "HTTPNoContent",
  16. "HTTPResetContent",
  17. "HTTPPartialContent",
  18. "HTTPMultipleChoices",
  19. "HTTPMovedPermanently",
  20. "HTTPFound",
  21. "HTTPSeeOther",
  22. "HTTPNotModified",
  23. "HTTPUseProxy",
  24. "HTTPTemporaryRedirect",
  25. "HTTPPermanentRedirect",
  26. "HTTPClientError",
  27. "HTTPBadRequest",
  28. "HTTPUnauthorized",
  29. "HTTPPaymentRequired",
  30. "HTTPForbidden",
  31. "HTTPNotFound",
  32. "HTTPMethodNotAllowed",
  33. "HTTPNotAcceptable",
  34. "HTTPProxyAuthenticationRequired",
  35. "HTTPRequestTimeout",
  36. "HTTPConflict",
  37. "HTTPGone",
  38. "HTTPLengthRequired",
  39. "HTTPPreconditionFailed",
  40. "HTTPRequestEntityTooLarge",
  41. "HTTPRequestURITooLong",
  42. "HTTPUnsupportedMediaType",
  43. "HTTPRequestRangeNotSatisfiable",
  44. "HTTPExpectationFailed",
  45. "HTTPMisdirectedRequest",
  46. "HTTPUnprocessableEntity",
  47. "HTTPFailedDependency",
  48. "HTTPUpgradeRequired",
  49. "HTTPPreconditionRequired",
  50. "HTTPTooManyRequests",
  51. "HTTPRequestHeaderFieldsTooLarge",
  52. "HTTPUnavailableForLegalReasons",
  53. "HTTPServerError",
  54. "HTTPInternalServerError",
  55. "HTTPNotImplemented",
  56. "HTTPBadGateway",
  57. "HTTPServiceUnavailable",
  58. "HTTPGatewayTimeout",
  59. "HTTPVersionNotSupported",
  60. "HTTPVariantAlsoNegotiates",
  61. "HTTPInsufficientStorage",
  62. "HTTPNotExtended",
  63. "HTTPNetworkAuthenticationRequired",
  64. )
  65. ############################################################
  66. # HTTP Exceptions
  67. ############################################################
  68. class HTTPException(Response, Exception):
  69. # You should set in subclasses:
  70. # status = 200
  71. status_code = -1
  72. empty_body = False
  73. __http_exception__ = True
  74. def __init__(
  75. self,
  76. *,
  77. headers: Optional[LooseHeaders] = None,
  78. reason: Optional[str] = None,
  79. body: Any = None,
  80. text: Optional[str] = None,
  81. content_type: Optional[str] = None,
  82. ) -> None:
  83. if body is not None:
  84. warnings.warn(
  85. "body argument is deprecated for http web exceptions",
  86. DeprecationWarning,
  87. )
  88. Response.__init__(
  89. self,
  90. status=self.status_code,
  91. headers=headers,
  92. reason=reason,
  93. body=body,
  94. text=text,
  95. content_type=content_type,
  96. )
  97. Exception.__init__(self, self.reason)
  98. if self.body is None and not self.empty_body:
  99. self.text = f"{self.status}: {self.reason}"
  100. def __bool__(self) -> bool:
  101. return True
  102. class HTTPError(HTTPException):
  103. """Base class for exceptions with status codes in the 400s and 500s."""
  104. class HTTPRedirection(HTTPException):
  105. """Base class for exceptions with status codes in the 300s."""
  106. class HTTPSuccessful(HTTPException):
  107. """Base class for exceptions with status codes in the 200s."""
  108. class HTTPOk(HTTPSuccessful):
  109. status_code = 200
  110. class HTTPCreated(HTTPSuccessful):
  111. status_code = 201
  112. class HTTPAccepted(HTTPSuccessful):
  113. status_code = 202
  114. class HTTPNonAuthoritativeInformation(HTTPSuccessful):
  115. status_code = 203
  116. class HTTPNoContent(HTTPSuccessful):
  117. status_code = 204
  118. empty_body = True
  119. class HTTPResetContent(HTTPSuccessful):
  120. status_code = 205
  121. empty_body = True
  122. class HTTPPartialContent(HTTPSuccessful):
  123. status_code = 206
  124. ############################################################
  125. # 3xx redirection
  126. ############################################################
  127. class _HTTPMove(HTTPRedirection):
  128. def __init__(
  129. self,
  130. location: StrOrURL,
  131. *,
  132. headers: Optional[LooseHeaders] = None,
  133. reason: Optional[str] = None,
  134. body: Any = None,
  135. text: Optional[str] = None,
  136. content_type: Optional[str] = None,
  137. ) -> None:
  138. if not location:
  139. raise ValueError("HTTP redirects need a location to redirect to.")
  140. super().__init__(
  141. headers=headers,
  142. reason=reason,
  143. body=body,
  144. text=text,
  145. content_type=content_type,
  146. )
  147. self.headers["Location"] = str(URL(location))
  148. self.location = location
  149. class HTTPMultipleChoices(_HTTPMove):
  150. status_code = 300
  151. class HTTPMovedPermanently(_HTTPMove):
  152. status_code = 301
  153. class HTTPFound(_HTTPMove):
  154. status_code = 302
  155. # This one is safe after a POST (the redirected location will be
  156. # retrieved with GET):
  157. class HTTPSeeOther(_HTTPMove):
  158. status_code = 303
  159. class HTTPNotModified(HTTPRedirection):
  160. # FIXME: this should include a date or etag header
  161. status_code = 304
  162. empty_body = True
  163. class HTTPUseProxy(_HTTPMove):
  164. # Not a move, but looks a little like one
  165. status_code = 305
  166. class HTTPTemporaryRedirect(_HTTPMove):
  167. status_code = 307
  168. class HTTPPermanentRedirect(_HTTPMove):
  169. status_code = 308
  170. ############################################################
  171. # 4xx client error
  172. ############################################################
  173. class HTTPClientError(HTTPError):
  174. pass
  175. class HTTPBadRequest(HTTPClientError):
  176. status_code = 400
  177. class HTTPUnauthorized(HTTPClientError):
  178. status_code = 401
  179. class HTTPPaymentRequired(HTTPClientError):
  180. status_code = 402
  181. class HTTPForbidden(HTTPClientError):
  182. status_code = 403
  183. class HTTPNotFound(HTTPClientError):
  184. status_code = 404
  185. class HTTPMethodNotAllowed(HTTPClientError):
  186. status_code = 405
  187. def __init__(
  188. self,
  189. method: str,
  190. allowed_methods: Iterable[str],
  191. *,
  192. headers: Optional[LooseHeaders] = None,
  193. reason: Optional[str] = None,
  194. body: Any = None,
  195. text: Optional[str] = None,
  196. content_type: Optional[str] = None,
  197. ) -> None:
  198. allow = ",".join(sorted(allowed_methods))
  199. super().__init__(
  200. headers=headers,
  201. reason=reason,
  202. body=body,
  203. text=text,
  204. content_type=content_type,
  205. )
  206. self.headers["Allow"] = allow
  207. self.allowed_methods = set(allowed_methods) # type: Set[str]
  208. self.method = method.upper()
  209. class HTTPNotAcceptable(HTTPClientError):
  210. status_code = 406
  211. class HTTPProxyAuthenticationRequired(HTTPClientError):
  212. status_code = 407
  213. class HTTPRequestTimeout(HTTPClientError):
  214. status_code = 408
  215. class HTTPConflict(HTTPClientError):
  216. status_code = 409
  217. class HTTPGone(HTTPClientError):
  218. status_code = 410
  219. class HTTPLengthRequired(HTTPClientError):
  220. status_code = 411
  221. class HTTPPreconditionFailed(HTTPClientError):
  222. status_code = 412
  223. class HTTPRequestEntityTooLarge(HTTPClientError):
  224. status_code = 413
  225. def __init__(self, max_size: float, actual_size: float, **kwargs: Any) -> None:
  226. kwargs.setdefault(
  227. "text",
  228. "Maximum request body size {} exceeded, "
  229. "actual body size {}".format(max_size, actual_size),
  230. )
  231. super().__init__(**kwargs)
  232. class HTTPRequestURITooLong(HTTPClientError):
  233. status_code = 414
  234. class HTTPUnsupportedMediaType(HTTPClientError):
  235. status_code = 415
  236. class HTTPRequestRangeNotSatisfiable(HTTPClientError):
  237. status_code = 416
  238. class HTTPExpectationFailed(HTTPClientError):
  239. status_code = 417
  240. class HTTPMisdirectedRequest(HTTPClientError):
  241. status_code = 421
  242. class HTTPUnprocessableEntity(HTTPClientError):
  243. status_code = 422
  244. class HTTPFailedDependency(HTTPClientError):
  245. status_code = 424
  246. class HTTPUpgradeRequired(HTTPClientError):
  247. status_code = 426
  248. class HTTPPreconditionRequired(HTTPClientError):
  249. status_code = 428
  250. class HTTPTooManyRequests(HTTPClientError):
  251. status_code = 429
  252. class HTTPRequestHeaderFieldsTooLarge(HTTPClientError):
  253. status_code = 431
  254. class HTTPUnavailableForLegalReasons(HTTPClientError):
  255. status_code = 451
  256. def __init__(
  257. self,
  258. link: str,
  259. *,
  260. headers: Optional[LooseHeaders] = None,
  261. reason: Optional[str] = None,
  262. body: Any = None,
  263. text: Optional[str] = None,
  264. content_type: Optional[str] = None,
  265. ) -> None:
  266. super().__init__(
  267. headers=headers,
  268. reason=reason,
  269. body=body,
  270. text=text,
  271. content_type=content_type,
  272. )
  273. self.headers["Link"] = '<%s>; rel="blocked-by"' % link
  274. self.link = link
  275. ############################################################
  276. # 5xx Server Error
  277. ############################################################
  278. # Response status codes beginning with the digit "5" indicate cases in
  279. # which the server is aware that it has erred or is incapable of
  280. # performing the request. Except when responding to a HEAD request, the
  281. # server SHOULD include an entity containing an explanation of the error
  282. # situation, and whether it is a temporary or permanent condition. User
  283. # agents SHOULD display any included entity to the user. These response
  284. # codes are applicable to any request method.
  285. class HTTPServerError(HTTPError):
  286. pass
  287. class HTTPInternalServerError(HTTPServerError):
  288. status_code = 500
  289. class HTTPNotImplemented(HTTPServerError):
  290. status_code = 501
  291. class HTTPBadGateway(HTTPServerError):
  292. status_code = 502
  293. class HTTPServiceUnavailable(HTTPServerError):
  294. status_code = 503
  295. class HTTPGatewayTimeout(HTTPServerError):
  296. status_code = 504
  297. class HTTPVersionNotSupported(HTTPServerError):
  298. status_code = 505
  299. class HTTPVariantAlsoNegotiates(HTTPServerError):
  300. status_code = 506
  301. class HTTPInsufficientStorage(HTTPServerError):
  302. status_code = 507
  303. class HTTPNotExtended(HTTPServerError):
  304. status_code = 510
  305. class HTTPNetworkAuthenticationRequired(HTTPServerError):
  306. status_code = 511