]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
Unified httpx.Version parameter
authorTom Christie <tom.christie@krakentechnologies.ltd>
Thu, 11 Jan 2024 13:54:52 +0000 (13:54 +0000)
committerTom Christie <tom.christie@krakentechnologies.ltd>
Thu, 11 Jan 2024 13:54:52 +0000 (13:54 +0000)
httpx/__init__.py
httpx/_client.py
httpx/_config.py
httpx/_transports/default.py
tests/client/test_async_client.py
tests/client/test_client.py
tests/test_config.py

index f61112f8b20e11be3395d6f9265082ad762a7638..2a03bf4e997a6b4b0a6a9d8745a2faffdb5f04f9 100644 (file)
@@ -2,7 +2,7 @@ from .__version__ import __description__, __title__, __version__
 from ._api import delete, get, head, options, patch, post, put, request, stream
 from ._auth import Auth, BasicAuth, DigestAuth, NetRCAuth
 from ._client import USE_CLIENT_DEFAULT, AsyncClient, Client
-from ._config import Limits, Proxy, Timeout, create_ssl_context
+from ._config import Limits, Proxy, Timeout, Version, create_ssl_context
 from ._content import ByteStream
 from ._exceptions import (
     CloseError,
@@ -126,6 +126,7 @@ __all__ = [
     "UnsupportedProtocol",
     "URL",
     "USE_CLIENT_DEFAULT",
+    "Version",
     "WriteError",
     "WriteTimeout",
     "WSGITransport",
index a0b4209c46c29a805475e96dfbac19b9f6e64992..14d1a192c7edc4b9094f64c7ccd11d177eeeaf39 100644 (file)
@@ -12,9 +12,11 @@ from ._config import (
     DEFAULT_LIMITS,
     DEFAULT_MAX_REDIRECTS,
     DEFAULT_TIMEOUT_CONFIG,
+    DEFAULT_VERSION,
     Limits,
     Proxy,
     Timeout,
+    Version,
 )
 from ._decoders import SUPPORTED_DECODERS
 from ._exceptions import (
@@ -630,8 +632,8 @@ class Client(BaseClient):
         cookies: typing.Optional[CookieTypes] = None,
         verify: VerifyTypes = True,
         cert: typing.Optional[CertTypes] = None,
-        http1: bool = True,
         http2: bool = False,
+        version: Version = DEFAULT_VERSION,
         proxy: typing.Optional[ProxyTypes] = None,
         proxies: typing.Optional[ProxiesTypes] = None,
         mounts: typing.Optional[
@@ -664,12 +666,12 @@ class Client(BaseClient):
             default_encoding=default_encoding,
         )
 
-        if http2:
+        if "HTTP/2" in version:
             try:
                 import h2  # noqa
             except ImportError:  # pragma: no cover
                 raise ImportError(
-                    "Using http2=True, but the 'h2' package is not installed. "
+                    "Configured 'HTTP/2', but the 'h2' package is not installed. "
                     "Make sure to install httpx using `pip install httpx[http2]`."
                 ) from None
 
@@ -682,14 +684,19 @@ class Client(BaseClient):
             if proxy:
                 raise RuntimeError("Use either `proxy` or 'proxies', not both.")
 
+        if http2:
+            raise RuntimeError(
+                "The 'http2' argument is now deprecated. "
+                "Use version=httpx.Version('HTTP/1.1', 'HTTP/2')."
+            )
+
         allow_env_proxies = trust_env and app is None and transport is None
         proxy_map = self._get_proxy_map(proxies or proxy, allow_env_proxies)
 
         self._transport = self._init_transport(
             verify=verify,
             cert=cert,
-            http1=http1,
-            http2=http2,
+            version=version,
             limits=limits,
             transport=transport,
             app=app,
@@ -702,8 +709,7 @@ class Client(BaseClient):
                 proxy,
                 verify=verify,
                 cert=cert,
-                http1=http1,
-                http2=http2,
+                version=version,
                 limits=limits,
                 trust_env=trust_env,
             )
@@ -720,8 +726,7 @@ class Client(BaseClient):
         self,
         verify: VerifyTypes = True,
         cert: typing.Optional[CertTypes] = None,
-        http1: bool = True,
-        http2: bool = False,
+        version: Version = DEFAULT_VERSION,
         limits: Limits = DEFAULT_LIMITS,
         transport: typing.Optional[BaseTransport] = None,
         app: typing.Optional[typing.Callable[..., typing.Any]] = None,
@@ -736,8 +741,7 @@ class Client(BaseClient):
         return HTTPTransport(
             verify=verify,
             cert=cert,
-            http1=http1,
-            http2=http2,
+            version=version,
             limits=limits,
             trust_env=trust_env,
         )
@@ -747,16 +751,14 @@ class Client(BaseClient):
         proxy: Proxy,
         verify: VerifyTypes = True,
         cert: typing.Optional[CertTypes] = None,
-        http1: bool = True,
-        http2: bool = False,
+        version: Version = DEFAULT_VERSION,
         limits: Limits = DEFAULT_LIMITS,
         trust_env: bool = True,
     ) -> BaseTransport:
         return HTTPTransport(
             verify=verify,
             cert=cert,
-            http1=http1,
-            http2=http2,
+            version=version,
             limits=limits,
             trust_env=trust_env,
             proxy=proxy,
@@ -1372,8 +1374,8 @@ class AsyncClient(BaseClient):
         cookies: typing.Optional[CookieTypes] = None,
         verify: VerifyTypes = True,
         cert: typing.Optional[CertTypes] = None,
-        http1: bool = True,
         http2: bool = False,
+        version: Version = DEFAULT_VERSION,
         proxy: typing.Optional[ProxyTypes] = None,
         proxies: typing.Optional[ProxiesTypes] = None,
         mounts: typing.Optional[
@@ -1406,32 +1408,37 @@ class AsyncClient(BaseClient):
             default_encoding=default_encoding,
         )
 
-        if http2:
+        if "HTTP/2" in version:
             try:
                 import h2  # noqa
             except ImportError:  # pragma: no cover
                 raise ImportError(
-                    "Using http2=True, but the 'h2' package is not installed. "
+                    "Configured 'HTTP/2', but the 'h2' package is not installed. "
                     "Make sure to install httpx using `pip install httpx[http2]`."
                 ) from None
 
         if proxies:
             message = (
-                "The 'proxies' argument is now deprecated."
-                " Use 'proxy' or 'mounts' instead."
+                "The 'proxies' argument is now deprecated. "
+                "Use 'proxy' or 'mounts' instead."
             )
             warnings.warn(message, DeprecationWarning)
             if proxy:
                 raise RuntimeError("Use either `proxy` or 'proxies', not both.")
 
+        if http2:
+            raise RuntimeError(
+                "The 'http2' argument is now deprecated. "
+                "Use version=httpx.Version('HTTP/1.1', 'HTTP/2')."
+            )
+
         allow_env_proxies = trust_env and app is None and transport is None
         proxy_map = self._get_proxy_map(proxies or proxy, allow_env_proxies)
 
         self._transport = self._init_transport(
             verify=verify,
             cert=cert,
-            http1=http1,
-            http2=http2,
+            version=version,
             limits=limits,
             transport=transport,
             app=app,
@@ -1445,8 +1452,7 @@ class AsyncClient(BaseClient):
                 proxy,
                 verify=verify,
                 cert=cert,
-                http1=http1,
-                http2=http2,
+                version=version,
                 limits=limits,
                 trust_env=trust_env,
             )
@@ -1462,8 +1468,7 @@ class AsyncClient(BaseClient):
         self,
         verify: VerifyTypes = True,
         cert: typing.Optional[CertTypes] = None,
-        http1: bool = True,
-        http2: bool = False,
+        version: Version = DEFAULT_VERSION,
         limits: Limits = DEFAULT_LIMITS,
         transport: typing.Optional[AsyncBaseTransport] = None,
         app: typing.Optional[typing.Callable[..., typing.Any]] = None,
@@ -1478,8 +1483,7 @@ class AsyncClient(BaseClient):
         return AsyncHTTPTransport(
             verify=verify,
             cert=cert,
-            http1=http1,
-            http2=http2,
+            version=version,
             limits=limits,
             trust_env=trust_env,
         )
@@ -1489,16 +1493,14 @@ class AsyncClient(BaseClient):
         proxy: Proxy,
         verify: VerifyTypes = True,
         cert: typing.Optional[CertTypes] = None,
-        http1: bool = True,
-        http2: bool = False,
+        version: Version = DEFAULT_VERSION,
         limits: Limits = DEFAULT_LIMITS,
         trust_env: bool = True,
     ) -> AsyncBaseTransport:
         return AsyncHTTPTransport(
             verify=verify,
             cert=cert,
-            http1=http1,
-            http2=http2,
+            version=version,
             limits=limits,
             trust_env=trust_env,
             proxy=proxy,
index 0cfd552e49b9c510fcf5c7466118dcd4ed574d5f..7b786a91ee51a6c2ba9a046a70c913cd76b69bac 100644 (file)
@@ -363,6 +363,21 @@ class Proxy:
         return f"Proxy({url_str}{auth_str}{headers_str})"
 
 
+class Version:
+    def __init__(self, *versions: str) -> None:
+        self._versions = sorted(set(versions))
+        if any([version not in ["HTTP/1.1", "HTTP/2"] for version in versions]):
+            raise ValueError("Supported versions are 'HTTP/1.1' and 'HTTP/2'")
+
+    def __contains__(self, version: str) -> bool:
+        return version in self._versions
+
+    def __repr__(self) -> str:
+        version_str = ", ".join([repr(version) for version in self._versions])
+        return f"Version({version_str})"
+
+
 DEFAULT_TIMEOUT_CONFIG = Timeout(timeout=5.0)
 DEFAULT_LIMITS = Limits(max_connections=100, max_keepalive_connections=20)
 DEFAULT_MAX_REDIRECTS = 20
+DEFAULT_VERSION = Version("HTTP/1.1")
index 14a087389a8ba910a6b4d88ef69527ba9eb335c0..774985d72c04df133cc0083aa59b3b5fc39fa7ba 100644 (file)
@@ -9,9 +9,11 @@ The following additional keyword arguments are currently supported by httpcore..
 
 Example usages...
 
-# Disable HTTP/2 on a single specific domain.
+# Enable HTTP/2, except on a single specific domain.
+
+ENABLE_HTTP2 = httpx.Version("HTTP/1.1", "HTTP/2")
 mounts = {
-    "all://": httpx.HTTPTransport(http2=True),
+    "all://": httpx.HTTPTransport(version=ENABLE_HTTP2),
     "all://*example.org": httpx.HTTPTransport()
 }
 
@@ -29,7 +31,14 @@ from types import TracebackType
 
 import httpcore
 
-from .._config import DEFAULT_LIMITS, Limits, Proxy, create_ssl_context
+from .._config import (
+    DEFAULT_LIMITS,
+    DEFAULT_VERSION,
+    Limits,
+    Proxy,
+    Version,
+    create_ssl_context,
+)
 from .._exceptions import (
     ConnectError,
     ConnectTimeout,
@@ -121,8 +130,7 @@ class HTTPTransport(BaseTransport):
         self,
         verify: VerifyTypes = True,
         cert: typing.Optional[CertTypes] = None,
-        http1: bool = True,
-        http2: bool = False,
+        version: Version = DEFAULT_VERSION,
         limits: Limits = DEFAULT_LIMITS,
         trust_env: bool = True,
         proxy: typing.Optional[ProxyTypes] = None,
@@ -140,8 +148,8 @@ class HTTPTransport(BaseTransport):
                 max_connections=limits.max_connections,
                 max_keepalive_connections=limits.max_keepalive_connections,
                 keepalive_expiry=limits.keepalive_expiry,
-                http1=http1,
-                http2=http2,
+                http1="HTTP/1.1" in version,
+                http2="HTTP/2" in version,
                 uds=uds,
                 local_address=local_address,
                 retries=retries,
@@ -162,8 +170,8 @@ class HTTPTransport(BaseTransport):
                 max_connections=limits.max_connections,
                 max_keepalive_connections=limits.max_keepalive_connections,
                 keepalive_expiry=limits.keepalive_expiry,
-                http1=http1,
-                http2=http2,
+                http1="HTTP/1.1" in version,
+                http2="HTTP/2" in version,
                 socket_options=socket_options,
             )
         elif proxy.url.scheme == "socks5":
@@ -187,8 +195,8 @@ class HTTPTransport(BaseTransport):
                 max_connections=limits.max_connections,
                 max_keepalive_connections=limits.max_keepalive_connections,
                 keepalive_expiry=limits.keepalive_expiry,
-                http1=http1,
-                http2=http2,
+                http1="HTTP/1.1" in version,
+                http2="HTTP/2" in version,
             )
         else:  # pragma: no cover
             raise ValueError(
@@ -262,8 +270,7 @@ class AsyncHTTPTransport(AsyncBaseTransport):
         self,
         verify: VerifyTypes = True,
         cert: typing.Optional[CertTypes] = None,
-        http1: bool = True,
-        http2: bool = False,
+        version: Version = DEFAULT_VERSION,
         limits: Limits = DEFAULT_LIMITS,
         trust_env: bool = True,
         proxy: typing.Optional[ProxyTypes] = None,
@@ -281,8 +288,8 @@ class AsyncHTTPTransport(AsyncBaseTransport):
                 max_connections=limits.max_connections,
                 max_keepalive_connections=limits.max_keepalive_connections,
                 keepalive_expiry=limits.keepalive_expiry,
-                http1=http1,
-                http2=http2,
+                http1="HTTP/1.1" in version,
+                http2="HTTP/2" in version,
                 uds=uds,
                 local_address=local_address,
                 retries=retries,
@@ -302,8 +309,8 @@ class AsyncHTTPTransport(AsyncBaseTransport):
                 max_connections=limits.max_connections,
                 max_keepalive_connections=limits.max_keepalive_connections,
                 keepalive_expiry=limits.keepalive_expiry,
-                http1=http1,
-                http2=http2,
+                http1="HTTP/1.1" in version,
+                http2="HTTP/2" in version,
                 socket_options=socket_options,
             )
         elif proxy.url.scheme == "socks5":
@@ -327,8 +334,8 @@ class AsyncHTTPTransport(AsyncBaseTransport):
                 max_connections=limits.max_connections,
                 max_keepalive_connections=limits.max_keepalive_connections,
                 keepalive_expiry=limits.keepalive_expiry,
-                http1=http1,
-                http2=http2,
+                http1="HTTP/1.1" in version,
+                http2="HTTP/2" in version,
             )
         else:  # pragma: no cover
             raise ValueError(
index 49664df5899dd477cd6dfa88fca2a0c947a0244c..c5bc177c92db7626f3b14f928d6df5f75e533968 100644 (file)
@@ -9,7 +9,8 @@ import httpx
 @pytest.mark.anyio
 async def test_get(server):
     url = server.url
-    async with httpx.AsyncClient(http2=True) as client:
+    ENABLE_HTTP2 = httpx.Version("HTTP/1.1", "HTTP/2")
+    async with httpx.AsyncClient(version=ENABLE_HTTP2) as client:
         response = await client.get(url)
     assert response.status_code == 200
     assert response.text == "Hello, world!"
@@ -367,7 +368,8 @@ async def test_cancellation_during_stream():
 @pytest.mark.anyio
 async def test_server_extensions(server):
     url = server.url
-    async with httpx.AsyncClient(http2=True) as client:
+    ENABLE_HTTP2 = httpx.Version("HTTP/1.1", "HTTP/2")
+    async with httpx.AsyncClient(version=ENABLE_HTTP2) as client:
         response = await client.get(url)
     assert response.status_code == 200
     assert response.extensions["http_version"] == b"HTTP/1.1"
index fcc6ec6a08ed6c89edff90e97c926171862e22ed..ec8b449be7cf6e925a3825ca85a37622f0eadc56 100644 (file)
@@ -13,7 +13,8 @@ def autodetect(content):
 
 def test_get(server):
     url = server.url
-    with httpx.Client(http2=True) as http:
+    ENABLE_HTTP2 = httpx.Version("HTTP/1.1", "HTTP/2")
+    with httpx.Client(version=ENABLE_HTTP2) as http:
         response = http.get(url)
     assert response.status_code == 200
     assert response.url == url
@@ -399,7 +400,8 @@ def test_all_mounted_transport():
 
 def test_server_extensions(server):
     url = server.url.copy_with(path="/http_version_2")
-    with httpx.Client(http2=True) as client:
+    ENABLE_HTTP2 = httpx.Version("HTTP/1.1", "HTTP/2")
+    with httpx.Client(version=ENABLE_HTTP2) as client:
         response = client.get(url)
     assert response.status_code == 200
     assert response.extensions["http_version"] == b"HTTP/1.1"
index 6f6ee4f575141ee2debefbf8342d3168064bb5e3..d24c54bb3ec05c338f2b1c6ad423e45a75f0e546 100644 (file)
@@ -221,3 +221,15 @@ def test_proxy_with_auth_from_url():
 def test_invalid_proxy_scheme():
     with pytest.raises(ValueError):
         httpx.Proxy("invalid://example.com")
+
+
+def test_version():
+    version = httpx.Version("HTTP/1.1", "HTTP/2")
+    assert "HTTP/1.1" in version
+    assert "HTTP/2" in version
+    assert repr(version) == "Version('HTTP/1.1', 'HTTP/2')"
+
+
+def test_invalid_version():
+    with pytest.raises(ValueError):
+        httpx.Version("HTTP/3")