Skip to content

Commit

Permalink
Support HTTPX 0.21.0 (#189)
Browse files Browse the repository at this point in the history
  • Loading branch information
lundberg authored Nov 15, 2021
1 parent b5953e2 commit 6b24546
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 43 deletions.
60 changes: 33 additions & 27 deletions respx/mocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
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
Expand Down Expand Up @@ -166,6 +166,10 @@ def _transport_for_url(self, *args, **kwargs):
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):
Expand Down Expand Up @@ -256,9 +260,9 @@ async def from_async_httpx_response(cls, httpx_response, target, **kwargs):
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",
Expand All @@ -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
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@
include_package_data=True,
zip_safe=False,
python_requires=">=3.6",
install_requires=["httpx>=0.20.0"],
install_requires=["httpx>=0.21.0"],
)
21 changes: 10 additions & 11 deletions tests/test_mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"


Expand Down
8 changes: 4 additions & 4 deletions tests/test_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)

0 comments on commit 6b24546

Please sign in to comment.