RedirectBodyUnavailable,
RedirectLoop,
TooManyRedirects,
+ HTTPError,
)
from .interfaces import AsyncDispatcher, ConcurrencyBackend, Dispatcher
from .models import (
)
if dispatch is None:
- async_dispatch = ConnectionPool(
+ async_dispatch: AsyncDispatcher = ConnectionPool(
verify=verify,
cert=cert,
timeout=timeout,
pool_limits=pool_limits,
backend=backend,
- ) # type: AsyncDispatcher
+ )
elif isinstance(dispatch, Dispatcher):
async_dispatch = ThreadedDispatcher(dispatch, backend)
else:
auth = HTTPBasicAuth(username=auth[0], password=auth[1])
request = auth(request)
- response = await self.send_handling_redirects(
- request,
- verify=verify,
- cert=cert,
- timeout=timeout,
- allow_redirects=allow_redirects,
- )
+ try:
+ response = await self.send_handling_redirects(
+ request,
+ verify=verify,
+ cert=cert,
+ timeout=timeout,
+ allow_redirects=allow_redirects,
+ )
+ except HTTPError as exc:
+ # Add the original request to any HTTPError
+ exc.request = request
+ raise
if not stream:
try:
# We perform these checks here, so that calls to `response.next()`
# will raise redirect errors if appropriate.
if len(history) > self.max_redirects:
- raise TooManyRedirects()
+ raise TooManyRedirects(response=history[-1])
if request.url in [response.url for response in history]:
- raise RedirectLoop()
+ raise RedirectLoop(response=history[-1])
response = await self.dispatch.send(
request, verify=verify, cert=cert, timeout=timeout
)
+
should_close_response = True
try:
assert isinstance(response, AsyncResponse)
response.history = list(history)
self.cookies.extract_cookies(response)
- history = history + [response]
+ history.append(response)
if allow_redirects and response.is_redirect:
request = self.build_redirect_request(request, response)
method = self.redirect_method(request, response)
url = self.redirect_url(request, response)
headers = self.redirect_headers(request, url)
- content = self.redirect_content(request, method)
+ content = self.redirect_content(request, method, response)
cookies = self.merge_cookies(request.cookies)
return AsyncRequest(
method=method, url=url, headers=headers, data=content, cookies=cookies
del headers["Authorization"]
return headers
- def redirect_content(self, request: AsyncRequest, method: str) -> bytes:
+ def redirect_content(
+ self, request: AsyncRequest, method: str, response: AsyncResponse
+ ) -> bytes:
"""
Return the body that should be used for the redirect request.
"""
if method != request.method and method == "GET":
return b""
if request.is_streaming:
- raise RedirectBodyUnavailable()
+ raise RedirectBodyUnavailable(response=response)
return request.content
writer = Writer(stream_writer=stream_writer, timeout=timeout)
protocol = Protocol.HTTP_2 if ident == "h2" else Protocol.HTTP_11
- return (reader, writer, protocol)
+ return reader, writer, protocol
async def run_in_threadpool(
self, func: typing.Callable, *args: typing.Any, **kwargs: typing.Any
assert isinstance(event, h11.Response)
break
http_version = "HTTP/%s" % event.http_version.decode("latin-1", errors="ignore")
- return (http_version, event.status_code, event.headers)
+ return http_version, event.status_code, event.headers
async def _receive_response_data(
self, timeout: TimeoutConfig = None
status_code = int(v.decode("ascii", errors="ignore"))
elif not k.startswith(b":"):
headers.append((k, v))
- return (status_code, headers)
+ return status_code, headers
async def body_iter(
self, stream_id: int, timeout: TimeoutConfig = None
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from .models import BaseRequest, BaseResponse # pragma: nocover
+
+
+class HTTPError(Exception):
+ """
+ Base class for Httpx exception
+ """
+
+ def __init__(self, request: 'BaseRequest' = None, response: 'BaseResponse' = None, *args) -> None:
+ self.response = response
+ self.request = request or getattr(self.response, "request", None)
+ super().__init__(*args)
+
+
# Timeout exceptions...
-class Timeout(Exception):
+class Timeout(HTTPError):
"""
A base class for all timeouts.
"""
# HTTP exceptions...
-class HttpError(Exception):
- """
- An HTTP error occurred.
- """
-
-
-class ProtocolError(Exception):
+class ProtocolError(HTTPError):
"""
Malformed HTTP.
"""
-class DecodingError(Exception):
+class DecodingError(HTTPError):
"""
Decoding of the response failed.
"""
# Redirect exceptions...
-class RedirectError(Exception):
+class RedirectError(HTTPError):
"""
Base class for HTTP redirect errors.
"""
# Stream exceptions...
-class StreamException(Exception):
+class StreamError(HTTPError):
"""
The base class for stream exceptions.
"""
-class StreamConsumed(StreamException):
+class StreamConsumed(StreamError):
"""
Attempted to read or stream response content, but the content has already
been streamed.
"""
-class ResponseNotRead(StreamException):
+class ResponseNotRead(StreamError):
"""
Attempted to access response content, without having called `read()`
after a streaming response.
"""
-class ResponseClosed(StreamException):
+class ResponseClosed(StreamError):
"""
Attempted to read or stream response content, but the request has been
closed.
# Other cases...
-class InvalidURL(Exception):
+class InvalidURL(HTTPError):
"""
URL was missing a hostname, or was not one of HTTP/HTTPS.
"""
-class CookieConflict(Exception):
+class CookieConflict(HTTPError):
"""
Attempted to lookup a cookie by name, but multiple cookies existed.
"""
"""
Base class for async dispatcher classes, that handle sending the request.
- Stubs out the interface, as well as providing a `.request()` convienence
+ Stubs out the interface, as well as providing a `.request()` convenience
implementation, to make it easy to use or test stand-alone dispatchers,
without requiring a complete `Client` instance.
"""
class Dispatcher:
"""
- Base class for syncronous dispatcher classes, that handle sending the request.
+ Base class for synchronous dispatcher classes, that handle sending the request.
- Stubs out the interface, as well as providing a `.request()` convienence
+ Stubs out the interface, as well as providing a `.request()` convenience
implementation, to make it easy to use or test stand-alone dispatchers,
without requiring a complete `Client` instance.
"""
class BaseWriter:
"""
- A stream writer. Abstracts away any asyncio-specfic interfaces
+ A stream writer. Abstracts away any asyncio-specific interfaces
into a more generic base class, that we can use with alternate
backend, or for stand-alone test cases.
"""
"""
A semaphore for use with connection pooling.
- Abstracts away any asyncio-specfic interfaces.
+ Abstracts away any asyncio-specific interfaces.
"""
async def acquire(self) -> None:
)
from .exceptions import (
CookieConflict,
- HttpError,
+ HTTPError,
InvalidURL,
ResponseClosed,
ResponseNotRead,
return content, content_type
def prepare(self) -> None:
- content = getattr(self, "content", None) # type: bytes
+ content: typing.Optional[bytes] = getattr(self, "content", None)
is_streaming = getattr(self, "is_streaming", False)
- auto_headers = [] # type: typing.List[typing.Tuple[bytes, bytes]]
+ auto_headers: typing.List[typing.Tuple[bytes, bytes]] = []
has_host = "host" in self.headers
has_user_agent = "user-agent" in self.headers
self.request = request
self.on_close = on_close
- self.next = None # typing.Optional[typing.Callable]
+ self.next: typing.Optional[typing.Callable] = None
@property
def reason_phrase(self) -> str:
content, depending on the Content-Encoding used in the response.
"""
if not hasattr(self, "_decoder"):
- decoders = [] # type: typing.List[Decoder]
+ decoders: typing.List[Decoder] = []
values = self.headers.getlist("content-encoding", split_commas=True)
for value in values:
value = value.strip().lower()
message = message.format(self, error_type="Server Error")
else:
message = ""
-
if message:
- raise HttpError(message)
+ raise HTTPError(message, response=self)
def json(self, **kwargs: typing.Any) -> typing.Union[dict, list]:
if self.charset_encoding is None and self.content and len(self.content) > 3:
)
if 400 <= status_code < 600:
- with pytest.raises(httpx.exceptions.HttpError):
+ with pytest.raises(httpx.exceptions.HTTPError) as exc_info:
response.raise_for_status()
+ assert exc_info.value.response == response
else:
assert response.raise_for_status() is None
response = client.request(
"GET", "http://127.0.0.1:8000/status/{}".format(status_code)
)
-
if 400 <= status_code < 600:
- with pytest.raises(httpx.exceptions.HttpError):
+ with pytest.raises(httpx.exceptions.HTTPError) as exc_info:
response.raise_for_status()
+ assert exc_info.value.response == response
else:
assert response.raise_for_status() is None
timeout: TimeoutConfig,
) -> typing.Tuple[BaseReader, BaseWriter, Protocol]:
self.server = MockHTTP2Server(self.app)
- return (self.server, self.server, Protocol.HTTP_2)
+ return self.server, self.server, Protocol.HTTP_2
class MockHTTP2Server(BaseReader, BaseWriter):