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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- `match_params` is now handling `bool`, `int` and `float` values in addition to `str`. Only str values were previously expected.
- A meaningful error will now be returned when a callback that does not return an `httpx.Response` is called.
### Added
- Support for httpx2 is implemented via a httpx/httpx2 compatibility module.

## [0.36.2] - 2026-04-09
### Fixed
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ classifiers = [
]
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?

"pytest==9.*",
]
dynamic = ["version"]
Expand Down
2 changes: 1 addition & 1 deletion pytest_httpx/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from collections.abc import Generator
from operator import methodcaller

import httpx
import pytest
from pytest import Config, FixtureRequest, MonkeyPatch

Check warning on line 5 in pytest_httpx/__init__.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Import "pytest" as a module.

See more on https://sonarcloud.io/project/issues?id=Colin-b_pytest_httpx&issues=AZ_YTsFMv0VEmVgEVsa9&open=AZ_YTsFMv0VEmVgEVsa9&pullRequest=239

from pytest_httpx._compat import httpx
from pytest_httpx._httpx_mock import HTTPXMock
from pytest_httpx._httpx_internals import IteratorStream
from pytest_httpx._options import _HTTPXMockOptions
Expand Down
31 changes: 31 additions & 0 deletions pytest_httpx/_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import warnings
from typing import TYPE_CHECKING


class PytestHTTPXDeprecationWarning(UserWarning):
pass


if TYPE_CHECKING:
import httpx2 as httpx
import httpcore2 as httpcore
else:
try:
import httpx2 as httpx
import httpcore2 as httpcore
except ModuleNotFoundError:
try:
import httpx # noqa: F401
import httpcore # noqa: F401
except ModuleNotFoundError:
raise RuntimeError(
"pytest-httpx requires the httpx2 package to be installed.\n"
"You can install it with:\n"
" $ pip install httpx2\n"
Comment on lines +22 to +24

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"

) from None
else:
warnings.warn(
"Using `httpx` with pytest-httpx is deprecated; install `httpx2` instead.",
PytestHTTPXDeprecationWarning,
stacklevel=2,
)
15 changes: 7 additions & 8 deletions pytest_httpx/_httpx_internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@
from typing import Union, Optional
from collections.abc import Sequence, Iterable, AsyncIterator, Iterator

import httpcore
import httpx

# TODO Get rid of this internal import
from httpx._content import IteratorByteStream, AsyncIteratorByteStream
from pytest_httpx._compat import httpx, httpcore

# Those types are internally defined within httpx._types
HeaderTypes = Union[
Expand All @@ -19,7 +15,11 @@
PrimitiveData = Optional[Union[str, int, float, bool]]


class IteratorStream(AsyncIteratorByteStream, IteratorByteStream):
# TODO Get rid of these internal classes

Check warning on line 18 in pytest_httpx/_httpx_internals.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=Colin-b_pytest_httpx&issues=AZ_YTsIjv0VEmVgEVsa-&open=AZ_YTsIjv0VEmVgEVsa-&pullRequest=239
class IteratorStream(
httpx._content.AsyncIteratorByteStream,
httpx._content.IteratorByteStream,
):
def __init__(self, stream: Iterable[bytes]):
class Stream:
def __iter__(self) -> Iterator[bytes]:
Expand All @@ -29,8 +29,7 @@
for chunk in stream:
yield chunk

AsyncIteratorByteStream.__init__(self, stream=Stream())
IteratorByteStream.__init__(self, stream=Stream())
super().__init__(stream=Stream())


def _to_httpx_url(url: httpcore.URL, headers: list[tuple[bytes, bytes]]) -> httpx.URL:
Expand Down
2 changes: 1 addition & 1 deletion pytest_httpx/_httpx_mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Union, Optional, Callable, Any
from collections.abc import Awaitable

import httpx
from pytest_httpx._compat import httpx

from pytest_httpx import _httpx_internals
from pytest_httpx._options import _HTTPXMockOptions
Expand Down
2 changes: 1 addition & 1 deletion pytest_httpx/_options.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Callable

import httpx
from pytest_httpx._compat import httpx


class _HTTPXMockOptions:
Expand Down
2 changes: 1 addition & 1 deletion pytest_httpx/_pretty_print.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Union

import httpx
from pytest_httpx._compat import httpx

from pytest_httpx._httpx_internals import _proxy_url
from pytest_httpx._request_matcher import _RequestMatcher
Expand Down
7 changes: 3 additions & 4 deletions pytest_httpx/_request_matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
from re import Pattern
from unittest.mock import ANY

import httpx
from httpx import QueryParams
from pytest_httpx._compat import httpx

from pytest_httpx._httpx_internals import _proxy_url, PrimitiveData
from pytest_httpx._options import _HTTPXMockOptions
Expand Down Expand Up @@ -39,7 +38,7 @@ def _url_match(
# Compare query parameters apart as order of parameters should not matter
received_params = to_params_dict(received.params)
expected_params = to_params_dict(
url_to_match.params if params is None else QueryParams(params)
url_to_match.params if params is None else httpx.QueryParams(params)
)
if params:
convert_back_mock_any(params, expected_params)
Expand All @@ -51,7 +50,7 @@ def _url_match(
return (received_params == expected_params) and (url == received_url)


def to_params_dict(params: QueryParams) -> dict[str, Union[str | list[str]]]:
def to_params_dict(params: httpx.QueryParams) -> dict[str, Union[str | list[str]]]:
"""Convert query parameters to a dict where the value is a string if the parameter has a single value and a list of string otherwise."""
d = {}
for key in params:
Expand Down