From 6e0ec84fe635303f209582d1781224911a9344d8 Mon Sep 17 00:00:00 2001 From: BrunoMmS Date: Tue, 25 Nov 2025 12:48:14 -0300 Subject: [PATCH 1/8] Add optional 'duration' parameter to MockTransport to set response.elapsed --- httpx/_transports/mock.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/httpx/_transports/mock.py b/httpx/_transports/mock.py index 8c418f59e0..1db368b063 100644 --- a/httpx/_transports/mock.py +++ b/httpx/_transports/mock.py @@ -4,6 +4,7 @@ from .._models import Request, Response from .base import AsyncBaseTransport, BaseTransport +from datetime import timedelta SyncHandler = typing.Callable[[Request], Response] AsyncHandler = typing.Callable[[Request], typing.Coroutine[None, None, Response]] @@ -13,8 +14,9 @@ class MockTransport(AsyncBaseTransport, BaseTransport): - def __init__(self, handler: SyncHandler | AsyncHandler) -> None: + def __init__(self, handler: SyncHandler | AsyncHandler, duration: float | None = None) -> None: self.handler = handler + self.duration = duration def handle_request( self, @@ -24,6 +26,8 @@ def handle_request( response = self.handler(request) if not isinstance(response, Response): # pragma: no cover raise TypeError("Cannot use an async handler in a sync Client") + + self.__apply_elapsed(response) return response async def handle_async_request( @@ -40,4 +44,10 @@ async def handle_async_request( if not isinstance(response, Response): response = await response + self.__apply_elapsed(response) return response + + def __apply_elapsed(self, response: Response) -> None: + if self.duration is not None: + response.elapsed = timedelta(seconds=self.duration) + \ No newline at end of file From cfe727522cedbdbc2d80d1fc0a979cc431f21e8a Mon Sep 17 00:00:00 2001 From: BrunoMmS Date: Tue, 25 Nov 2025 18:24:34 -0300 Subject: [PATCH 2/8] MockTransport: ensure .elapsed is always set and preserve handler-defined values --- httpx/_transports/mock.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/httpx/_transports/mock.py b/httpx/_transports/mock.py index 1db368b063..2323638475 100644 --- a/httpx/_transports/mock.py +++ b/httpx/_transports/mock.py @@ -14,9 +14,9 @@ class MockTransport(AsyncBaseTransport, BaseTransport): - def __init__(self, handler: SyncHandler | AsyncHandler, duration: float | None = None) -> None: + def __init__(self, handler: SyncHandler | AsyncHandler, delay: timedelta | None = None) -> None: self.handler = handler - self.duration = duration + self.delay = delay def handle_request( self, @@ -27,7 +27,7 @@ def handle_request( if not isinstance(response, Response): # pragma: no cover raise TypeError("Cannot use an async handler in a sync Client") - self.__apply_elapsed(response) + self._apply_elapsed(response) return response async def handle_async_request( @@ -44,10 +44,18 @@ async def handle_async_request( if not isinstance(response, Response): response = await response - self.__apply_elapsed(response) + self._apply_elapsed(response) return response - def __apply_elapsed(self, response: Response) -> None: - if self.duration is not None: - response.elapsed = timedelta(seconds=self.duration) + def _apply_elapsed(self, response): + #- If the handler already set `response._elapsed`, it is preserved. + #- If a delay was provided to MockTransport, `.elapsed` is set to that duration. + #- If no delay is provided, `.elapsed` is explicitly set to None. + if hasattr(response, "_elapsed"): + return + + if self.delay is not None: + response._elapsed = timedelta(seconds=self.delay) + else: + response._elapsed = None \ No newline at end of file From 5061e2461501473efcaf67c7a1a90e752160e6d6 Mon Sep 17 00:00:00 2001 From: BrunoMmS Date: Fri, 28 Nov 2025 10:49:55 -0300 Subject: [PATCH 3/8] test(mocktransport): add tests for delay and elapsed handling --- tests/client/test_client.py | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/client/test_client.py b/tests/client/test_client.py index 657839018a..f6cdb25396 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -460,3 +460,41 @@ def cp1252_but_no_content_type(request): assert response.reason_phrase == "OK" assert response.encoding == "ISO-8859-1" assert response.text == text + + +def test_mocktransport_preserves_handler_elapsed(): + def handler(request): + r = httpx.Response(200) + r.elapsed = timedelta(seconds=1) + return r + + transport = httpx.MockTransport(handler, delay=0.5) + client = httpx.Client(transport=transport) + + response = client.get("https://example.com") + + assert response.elapsed == timedelta(seconds=1) + + +def test_mocktransport_sets_elapsed_to_delay(): + def handler(request): + return httpx.Response(200) + + transport = httpx.MockTransport(handler, delay=0.5) + client = httpx.Client(transport=transport) + + response = client.get("https://example.com") + + assert response.elapsed == timedelta(seconds=0.5) + + +def test_mocktransport_sets_elapsed_none_when_no_delay(): + def handler(request): + return httpx.Response(200) + + transport = httpx.MockTransport(handler) + client = httpx.Client(transport=transport) + + response = client.get("https://example.com") + + assert response.elapsed is None \ No newline at end of file From 6c850fc51f8916bc13448f396bda9fc324fe3deb Mon Sep 17 00:00:00 2001 From: BrunoMmS Date: Fri, 28 Nov 2025 11:29:33 -0300 Subject: [PATCH 4/8] fix:conversion from float number to timedelta --- httpx/_transports/mock.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/httpx/_transports/mock.py b/httpx/_transports/mock.py index 2323638475..8afb1f4d4a 100644 --- a/httpx/_transports/mock.py +++ b/httpx/_transports/mock.py @@ -55,7 +55,10 @@ def _apply_elapsed(self, response): return if self.delay is not None: - response._elapsed = timedelta(seconds=self.delay) + if isinstance(self.delay, timedelta): + response._elapsed = self.delay + else: + response._elapsed = timedelta(seconds=self.delay) else: response._elapsed = None \ No newline at end of file From 1d0f614e96f98f750f7f8650b33b8df4b06de7cd Mon Sep 17 00:00:00 2001 From: BrunoMmS Date: Fri, 28 Nov 2025 11:40:10 -0300 Subject: [PATCH 5/8] . --- httpx/_transports/mock.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/httpx/_transports/mock.py b/httpx/_transports/mock.py index 8afb1f4d4a..426e988959 100644 --- a/httpx/_transports/mock.py +++ b/httpx/_transports/mock.py @@ -48,9 +48,9 @@ async def handle_async_request( return response def _apply_elapsed(self, response): - #- If the handler already set `response._elapsed`, it is preserved. - #- If a delay was provided to MockTransport, `.elapsed` is set to that duration. - #- If no delay is provided, `.elapsed` is explicitly set to None. + # If the handler already set `response._elapsed`, it is preserved. + # If a delay was provided to MockTransport, `.elapsed` is set to that duration. + # If no delay is provided, `.elapsed` is explicitly set to None. if hasattr(response, "_elapsed"): return From 1b4495c8e0070f55a7bae0c3d35e981346b35b0e Mon Sep 17 00:00:00 2001 From: BrunoMmS Date: Fri, 28 Nov 2025 11:58:14 -0300 Subject: [PATCH 6/8] formating --- httpx/_transports/mock.py | 5 +++-- tests/client/test_client.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/httpx/_transports/mock.py b/httpx/_transports/mock.py index 426e988959..e8557991ca 100644 --- a/httpx/_transports/mock.py +++ b/httpx/_transports/mock.py @@ -14,7 +14,9 @@ class MockTransport(AsyncBaseTransport, BaseTransport): - def __init__(self, handler: SyncHandler | AsyncHandler, delay: timedelta | None = None) -> None: + def __init__( + self, handler: SyncHandler | AsyncHandler, delay: timedelta | None = None + ) -> None: self.handler = handler self.delay = delay @@ -61,4 +63,3 @@ def _apply_elapsed(self, response): response._elapsed = timedelta(seconds=self.delay) else: response._elapsed = None - \ No newline at end of file diff --git a/tests/client/test_client.py b/tests/client/test_client.py index f6cdb25396..c9f6e404be 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -497,4 +497,4 @@ def handler(request): response = client.get("https://example.com") - assert response.elapsed is None \ No newline at end of file + assert response.elapsed is None From a6c0e29e2ee7fb63101474fac9ae03b21be08852 Mon Sep 17 00:00:00 2001 From: BrunoMmS Date: Fri, 28 Nov 2025 12:14:08 -0300 Subject: [PATCH 7/8] fix: formatting, static types and timedelta in test for mock elapsed --- httpx/_transports/mock.py | 10 ++-------- tests/client/test_client.py | 4 ++-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/httpx/_transports/mock.py b/httpx/_transports/mock.py index e8557991ca..e838b5a1ed 100644 --- a/httpx/_transports/mock.py +++ b/httpx/_transports/mock.py @@ -49,17 +49,11 @@ async def handle_async_request( self._apply_elapsed(response) return response - def _apply_elapsed(self, response): + def _apply_elapsed(self, response: Response) -> None: # If the handler already set `response._elapsed`, it is preserved. # If a delay was provided to MockTransport, `.elapsed` is set to that duration. # If no delay is provided, `.elapsed` is explicitly set to None. if hasattr(response, "_elapsed"): return - if self.delay is not None: - if isinstance(self.delay, timedelta): - response._elapsed = self.delay - else: - response._elapsed = timedelta(seconds=self.delay) - else: - response._elapsed = None + response._elapsed = self.delay diff --git a/tests/client/test_client.py b/tests/client/test_client.py index c9f6e404be..179bcdbe7a 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -468,7 +468,7 @@ def handler(request): r.elapsed = timedelta(seconds=1) return r - transport = httpx.MockTransport(handler, delay=0.5) + transport = httpx.MockTransport(handler, delay=timedelta(seconds=0.5)) client = httpx.Client(transport=transport) response = client.get("https://example.com") @@ -480,7 +480,7 @@ def test_mocktransport_sets_elapsed_to_delay(): def handler(request): return httpx.Response(200) - transport = httpx.MockTransport(handler, delay=0.5) + transport = httpx.MockTransport(handler, delay=timedelta(seconds=0.5)) client = httpx.Client(transport=transport) response = client.get("https://example.com") From d5721c9eded948e7dfc9540433f2e454f1d6f4ff Mon Sep 17 00:00:00 2001 From: BrunoMmS Date: Fri, 28 Nov 2025 12:31:37 -0300 Subject: [PATCH 8/8] fix: constructor type for delay only timedelta and default v alue timedelta(0) --- httpx/_transports/mock.py | 4 ++-- tests/client/test_client.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/httpx/_transports/mock.py b/httpx/_transports/mock.py index e838b5a1ed..d521838977 100644 --- a/httpx/_transports/mock.py +++ b/httpx/_transports/mock.py @@ -1,10 +1,10 @@ from __future__ import annotations import typing +from datetime import timedelta from .._models import Request, Response from .base import AsyncBaseTransport, BaseTransport -from datetime import timedelta SyncHandler = typing.Callable[[Request], Response] AsyncHandler = typing.Callable[[Request], typing.Coroutine[None, None, Response]] @@ -15,7 +15,7 @@ class MockTransport(AsyncBaseTransport, BaseTransport): def __init__( - self, handler: SyncHandler | AsyncHandler, delay: timedelta | None = None + self, handler: SyncHandler | AsyncHandler, delay: timedelta = timedelta(0) ) -> None: self.handler = handler self.delay = delay diff --git a/tests/client/test_client.py b/tests/client/test_client.py index 179bcdbe7a..a7fd670a08 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -497,4 +497,4 @@ def handler(request): response = client.get("https://example.com") - assert response.elapsed is None + assert response.elapsed == timedelta(0)