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