Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to httpcore 0.10 #1126

Merged
merged 9 commits into from
Aug 7, 2020
Merged
6 changes: 6 additions & 0 deletions httpx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
HTTPError,
HTTPStatusError,
InvalidURL,
LocalProtocolError,
NetworkError,
NotRedirectResponse,
PoolTimeout,
ProtocolError,
ProxyError,
ReadError,
ReadTimeout,
RemoteProtocolError,
RequestBodyUnavailable,
RequestError,
RequestNotRead,
Expand All @@ -29,6 +31,7 @@
TimeoutException,
TooManyRedirects,
TransportError,
UnsupportedProtocol,
WriteError,
WriteTimeout,
)
Expand Down Expand Up @@ -72,6 +75,9 @@
"HTTPError",
"HTTPStatusError",
"InvalidURL",
"UnsupportedProtocol",
"LocalProtocolError",
"RemoteProtocolError",
"NetworkError",
"NotRedirectResponse",
"PoolTimeout",
Expand Down
25 changes: 4 additions & 21 deletions httpx/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from ._content_streams import ContentStream
from ._exceptions import (
HTTPCORE_EXC_MAP,
InvalidURL,
RequestBodyUnavailable,
TooManyRedirects,
map_exceptions,
Expand All @@ -45,7 +44,6 @@
from ._utils import (
NetRCInfo,
URLPattern,
enforce_http_url,
get_environment_proxies,
get_logger,
same_origin,
Expand Down Expand Up @@ -326,11 +324,6 @@ def _redirect_url(self, request: Request, response: Response) -> URL:

url = URL(location)

# Check that we can handle the scheme
if url.scheme and url.scheme not in ("http", "https"):
message = f'Scheme "{url.scheme}" not supported.'
raise InvalidURL(message, request=request)

# Handle malformed 'Location' headers that are "absolute" form, have no host.
# See: https://github.com/encode/httpx/issues/771
if url.scheme and not url.host:
Expand Down Expand Up @@ -550,14 +543,11 @@ def _init_proxy_transport(
http2=http2,
)

def _transport_for_url(self, request: Request) -> httpcore.SyncHTTPTransport:
def _transport_for_url(self, url: URL) -> httpcore.SyncHTTPTransport:
"""
Returns the transport instance that should be used for a given URL.
This will either be the standard connection pool, or a proxy.
"""
url = request.url
enforce_http_url(request)

for pattern, transport in self._proxies.items():
if pattern.matches(url):
return self._transport if transport is None else transport
Expand Down Expand Up @@ -602,10 +592,6 @@ def send(
allow_redirects: bool = True,
timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
) -> Response:
if request.url.scheme not in ("http", "https"):
message = 'URL scheme must be "http" or "https".'
raise InvalidURL(message, request=request)

timeout = self.timeout if isinstance(timeout, UnsetType) else Timeout(timeout)

auth = self._build_auth(request, auth)
Expand Down Expand Up @@ -696,7 +682,7 @@ def _send_single_request(self, request: Request, timeout: Timeout) -> Response:
"""
Sends a single request, without handling any redirections.
"""
transport = self._transport_for_url(request)
transport = self._transport_for_url(request.url)

with map_exceptions(HTTPCORE_EXC_MAP, request=request):
(
Expand Down Expand Up @@ -1081,14 +1067,11 @@ def _init_proxy_transport(
http2=http2,
)

def _transport_for_url(self, request: Request) -> httpcore.AsyncHTTPTransport:
def _transport_for_url(self, url: URL) -> httpcore.AsyncHTTPTransport:
"""
Returns the transport instance that should be used for a given URL.
This will either be the standard connection pool, or a proxy.
"""
url = request.url
enforce_http_url(request)

for pattern, transport in self._proxies.items():
if pattern.matches(url):
return self._transport if transport is None else transport
Expand Down Expand Up @@ -1226,7 +1209,7 @@ async def _send_single_request(
"""
Sends a single request, without handling any redirections.
"""
transport = self._transport_for_url(request)
transport = self._transport_for_url(request.url)

with map_exceptions(HTTPCORE_EXC_MAP, request=request):
(
Expand Down
124 changes: 86 additions & 38 deletions httpx/_exceptions.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
"""
Our exception hierarchy:

* RequestError
+ TransportError
- TimeoutException
· ConnectTimeout
· ReadTimeout
· WriteTimeout
· PoolTimeout
- NetworkError
· ConnectError
· ReadError
· WriteError
· CloseError
- ProxyError
- ProtocolError
+ DecodingError
+ TooManyRedirects
+ RequestBodyUnavailable
+ InvalidURL
* HTTPStatusError
* HTTPError
x RequestError
+ TransportError
- TimeoutException
· ConnectTimeout
· ReadTimeout
· WriteTimeout
· PoolTimeout
- NetworkError
· ConnectError
· ReadError
· WriteError
· CloseError
- ProtocolError
· LocalProtocolError
· RemoteProtocolError
- ProxyError
- UnsupportedProtocol
+ DecodingError
+ TooManyRedirects
+ RequestBodyUnavailable
x HTTPStatusError
* NotRedirectResponse
* CookieConflict
* StreamError
+ StreamConsumed
+ ResponseNotRead
+ RequestNotRead
+ ResponseClosed
x StreamConsumed
x ResponseNotRead
x RequestNotRead
x ResponseClosed
"""
import contextlib
import typing
Expand All @@ -37,16 +40,36 @@
from ._models import Request, Response # pragma: nocover


class RequestError(Exception):
class HTTPError(Exception):
"""
Base class for all exceptions that may occur when issuing a `.request()`.
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.message}")
"""

def __init__(self, message: str, *, request: "Request") -> None:
super().__init__(message)
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:
super().__init__(message, request=request)


class TransportError(RequestError):
"""
Base class for all exceptions that are mapped from the httpcore API.
Expand Down Expand Up @@ -132,9 +155,35 @@ class ProxyError(TransportError):
"""


class UnsupportedProtocol(TransportError):
"""
Attempted to make a request to a no-supported protocol.
tomchristie marked this conversation as resolved.
Show resolved Hide resolved

For example issuing a request to `ftp://www.example.com`.
"""


class ProtocolError(TransportError):
"""
A protocol was violated by the server.
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 exaample, returning malformed HTTP.
"""


Expand All @@ -160,16 +209,10 @@ class RequestBodyUnavailable(RequestError):
"""


class InvalidURL(RequestError):
"""
URL was missing a hostname, or was not one of HTTP/HTTPS.
"""


# Client errors


class HTTPStatusError(Exception):
class HTTPStatusError(HTTPError):
"""
Response sent an error HTTP status.

Expand All @@ -179,8 +222,7 @@ class HTTPStatusError(Exception):
def __init__(
self, message: str, *, request: "Request", response: "Response"
) -> None:
super().__init__(message)
self.request = request
super().__init__(message, request=request)
self.response = response


Expand Down Expand Up @@ -277,9 +319,12 @@ def __init__(self) -> None:
super().__init__(message)


# We're continuing to expose this earlier naming at the moment.
# It is due to be deprecated. Don't use it.
HTTPError = RequestError
# The `InvalidURL` class is no longer required. It was being used to enforce only
# 'http'/'https' URLs being requested, but is now treated instead at the
# transport layer using `UnsupportedProtocol()`.`

# We are currently still exposing this class, but it will be removed in 1.0.
InvalidURL = UnsupportedProtocol


@contextlib.contextmanager
Expand Down Expand Up @@ -320,5 +365,8 @@ def map_exceptions(
httpcore.WriteError: WriteError,
httpcore.CloseError: CloseError,
httpcore.ProxyError: ProxyError,
httpcore.UnsupportedProtocol: UnsupportedProtocol,
httpcore.ProtocolError: ProtocolError,
httpcore.LocalProtocolError: LocalProtocolError,
httpcore.RemoteProtocolError: RemoteProtocolError,
}
27 changes: 4 additions & 23 deletions httpx/_transports/asgi.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
from typing import (
TYPE_CHECKING,
AsyncIterator,
Callable,
Dict,
List,
Optional,
Tuple,
Union,
)
from typing import TYPE_CHECKING, Callable, List, Mapping, Optional, Tuple, Union

import httpcore
import sniffio
Expand All @@ -31,10 +22,6 @@ def create_event() -> "Event":
return asyncio.Event()


async def async_byte_iterator(bytestring: bytes) -> AsyncIterator[bytes]:
yield bytestring


class ASGITransport(httpcore.AsyncHTTPTransport):
"""
A custom AsyncTransport that handles sending requests directly to an ASGI app.
Expand Down Expand Up @@ -86,14 +73,10 @@ async def request(
url: Tuple[bytes, bytes, Optional[int], bytes],
headers: List[Tuple[bytes, bytes]] = None,
stream: httpcore.AsyncByteStream = None,
timeout: Dict[str, Optional[float]] = None,
timeout: Mapping[str, Optional[float]] = None,
) -> Tuple[bytes, int, bytes, List[Tuple[bytes, bytes]], httpcore.AsyncByteStream]:
headers = [] if headers is None else headers
stream = (
httpcore.AsyncByteStream(async_byte_iterator(b""))
if stream is None
else stream
)
stream = httpcore.AsyncByteStream(content=b"") if stream is None else stream

# ASGI scope.
scheme, host, port, full_path = url
Expand Down Expand Up @@ -170,8 +153,6 @@ async def send(message: dict) -> None:
assert status_code is not None
assert response_headers is not None

response_body = b"".join(body_parts)

stream = httpcore.AsyncByteStream(async_byte_iterator(response_body))
stream = httpcore.AsyncByteStream(content=b"".join(body_parts))

return (b"HTTP/1.1", status_code, b"", response_headers, stream)
4 changes: 2 additions & 2 deletions httpx/_transports/urllib3.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import socket
from typing import Dict, Iterator, List, Optional, Tuple
from typing import Iterator, List, Mapping, Optional, Tuple

import httpcore

Expand Down Expand Up @@ -45,7 +45,7 @@ def request(
url: Tuple[bytes, bytes, Optional[int], bytes],
headers: List[Tuple[bytes, bytes]] = None,
stream: httpcore.SyncByteStream = None,
timeout: Dict[str, Optional[float]] = None,
timeout: Mapping[str, Optional[float]] = None,
) -> Tuple[bytes, int, bytes, List[Tuple[bytes, bytes]], httpcore.SyncByteStream]:
headers = [] if headers is None else headers
stream = ByteStream(b"") if stream is None else stream
Expand Down
Loading