|
""" |
|
Our exception hierarchy: |
|
|
|
* HTTPError |
|
x RequestError |
|
+ TransportError |
|
- TimeoutException |
|
路 ConnectTimeout |
|
路 ReadTimeout |
|
路 WriteTimeout |
|
路 PoolTimeout |
|
- NetworkError |
|
路 ConnectError |
|
路 ReadError |
|
路 WriteError |
|
路 CloseError |
|
- ProtocolError |
|
路 LocalProtocolError |
|
路 RemoteProtocolError |
|
- ProxyError |
|
- UnsupportedProtocol |
|
+ DecodingError |
|
+ TooManyRedirects |
|
x HTTPStatusError |
|
* InvalidURL |
|
* CookieConflict |
|
* StreamError |
|
x StreamConsumed |
|
x StreamClosed |
|
x ResponseNotRead |
|
x RequestNotRead |
|
""" |
|
from __future__ import annotations |
|
|
|
import contextlib |
|
import typing |
|
|
|
if typing.TYPE_CHECKING: |
|
from ._models import Request, Response |
|
|
|
|
|
class HTTPError(Exception): |
|
""" |
|
Base class for `RequestError` and `HTTPStatusError`. |
|
|
|
Useful for `try...except` blocks when issuing a request, |
|
and then calling `.raise_for_status()`. |
|
|
|
For example: |
|
|
|
``` |
|
try: |
|
response = httpx.get("https://www.example.com") |
|
response.raise_for_status() |
|
except httpx.HTTPError as exc: |
|
print(f"HTTP Exception for {exc.request.url} - {exc}") |
|
``` |
|
""" |
|
|
|
def __init__(self, message: str) -> None: |
|
super().__init__(message) |
|
self._request: Request | None = None |
|
|
|
@property |
|
def request(self) -> Request: |
|
if self._request is None: |
|
raise RuntimeError("The .request property has not been set.") |
|
return self._request |
|
|
|
@request.setter |
|
def request(self, request: Request) -> None: |
|
self._request = request |
|
|
|
|
|
class RequestError(HTTPError): |
|
""" |
|
Base class for all exceptions that may occur when issuing a `.request()`. |
|
""" |
|
|
|
def __init__(self, message: str, *, request: Request | None = None) -> None: |
|
super().__init__(message) |
|
|
|
|
|
|
|
|
|
|
|
|
|
self._request = request |
|
|
|
|
|
class TransportError(RequestError): |
|
""" |
|
Base class for all exceptions that occur at the level of the Transport API. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
class TimeoutException(TransportError): |
|
""" |
|
The base class for timeout errors. |
|
|
|
An operation has timed out. |
|
""" |
|
|
|
|
|
class ConnectTimeout(TimeoutException): |
|
""" |
|
Timed out while connecting to the host. |
|
""" |
|
|
|
|
|
class ReadTimeout(TimeoutException): |
|
""" |
|
Timed out while receiving data from the host. |
|
""" |
|
|
|
|
|
class WriteTimeout(TimeoutException): |
|
""" |
|
Timed out while sending data to the host. |
|
""" |
|
|
|
|
|
class PoolTimeout(TimeoutException): |
|
""" |
|
Timed out waiting to acquire a connection from the pool. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
class NetworkError(TransportError): |
|
""" |
|
The base class for network-related errors. |
|
|
|
An error occurred while interacting with the network. |
|
""" |
|
|
|
|
|
class ReadError(NetworkError): |
|
""" |
|
Failed to receive data from the network. |
|
""" |
|
|
|
|
|
class WriteError(NetworkError): |
|
""" |
|
Failed to send data through the network. |
|
""" |
|
|
|
|
|
class ConnectError(NetworkError): |
|
""" |
|
Failed to establish a connection. |
|
""" |
|
|
|
|
|
class CloseError(NetworkError): |
|
""" |
|
Failed to close a connection. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
class ProxyError(TransportError): |
|
""" |
|
An error occurred while establishing a proxy connection. |
|
""" |
|
|
|
|
|
class UnsupportedProtocol(TransportError): |
|
""" |
|
Attempted to make a request to an unsupported protocol. |
|
|
|
For example issuing a request to `ftp://www.example.com`. |
|
""" |
|
|
|
|
|
class ProtocolError(TransportError): |
|
""" |
|
The protocol was violated. |
|
""" |
|
|
|
|
|
class LocalProtocolError(ProtocolError): |
|
""" |
|
A protocol was violated by the client. |
|
|
|
For example if the user instantiated a `Request` instance explicitly, |
|
failed to include the mandatory `Host:` header, and then issued it directly |
|
using `client.send()`. |
|
""" |
|
|
|
|
|
class RemoteProtocolError(ProtocolError): |
|
""" |
|
The protocol was violated by the server. |
|
|
|
For example, returning malformed HTTP. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
class DecodingError(RequestError): |
|
""" |
|
Decoding of the response failed, due to a malformed encoding. |
|
""" |
|
|
|
|
|
class TooManyRedirects(RequestError): |
|
""" |
|
Too many redirects. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
class HTTPStatusError(HTTPError): |
|
""" |
|
The response had an error HTTP status of 4xx or 5xx. |
|
|
|
May be raised when calling `response.raise_for_status()` |
|
""" |
|
|
|
def __init__(self, message: str, *, request: Request, response: Response) -> None: |
|
super().__init__(message) |
|
self.request = request |
|
self.response = response |
|
|
|
|
|
class InvalidURL(Exception): |
|
""" |
|
URL is improperly formed or cannot be parsed. |
|
""" |
|
|
|
def __init__(self, message: str) -> None: |
|
super().__init__(message) |
|
|
|
|
|
class CookieConflict(Exception): |
|
""" |
|
Attempted to lookup a cookie by name, but multiple cookies existed. |
|
|
|
Can occur when calling `response.cookies.get(...)`. |
|
""" |
|
|
|
def __init__(self, message: str) -> None: |
|
super().__init__(message) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class StreamError(RuntimeError): |
|
""" |
|
The base class for stream exceptions. |
|
|
|
The developer made an error in accessing the request stream in |
|
an invalid way. |
|
""" |
|
|
|
def __init__(self, message: str) -> None: |
|
super().__init__(message) |
|
|
|
|
|
class StreamConsumed(StreamError): |
|
""" |
|
Attempted to read or stream content, but the content has already |
|
been streamed. |
|
""" |
|
|
|
def __init__(self) -> None: |
|
message = ( |
|
"Attempted to read or stream some content, but the content has " |
|
"already been streamed. For requests, this could be due to passing " |
|
"a generator as request content, and then receiving a redirect " |
|
"response or a secondary request as part of an authentication flow." |
|
"For responses, this could be due to attempting to stream the response " |
|
"content more than once." |
|
) |
|
super().__init__(message) |
|
|
|
|
|
class StreamClosed(StreamError): |
|
""" |
|
Attempted to read or stream response content, but the request has been |
|
closed. |
|
""" |
|
|
|
def __init__(self) -> None: |
|
message = ( |
|
"Attempted to read or stream content, but the stream has " "been closed." |
|
) |
|
super().__init__(message) |
|
|
|
|
|
class ResponseNotRead(StreamError): |
|
""" |
|
Attempted to access streaming response content, without having called `read()`. |
|
""" |
|
|
|
def __init__(self) -> None: |
|
message = ( |
|
"Attempted to access streaming response content," |
|
" without having called `read()`." |
|
) |
|
super().__init__(message) |
|
|
|
|
|
class RequestNotRead(StreamError): |
|
""" |
|
Attempted to access streaming request content, without having called `read()`. |
|
""" |
|
|
|
def __init__(self) -> None: |
|
message = ( |
|
"Attempted to access streaming request content," |
|
" without having called `read()`." |
|
) |
|
super().__init__(message) |
|
|
|
|
|
@contextlib.contextmanager |
|
def request_context( |
|
request: Request | None = None, |
|
) -> typing.Iterator[None]: |
|
""" |
|
A context manager that can be used to attach the given request context |
|
to any `RequestError` exceptions that are raised within the block. |
|
""" |
|
try: |
|
yield |
|
except RequestError as exc: |
|
if request is not None: |
|
exc.request = request |
|
raise exc |
|
|