Skip to content

Add support for httpx2 - #239

Open
Klavionik wants to merge 29 commits into
Colin-b:developfrom
Klavionik:add_httpx2_support
Open

Klavionik wants to merge 29 commits into
Colin-b:developfrom
Klavionik:add_httpx2_support

Conversation

@Klavionik

Copy link
Copy Markdown

Hey! I'm trying to add support for the httpx2 package, following the guidelines from the authors.

This PR allows users of the package to seamlessly migrate their application and test code from httpx, just by updating the package. Users who have no intentions to migrate will be fine as well: the added _compat.py module tries to use httpx2 first, but falls back to httpx.

This is a highly anticipated change I assume, so I'm looking forward for a review and any comments/suggestions are very welcome.

@sonarqubecloud

sonarqubecloud Bot commented Aug 6, 2026

Copy link
Copy Markdown

Comment thread pyproject.toml Outdated
]
dependencies = [
"httpx==0.28.*",
"httpx2==2.*",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

perhaps it's worth doing this as an extra?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I would rather remove both httpx and httpx2 as dependencies. Make them optional (as the suggestion above, an extra dependency for each)

Comment thread pytest_httpx/_compat.py Outdated
Comment on lines +22 to +24
"pytest-httpx requires the httpx2 package to be installed.\n"
"You can install it with:\n"
" $ pip install httpx2\n"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would leave this choice up to the user and not talk about the need, because migration does not happen so quickly.

"Neither `httpx2` nor the legacy `httpx` package is installed.\n"
"Install `httpx2` with:\n"
"    $ pip install httpx2\n"

@bagowix

bagowix commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@Colin-b Hi! Could we prioritize this, if possible, please?

@Klavionik

Copy link
Copy Markdown
Author

@Colin-b Kind reminder. 🙏🏻

@Colin-b Colin-b left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I feel like it needs more work, I envision the following in term of support:

  1. only httpx2 installed -> valid
  2. only httpx installed -> valid
  3. both installed -> use httpx2

This would need to be documented, and ofc the test suite will need to be updated, same for the CI that will need to run in 3 different dependency setup.

I am however not asking you to do it, I can review this proposal a bit more and add those changes.

@Klavionik

Copy link
Copy Markdown
Author

I could work on these changes if it helps.

So, the plan would be:

  1. Turn httpx and httpx2 into extra dependencies. pip install pytest-httpx will then install just this package.
  2. Update the docs to explain the new ways to install pytest-httpx.
  3. Update the test suite and the CI config to run in three environments: only httpx, only httpx2, httpx + httpx2. Perhaps add a test case to cover the compatibility layer itself?

Is that correct?

I envision the following in term of support:
...

I believe it works exactly this way right now.

@Colin-b

Colin-b commented Aug 31, 2026

Copy link
Copy Markdown
Owner

If you feel like it, be my guest!
Note that I made a mistake in my suggestion when both are installed, and I think it makes it slightly less straightforward to handle.
When both dependencies are installed, we should mock both, I did not put thoughts into the actual implementation but I would like to avoid as much code duplication as possible.

@Klavionik

Klavionik commented Sep 8, 2026 •

Copy link
Copy Markdown
Author

I'm struggling to understand what would be the use case of this. I mean, why would anyone install two basically identical libraries under different names, and use both at the same time?

Oh, nevermind, I got this. 😌 Anyway, yeah, I gotta try, but this is indeed trickier than I expected.

@Colin-b

Colin-b commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Hi @Klavionik , just letting you know that I am also looking at introducing support for both httpx and httpx2 on my end. I am far from done, and I might not invest too much time into it right now so feel free to continue pushing for this.

It requires quite an extensive amount of changes to support all 3 combination (httpx alone, httpx2 alone, both httpx and httpx2 installed and mocked) so I don't really have a good view on the direction I want to take. If you want to start with only support for one or the other it is fine as well as it is obviously way easier.

@Klavionik

Klavionik commented Oct 14, 2026 •

Copy link
Copy Markdown
Author

@Colin-b So, it’s a lot of change, and I feel like I need to provide some kind of an overview.

The approach I’ve tried here is to collect httpx modules as first-class citizens and expose them as “backends” via a helper function get_backends. Then at fixture setup, I apply mocking for each backend, capturing it within a mock closure. When handling requests, HTTPXMock receives the corresponding backend and sets it as a private attribute of the request, to retrieve and use later when we need to compare or construct httpx-specific objects (Response, URL, etc.). Basically, that is the whole idea.

To make it work with any combination of httpx and httpx2, I have decoupled the library and the tests from any global runtime dependency on httpx. A module-level httpx is only used for type hints. Except for the tests that have to run with both backends installed, of course.

I’ve tried to be as subtle as possible, keeping method signatures the same, hence the usages of get_backend all over the place. It’s probably not that neat as injecting the backend, but I would argue that in this case it doesn't matter that much. And I think this way it will be easier to remove them when support for httpx is dropped in the future.

To run the tests in all three environments I’ve used tox, both locally and in CI. It runs ok on my machine, but I could not verify the CI (for obvious reasons).

P.S. And as for the quality gate failing because of code duplication - it's in the tests, and I'm not sure if there's a better way.

@Klavionik

Klavionik commented Oct 19, 2026 •

Copy link
Copy Markdown
Author

I've found out that there was a missing piece in the solution: public API requires type hints that allow for both httpx and httpx2. So I've added a new commit updating some of the the type hints.

Upd.: Not sure I'm happy about the return type of get_request(s) though. The current signature may complicate things for the client code, requiring to use the other library to correctly type hint functions that receive the request (or use type guards).

def process_request(request: httpx.Request | httpx2.Request):
    ...

request = httpx_mock.get_request()

assert request is not None
process_request(request)

On the other hand, right now I can't think of a different way to handle this. Maybe I'll try having a "generic httpx request" type that replicates the interface of a true request, but that's a lot of the API surface to cover. Dunno.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
4.2% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

This branch has not been deployed

No deployments
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.

3 participants