From 0dd72fce4ab1abd62ebf5a1f60f97492bd14f0e6 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 20 Sep 2023 09:46:39 +0100 Subject: [PATCH 1/8] Make async dependencies optional. --- README.md | 19 +++++++++++-------- docs/async.md | 18 ++++++++++++++++++ docs/http2.md | 2 +- httpx/_transports/asgi.py | 4 ++-- httpx/_transports/default.py | 9 ++++++++- httpx/_utils.py | 21 ++------------------- pyproject.toml | 13 +++++++++---- requirements.txt | 3 +-- 8 files changed, 52 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 62fb295d17..82cddb1098 100644 --- a/README.md +++ b/README.md @@ -97,10 +97,12 @@ Install with pip: $ pip install httpx ``` -Or, to include the optional HTTP/2 support, use: +There are also a number of optional dependancies. + +For example to include asyncio and HTTP/2 support, use: ```shell -$ pip install httpx[http2] +$ pip install 'httpx[asyncio,http2]' ``` HTTPX requires Python 3.8+. @@ -129,15 +131,16 @@ The HTTPX project relies on these excellent libraries: * `h11` - HTTP/1.1 support. * `certifi` - SSL certificates. * `idna` - Internationalized domain name support. -* `sniffio` - Async library autodetection. As well as these optional installs: -* `h2` - HTTP/2 support. *(Optional, with `httpx[http2]`)* -* `socksio` - SOCKS proxy support. *(Optional, with `httpx[socks]`)* -* `rich` - Rich terminal support. *(Optional, with `httpx[cli]`)* -* `click` - Command line client support. *(Optional, with `httpx[cli]`)* -* `brotli` or `brotlicffi` - Decoding for "brotli" compressed responses. *(Optional, with `httpx[brotli]`)* +* `anyio`, `sniffio` - Async support for `asyncio`. *(Optional, with `httpx['asyncio']`)* +* `trio`, `sniffio` - Async support for `trio`. *(Optional, with `httpx['trio']`)* +* `h2` - HTTP/2 support. *(Optional, with `httpx['http2']`)* +* `socksio` - SOCKS proxy support. *(Optional, with `httpx['socks']`)* +* `rich` - Rich terminal support. *(Optional, with `httpx['cli']`)* +* `click` - Command line client support. *(Optional, with `httpx['cli']`)* +* `brotli` or `brotlicffi` - Decoding for "brotli" compressed responses. *(Optional, with `httpx['brotli']`)* A huge amount of credit is due to `requests` for the API layout that much of this work follows, as well as to `urllib3` for plenty of design diff --git a/docs/async.md b/docs/async.md index 1138c30c56..b78148d66b 100644 --- a/docs/async.md +++ b/docs/async.md @@ -10,6 +10,24 @@ long-lived network connections such as WebSockets. If you're working with an async web framework then you'll also want to use an async client for sending outgoing HTTP requests. +## Enabling Async support + +To enable async support you'll need to install some additional dependencies: + +If you're using Python's [standard `asyncio` support](https://docs.python.org/3/library/asyncio.html) then: + +```shell +$ pip install httpx['asyncio'] +``` + +Or, if you're working with the [`trio` third party package](https://trio.readthedocs.io/en/stable/): + +```shell +$ pip install httpx['trio'] +``` + +We highly recommend `trio` for async support. The `trio` project [pioneered the principles of structured concurrency](https://en.wikipedia.org/wiki/Structured_concurrency), and has a more carefully constrained API against which to work from. + ## Making Async requests To make asynchronous requests, you'll need an `AsyncClient`. diff --git a/docs/http2.md b/docs/http2.md index 3cab09d912..e138c605ad 100644 --- a/docs/http2.md +++ b/docs/http2.md @@ -28,7 +28,7 @@ trying out our HTTP/2 support. You can do so by first making sure to install the optional HTTP/2 dependencies... ```shell -$ pip install httpx[http2] +$ pip install httpx['http2'] ``` And then instantiating a client with HTTP/2 support enabled: diff --git a/httpx/_transports/asgi.py b/httpx/_transports/asgi.py index f67f0fbd5b..5b6b34446d 100644 --- a/httpx/_transports/asgi.py +++ b/httpx/_transports/asgi.py @@ -1,7 +1,5 @@ import typing -import sniffio - from .._models import Request, Response from .._types import AsyncByteStream from .base import AsyncBaseTransport @@ -25,6 +23,8 @@ def create_event() -> "Event": + import sniffio + if sniffio.current_async_library() == "trio": import trio diff --git a/httpx/_transports/default.py b/httpx/_transports/default.py index 7dba5b8208..fd53c77fb9 100644 --- a/httpx/_transports/default.py +++ b/httpx/_transports/default.py @@ -64,7 +64,7 @@ def map_httpcore_exceptions() -> typing.Iterator[None]: try: yield - except Exception as exc: # noqa: PIE-786 + except Exception as exc: mapped_exc = None for from_exc, to_exc in HTTPCORE_EXC_MAP.items(): @@ -269,6 +269,13 @@ def __init__( retries: int = 0, socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, ) -> None: + try: + import sniffio # noqa: F401 + except ImportError: # pragma: nocover + raise RuntimeError( + "Using httpx in async mode, but neither httpx['asyncio'] or asyncio['trio'] is installed." + ) + ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env) if proxy is None: diff --git a/httpx/_utils.py b/httpx/_utils.py index 1775b1a1ef..a49996402b 100644 --- a/httpx/_utils.py +++ b/httpx/_utils.py @@ -9,8 +9,6 @@ from pathlib import Path from urllib.request import getproxies -import sniffio - from ._types import PrimitiveData if typing.TYPE_CHECKING: # pragma: no cover @@ -322,33 +320,18 @@ def peek_filelike_length(stream: typing.Any) -> typing.Optional[int]: class Timer: - async def _get_time(self) -> float: - library = sniffio.current_async_library() - if library == "trio": - import trio - - return trio.current_time() - elif library == "curio": # pragma: no cover - import curio - - return typing.cast(float, await curio.clock()) - - import asyncio - - return asyncio.get_event_loop().time() - def sync_start(self) -> None: self.started = time.perf_counter() async def async_start(self) -> None: - self.started = await self._get_time() + self.started = time.perf_counter() def sync_elapsed(self) -> float: now = time.perf_counter() return now - self.started async def async_elapsed(self) -> float: - now = await self._get_time() + now = time.perf_counter() return now - self.started diff --git a/pyproject.toml b/pyproject.toml index 753e671ebc..7134b2bbd5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,9 +28,8 @@ classifiers = [ ] dependencies = [ "certifi", - "httpcore>=0.18.0,<0.19.0", + "httpcore>=1.0.0,<2.0.0", "idna", - "sniffio", ] dynamic = ["readme", "version"] @@ -45,10 +44,16 @@ cli = [ "rich>=10,<14", ] http2 = [ - "h2>=3,<5", + "httpcore['http2']", ] socks = [ - "socksio==1.*", + "httpcore['socks']", +] +asyncio = [ + "httpcore['asyncio']" +] +trio = [ + "httpcore['trio']" ] [project.scripts] diff --git a/requirements.txt b/requirements.txt index a884446efa..1503d820f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # On the other hand, we're not pinning package dependencies, because our tests # needs to pass with the latest version of the packages. # Reference: https://github.com/encode/httpx/pull/1721#discussion_r661241588 --e .[brotli,cli,http2,socks] +-e .[asyncio,trio,brotli,cli,http2,socks] # Optional charset auto-detection # Used in our test cases @@ -26,7 +26,6 @@ mypy==1.5.1 types-certifi==2021.10.8.2 pytest==7.4.0 ruff==0.0.286 -trio==0.22.2 trio-typing==0.8.0 trustme==1.1.0 uvicorn==0.22.0 From f4dd500f0ea4fdae2c59d6e56dbff0ff72cffc54 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 20 Sep 2023 10:10:12 +0100 Subject: [PATCH 2/8] Fix dependency formatting --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7134b2bbd5..7daaeddc8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,16 +44,16 @@ cli = [ "rich>=10,<14", ] http2 = [ - "httpcore['http2']", + "httpcore[http2]", ] socks = [ - "httpcore['socks']", + "httpcore[socks]", ] asyncio = [ - "httpcore['asyncio']" + "httpcore[asyncio]" ] trio = [ - "httpcore['trio']" + "httpcore[trio]" ] [project.scripts] From 56b2fb05337ba83355f2fd3b005359d72171f247 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 12 Oct 2023 10:42:02 +0100 Subject: [PATCH 3/8] Update README.md Co-authored-by: T-256 <132141463+T-256@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 82cddb1098..f3238242ec 100644 --- a/README.md +++ b/README.md @@ -134,8 +134,8 @@ The HTTPX project relies on these excellent libraries: As well as these optional installs: -* `anyio`, `sniffio` - Async support for `asyncio`. *(Optional, with `httpx['asyncio']`)* -* `trio`, `sniffio` - Async support for `trio`. *(Optional, with `httpx['trio']`)* +* `anyio` - Async support for `asyncio`. *(Optional, with `httpx['asyncio']`)* +* `trio` - Async support for `trio`. *(Optional, with `httpx['trio']`)* * `h2` - HTTP/2 support. *(Optional, with `httpx['http2']`)* * `socksio` - SOCKS proxy support. *(Optional, with `httpx['socks']`)* * `rich` - Rich terminal support. *(Optional, with `httpx['cli']`)* From 9b36a208b3357913bc032d01d611db57ddccec6f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 28 Dec 2023 20:35:16 +0000 Subject: [PATCH 4/8] Update httpx/_transports/default.py --- httpx/_transports/default.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/httpx/_transports/default.py b/httpx/_transports/default.py index 4f6bb94084..9e9ce90e02 100644 --- a/httpx/_transports/default.py +++ b/httpx/_transports/default.py @@ -276,7 +276,8 @@ def __init__( import sniffio # noqa: F401 except ImportError: # pragma: nocover raise RuntimeError( - "Using httpx in async mode, but neither httpx['asyncio'] or asyncio['trio'] is installed." + "Using httpx in async mode, but neither " + "httpx['asyncio'] or asyncio['trio'] is installed." ) ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env) From 88d93240818efb601f3bc34fb73d5b45f78598b3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 15 Jan 2024 16:57:22 +0000 Subject: [PATCH 5/8] Update docs/async.md Co-authored-by: Zanie Blue --- docs/async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/async.md b/docs/async.md index b78148d66b..f0d7e46f5a 100644 --- a/docs/async.md +++ b/docs/async.md @@ -17,7 +17,7 @@ To enable async support you'll need to install some additional dependencies: If you're using Python's [standard `asyncio` support](https://docs.python.org/3/library/asyncio.html) then: ```shell -$ pip install httpx['asyncio'] +$ pip install "httpx[asyncio]" ``` Or, if you're working with the [`trio` third party package](https://trio.readthedocs.io/en/stable/): From 3ebb3a5b9b4483795b9608a3f908b7c02d568af6 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 27 Sep 2024 09:48:50 +0100 Subject: [PATCH 6/8] Update docs/http2.md Co-authored-by: T-256 <132141463+T-256@users.noreply.github.com> --- docs/http2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/http2.md b/docs/http2.md index e138c605ad..a865b55a26 100644 --- a/docs/http2.md +++ b/docs/http2.md @@ -28,7 +28,7 @@ trying out our HTTP/2 support. You can do so by first making sure to install the optional HTTP/2 dependencies... ```shell -$ pip install httpx['http2'] +$ pip install 'httpx[http2]' ``` And then instantiating a client with HTTP/2 support enabled: From 6a4ea7a1febc3d9f55f1359a44bea0d1341d17ff Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 27 Sep 2024 17:04:54 +0100 Subject: [PATCH 7/8] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 15d52d45f3..fc759c6a9b 100644 --- a/README.md +++ b/README.md @@ -138,8 +138,9 @@ As well as these optional installs: * `trio` - Async support for `trio`. *(Optional, with `httpx['trio']`)* * `h2` - HTTP/2 support. *(Optional, with `httpx['http2']`)* * `socksio` - SOCKS proxy support. *(Optional, with `httpx['socks']`)* -* `rich` - Rich terminal support. *(Optional, with `httpx['cli']`)* +* `rich` - Command line client support. *(Optional, with `httpx['cli']`)* * `click` - Command line client support. *(Optional, with `httpx['cli']`)* +* `pygments` - Command line client support. *(Optional, with `httpx['cli']`)* * `brotli` or `brotlicffi` - Decoding for "brotli" compressed responses. *(Optional, with `httpx['brotli']`)* * `zstandard` - Decoding for "zstd" compressed responses. *(Optional, with `httpx[zstd]`)* From 85886d7d43c2dc37c286d088059910c00d3104af Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 27 Sep 2024 17:05:51 +0100 Subject: [PATCH 8/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc759c6a9b..9bf6026d17 100644 --- a/README.md +++ b/README.md @@ -138,8 +138,8 @@ As well as these optional installs: * `trio` - Async support for `trio`. *(Optional, with `httpx['trio']`)* * `h2` - HTTP/2 support. *(Optional, with `httpx['http2']`)* * `socksio` - SOCKS proxy support. *(Optional, with `httpx['socks']`)* -* `rich` - Command line client support. *(Optional, with `httpx['cli']`)* * `click` - Command line client support. *(Optional, with `httpx['cli']`)* +* `rich` - Command line client support. *(Optional, with `httpx['cli']`)* * `pygments` - Command line client support. *(Optional, with `httpx['cli']`)* * `brotli` or `brotlicffi` - Decoding for "brotli" compressed responses. *(Optional, with `httpx['brotli']`)* * `zstandard` - Decoding for "zstd" compressed responses. *(Optional, with `httpx[zstd]`)*