]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
response.elapsed now reflects entire request/response time. (#692)
authorTom Christie <tom@tomchristie.com>
Sun, 29 Dec 2019 16:56:18 +0000 (16:56 +0000)
committerGitHub <noreply@github.com>
Sun, 29 Dec 2019 16:56:18 +0000 (16:56 +0000)
* Changed behaviour of elapsed on response

* Fixed api docs for Response elapsed

* Minor tweaks to 'request.elapsed'

* Response instantiated with content should have elapsed==0

* Fix elapsed time on immediately closed responses.

docs/api.md
httpx/client.py
httpx/models.py
tests/models/test_responses.py

index 3f45c906524d465715e2c158b870dee0af91df9d..ae4d201eb51a77fef3b3210e9be366e6eb9ccd66 100644 (file)
@@ -56,8 +56,7 @@
 * `.cookies` - **Cookies**
 * `.history` - **List[Response]**
 * `.elapsed` - **[timedelta](https://docs.python.org/3/library/datetime.html)**
-  * The amount of time elapsed between sending the first byte and parsing the headers (not including time spent reading
-  the response).  Use
+  * The amount of time elapsed between sending the request and calling `close()` on the corresponding response received for that request.
   [total_seconds()](https://docs.python.org/3/library/datetime.html#datetime.timedelta.total_seconds) to correctly get
   the total elapsed seconds.
 * `def .raise_for_status()` - **None**
index d2ae2fc976b42c7e23fdae6867066ed590603b99..9b594008f6a20906eb44ba6bfc354ba55fe82d30 100644 (file)
@@ -47,7 +47,7 @@ from .models import (
     URLTypes,
 )
 from .status_codes import codes
-from .utils import ElapsedTimer, NetRCInfo, get_environment_proxies, get_logger
+from .utils import NetRCInfo, get_environment_proxies, get_logger
 
 logger = get_logger(__name__)
 
@@ -593,10 +593,7 @@ class AsyncClient:
         dispatcher = self.dispatcher_for_url(request.url)
 
         try:
-            with ElapsedTimer() as timer:
-                response = await dispatcher.send(request, timeout=timeout)
-            response.elapsed = timer.elapsed
-            response.request = request
+            response = await dispatcher.send(request, timeout=timeout)
         except HTTPError as exc:
             # Add the original request to any HTTPError unless
             # there'a already a request attached in the case of
index 8b219d326e39ebb703b7f5e28628002918632f95..1cbb6abd67a86b1c49eb9f2f6541ea5174db04f8 100644 (file)
@@ -34,6 +34,7 @@ from .exceptions import (
 )
 from .status_codes import StatusCode
 from .utils import (
+    ElapsedTimer,
     flatten_queryparams,
     guess_json_utf,
     is_known_encoding,
@@ -607,6 +608,7 @@ class Request:
         else:
             self.stream = encode(data, files, json)
 
+        self.timer = ElapsedTimer()
         self.prepare()
 
     def prepare(self) -> None:
@@ -661,14 +663,12 @@ class Response:
         stream: ContentStream = None,
         content: bytes = None,
         history: typing.List["Response"] = None,
-        elapsed: datetime.timedelta = None,
     ):
         self.status_code = status_code
         self.http_version = http_version
         self.headers = Headers(headers)
 
         self.request = request
-        self.elapsed = datetime.timedelta(0) if elapsed is None else elapsed
         self.call_next: typing.Optional[typing.Callable] = None
 
         self.history = [] if history is None else list(history)
@@ -677,11 +677,25 @@ class Response:
             self.is_closed = True
             self.is_stream_consumed = True
             self._raw_content = content or b""
+            self._elapsed = request.timer.elapsed
         else:
             self.is_closed = False
             self.is_stream_consumed = False
             self._raw_stream = stream
 
+    @property
+    def elapsed(self) -> datetime.timedelta:
+        """
+        Returns the time taken for the complete request/response
+        cycle to complete.
+        """
+        if not hasattr(self, "_elapsed"):
+            raise RuntimeError(
+                "'.elapsed' may only be accessed after the response "
+                "has been read or closed."
+            )
+        return self._elapsed
+
     @property
     def reason_phrase(self) -> str:
         return StatusCode.get_reason_phrase(self.status_code)
@@ -932,6 +946,7 @@ class Response:
         """
         if not self.is_closed:
             self.is_closed = True
+            self._elapsed = self.request.timer.elapsed
             if hasattr(self, "_raw_stream"):
                 await self._raw_stream.aclose()
 
index 805b2a6593649b0395c8ad610accd08949c73b0a..f39d4ff5fd5e62df7d5d8402fa2108f00aee135d 100644 (file)
@@ -20,13 +20,15 @@ async def async_streaming_body():
     yield b"world!"
 
 
-def test_response():
+@pytest.mark.asyncio
+async def test_response():
     response = httpx.Response(200, content=b"Hello, world!", request=REQUEST)
+
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
     assert response.text == "Hello, world!"
     assert response.request is REQUEST
-    assert response.elapsed == datetime.timedelta(0)
+    assert response.elapsed >= datetime.timedelta(0)
     assert not response.is_error