Repository: encode/httpx

Issue:
More concise Timeout parameters.
Follow up to #1085

Since we're on out final 1.0 polishing run-in I think we should place the `httpx.Timeout(5.0, connect_timeout=60.0)` style on a gentle deprecation with 0.14, in favour of the more concise style of `httpx.Timeout(5.0, connect=60.0)`.

I'm milestoning this for 0.14 so that we can track the remaining items for it, and aim for a release sometime this week.


Discussion:

Resolving patch:
diff --git a/httpx/_config.py b/httpx/_config.py
--- a/httpx/_config.py
+++ b/httpx/_config.py
@@ -9,7 +9,7 @@
 
 from ._models import URL, Headers
 from ._types import CertTypes, HeaderTypes, TimeoutTypes, URLTypes, VerifyTypes
-from ._utils import get_ca_bundle_from_env, get_logger
+from ._utils import get_ca_bundle_from_env, get_logger, warn_deprecated
 
 DEFAULT_CIPHERS = ":".join(
     [
@@ -196,51 +196,86 @@ class Timeout:
 
     **Usage**:
 
-    Timeout(None)                       # No timeouts.
-    Timeout(5.0)                        # 5s timeout on all operations.
-    Timeout(None, connect_timeout=5.0)  # 5s timeout on connect, no other timeouts.
-    Timeout(5.0, connect_timeout=10.0)  # 10s timeout on connect. 5s timeout elsewhere.
-    Timeout(5.0, pool_timeout=None)     # No timeout on acquiring connection from pool.
-                                        # 5s timeout elsewhere.
+    Timeout(None)               # No timeouts.
+    Timeout(5.0)                # 5s timeout on all operations.
+    Timeout(None, connect=5.0)  # 5s timeout on connect, no other timeouts.
+    Timeout(5.0, connect=10.0)  # 10s timeout on connect. 5s timeout elsewhere.
+    Timeout(5.0, pool=None)     # No timeout on acquiring connection from pool.
+                                # 5s timeout elsewhere.
     """
 
     def __init__(
         self,
         timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
         *,
+        connect: typing.Union[None, float, UnsetType] = UNSET,
+        read: typing.Union[None, float, UnsetType] = UNSET,
+        write: typing.Union[None, float, UnsetType] = UNSET,
+        pool: typing.Union[None, float, UnsetType] = UNSET,
+        # Deprecated aliases.
         connect_timeout: typing.Union[None, float, UnsetType] = UNSET,
         read_timeout: typing.Union[None, float, UnsetType] = UNSET,
         write_timeout: typing.Union[None, float, UnsetType] = UNSET,
         pool_timeout: typing.Union[None, float, UnsetType] = UNSET,
     ):
+        if not isinstance(connect_timeout, UnsetType):
+            warn_deprecated(
+                "httpx.Timeout(..., connect_timeout=...) is deprecated and will "
+                "raise errors in a future version. "
+                "Use httpx.Timeout(..., connect=...) instead."
+            )
+            connect = connect_timeout
+
+        if not isinstance(read_timeout, UnsetType):
+            warn_deprecated(
+                "httpx.Timeout(..., read_timeout=...) is deprecated and will "
+                "raise errors in a future version. "
+                "Use httpx.Timeout(..., write=...) instead."
+            )
+            read = read_timeout
+
+        if not isinstance(write_timeout, UnsetType):
+            warn_deprecated(
+                "httpx.Timeout(..., write_timeout=...) is deprecated and will "
+                "raise errors in a future version. "
+                "Use httpx.Timeout(..., write=...) instead."
+            )
+            write = write_timeout
+
+        if not isinstance(pool_timeout, UnsetType):
+            warn_deprecated(
+                "httpx.Timeout(..., pool_timeout=...) is deprecated and will "
+                "raise errors in a future version. "
+                "Use httpx.Timeout(..., pool=...) instead."
+            )
+            pool = pool_timeout
+
         if isinstance(timeout, Timeout):
             # Passed as a single explicit Timeout.
-            assert connect_timeout is UNSET
-            assert read_timeout is UNSET
-            assert write_timeout is UNSET
-            assert pool_timeout is UNSET
-            self.connect_timeout = (
-                timeout.connect_timeout
-            )  # type: typing.Optional[float]
-            self.read_timeout = timeout.read_timeout  # type: typing.Optional[float]
-            self.write_timeout = timeout.write_timeout  # type: typing.Optional[float]
-            self.pool_timeout = timeout.pool_timeout  # type: typing.Optional[float]
+            assert connect is UNSET
+            assert read is UNSET
+            assert write is UNSET
+            assert pool is UNSET
+            self.connect = timeout.connect  # type: typing.Optional[float]
+            self.read = timeout.read  # type: typing.Optional[float]
+            self.write = timeout.write  # type: typing.Optional[float]
+            self.pool = timeout.pool  # type: typing.Optional[float]
         elif isinstance(timeout, tuple):
             # Passed as a tuple.
-            self.connect_timeout = timeout[0]
-            self.read_timeout = timeout[1]
-            self.write_timeout = None if len(timeout) < 3 else timeout[2]
-            self.pool_timeout = None if len(timeout) < 4 else timeout[3]
+            self.connect = timeout[0]
+            self.read = timeout[1]
+            self.write = None if len(timeout) < 3 else timeout[2]
+            self.pool = None if len(timeout) < 4 else timeout[3]
         elif not (
-            isinstance(connect_timeout, UnsetType)
-            or isinstance(read_timeout, UnsetType)
-            or isinstance(write_timeout, UnsetType)
-            or isinstance(pool_timeout, UnsetType)
+            isinstance(connect, UnsetType)
+            or isinstance(read, UnsetType)
+            or isinstance(write, UnsetType)
+            or isinstance(pool, UnsetType)
         ):
-            self.connect_timeout = connect_timeout
-            self.read_timeout = read_timeout
-            self.write_timeout = write_timeout
-            self.pool_timeout = pool_timeout
+            self.connect = connect
+            self.read = read
+            self.write = write
+            self.pool = pool
         else:
             if isinstance(timeout, UnsetType):
                 warnings.warn(
@@ -250,54 +285,35 @@ def __init__(
                     DeprecationWarning,
                 )
                 timeout = None
-            self.connect_timeout = (
-                timeout if isinstance(connect_timeout, UnsetType) else connect_timeout
-            )
-            self.read_timeout = (
-                timeout if isinstance(read_timeout, UnsetType) else read_timeout
-            )
-            self.write_timeout = (
-                timeout if isinstance(write_timeout, UnsetType) else write_timeout
-            )
-            self.pool_timeout = (
-                timeout if isinstance(pool_timeout, UnsetType) else pool_timeout
-            )
+            self.connect = timeout if isinstance(connect, UnsetType) else connect
+            self.read = timeout if isinstance(read, UnsetType) else read
+            self.write = timeout if isinstance(write, UnsetType) else write
+            self.pool = timeout if isinstance(pool, UnsetType) else pool
 
     def as_dict(self) -> typing.Dict[str, typing.Optional[float]]:
         return {
-            "connect": self.connect_timeout,
-            "read": self.read_timeout,
-            "write": self.write_timeout,
-            "pool": self.pool_timeout,
+            "connect": self.connect,
+            "read": self.read,
+            "write": self.write,
+            "pool": self.pool,
         }
 
     def __eq__(self, other: typing.Any) -> bool:
         return (
             isinstance(other, self.__class__)
-            and self.connect_timeout == other.connect_timeout
-            and self.read_timeout == other.read_timeout
-            and self.write_timeout == other.write_timeout
-            and self.pool_timeout == other.pool_timeout
+            and self.connect == other.connect
+            and self.read == other.read
+            and self.write == other.write
+            and self.pool == other.pool
         )
 
     def __repr__(self) -> str:
         class_name = self.__class__.__name__
-        if (
-            len(
-                {
-                    self.connect_timeout,
-                    self.read_timeout,
-                    self.write_timeout,
-                    self.pool_timeout,
-                }
-            )
-            == 1
-        ):
-            return f"{class_name}(timeout={self.connect_timeout})"
+        if len({self.connect, self.read, self.write, self.pool}) == 1:
+            return f"{class_name}(timeout={self.connect})"
         return (
-            f"{class_name}(connect_timeout={self.connect_timeout}, "
-            f"read_timeout={self.read_timeout}, write_timeout={self.write_timeout}, "
-            f"pool_timeout={self.pool_timeout})"
+            f"{class_name}(connect={self.connect}, "
+            f"read={self.read}, write={self.write}, pool={self.pool})"
         )
 
 


Test patch:
diff --git a/tests/test_config.py b/tests/test_config.py
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -116,18 +116,16 @@ def test_timeout_eq():
 
 
 def test_timeout_all_parameters_set():
-    timeout = httpx.Timeout(
-        connect_timeout=5.0, read_timeout=5.0, write_timeout=5.0, pool_timeout=5.0
-    )
+    timeout = httpx.Timeout(connect=5.0, read=5.0, write=5.0, pool=5.0)
     assert timeout == httpx.Timeout(timeout=5.0)
 
 
 def test_timeout_from_nothing():
     timeout = httpx.Timeout(None)
-    assert timeout.connect_timeout is None
-    assert timeout.read_timeout is None
-    assert timeout.write_timeout is None
-    assert timeout.pool_timeout is None
+    assert timeout.connect is None
+    assert timeout.read is None
+    assert timeout.write is None
+    assert timeout.pool is None
 
 
 def test_timeout_from_none():
@@ -136,23 +134,23 @@ def test_timeout_from_none():
 
 
 def test_timeout_from_one_none_value():
-    timeout = httpx.Timeout(None, read_timeout=None)
+    timeout = httpx.Timeout(None, read=None)
     assert timeout == httpx.Timeout(None)
 
 
 def test_timeout_from_one_value():
-    timeout = httpx.Timeout(None, read_timeout=5.0)
+    timeout = httpx.Timeout(None, read=5.0)
     assert timeout == httpx.Timeout(timeout=(None, 5.0, None, None))
 
 
 def test_timeout_from_one_value_and_default():
-    timeout = httpx.Timeout(5.0, pool_timeout=60.0)
+    timeout = httpx.Timeout(5.0, pool=60.0)
     assert timeout == httpx.Timeout(timeout=(5.0, 5.0, 5.0, 60.0))
 
 
 def test_timeout_missing_default():
     with pytest.warns(DeprecationWarning):
-        timeout = httpx.Timeout(pool_timeout=60.0)
+        timeout = httpx.Timeout(pool=60.0)
         assert timeout == httpx.Timeout(timeout=(None, None, None, 60.0))
 
 
@@ -170,11 +168,8 @@ def test_timeout_repr():
     timeout = httpx.Timeout(timeout=5.0)
     assert repr(timeout) == "Timeout(timeout=5.0)"
 
-    timeout = httpx.Timeout(None, read_timeout=5.0)
-    assert repr(timeout) == (
-        "Timeout(connect_timeout=None, read_timeout=5.0, "
-        "write_timeout=None, pool_timeout=None)"
-    )
+    timeout = httpx.Timeout(None, read=5.0)
+    assert repr(timeout) == "Timeout(connect=None, read=5.0, write=None, pool=None)"
 
 
 @pytest.mark.skipif(
diff --git a/tests/test_timeouts.py b/tests/test_timeouts.py
--- a/tests/test_timeouts.py
+++ b/tests/test_timeouts.py
@@ -5,7 +5,7 @@
 
 @pytest.mark.usefixtures("async_environment")
 async def test_read_timeout(server):
-    timeout = httpx.Timeout(None, read_timeout=1e-6)
+    timeout = httpx.Timeout(None, read=1e-6)
 
     async with httpx.AsyncClient(timeout=timeout) as client:
         with pytest.raises(httpx.ReadTimeout):
@@ -14,7 +14,7 @@ async def test_read_timeout(server):
 
 @pytest.mark.usefixtures("async_environment")
 async def test_write_timeout(server):
-    timeout = httpx.Timeout(None, write_timeout=1e-6)
+    timeout = httpx.Timeout(None, write=1e-6)
 
     async with httpx.AsyncClient(timeout=timeout) as client:
         with pytest.raises(httpx.WriteTimeout):
@@ -24,7 +24,7 @@ async def test_write_timeout(server):
 
 @pytest.mark.usefixtures("async_environment")
 async def test_connect_timeout(server):
-    timeout = httpx.Timeout(None, connect_timeout=1e-6)
+    timeout = httpx.Timeout(None, connect=1e-6)
 
     async with httpx.AsyncClient(timeout=timeout) as client:
         with pytest.raises(httpx.ConnectTimeout):
@@ -35,9 +35,23 @@ async def test_connect_timeout(server):
 @pytest.mark.usefixtures("async_environment")
 async def test_pool_timeout(server):
     pool_limits = httpx.PoolLimits(max_connections=1)
-    timeout = httpx.Timeout(None, pool_timeout=1e-4)
+    timeout = httpx.Timeout(None, pool=1e-4)
 
     async with httpx.AsyncClient(pool_limits=pool_limits, timeout=timeout) as client:
         async with client.stream("GET", server.url):
             with pytest.raises(httpx.PoolTimeout):
                 await client.get("http://localhost:8000/")
+
+
+def test_deprecated_verbose_timeout_params():
+    with pytest.warns(DeprecationWarning):
+        httpx.Timeout(None, read_timeout=1.0)
+
+    with pytest.warns(DeprecationWarning):
+        httpx.Timeout(None, write_timeout=1.0)
+
+    with pytest.warns(DeprecationWarning):
+        httpx.Timeout(None, connect_timeout=1.0)
+
+    with pytest.warns(DeprecationWarning):
+        httpx.Timeout(None, pool_timeout=1.0)