web_exceptions.py 10 KB

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