1 """ 2 Our 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 34 from __future__ import annotations 35 36 import contextlib 37 import typing 38 39 if 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 74 class 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 107 class 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 123 class 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 132 class TimeoutException(TransportError): 133 """ 134 The base class for timeout errors. 135 136 An operation has timed out. 137 """ 138 139 140 class ConnectTimeout(TimeoutException): 141 """ 142 Timed out while connecting to the host. 143 """ 144 145 146 class ReadTimeout(TimeoutException): 147 """ 148 Timed out while receiving data from the host. 149 """ 150 151 152 class WriteTimeout(TimeoutException): 153 """ 154 Timed out while sending data to the host. 155 """ 156 157 158 class PoolTimeout(TimeoutException): 159 """ 160 Timed out waiting to acquire a connection from the pool. 161 """ 162 163 164 # Core networking exceptions... 165 166 167 class NetworkError(TransportError): 168 """ 169 The base class for network-related errors. 170 171 An error occurred while interacting with the network. 172 """ 173 174 175 class ReadError(NetworkError): 176 """ 177 Failed to receive data from the network. 178 """ 179 180 181 class WriteError(NetworkError): 182 """ 183 Failed to send data through the network. 184 """ 185 186 187 class ConnectError(NetworkError): 188 """ 189 Failed to establish a connection. 190 """ 191 192 193 class CloseError(NetworkError): 194 """ 195 Failed to close a connection. 196 """ 197 198 199 # Other transport exceptions... 200 201 202 class ProxyError(TransportError): 203 """ 204 An error occurred while establishing a proxy connection. 205 """ 206 207 208 class 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 216 class ProtocolError(TransportError): 217 """ 218 The protocol was violated. 219 """ 220 221 222 class 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 232 class 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 243 class DecodingError(RequestError): 244 """ 245 Decoding of the response failed, due to a malformed encoding. 246 """ 247 248 249 class TooManyRedirects(RequestError): 250 """ 251 Too many redirects. 252 """ 253 254 255 # Client errors 256 257 258 class 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 271 class 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 280 class 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 297 class 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 309 class 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 327 class 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 340 class 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 353 class 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 367 def 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 380