_exceptions.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. """
  2. Our exception hierarchy:
  3. * HTTPError
  4. x RequestError
  5. + TransportError
  6. - TimeoutException
  7. · ConnectTimeout
  8. · ReadTimeout
  9. · WriteTimeout
  10. · PoolTimeout
  11. - NetworkError
  12. · ConnectError
  13. · ReadError
  14. · WriteError
  15. · CloseError
  16. - ProtocolError
  17. · LocalProtocolError
  18. · RemoteProtocolError
  19. - ProxyError
  20. - UnsupportedProtocol
  21. + DecodingError
  22. + TooManyRedirects
  23. x HTTPStatusError
  24. * InvalidURL
  25. * CookieConflict
  26. * StreamError
  27. x StreamConsumed
  28. x StreamClosed
  29. x ResponseNotRead
  30. x RequestNotRead
  31. """
  32. import contextlib
  33. import typing
  34. if typing.TYPE_CHECKING:
  35. from ._models import Request, Response # pragma: no cover
  36. class HTTPError(Exception):
  37. """
  38. Base class for `RequestError` and `HTTPStatusError`.
  39. Useful for `try...except` blocks when issuing a request,
  40. and then calling `.raise_for_status()`.
  41. For example:
  42. ```
  43. try:
  44. response = httpx.get("https://www.example.com")
  45. response.raise_for_status()
  46. except httpx.HTTPError as exc:
  47. print(f"HTTP Exception for {exc.request.url} - {exc}")
  48. ```
  49. """
  50. def __init__(self, message: str) -> None:
  51. super().__init__(message)
  52. self._request: typing.Optional["Request"] = None
  53. @property
  54. def request(self) -> "Request":
  55. if self._request is None:
  56. raise RuntimeError("The .request property has not been set.")
  57. return self._request
  58. @request.setter
  59. def request(self, request: "Request") -> None:
  60. self._request = request
  61. class RequestError(HTTPError):
  62. """
  63. Base class for all exceptions that may occur when issuing a `.request()`.
  64. """
  65. def __init__(
  66. self, message: str, *, request: typing.Optional["Request"] = None
  67. ) -> None:
  68. super().__init__(message)
  69. # At the point an exception is raised we won't typically have a request
  70. # instance to associate it with.
  71. #
  72. # The 'request_context' context manager is used within the Client and
  73. # Response methods in order to ensure that any raised exceptions
  74. # have a `.request` property set on them.
  75. self._request = request
  76. class TransportError(RequestError):
  77. """
  78. Base class for all exceptions that occur at the level of the Transport API.
  79. """
  80. # Timeout exceptions...
  81. class TimeoutException(TransportError):
  82. """
  83. The base class for timeout errors.
  84. An operation has timed out.
  85. """
  86. class ConnectTimeout(TimeoutException):
  87. """
  88. Timed out while connecting to the host.
  89. """
  90. class ReadTimeout(TimeoutException):
  91. """
  92. Timed out while receiving data from the host.
  93. """
  94. class WriteTimeout(TimeoutException):
  95. """
  96. Timed out while sending data to the host.
  97. """
  98. class PoolTimeout(TimeoutException):
  99. """
  100. Timed out waiting to acquire a connection from the pool.
  101. """
  102. # Core networking exceptions...
  103. class NetworkError(TransportError):
  104. """
  105. The base class for network-related errors.
  106. An error occurred while interacting with the network.
  107. """
  108. class ReadError(NetworkError):
  109. """
  110. Failed to receive data from the network.
  111. """
  112. class WriteError(NetworkError):
  113. """
  114. Failed to send data through the network.
  115. """
  116. class ConnectError(NetworkError):
  117. """
  118. Failed to establish a connection.
  119. """
  120. class CloseError(NetworkError):
  121. """
  122. Failed to close a connection.
  123. """
  124. # Other transport exceptions...
  125. class ProxyError(TransportError):
  126. """
  127. An error occurred while establishing a proxy connection.
  128. """
  129. class UnsupportedProtocol(TransportError):
  130. """
  131. Attempted to make a request to an unsupported protocol.
  132. For example issuing a request to `ftp://www.example.com`.
  133. """
  134. class ProtocolError(TransportError):
  135. """
  136. The protocol was violated.
  137. """
  138. class LocalProtocolError(ProtocolError):
  139. """
  140. A protocol was violated by the client.
  141. For example if the user instantiated a `Request` instance explicitly,
  142. failed to include the mandatory `Host:` header, and then issued it directly
  143. using `client.send()`.
  144. """
  145. class RemoteProtocolError(ProtocolError):
  146. """
  147. The protocol was violated by the server.
  148. For example, returning malformed HTTP.
  149. """
  150. # Other request exceptions...
  151. class DecodingError(RequestError):
  152. """
  153. Decoding of the response failed, due to a malformed encoding.
  154. """
  155. class TooManyRedirects(RequestError):
  156. """
  157. Too many redirects.
  158. """
  159. # Client errors
  160. class HTTPStatusError(HTTPError):
  161. """
  162. The response had an error HTTP status of 4xx or 5xx.
  163. May be raised when calling `response.raise_for_status()`
  164. """
  165. def __init__(
  166. self, message: str, *, request: "Request", response: "Response"
  167. ) -> None:
  168. super().__init__(message)
  169. self.request = request
  170. self.response = response
  171. class InvalidURL(Exception):
  172. """
  173. URL is improperly formed or cannot be parsed.
  174. """
  175. def __init__(self, message: str) -> None:
  176. super().__init__(message)
  177. class CookieConflict(Exception):
  178. """
  179. Attempted to lookup a cookie by name, but multiple cookies existed.
  180. Can occur when calling `response.cookies.get(...)`.
  181. """
  182. def __init__(self, message: str) -> None:
  183. super().__init__(message)
  184. # Stream exceptions...
  185. # These may occur as the result of a programming error, by accessing
  186. # the request/response stream in an invalid manner.
  187. class StreamError(RuntimeError):
  188. """
  189. The base class for stream exceptions.
  190. The developer made an error in accessing the request stream in
  191. an invalid way.
  192. """
  193. def __init__(self, message: str) -> None:
  194. super().__init__(message)
  195. class StreamConsumed(StreamError):
  196. """
  197. Attempted to read or stream content, but the content has already
  198. been streamed.
  199. """
  200. def __init__(self) -> None:
  201. message = (
  202. "Attempted to read or stream some content, but the content has "
  203. "already been streamed. For requests, this could be due to passing "
  204. "a generator as request content, and then receiving a redirect "
  205. "response or a secondary request as part of an authentication flow."
  206. "For responses, this could be due to attempting to stream the response "
  207. "content more than once."
  208. )
  209. super().__init__(message)
  210. class StreamClosed(StreamError):
  211. """
  212. Attempted to read or stream response content, but the request has been
  213. closed.
  214. """
  215. def __init__(self) -> None:
  216. message = (
  217. "Attempted to read or stream content, but the stream has " "been closed."
  218. )
  219. super().__init__(message)
  220. class ResponseNotRead(StreamError):
  221. """
  222. Attempted to access streaming response content, without having called `read()`.
  223. """
  224. def __init__(self) -> None:
  225. message = (
  226. "Attempted to access streaming response content,"
  227. " without having called `read()`."
  228. )
  229. super().__init__(message)
  230. class RequestNotRead(StreamError):
  231. """
  232. Attempted to access streaming request content, without having called `read()`.
  233. """
  234. def __init__(self) -> None:
  235. message = (
  236. "Attempted to access streaming request content,"
  237. " without having called `read()`."
  238. )
  239. super().__init__(message)
  240. @contextlib.contextmanager
  241. def request_context(
  242. request: typing.Optional["Request"] = None,
  243. ) -> typing.Iterator[None]:
  244. """
  245. A context manager that can be used to attach the given request context
  246. to any `RequestError` exceptions that are raised within the block.
  247. """
  248. try:
  249. yield
  250. except RequestError as exc:
  251. if request is not None:
  252. exc.request = request
  253. raise exc