]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
Make async dependencies optional.
authorTom Christie <tom@tomchristie.com>
Wed, 20 Sep 2023 08:46:39 +0000 (09:46 +0100)
committerTom Christie <tom@tomchristie.com>
Wed, 20 Sep 2023 08:46:39 +0000 (09:46 +0100)
README.md
docs/async.md
docs/http2.md
httpx/_transports/asgi.py
httpx/_transports/default.py
httpx/_utils.py
pyproject.toml
requirements.txt

index 62fb295d17bc59a76a8f924e852c73b34b5ae064..82cddb1098f28c9f1af80234715cc18cf731e518 100644 (file)
--- 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
index 1138c30c56b0c4bb2ec4dbacc99d157537b849ef..b78148d66b039d077f5309fbb9c48a540c23e23d 100644 (file)
@@ -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`.
index 3cab09d912093cb1c5ffdd3fdcb4a553aaa7890b..e138c605ad9a1f92f1efd8bab9e776b6e10bd99f 100644 (file)
@@ -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:
index f67f0fbd5b5fb2af1a2137554bc7e9f2539c1127..5b6b34446dd88200653091bff043b19f350c9ef3 100644 (file)
@@ -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
 
index 7dba5b8208a5129c930e74e30178f8b5b5e5aa6f..fd53c77fb99a8de95218c73938e676ab4dd969b9 100644 (file)
@@ -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:
index 1775b1a1ef9af54a4606c213718f74421e347fa6..a49996402b4fdad6c49d948fc5d7fb2647c5c6b7 100644 (file)
@@ -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
 
 
index 753e671ebc37f32579ebeb510a083cd52dee4a60..7134b2bbd5cdc4a882063667fab176dcb280013a 100644 (file)
@@ -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]
index a884446efa00813de51c3d0495aef2dee28d8433..1503d820f76dd6452d1b026d091ac44f5c67593a 100644 (file)
@@ -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