Skip to content
Open
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
1 change: 0 additions & 1 deletion docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ The following HTTP libraries are supported:
- ``urllib2``
- ``urllib3``
- ``httpx``
- ``httpcore``

Speed
-----
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ tests = [
"boto3",
"cryptography",
"httpbin",
"httpcore",
"httplib2",
"httpx",
"pycurl; platform_python_implementation !='PyPy'",
"httpx-curl-cffi",
"pycurl; platform_python_implementation != 'PyPy'",
"pyreqwest; python_version >= '3.11'",
"pytest",
"pytest-aiohttp",
"pytest-asyncio",
Expand Down
5 changes: 3 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ tests =
boto3
cryptography
httpbin
httpcore
httplib2
httpx
pycurl; platform_python_implementation !='PyPy'
httpx-curl-cffi
pycurl; platform_python_implementation != 'PyPy'
pyreqwest; python_version >= '3.11'
pytest
pytest-aiohttp
pytest-asyncio
Expand Down
86 changes: 84 additions & 2 deletions tests/integration/test_httpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ def client(self):

def __call__(self, *args, **kwargs):
if hasattr(self, "_client"):
return self.client.request(*args, timeout=60, **kwargs)
return self.client.request(*args, **kwargs)

# Use one-time context and dispose of the client afterwards
with self:
return self.client.request(*args, timeout=60, **kwargs)
return self.client.request(*args, **kwargs)

def stream(self, *args, **kwargs):
if hasattr(self, "_client"):
Expand Down Expand Up @@ -125,6 +125,19 @@ def yml(tmpdir, request):
return str(tmpdir.join(request.function.__name__ + ".yaml"))


def test_response_elapsed(tmpdir, httpbin, do_request):
url = httpbin.url

with vcr.use_cassette(str(tmpdir.join("elapsed.yaml"))):
response = do_request()("GET", url)

with vcr.use_cassette(str(tmpdir.join("elapsed.yaml"))):
cassette_response = do_request()("GET", url)

assert response.elapsed
assert cassette_response.elapsed


def test_status(tmpdir, httpbin, do_request):
url = httpbin.url

Expand Down Expand Up @@ -365,3 +378,72 @@ async def run():
assert cassette.play_count == 1

asyncio.run(run())


def test_custom_transports(tmpdir, httpbin):
url = httpbin.url
transport = httpx.MockTransport(lambda _: httpx.Response(200, json={"text": "Hello, world!"}))

with vcr.use_cassette(str(tmpdir.join("mock_transport.yaml"))):
response = DoSyncRequest(transport=transport)("GET", url)

with vcr.use_cassette(str(tmpdir.join("mock_transport.yaml"))) as cassette:
cassette_response = DoSyncRequest(transport=transport)("GET", url)

assert cassette_response.status_code == response.status_code
assert cassette.play_count == 1

try:
from httpx_curl_cffi import AsyncCurlTransport, CurlTransport
except ImportError:
pass
else:
with vcr.use_cassette(str(tmpdir.join("curl_transport.yaml"))):
response = DoSyncRequest(transport=CurlTransport())("GET", url)

with vcr.use_cassette(str(tmpdir.join("curl_transport.yaml"))) as cassette:
cassette_response = DoSyncRequest(transport=CurlTransport())("GET", url)

assert cassette_response.status_code == response.status_code
assert cassette.play_count == 1

with vcr.use_cassette(str(tmpdir.join("async_curl_transport.yaml"))):
response = DoAsyncRequest(transport=AsyncCurlTransport())("GET", url)

with vcr.use_cassette(str(tmpdir.join("async_curl_transport.yaml"))) as cassette:
cassette_response = DoAsyncRequest(transport=AsyncCurlTransport())("GET", url)

assert cassette_response.status_code == response.status_code
assert cassette.play_count == 1

try:
from pyreqwest.compatibility.httpx import HttpxTransport, SyncHttpxTransport
except ImportError:
pass
else:
with vcr.use_cassette(str(tmpdir.join("pyreqwest_transport.yaml"))):
response = DoSyncRequest(transport=SyncHttpxTransport())("GET", url)

with vcr.use_cassette(str(tmpdir.join("pyreqwest_transport.yaml"))) as cassette:
cassette_response = DoSyncRequest(transport=SyncHttpxTransport())("GET", url)

assert cassette_response.status_code == response.status_code
assert cassette.play_count == 1

with vcr.use_cassette(str(tmpdir.join("async_pyreqwest_transport.yaml"))):
response = DoAsyncRequest(transport=HttpxTransport())("GET", url)

with vcr.use_cassette(str(tmpdir.join("async_pyreqwest_transport.yaml"))) as cassette:
cassette_response = DoAsyncRequest(transport=HttpxTransport())("GET", url)

assert cassette_response.status_code == response.status_code
assert cassette.play_count == 1


def test_connection_limits(tmpdir, httpbin, do_request):
url = httpbin.url
limits = httpx.Limits(max_connections=1)

with vcr.use_cassette(str(tmpdir.join("connection_limits.yaml"))), do_request(limits=limits) as client:
for _ in range(2):
client("GET", url)
149 changes: 133 additions & 16 deletions vcr/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,36 @@


try:
import httpcore
import httpx
except ImportError: # pragma: no cover
pass
else:
_HttpcoreConnectionPool_handle_request = httpcore.ConnectionPool.handle_request
_HttpcoreAsyncConnectionPool_handle_async_request = httpcore.AsyncConnectionPool.handle_async_request
_HttpxHttpTransport_handle_request = httpx.HTTPTransport.handle_request
_HttpxAsyncHttpTransport_handle_async_request = httpx.AsyncHTTPTransport.handle_async_request
_HttpxWsgiTransport_handle_request = httpx.WSGITransport.handle_request
_HttpxAsgiTransport_handle_async_request = httpx.ASGITransport.handle_async_request
_HttpxMockTransport_handle_request = httpx.MockTransport.handle_request
_HttpxMockTransport_handle_async_request = httpx.MockTransport.handle_async_request

try:
import httpx_curl_cffi
except ImportError: # pragma: no cover
pass
else:
_HttpxCurlTransport_handle_request = httpx_curl_cffi.CurlTransport.handle_request
_HttpxAsyncCurlTransport_handle_async_request = httpx_curl_cffi.AsyncCurlTransport.handle_async_request

try:
import pyreqwest.compatibility.httpx
except ImportError: # pragma: no cover
pass
else:
_HttpxSyncPyreqwestTransport_handle_request = (
pyreqwest.compatibility.httpx.SyncHttpxTransport.handle_request
)
_HttpxPyreqwestTransport_handle_async_request = (
pyreqwest.compatibility.httpx.HttpxTransport.handle_async_request
)


class CassettePatcherBuilder:
Expand All @@ -121,7 +145,7 @@ def build(self):
self._httplib2(),
self._tornado(),
self._aiohttp(),
self._httpcore(),
self._httpx(),
self._build_patchers_from_mock_triples(self._cassette.custom_patches),
)

