web_exceptions.py 10 KB

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