From e37d9e42b3979eb4f80520a781b5fddf8283d7c7 Mon Sep 17 00:00:00 2001 From: Slava Skvortsov Date: Sat, 3 Oct 2020 13:29:51 +0200 Subject: [PATCH 1/7] Add last_request property --- respx/models.py | 14 +++++++++++--- tests/test_stats.py | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/respx/models.py b/respx/models.py index ea857ab..06602f3 100644 --- a/respx/models.py +++ b/respx/models.py @@ -235,19 +235,27 @@ def __init__( self.stats = mock.MagicMock() @property - def called(self): + def called(self) -> bool: return self.stats.called @property - def call_count(self): + def call_count(self) -> int: return self.stats.call_count @property - def calls(self): + def calls(self) -> List[Tuple[Request, ResponseTemplate]]: return [ (request, response) for (request, response), _ in self.stats.call_args_list ] + @property + def last_request(self) -> Optional[Request]: + calls = self.calls + if not calls: + return None + + return calls[-1][0] + def get_url(self) -> Optional[URLPatternTypes]: return self._url diff --git a/tests/test_stats.py b/tests/test_stats.py index d3bb157..82c385a 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -57,6 +57,7 @@ async def test(backend): assert foobar1.called is False assert foobar1.call_count == len(foobar1.calls) assert foobar1.call_count == 0 + assert foobar1.last_request is None assert respx.stats.call_count == len(respx.calls) assert respx.stats.call_count == 0 @@ -72,6 +73,7 @@ async def test(backend): _request, _response = foobar1.calls[-1] assert isinstance(_request, httpx.Request) assert isinstance(_response, httpx.Response) + assert foobar1.last_request is _request assert _request.method == "GET" assert _request.url == url assert _response.status_code == 202 From f34afda81f4ac206185c9a1df3151ef6241cda28 Mon Sep 17 00:00:00 2001 From: Slava Skvortsov Date: Sat, 3 Oct 2020 15:45:38 +0200 Subject: [PATCH 2/7] Add Call and CallList classes --- respx/api.py | 4 ++-- respx/models.py | 43 +++++++++++++++++++++++++++++++------------ respx/transports.py | 8 ++++---- tests/test_mock.py | 4 +++- tests/test_stats.py | 7 ++++--- 5 files changed, 44 insertions(+), 22 deletions(-) diff --git a/respx/api.py b/respx/api.py index e6f003a..480c088 100644 --- a/respx/api.py +++ b/respx/api.py @@ -1,13 +1,13 @@ from typing import Callable, Optional, Pattern, Union, overload from .mocks import MockTransport -from .models import ContentDataTypes, DefaultType, HeaderTypes, RequestPattern +from .models import CallList, ContentDataTypes, DefaultType, HeaderTypes, RequestPattern mock = MockTransport(assert_all_called=False) aliases = mock.aliases stats = mock.stats -calls = mock.calls +calls: CallList = mock.calls def start() -> None: diff --git a/respx/models.py b/respx/models.py index 06602f3..91fee4f 100644 --- a/respx/models.py +++ b/respx/models.py @@ -1,14 +1,18 @@ import inspect import json as jsonlib import re +from collections import namedtuple from functools import partial from typing import ( + TYPE_CHECKING, Any, AsyncIterator, Callable, Dict, + Generator, Iterator, List, + NamedTuple, Optional, Pattern, Sequence, @@ -23,6 +27,10 @@ from httpcore import AsyncByteStream, SyncByteStream from httpx import Headers as HTTPXHeaders # TODO: Drop usage +if TYPE_CHECKING: + from unittest.mock import _CallList + + URL = Tuple[bytes, bytes, Optional[int], bytes] Headers = List[Tuple[bytes, bytes]] Request = Tuple[ @@ -108,6 +116,27 @@ def build_response( return httpx_response +class Call(NamedTuple): + request: httpx.Request + response: Optional[httpx.Response] + + +class CallList(list): + def __iter__(self) -> Generator[Call, None, None]: + yield from super().__iter__() + + @classmethod + def from_unittest_call_list(cls, call_list: "_CallList") -> "CallList": + return cls(Call(request, response) for (request, response), _ in call_list) + + @property + def last(self) -> Optional[Call]: + if self: + return self[-1] + + return None + + class ContentStream(AsyncByteStream, SyncByteStream): def __init__(self, content: bytes) -> None: self._content = content @@ -243,18 +272,8 @@ def call_count(self) -> int: return self.stats.call_count @property - def calls(self) -> List[Tuple[Request, ResponseTemplate]]: - return [ - (request, response) for (request, response), _ in self.stats.call_args_list - ] - - @property - def last_request(self) -> Optional[Request]: - calls = self.calls - if not calls: - return None - - return calls[-1][0] + def calls(self) -> CallList: + return CallList.from_unittest_call_list(self.stats.call_args_list) def get_url(self) -> Optional[URLPatternTypes]: return self._url diff --git a/respx/transports.py b/respx/transports.py index 29069fb..db98e5c 100644 --- a/respx/transports.py +++ b/respx/transports.py @@ -1,6 +1,7 @@ from typing import Any, Callable, Dict, List, Optional, Pattern, Tuple, Union, overload from unittest import mock +import httpx from httpcore import ( AsyncByteStream, AsyncHTTPTransport, @@ -11,6 +12,7 @@ from .models import ( URL, AsyncResponse, + CallList, ContentDataTypes, DefaultType, Headers, @@ -40,7 +42,7 @@ def __init__( self.aliases: Dict[str, RequestPattern] = {} self.stats = mock.MagicMock() - self.calls: List[Tuple[Request, Optional[Response]]] = [] + self.calls: CallList = CallList() def clear(self): """ @@ -284,9 +286,7 @@ def record( self.stats(request, response) # Copy stats due to unwanted use of property refs in the high-level api - self.calls[:] = ( - (request, response) for (request, response), _ in self.stats.call_args_list - ) + self.calls[:] = CallList.from_unittest_call_list(self.stats.call_args_list) def assert_all_called(self) -> None: assert all( diff --git a/tests/test_mock.py b/tests/test_mock.py index 05aedf9..c1a96e2 100644 --- a/tests/test_mock.py +++ b/tests/test_mock.py @@ -226,9 +226,11 @@ async def test(respx_mock): assert respx.stats.call_count == 0 assert respx_mock.stats.call_count == 1 - _request, _response = respx_mock.calls[-1] + _request, _response = respx_mock.calls.last assert _request is not None assert _response is not None + assert respx_mock.calls.last.request is _request + assert respx_mock.calls.last.response is _response assert _request.url == "https://some.thing/" await test() diff --git a/tests/test_stats.py b/tests/test_stats.py index 82c385a..487b9ca 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -57,7 +57,7 @@ async def test(backend): assert foobar1.called is False assert foobar1.call_count == len(foobar1.calls) assert foobar1.call_count == 0 - assert foobar1.last_request is None + assert foobar1.calls.last is None assert respx.stats.call_count == len(respx.calls) assert respx.stats.call_count == 0 @@ -73,7 +73,8 @@ async def test(backend): _request, _response = foobar1.calls[-1] assert isinstance(_request, httpx.Request) assert isinstance(_response, httpx.Response) - assert foobar1.last_request is _request + assert foobar1.calls.last.request is _request + assert foobar1.calls.last.response is _response assert _request.method == "GET" assert _request.url == url assert _response.status_code == 202 @@ -107,8 +108,8 @@ async def test(backend): if isinstance(backend, TrioBackend): trio.run(test, backend) else: + loop = asyncio.new_event_loop() try: - loop = asyncio.new_event_loop() loop.run_until_complete(test(backend)) finally: loop.close() From e052c38be7894f70c6b7639f0e31c38f0a5a741c Mon Sep 17 00:00:00 2001 From: Slava Skvortsov Date: Sat, 3 Oct 2020 15:58:46 +0200 Subject: [PATCH 3/7] Flake8 --- respx/models.py | 1 - respx/transports.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/respx/models.py b/respx/models.py index 91fee4f..33943e9 100644 --- a/respx/models.py +++ b/respx/models.py @@ -1,7 +1,6 @@ import inspect import json as jsonlib import re -from collections import namedtuple from functools import partial from typing import ( TYPE_CHECKING, diff --git a/respx/transports.py b/respx/transports.py index db98e5c..82dd9b6 100644 --- a/respx/transports.py +++ b/respx/transports.py @@ -1,7 +1,6 @@ from typing import Any, Callable, Dict, List, Optional, Pattern, Tuple, Union, overload from unittest import mock -import httpx from httpcore import ( AsyncByteStream, AsyncHTTPTransport, @@ -19,7 +18,6 @@ HeaderTypes, Request, RequestPattern, - Response, ResponseTemplate, SyncResponse, build_request, From 518f4d71c3a108e9ba0bd34445ee7913a510f47d Mon Sep 17 00:00:00 2001 From: SlavaSkvortsov Date: Tue, 6 Oct 2020 10:15:33 +0200 Subject: [PATCH 4/7] Update respx/models.py Co-authored-by: Jonas Lundberg --- respx/models.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/respx/models.py b/respx/models.py index 33943e9..dade4de 100644 --- a/respx/models.py +++ b/respx/models.py @@ -130,10 +130,7 @@ def from_unittest_call_list(cls, call_list: "_CallList") -> "CallList": @property def last(self) -> Optional[Call]: - if self: - return self[-1] - - return None + return self[-1] if len(self) else None class ContentStream(AsyncByteStream, SyncByteStream): From 42a4d87bbbc67f86bc906260397c9071ddb73b8f Mon Sep 17 00:00:00 2001 From: Slava Skvortsov Date: Tue, 6 Oct 2020 19:33:42 +0200 Subject: [PATCH 5/7] rm len --- respx/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/respx/models.py b/respx/models.py index dade4de..4905a89 100644 --- a/respx/models.py +++ b/respx/models.py @@ -130,7 +130,7 @@ def from_unittest_call_list(cls, call_list: "_CallList") -> "CallList": @property def last(self) -> Optional[Call]: - return self[-1] if len(self) else None + return self[-1] if self else None class ContentStream(AsyncByteStream, SyncByteStream): From b038026c2589abcce5341e948e03fdfe36d9d5bc Mon Sep 17 00:00:00 2001 From: Slava Skvortsov Date: Tue, 6 Oct 2020 19:45:14 +0200 Subject: [PATCH 6/7] Clean after merge --- respx/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/respx/models.py b/respx/models.py index 8156c64..2334c75 100644 --- a/respx/models.py +++ b/respx/models.py @@ -8,7 +8,6 @@ Callable, Dict, Generator, - Iterator, Iterable, List, NamedTuple, From 6064c8e17303a359805f2464335d74902b6feb52 Mon Sep 17 00:00:00 2001 From: Slava Skvortsov Date: Tue, 6 Oct 2020 19:54:38 +0200 Subject: [PATCH 7/7] Add nocover --- respx/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/respx/models.py b/respx/models.py index 2334c75..881ea3e 100644 --- a/respx/models.py +++ b/respx/models.py @@ -25,7 +25,7 @@ from httpcore import AsyncByteStream, SyncByteStream if TYPE_CHECKING: - from unittest.mock import _CallList + from unittest.mock import _CallList # pragma: nocover URL = Tuple[bytes, bytes, Optional[int], bytes]