Expand Down Expand Up @@ -304,22 +328,79 @@ def _aiohttp(self):
yield client.ClientSession, "_request", new_request

@_build_patchers_from_mock_triples_decorator
def _httpcore(self):
def _httpx(self):
try:
import httpx
except ImportError: # pragma: no cover
pass
else:
from .stubs.httpx_stubs import vcr_handle_async_request, vcr_handle_request

new_handle_request = vcr_handle_request(self._cassette, _HttpxHttpTransport_handle_request)
yield httpx.HTTPTransport, "handle_request", new_handle_request

new_handle_async_request = vcr_handle_async_request(
self._cassette,
_HttpxAsyncHttpTransport_handle_async_request,
)
yield httpx.AsyncHTTPTransport, "handle_async_request", new_handle_async_request

new_handle_request = vcr_handle_request(self._cassette, _HttpxWsgiTransport_handle_request)
yield httpx.WSGITransport, "handle_request", new_handle_request

new_handle_async_request = vcr_handle_async_request(
self._cassette,
_HttpxAsgiTransport_handle_async_request,
)
yield httpx.ASGITransport, "handle_async_request", new_handle_async_request

new_handle_request = vcr_handle_request(self._cassette, _HttpxMockTransport_handle_request)
yield httpx.MockTransport, "handle_request", new_handle_request

