Skip to content

Conversation

seowalex
Copy link

@seowalex seowalex commented Sep 17, 2025

Motivation

VCR.py does not work with alternative transports such as Hishel (an HTTP caching library), due to 2 problems. Firstly, as noted by #941, VCR.py always performs a sync read, which conflicts with Hishel's usage of an AsyncIterable in async requests. Secondly, even after the first problem is fixed, VCR.py still does not work properly with Hishel as the patch target is at a higher level than the HTTPX transport layer, causing the caching transport used by Hishel to be entirely bypassed.

Background

HTTPX support was purported to be fixed by #784, but unfortunately some issues surrounding the use of custom HTTPX transports remain, as explained above. The root cause is that the patch target is wrong (which the #784 also tried to fix, claiming to use the "lowest, most atomic request for data over the wire"). However, I believe that the "lowest, most atomic request" is actually at the httpcore level, and not the httpx level.

Summary of changes

Change patch target from httpx.Client._send_single_request/httpx.AsyncClient._send_single_request to httpcore.ConnectionPool.handle_request/httpcore.AsyncConnectionPool.handle_async_request

#784 targeted httpx.Client._send_single_request/httpx.AsyncClient._send_single_request, however, this PR targets httpcore.ConnectionPool.handle_request/httpcore.AsyncConnectionPool.handle_async_request. Tracing the connection between the former and the latter shows that this is likely the more correct target:

httpx.Client._send_single_request/httpx.AsyncClient._send_single_request -> httpx.HTTPTransport.handle_request/httpx.AsyncHTTPTransport.handle_async_request -> httpcore.ConnectionPool.handle_request/httpcore.AsyncConnectionPool.handle_async_request

As demonstrated above, the previous patch target was at a level above the HTTPX transport layer (rendering any custom transports useless), but the new patch target is at a level below the HTTPX transport layer (allowing custom transports to work with VCR.py).

A natural question is why httpcore.HTTPConnection.handle_request/httpcore.AsyncHTTPConnection.handle_async_request is not patched instead, given it is the lowest level in httpcore. I believe it is not a good candidate due to 2 reasons:

  1. There are multiple different connection classes serving different purposes such as Socks5Connection, ForwardHTTPConnection and TunnelHTTPConnection, all of which would have to be patched.
    1. Even if we patch all the classes, it does not handle the case where a new connection type is added.
    2. The logic of delegating to different connection types is handled by the connection pools, making it a more natural target for patching.
  2. Patching at the connection level would mean that the connection pool logic matching requests and connections would need to be invoked each time, even when cached responses are used, which does not seem desirable.

Encode/decode headers using ASCII

Per the HTTP specification, ASCII encoding should be used for headers, not UTF-8.

Properly consume the response stream

In #784, the response stream is retrieved using read/aread. However, this is not correct, as HTTPX will consume the response stream iterator, causing VCR.py to hang indefinitely when trying to read from an empty iterator. To solve this, the response stream is reconstructed after being consumed. This should also solve the related problem in #895.

Remove unnecessary history handling

Since we are now operating at a lower level, history no longer needs to be specially handled.

Clean up function parameters

Function parameters have been made as specific as possible, instead of relying on *args and **kwargs.

Tests

The proposed changes pass all current HTTPX tests.

@seowalex seowalex force-pushed the master branch 3 times, most recently from c6a8d54 to 1179f25 Compare September 19, 2025 08:16
@seowalex
Copy link
Author

@jairhenrique @hartwork could you review this?

Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes HTTPX support in VCR.py by changing the patch target from HTTPX's client layer to the lower-level httpcore transport layer, enabling compatibility with custom HTTPX transports like Hishel. The change addresses issues with async response stream handling and improves header encoding consistency.

  • Replaces httpx_stubs.py with httpcore_stubs.py to patch at the transport layer
  • Updates patch target from httpx.Client._send_single_request to httpcore.ConnectionPool.handle_request
  • Fixes header encoding to use ASCII instead of UTF-8 per HTTP specification

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
vcr/stubs/httpx_stubs.py Complete removal of HTTPX-level patching implementation
vcr/stubs/httpcore_stubs.py New httpcore-level implementation with proper stream handling and ASCII header encoding
vcr/patch.py Updates patch targets from httpx to httpcore and imports new stub functions
setup.py Adds httpcore as test dependency
docs/installation.rst Documents httpcore as supported HTTP library

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@seowalex
Copy link
Author

I have addressed the comments, let me know if anything else is needed.

@seowalex
Copy link
Author

Hi @jairhenrique, is there anything I can do to help move this along?

@codecov-commenter
Copy link

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 95.09804% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.34%. Comparing base (2c4df79) to head (2acc9c1).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
vcr/stubs/httpcore_stubs.py 94.56% 4 Missing and 1 partial ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #943      +/-   ##
==========================================
+ Coverage   92.23%   92.34%   +0.10%     
==========================================
  Files          27       27              
  Lines        1868     1854      -14     
  Branches      249      247       -2     
==========================================
- Hits         1723     1712      -11     
+ Misses         97       95       -2     
+ Partials       48       47       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jairhenrique
Copy link
Collaborator

@seowalex LGTM

@kevin1024 @hartwork can you help on this review?

@hartwork
Copy link
Collaborator

@jairhenrique sorry, no time for this atm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants