""" 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 # pragma: no cover 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) # At the point an exception is raised we won't typically have a request # instance to associate it with. # # The 'request_context' context manager is used within the Client and # Response methods in order to ensure that any raised exceptions # have a `.request` property set on them. self._request = request class TransportError(RequestError): """ Base class for all exceptions that occur at the level of the Transport API. """ # Timeout exceptions... 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. """ # Core networking exceptions... 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. """ # Other transport exceptions... 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. """ # Other request exceptions... class DecodingError(RequestError): """ Decoding of the response failed, due to a malformed encoding. """ class TooManyRedirects(RequestError): """ Too many redirects. """ # Client errors 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) # Stream exceptions... # These may occur as the result of a programming error, by accessing # the request/response stream in an invalid manner. 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