Repository: lundberg/respx
Pull request #189 (2021-11-15 15:23:06)

Support HTTPX 0.21.0


Patch:
diff --git a/respx/mocks.py b/respx/mocks.py
index 2892ad5..6ea97ef 100644
--- a/respx/mocks.py
+++ b/respx/mocks.py
@@ -4,8 +4,8 @@ from types import MappingProxyType
 from typing import TYPE_CHECKING, ClassVar, Dict, List, Type
 from unittest import mock
 
+import httpcore
 import httpx
-from httpcore import AsyncIteratorByteStream, IteratorByteStream
 
 from .models import AllMockedAssertionError, PassThrough
 from .transports import TryTransport
@@ -166,6 +166,10 @@ class HTTPXMocker(Mocker):
 class AbstractRequestMocker(Mocker):
     @classmethod
     def mock(cls, spec):
+        if spec.__name__ not in cls.target_methods:
+            # Prevent mocking mock
+            return spec
+
         argspec = inspect.getfullargspec(spec)
 
         def mock(self, *args, **kwargs):
@@ -256,9 +260,9 @@ class AbstractRequestMocker(Mocker):
 class HTTPCoreMocker(AbstractRequestMocker):
     name = "httpcore"
     targets = [
-        "httpcore._sync.connection.SyncHTTPConnection",
-        "httpcore._sync.connection_pool.SyncConnectionPool",
-        "httpcore._sync.http_proxy.SyncHTTPProxy",
+        "httpcore._sync.connection.HTTPConnection",
+        "httpcore._sync.connection_pool.ConnectionPool",
+        "httpcore._sync.http_proxy.HTTPProxy",
         "httpcore._async.connection.AsyncHTTPConnection",
         "httpcore._async.connection_pool.AsyncConnectionPool",
         "httpcore._async.http_proxy.AsyncHTTPProxy",
@@ -268,59 +272,61 @@ class HTTPCoreMocker(AbstractRequestMocker):
     @classmethod
     def prepare_sync_request(cls, httpx_request, **kwargs):
         """
-        Sync pre-read request body, and update transport request args.
+        Sync pre-read request body, and update transport request arg.
         """
         httpx_request, kwargs = super().prepare_sync_request(httpx_request, **kwargs)
-        kwargs["stream"] = httpx_request.stream
+        kwargs["request"].stream = httpx_request.stream
         return httpx_request, kwargs
 
     @classmethod
     async def prepare_async_request(cls, httpx_request, **kwargs):
         """
-        Async pre-read request body, and update transport request args.
+        Async pre-read request body, and update transport request arg.
         """
         httpx_request, kwargs = await super().prepare_async_request(
             httpx_request, **kwargs
         )
-        kwargs["stream"] = httpx_request.stream
+        kwargs["request"].stream = httpx_request.stream
         return httpx_request, kwargs
 
     @classmethod
     def to_httpx_request(cls, **kwargs):
         """
-        Create a `HTTPX` request from transport request args.
+        Create a `HTTPX` request from transport request arg.
         """
+        request = kwargs["request"]
+        raw_url = (
+            request.url.scheme,
+            request.url.host,
+            request.url.port,
+            request.url.target,
+        )
         return httpx.Request(
-            kwargs["method"],
-            kwargs["url"],
-            headers=kwargs.get("headers"),
-            stream=kwargs.get("stream"),
-            extensions=kwargs.get("extensions"),
+            request.method,
+            raw_url,
+            headers=request.headers,
+            stream=request.stream,
+            extensions=request.extensions,
         )
 
     @classmethod
     def from_sync_httpx_response(cls, httpx_response, target, **kwargs):
         """
-        Create a transport return tuple from `HTTPX` response.
+        Create a `httpcore` response from a `HTTPX` response.
         """
-        return (
-            httpx_response.status_code,
-            httpx_response.headers.raw,
-            IteratorByteStream(httpx_response.stream.__iter__()),
-            httpx_response.extensions,
+        return httpcore.Response(
+            status=httpx_response.status_code,
+            headers=httpx_response.headers.raw,
+            content=httpx_response.stream,
+            extensions=httpx_response.extensions,
         )
 
     @classmethod
     async def from_async_httpx_response(cls, httpx_response, target, **kwargs):
         """
-        Create a transport return tuple from `HTTPX` response.
+        Create a `httpcore` response from a `HTTPX` response.
         """
-        return (
-            httpx_response.status_code,
-            httpx_response.headers.raw,
-            AsyncIteratorByteStream(httpx_response.stream.__aiter__()),
-            httpx_response.extensions,
-        )
+        return cls.from_sync_httpx_response(httpx_response, target, **kwargs)
 
 
 DEFAULT_MOCKER: str = HTTPCoreMocker.name
diff --git a/setup.py b/setup.py
index 35f6f2c..a1e41c9 100644
--- a/setup.py
+++ b/setup.py
@@ -40,5 +40,5 @@ setup(
     include_package_data=True,
     zip_safe=False,
     python_requires=">=3.6",
-    install_requires=["httpx>=0.20.0"],
+    install_requires=["httpx>=0.21.0"],
 )


Test patch:
diff --git a/tests/test_mock.py b/tests/test_mock.py
index 30f6b4a..6583968 100644
--- a/tests/test_mock.py
+++ b/tests/test_mock.py
@@ -456,7 +456,7 @@ async def test_assert_all_mocked(client, assert_all_mocked, raises):
 def test_add_remove_targets():
     from respx.mocks import HTTPCoreMocker
 
-    target = "httpcore._sync.connection.SyncHTTPConnection"
+    target = "httpcore._sync.connection.HTTPConnection"
     assert HTTPCoreMocker.targets.count(target) == 1
     HTTPCoreMocker.add_targets(target)
     assert HTTPCoreMocker.targets.count(target) == 1
@@ -709,20 +709,19 @@ async def test_httpcore_request(url, port):
     async with MockRouter(using="httpcore") as router:
         router.get(url) % dict(text="foobar")
 
-        with httpcore.SyncConnectionPool() as http:
-            (status_code, headers, stream, ext) = http.handle_request(
-                method=b"GET", url=(b"https", b"foo.bar", port, b"/")
-            )
+        request = httpcore.Request(
+            b"GET",
+            httpcore.URL(scheme=b"https", host=b"foo.bar", port=port, target=b"/"),
+        )
 
-            body = b"".join([chunk for chunk in stream])
+        with httpcore.ConnectionPool() as http:
+            response = http.handle_request(request)
+            body = response.read()
             assert body == b"foobar"
 
         async with httpcore.AsyncConnectionPool() as http:
-            (status_code, headers, stream, ext) = await http.handle_async_request(
-                method=b"GET", url=(b"https", b"foo.bar", port, b"/")
-            )
-
-            body = b"".join([chunk async for chunk in stream])
+            response = await http.handle_async_request(request)
+            body = await response.aread()
             assert body == b"foobar"
 
 
diff --git a/tests/test_stats.py b/tests/test_stats.py
index 045357f..4089d34 100644
--- a/tests/test_stats.py
+++ b/tests/test_stats.py
@@ -92,9 +92,9 @@ async def backend_test(backend):
 def test_asyncio():
     import asyncio
 
-    from httpcore._backends.asyncio import AsyncioBackend
+    from httpcore.backends.asyncio import AsyncIOBackend
 
-    backend = AsyncioBackend()
+    backend = AsyncIOBackend()  # TODO: Why instantiate a backend?
     loop = asyncio.new_event_loop()
     try:
         loop.run_until_complete(backend_test(backend))
@@ -104,7 +104,7 @@ def test_asyncio():
 
 def test_trio():  # pragma: nocover
     import trio
-    from httpcore._backends.trio import TrioBackend
+    from httpcore.backends.trio import TrioBackend
 
-    backend = TrioBackend()
+    backend = TrioBackend()  # TODO: Why instantiate a backend?
     trio.run(backend_test, backend)