new_handle_async_request = vcr_handle_async_request(
self._cassette,
_HttpxMockTransport_handle_async_request,
)
yield httpx.MockTransport, "handle_async_request", new_handle_async_request

try:
import httpcore
import httpx_curl_cffi
except ImportError: # pragma: no cover
pass
else:
from .stubs.httpcore_stubs import vcr_handle_async_request, vcr_handle_request
from .stubs.httpx_stubs import vcr_handle_async_request, vcr_handle_request

new_handle_request = vcr_handle_request(self._cassette, _HttpcoreConnectionPool_handle_request)
yield httpcore.ConnectionPool, "handle_request", new_handle_request
new_handle_request = vcr_handle_request(self._cassette, _HttpxCurlTransport_handle_request)
yield httpx_curl_cffi.CurlTransport, "handle_request", new_handle_request

new_handle_async_request = vcr_handle_async_request(
self._cassette,
_HttpcoreAsyncConnectionPool_handle_async_request,
_HttpxAsyncCurlTransport_handle_async_request,
)
yield httpx_curl_cffi.AsyncCurlTransport, "handle_async_request", new_handle_async_request

try:
import pyreqwest.compatibility.httpx
except ImportError: # pragma: no cover
pass
else:
from .stubs.httpx_stubs import vcr_handle_async_request, vcr_handle_request

new_handle_request = vcr_handle_request(
self._cassette,
_HttpxSyncPyreqwestTransport_handle_request,
)
yield pyreqwest.compatibility.httpx.SyncHttpxTransport, "handle_request", new_handle_request

new_handle_async_request = vcr_handle_async_request(
self._cassette,
_HttpxPyreqwestTransport_handle_async_request,
)
yield (
pyreqwest.compatibility.httpx.HttpxTransport,
"handle_async_request",
new_handle_async_request,
)
yield httpcore.AsyncConnectionPool, "handle_async_request", new_handle_async_request

def _urllib3_patchers(self, cpool, conn, stubs):
http_connection_remover = ConnectionRemover(
Expand Down Expand Up @@ -448,19 +529,55 @@ def reset_patchers():
yield mock.patch.object(curl.CurlAsyncHTTPClient, "fetch_impl", _CurlAsyncHTTPClient_fetch_impl)

try:
import httpcore
import httpx
except ImportError: # pragma: no cover
pass
else:
yield mock.patch.object(
httpx.HTTPTransport,
"handle_request",
_HttpxHttpTransport_handle_request,
)
yield mock.patch.object(
httpx.AsyncHTTPTransport,
"handle_async_request",
_HttpxAsyncHttpTransport_handle_async_request,
)
yield mock.patch.object(
httpx.WSGITransport,
"handle_request",
_HttpxWsgiTransport_handle_request,
)
yield mock.patch.object(
httpx.ASGITransport,
"handle_async_request",
_HttpxAsgiTransport_handle_async_request,
)
yield mock.patch.object(
httpx.MockTransport,
"handle_request",
_HttpxMockTransport_handle_request,
)
yield mock.patch.object(
httpx.MockTransport,
"handle_async_request",
_HttpxMockTransport_handle_async_request,
)

try:
import httpx_curl_cffi
except ImportError: # pragma: no cover
pass
else:
yield mock.patch.object(
httpcore.ConnectionPool,
httpx_curl_cffi.CurlTransport,
"handle_request",
_HttpcoreConnectionPool_handle_request,
_HttpxCurlTransport_handle_request,
)
yield mock.patch.object(
httpcore.AsyncConnectionPool,
httpx_curl_cffi.AsyncCurlTransport,
"handle_async_request",
_HttpcoreAsyncConnectionPool_handle_async_request,
_HttpxAsyncCurlTransport_handle_async_request,
)


Expand Down
Loading
Loading