From: Tom Christie Date: Wed, 20 Sep 2023 08:46:39 +0000 (+0100) Subject: Make async dependencies optional. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0dd72fce4ab1abd62ebf5a1f60f97492bd14f0e6;p=thirdparty%2Fhttpx.git Make async dependencies optional. --- diff --git a/README.md b/README.md index 62fb295d..82cddb10 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 1138c30c..b78148d6 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 3cab09d9..e138c605 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 f67f0fbd..5b6b3444 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 @@ _ASGIApp = typing.Callable[ 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 7dba5b82..fd53c77f 100644 --- a/httpx/_transports/default.py +++ b/httpx/_transports/default.py @@ -64,7 +64,7 @@ SOCKET_OPTION = typing.Union[ 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 @@ class AsyncHTTPTransport(AsyncBaseTransport): 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 1775b1a1..a4999640 100644 --- a/httpx/_utils.py +++ b/httpx/_utils.py @@ -9,8 +9,6 @@ import typing 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 753e671e..7134b2bb 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 a884446e..1503d820 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