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