Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support HTTPX 0.21.0 #189

Merged
merged 1 commit into from
Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)