Drop urllib3 in favor of public gist (#1182)

* Drop urllib3 in favor of public gist

* Drop urllib3 coverage omit

* Drop recommendation to use urllib3 transport during Requests migration

* Add urllib3-transport to 3p pkgs

* Drop urllib3 from dependencies list in README / docs home page

Co-authored-by: Tom Christie <tom@tomchristie.com>
This commit is contained in:
Florimond Manca 2020-09-04 22:56:36 +02:00 committed by GitHub
parent 642aabdac0
commit 8fa87650b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 25 additions and 160 deletions

View File

@ -122,7 +122,6 @@ The HTTPX project relies on these excellent libraries:
* `rfc3986` - URL parsing & normalization.
* `idna` - Internationalized domain name support.
* `sniffio` - Async library autodetection.
* `urllib3` - Support for the `httpx.URLLib3Transport` class. *(Optional)*
* `brotlipy` - Decoding for "brotli" compressed responses. *(Optional)*
A huge amount of credit is due to `requests` for the API layout that

View File

@ -809,6 +809,8 @@ HTTPX's `Client` also accepts a `transport` argument. This argument allows you
to provide a custom Transport object that will be used to perform the actual
sending of the requests.
### Usage
For some advanced configuration you might need to instantiate a transport
class directly, and pass it to the client instance. The `httpcore` package
provides a `local_address` configuration that is only available via this
@ -850,18 +852,19 @@ do not include any default values for configuring aspects such as the
connection pooling details, so you'll need to provide more explicit
configuration when using this API.
HTTPX also currently ships with a transport that uses the excellent
[`urllib3` library](https://urllib3.readthedocs.io/en/latest/), which can be
used with the sync `Client`...
### urllib3 transport
This [public gist](https://gist.github.com/florimondmanca/d56764d78d748eb9f73165da388e546e) provides a transport that uses the excellent [`urllib3` library](https://urllib3.readthedocs.io/en/latest/), and can be used with the sync `Client`...
```pycon
>>> import httpx
>>> client = httpx.Client(transport=httpx.URLLib3Transport())
>>> from urllib3_transport import URLLib3Transport
>>> client = httpx.Client(transport=URLLib3Transport())
>>> client.get("https://example.org")
<Response [200 OK]>
```
Note that you'll need to install the `urllib3` package to use `URLLib3Transport`.
### Writing custom transports
A transport instance must implement the Transport API defined by
[`httpcore`](https://www.encode.io/httpcore/api/). You

View File

@ -83,3 +83,9 @@ Besides, `httpx.Request()` does not support the `auth`, `timeout`, `allow_redire
## Mocking
If you need to mock HTTPX the same way that test utilities like `responses` and `requests-mock` does for `requests`, see [RESPX](https://github.com/lundberg/respx).
## Networking layer
`requests` defers most of its HTTP networking code to the excellent [`urllib3` library](https://urllib3.readthedocs.io/en/latest/).
On the other hand, HTTPX uses [HTTPCore](https://github.com/encode/httpcore) as its core HTTP networking layer, which is a different project than `urllib3`.

View File

@ -114,7 +114,6 @@ The HTTPX project relies on these excellent libraries:
* `rfc3986` - URL parsing & normalization.
* `idna` - Internationalized domain name support.
* `sniffio` - Async library autodetection.
* `urllib3` - Support for the `httpx.URLLib3Transport` class. *(Optional)*
* `brotlipy` - Decoding for "brotli" compressed responses. *(Optional)*
A huge amount of credit is due to `requests` for the API layout that

View File

@ -23,3 +23,13 @@ An asynchronous GitHub API library. Includes [HTTPX support](https://gidgethub.r
[GitHub](https://github.com/lundberg/respx) - [Documentation](https://lundberg.github.io/respx/)
A utility for mocking out the Python HTTPX library.
## Gists
<!-- NOTE: this list is in alphabetical order. -->
### urllib3-transport
[GitHub](https://gist.github.com/florimondmanca/d56764d78d748eb9f73165da388e546e)
This public gist provides an example implementation for a [custom transport](/advanced#custom-transports) implementation on top of the battle-tested [`urllib3`](https://urllib3.readthedocs.io) library.

View File

@ -38,7 +38,6 @@ from ._exceptions import (
from ._models import URL, Cookies, Headers, QueryParams, Request, Response
from ._status_codes import StatusCode, codes
from ._transports.asgi import ASGITransport
from ._transports.urllib3 import URLLib3ProxyTransport, URLLib3Transport
from ._transports.wsgi import WSGITransport
__all__ = [
@ -101,8 +100,6 @@ __all__ = [
"TransportError",
"UnsupportedProtocol",
"URL",
"URLLib3ProxyTransport",
"URLLib3Transport",
"WriteError",
"WriteTimeout",
"WSGITransport",

View File

@ -1,149 +0,0 @@
import socket
from typing import Iterator, List, Mapping, Optional, Tuple
import httpcore
from .._config import create_ssl_context
from .._content_streams import ByteStream, IteratorStream
from .._exceptions import NetworkError, map_exceptions
from .._types import CertTypes, VerifyTypes
try:
import urllib3
from urllib3.exceptions import MaxRetryError, SSLError
except ImportError: # pragma: nocover
urllib3 = None
class URLLib3Transport(httpcore.SyncHTTPTransport):
def __init__(
self,
*,
verify: VerifyTypes = True,
cert: CertTypes = None,
trust_env: bool = None,
pool_connections: int = 10,
pool_maxsize: int = 10,
pool_block: bool = False,
):
assert (
urllib3 is not None
), "urllib3 must be installed in order to use URLLib3Transport"
self.pool = urllib3.PoolManager(
ssl_context=create_ssl_context(
verify=verify, cert=cert, trust_env=trust_env, http2=False
),
num_pools=pool_connections,
maxsize=pool_maxsize,
block=pool_block,
)
def request(
self,
method: bytes,
url: Tuple[bytes, bytes, Optional[int], bytes],
headers: List[Tuple[bytes, bytes]] = None,
stream: httpcore.SyncByteStream = 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
timeout = {} if timeout is None else timeout
urllib3_timeout = urllib3.util.Timeout(
connect=timeout.get("connect"), read=timeout.get("read")
)
chunked = False
content_length = 0
for header_key, header_value in headers:
header_key = header_key.lower()
if header_key == b"transfer-encoding":
chunked = header_value == b"chunked"
if header_key == b"content-length":
content_length = int(header_value.decode("ascii"))
body = stream if chunked or content_length else None
scheme, host, port, path = url
default_port = {b"http": 80, "https": 443}.get(scheme)
if port is None or port == default_port:
url_str = "%s://%s%s" % (
scheme.decode("ascii"),
host.decode("ascii"),
path.decode("ascii"),
)
else:
url_str = "%s://%s:%d%s" % (
scheme.decode("ascii"),
host.decode("ascii"),
port,
path.decode("ascii"),
)
with map_exceptions(
{
MaxRetryError: NetworkError,
SSLError: NetworkError,
socket.error: NetworkError,
}
):
conn = self.pool.urlopen(
method=method.decode(),
url=url_str,
headers={
key.decode("ascii"): value.decode("ascii") for key, value in headers
},
body=body,
redirect=False,
assert_same_host=False,
retries=0,
preload_content=False,
chunked=chunked,
timeout=urllib3_timeout,
pool_timeout=timeout.get("pool"),
)
def response_bytes() -> Iterator[bytes]:
with map_exceptions({socket.error: NetworkError}):
for chunk in conn.stream(4096, decode_content=False):
yield chunk
status_code = conn.status
headers = list(conn.headers.items())
response_stream = IteratorStream(
iterator=response_bytes(), close_func=conn.release_conn
)
return (b"HTTP/1.1", status_code, conn.reason, headers, response_stream)
def close(self) -> None:
self.pool.clear()
class URLLib3ProxyTransport(URLLib3Transport):
def __init__(
self,
*,
proxy_url: str,
proxy_headers: dict = None,
verify: VerifyTypes = True,
cert: CertTypes = None,
trust_env: bool = None,
pool_connections: int = 10,
pool_maxsize: int = 10,
pool_block: bool = False,
):
assert (
urllib3 is not None
), "urllib3 must be installed in order to use URLLib3ProxyTransport"
self.pool = urllib3.ProxyManager(
proxy_url=proxy_url,
proxy_headers=proxy_headers,
ssl_context=create_ssl_context(
verify=verify, cert=cert, trust_env=trust_env, http2=False
),
num_pools=pool_connections,
maxsize=pool_maxsize,
block=pool_block,
)

View File

@ -8,4 +8,4 @@ export SOURCE_FILES="httpx tests"
set -x
${PREFIX}coverage report --omit=httpx/_transports/urllib3.py --show-missing --skip-covered --fail-under=100
${PREFIX}coverage report --show-missing --skip-covered --fail-under=100