Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-pytest-httpx for
openSUSE:Factory checked in at 2026-04-14 17:49:14
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pytest-httpx (Old)
and /work/SRC/openSUSE:Factory/.python-pytest-httpx.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pytest-httpx"
Tue Apr 14 17:49:14 2026 rev:13 rq:1346510 version:0.36.2
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pytest-httpx/python-pytest-httpx.changes
2024-12-16 19:17:10.138543991 +0100
+++
/work/SRC/openSUSE:Factory/.python-pytest-httpx.new.21863/python-pytest-httpx.changes
2026-04-14 17:49:55.875536869 +0200
@@ -1,0 +2,19 @@
+Mon Apr 13 06:12:01 UTC 2026 - Steve Kowalik <[email protected]>
+
+- Update to 0.36.2:
+ ## Changed
+ * pytest required version is now 9.
+ ## Added
+ * Explicit support for python 3.14.
+ * match_params parameter is now available on responses and callbacks
+ registration, as well as request(s) retrieval. Allowing to provide query
+ parameters as a dict instead of being part of the matched URL.
+ + This parameter allows to perform partial query params matching.
+ ## Fixed
+ * URL with more than one value for the same parameter were not matched
+ properly (matching was performed on the first value).
+ * httpx_mock.add_exception is now properly documented.
+ ## Removed
+ * python 3.9 is not supported anymore.
+
+-------------------------------------------------------------------
Old:
----
pytest_httpx-0.35.0-gh.tar.gz
New:
----
_scmsync.obsinfo
build.specials.obscpio
pytest_httpx-0.36.2-gh.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-pytest-httpx.spec ++++++
--- /var/tmp/diff_new_pack.jKbdFe/_old 2026-04-14 17:49:56.983582669 +0200
+++ /var/tmp/diff_new_pack.jKbdFe/_new 2026-04-14 17:49:56.987582835 +0200
@@ -18,24 +18,24 @@
%{?sle15_python_module_pythons}
Name: python-pytest-httpx
-Version: 0.35.0
+Version: 0.36.2
Release: 0
Summary: Send responses to httpx
License: MIT
URL: https://colin-b.github.io/pytest_httpx/
-Source:
https://github.com/Colin-b/pytest_httpx/archive/refs/tags/v%{version}.tar.gz#/pytest_httpx-%{version}-gh.tar.gz
-BuildRequires: %{python_module base >= 3.9}
+Source:
https://github.com/Colin-b/pytest_httpx/archive/refs/tags/%{version}.tar.gz#/pytest_httpx-%{version}-gh.tar.gz
+BuildRequires: %{python_module base >= 3.10}
BuildRequires: %{python_module pip}
BuildRequires: %{python_module setuptools}
BuildRequires: %{python_module wheel}
BuildRequires: python-rpm-macros
# SECTION test requirements
BuildRequires: %{python_module httpx >= 0.28.0 with %python-httpx < 0.29}
-BuildRequires: %{python_module pytest >= 8.0}
+BuildRequires: %{python_module pytest >= 9.0}
BuildRequires: %{python_module pytest-asyncio >= 0.24.0}
# /SECTION
BuildRequires: fdupes
-Requires: python-pytest >= 8.0
+Requires: python-pytest >= 9.0
Requires: (python-httpx >= 0.28.0 with python-httpx < 0.29)
BuildArch: noarch
%python_subpackages
++++++ _scmsync.obsinfo ++++++
mtime: 1776060734
commit: 23fd6bbfcc31b2e1c5c97ca8f2ec249fe643afb1e497683acf8e94b0d690fd98
url: https://src.opensuse.org/python-pytest/python-pytest-httpx.git
revision: 23fd6bbfcc31b2e1c5c97ca8f2ec249fe643afb1e497683acf8e94b0d690fd98
projectscmsync: https://src.opensuse.org/python-pytest/_ObsPrj.git
++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore 1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore 2026-04-14 02:15:49.000000000 +0200
@@ -0,0 +1 @@
+.osc
++++++ pytest_httpx-0.35.0-gh.tar.gz -> pytest_httpx-0.36.2-gh.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpx-0.35.0/.github/workflows/release.yml
new/pytest_httpx-0.36.2/.github/workflows/release.yml
--- old/pytest_httpx-0.35.0/.github/workflows/release.yml 2024-11-28
20:16:16.000000000 +0100
+++ new/pytest_httpx-0.36.2/.github/workflows/release.yml 2026-04-09
15:55:54.000000000 +0200
@@ -1,26 +1,46 @@
-name: Release
+name: Publish Python 🐍 distribution 📦 to PyPI
on:
push:
- branches:
- - master
+ tags:
+ - "*"
jobs:
build:
-
+ name: Build distribution 📦
runs-on: ubuntu-latest
-
steps:
- - uses: actions/checkout@v4
- - name: Set up Python
- uses: actions/setup-python@v5
+ - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2
+ - name: Set up Python 🐍
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #
v6.2.0
with:
- python-version: '3.13'
- - name: Create packages
+ python-version: '3.14'
+ - name: Build a binary wheel and a source tarball
run: |
- python -m pip install build
+ python -m pip install pip==26.0.1
+ python -m pip install build==1.4.2
python -m build .
- - name: Publish packages
- run: |
- python -m pip install twine
- python -m twine upload dist/* --skip-existing --username __token__
--password ${{ secrets.pypi_password }}
+ - name: Store the distribution packages
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #
v7.0.0
+ with:
+ name: python-package-distributions
+ path: dist/
+ pypi-publish:
+ name: >-
+ Publish Python 🐍 distribution 📦 to PyPI
+ needs:
+ - build
+ runs-on: ubuntu-latest
+ environment:
+ name: pypi
+ url: https://pypi.org/project/pytest-httpx/${{ github.ref_name }}
+ permissions:
+ id-token: write
+ steps:
+ - name: Download all the dists
+ uses:
actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: python-package-distributions
+ path: dist/
+ - name: Publish distribution 📦 to PyPI
+ uses:
pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpx-0.35.0/.github/workflows/test.yml
new/pytest_httpx-0.36.2/.github/workflows/test.yml
--- old/pytest_httpx-0.35.0/.github/workflows/test.yml 2024-11-28
20:16:16.000000000 +0100
+++ new/pytest_httpx-0.36.2/.github/workflows/test.yml 2026-04-09
15:55:54.000000000 +0200
@@ -8,22 +8,26 @@
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
+ python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #
v6.2.0
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
- python -m pip install --upgrade pip
+ python -m pip install pip==26.0.1
python -m pip install -e .[testing]
+ python -m pip install pre-commit==4.5.1
+ - name: Ensure pre-commit was applied
+ run: |
+ pre-commit run --all-files
- name: Test
run: |
pytest --cov=pytest_httpx --cov-fail-under=100
--cov-report=term-missing --runpytest=subprocess
- name: Test packages creation
run: |
- python -m pip install build
+ python -m pip install build==1.4.2
python -m build .
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpx-0.35.0/.pre-commit-config.yaml
new/pytest_httpx-0.36.2/.pre-commit-config.yaml
--- old/pytest_httpx-0.35.0/.pre-commit-config.yaml 2024-11-28
20:16:16.000000000 +0100
+++ new/pytest_httpx-0.36.2/.pre-commit-config.yaml 2026-04-09
15:55:54.000000000 +0200
@@ -1,5 +1,5 @@
repos:
- repo: https://github.com/psf/black
- rev: 24.10.0
+ rev: 26.3.1
hooks:
- id: black
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpx-0.35.0/CHANGELOG.md
new/pytest_httpx-0.36.2/CHANGELOG.md
--- old/pytest_httpx-0.35.0/CHANGELOG.md 2024-11-28 20:16:16.000000000
+0100
+++ new/pytest_httpx-0.36.2/CHANGELOG.md 2026-04-09 15:55:54.000000000
+0200
@@ -6,6 +6,27 @@
## [Unreleased]
+## [0.36.2] - 2026-04-09
+### Fixed
+- Document how to ignore query parameters while matching on URL.
+
+## [0.36.0] - 2025-12-02
+### Changed
+- `pytest` required version is now `9`.
+
+### Added
+- Explicit support for python `3.14`.
+- `match_params` parameter is now available on responses and callbacks
registration, as well as request(s) retrieval. Allowing to provide query
parameters as a dict instead of being part of the matched URL.
+ - This parameter allows to perform partial query params matching ([refer to
documentation](README.md#matching-on-query-parameters) for more information).
+
+### Fixed
+- URL with more than one value for the same parameter were not matched
properly (matching was performed on the first value).
+- `httpx_mock.add_exception` is now properly documented (accepts
`BaseException` instead of `Exception`).
+
+### Removed
+- `pytest` `8` is not supported anymore.
+- python `3.9` is not supported anymore.
+
## [0.35.0] - 2024-11-28
### Changed
- Requires [`httpx`](https://www.python-httpx.org)==0.28.\*
@@ -408,7 +429,9 @@
### Added
- First release, should be considered as unstable for now as design might
change.
-[Unreleased]: https://github.com/Colin-b/pytest_httpx/compare/v0.35.0...HEAD
+[Unreleased]: https://github.com/Colin-b/pytest_httpx/compare/0.36.2...HEAD
+[0.36.2]: https://github.com/Colin-b/pytest_httpx/compare/v0.36.0...0.36.2
+[0.36.0]: https://github.com/Colin-b/pytest_httpx/compare/v0.35.0...v0.36.0
[0.35.0]: https://github.com/Colin-b/pytest_httpx/compare/v0.34.0...v0.35.0
[0.34.0]: https://github.com/Colin-b/pytest_httpx/compare/v0.33.0...v0.34.0
[0.33.0]: https://github.com/Colin-b/pytest_httpx/compare/v0.32.0...v0.33.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpx-0.35.0/LICENSE
new/pytest_httpx-0.36.2/LICENSE
--- old/pytest_httpx-0.35.0/LICENSE 2024-11-28 20:16:16.000000000 +0100
+++ new/pytest_httpx-0.36.2/LICENSE 2026-04-09 15:55:54.000000000 +0200
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 Colin Bounouar
+Copyright (c) 2026 Colin Bounouar
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpx-0.35.0/README.md
new/pytest_httpx-0.36.2/README.md
--- old/pytest_httpx-0.35.0/README.md 2024-11-28 20:16:16.000000000 +0100
+++ new/pytest_httpx-0.36.2/README.md 2026-04-09 15:55:54.000000000 +0200
@@ -5,7 +5,7 @@
<a href="https://github.com/Colin-b/pytest_httpx/actions"><img alt="Build
status"
src="https://github.com/Colin-b/pytest_httpx/workflows/Release/badge.svg"></a>
<a href="https://github.com/Colin-b/pytest_httpx/actions"><img alt="Coverage"
src="https://img.shields.io/badge/coverage-100%25-brightgreen"></a>
<a href="https://github.com/psf/black"><img alt="Code style: black"
src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
-<a href="https://github.com/Colin-b/pytest_httpx/actions"><img alt="Number of
tests" src="https://img.shields.io/badge/tests-272 passed-blue"></a>
+<a href="https://github.com/Colin-b/pytest_httpx/actions"><img alt="Number of
tests" src="https://img.shields.io/badge/tests-298 passed-blue"></a>
<a href="https://pypi.org/project/pytest-httpx/"><img alt="Number of
downloads" src="https://img.shields.io/pypi/dm/pytest_httpx"></a>
</p>
@@ -107,6 +107,50 @@
response = client.get("https://test_url?a=1&b=2")
```
+##### Ignoring query parameters
+
+Use a python [re.Pattern](https://docs.python.org/3/library/re.html) instance
to ignore query parameters while matching on the URL.
+
+```python
+import httpx
+import re
+from pytest_httpx import HTTPXMock
+
+
+def test_url_as_pattern_ignoring_query_parameters(httpx_mock: HTTPXMock):
+ httpx_mock.add_response(url=re.compile("https://test_url/something.*"))
+
+ with httpx.Client() as client:
+ response = client.get("https://test_url/something?a=1&b=2")
+ assert response.content == b""
+```
+
+#### Matching on query parameters
+
+Use `match_params` to partially match query parameters without having to
provide a regular expression as `url`.
+
+If this parameter is provided, `url` parameter must not contain any query
parameter.
+
+All query parameters have to be provided (as `str`). You can however use
`unittest.mock.ANY` to do partial matching.
+
+```python
+import httpx
+from pytest_httpx import HTTPXMock
+from unittest.mock import ANY
+
+def test_partial_params_matching(httpx_mock: HTTPXMock):
+ httpx_mock.add_response(url="https://test_url", match_params={"a": "1",
"b": ANY})
+
+ with httpx.Client() as client:
+ response = client.get("https://test_url?a=1&b=2")
+
+def test_partial_multi_params_matching(httpx_mock: HTTPXMock):
+ httpx_mock.add_response(url="https://test_url", match_params={"a": ["1",
"3"], "b": ["2", ANY]})
+
+ with httpx.Client() as client:
+ response = client.get("https://test_url?a=1&b=2&a=3&b=4")
+```
+
#### Matching on HTTP method
Use `method` parameter to specify the HTTP method (POST, PUT, DELETE, PATCH,
HEAD) to reply to.
@@ -473,7 +517,7 @@
Cookies are sent in the `set-cookie` HTTP header.
-You can then send cookies in the response by setting the `set-cookie` header
with [the value following key=value
format]((https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)).
+You can then send cookies in the response by setting the `set-cookie` header
with [the value following key=value
format](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).
```python
import httpx
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpx-0.35.0/pyproject.toml
new/pytest_httpx-0.36.2/pyproject.toml
--- old/pytest_httpx-0.35.0/pyproject.toml 2024-11-28 20:16:16.000000000
+0100
+++ new/pytest_httpx-0.36.2/pyproject.toml 2026-04-09 15:55:54.000000000
+0200
@@ -6,13 +6,13 @@
name = "pytest-httpx"
description = "Send responses to httpx."
readme = "README.md"
-requires-python = ">=3.9"
+requires-python = ">=3.10"
license = {file = "LICENSE"}
authors = [
- { name = "Colin Bounouar", email = "[email protected]" },
+ { name = "Colin Bounouar", email = "[email protected]" },
]
maintainers = [
- { name = "Colin Bounouar", email = "[email protected]" },
+ { name = "Colin Bounouar", email = "[email protected]" },
]
keywords = [
"httpx",
@@ -27,18 +27,18 @@
"Natural Language :: English",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Software Development :: Build Tools",
"Typing :: Typed",
]
dependencies = [
"httpx==0.28.*",
- "pytest==8.*",
+ "pytest==9.*",
]
dynamic = ["version"]
@@ -51,9 +51,9 @@
[project.optional-dependencies]
testing = [
# Used to check coverage
- "pytest-cov==6.*",
+ "pytest-cov==7.*",
# Used to run async tests
- "pytest-asyncio==0.24.*",
+ "pytest-asyncio==1.*",
]
[project.entry-points.pytest11]
@@ -62,6 +62,9 @@
[tool.setuptools.dynamic]
version = {attr = "pytest_httpx.version.__version__"}
-[tool.pytest.ini_options]
+[tool.pytest]
# Silence deprecation warnings about option
"asyncio_default_fixture_loop_scope"
asyncio_default_fixture_loop_scope = "function"
+
+[tool.coverage.run]
+patch = ["subprocess"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpx-0.35.0/pytest_httpx/_httpx_internals.py
new/pytest_httpx-0.36.2/pytest_httpx/_httpx_internals.py
--- old/pytest_httpx-0.35.0/pytest_httpx/_httpx_internals.py 2024-11-28
20:16:16.000000000 +0100
+++ new/pytest_httpx-0.36.2/pytest_httpx/_httpx_internals.py 2026-04-09
15:55:54.000000000 +0200
@@ -52,7 +52,7 @@
def _proxy_url(
- real_transport: Union[httpx.HTTPTransport, httpx.AsyncHTTPTransport]
+ real_transport: Union[httpx.HTTPTransport, httpx.AsyncHTTPTransport],
) -> Optional[httpx.URL]:
if isinstance(
real_pool := real_transport._pool, (httpcore.HTTPProxy,
httpcore.AsyncHTTPProxy)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpx-0.35.0/pytest_httpx/_httpx_mock.py
new/pytest_httpx-0.36.2/pytest_httpx/_httpx_mock.py
--- old/pytest_httpx-0.35.0/pytest_httpx/_httpx_mock.py 2024-11-28
20:16:16.000000000 +0100
+++ new/pytest_httpx-0.36.2/pytest_httpx/_httpx_mock.py 2026-04-09
15:55:54.000000000 +0200
@@ -58,7 +58,7 @@
:param html: HTTP body of the response (as HTML string content).
:param stream: HTTP body of the response (as httpx.SyncByteStream or
httpx.AsyncByteStream) as stream content.
:param json: HTTP body of the response (if JSON should be used as
content type) if data is not provided.
- :param url: Full URL identifying the request(s) to match.
+ :param url: Full URL identifying the request(s) to match. Use in
addition to match_params if you do not want to provide query parameters as part
of the URL.
Can be a str, a re.Pattern instance or a httpx.URL instance.
:param method: HTTP method identifying the request(s) to match.
:param proxy_url: Full proxy URL identifying the request(s) to match.
@@ -68,6 +68,8 @@
:param match_json: JSON decoded HTTP body identifying the request(s)
to match. Must be JSON encodable.
:param match_data: Multipart data (excluding files) identifying the
request(s) to match. Must be a dictionary.
:param match_files: Multipart files identifying the request(s) to
match. Refer to httpx documentation for more information on supported values:
https://www.python-httpx.org/advanced/clients/#multipart-file-encoding
+ :param match_extensions: Extensions identifying the request(s) to
match. Must be a dictionary.
+ :param match_params: Query string parameters identifying the
request(s) to match (if not provided as part of URL already). Must be a
dictionary with str keys (parameter name) and str values (or a list of str
values if parameter is provided more than once).
:param is_optional: True will mark this response as optional, False
will expect a request matching it. Must be a boolean. Default to the opposite
of assert_all_responses_were_requested option value (itself defaulting to True,
meaning this parameter default to False).
:param is_reusable: True will allow re-using this response even if it
already matched, False prevent re-using it. Must be a boolean. Default to the
can_send_already_matched_responses option value (itself defaulting to False).
"""
@@ -101,7 +103,7 @@
:param callback: The callable that will be called upon reception of
the matched request.
It must expect one parameter, the received httpx.Request and should
return a httpx.Response.
- :param url: Full URL identifying the request(s) to match.
+ :param url: Full URL identifying the request(s) to match. Use in
addition to match_params if you do not want to provide query parameters as part
of the URL.
Can be a str, a re.Pattern instance or a httpx.URL instance.
:param method: HTTP method identifying the request(s) to match.
:param proxy_url: Full proxy URL identifying the request(s) to match.
@@ -112,17 +114,18 @@
:param match_data: Multipart data (excluding files) identifying the
request(s) to match. Must be a dictionary.
:param match_files: Multipart files identifying the request(s) to
match. Refer to httpx documentation for more information on supported values:
https://www.python-httpx.org/advanced/clients/#multipart-file-encoding
:param match_extensions: Extensions identifying the request(s) to
match. Must be a dictionary.
+ :param match_params: Query string parameters identifying the
request(s) to match (if not provided as part of URL already). Must be a
dictionary with str keys (parameter name) and str values (or a list of str
values if parameter is provided more than once).
:param is_optional: True will mark this callback as optional, False
will expect a request matching it. Must be a boolean. Default to the opposite
of assert_all_responses_were_requested option value (itself defaulting to True,
meaning this parameter default to False).
:param is_reusable: True will allow re-using this callback even if it
already matched, False prevent re-using it. Must be a boolean. Default to the
can_send_already_matched_responses option value (itself defaulting to False).
"""
self._callbacks.append((_RequestMatcher(self._options, **matchers),
callback))
- def add_exception(self, exception: Exception, **matchers: Any) -> None:
+ def add_exception(self, exception: BaseException, **matchers: Any) -> None:
"""
Raise an exception if a request match.
:param exception: The exception that will be raised upon reception of
the matched request.
- :param url: Full URL identifying the request(s) to match.
+ :param url: Full URL identifying the request(s) to match. Use in
addition to match_params if you do not want to provide query parameters as part
of the URL.
Can be a str, a re.Pattern instance or a httpx.URL instance.
:param method: HTTP method identifying the request(s) to match.
:param proxy_url: Full proxy URL identifying the request(s) to match.
@@ -133,6 +136,7 @@
:param match_data: Multipart data (excluding files) identifying the
request(s) to match. Must be a dictionary.
:param match_files: Multipart files identifying the request(s) to
match. Refer to httpx documentation for more information on supported values:
https://www.python-httpx.org/advanced/clients/#multipart-file-encoding
:param match_extensions: Extensions identifying the request(s) to
match. Must be a dictionary.
+ :param match_params: Query string parameters identifying the
request(s) to match (if not provided as part of URL already). Must be a
dictionary with str keys (parameter name) and str values (or a list of str
values if parameter is provided more than once).
:param is_optional: True will mark this exception response as
optional, False will expect a request matching it. Must be a boolean. Default
to the opposite of assert_all_responses_were_requested option value (itself
defaulting to True, meaning this parameter default to False).
:param is_reusable: True will allow re-using this exception response
even if it already matched, False prevent re-using it. Must be a boolean.
Default to the can_send_already_matched_responses option value (itself
defaulting to False).
"""
@@ -261,7 +265,7 @@
"""
Return all requests sent that match (empty list if no requests were
matched).
- :param url: Full URL identifying the requests to retrieve.
+ :param url: Full URL identifying the requests to retrieve. Use in
addition to match_params if you do not want to provide query parameters as part
of the URL.
Can be a str, a re.Pattern instance or a httpx.URL instance.
:param method: HTTP method identifying the requests to retrieve. Must
be an upper-cased string value.
:param proxy_url: Full proxy URL identifying the requests to retrieve.
@@ -272,6 +276,7 @@
:param match_data: Multipart data (excluding files) identifying the
requests to retrieve. Must be a dictionary.
:param match_files: Multipart files identifying the requests to
retrieve. Refer to httpx documentation for more information on supported
values: https://www.python-httpx.org/advanced/clients/#multipart-file-encoding
:param match_extensions: Extensions identifying the requests to
retrieve. Must be a dictionary.
+ :param match_params: Query string parameters identifying the requests
to retrieve (if not provided as part of URL already). Must be a dictionary with
str keys (parameter name) and str values (or a list of str values if parameter
is provided more than once).
"""
matcher = _RequestMatcher(self._options, **matchers)
return [
@@ -284,7 +289,7 @@
"""
Return the single request that match (or None).
- :param url: Full URL identifying the request to retrieve.
+ :param url: Full URL identifying the request to retrieve. Use in
addition to match_params if you do not want to provide query parameters as part
of the URL.
Can be a str, a re.Pattern instance or a httpx.URL instance.
:param method: HTTP method identifying the request to retrieve. Must
be an upper-cased string value.
:param proxy_url: Full proxy URL identifying the request to retrieve.
@@ -295,6 +300,7 @@
:param match_data: Multipart data (excluding files) identifying the
request to retrieve. Must be a dictionary.
:param match_files: Multipart files identifying the request to
retrieve. Refer to httpx documentation for more information on supported
values: https://www.python-httpx.org/advanced/clients/#multipart-file-encoding
:param match_extensions: Extensions identifying the request to
retrieve. Must be a dictionary.
+ :param match_params: Query string parameters identifying the request
to retrieve (if not provided as part of URL already). Must be a dictionary with
str keys (parameter name) and str values (or a list of str values if parameter
is provided more than once).
:raises AssertionError: in case more than one request match.
"""
requests = self.get_requests(**matchers)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpx-0.35.0/pytest_httpx/_request_matcher.py
new/pytest_httpx-0.36.2/pytest_httpx/_request_matcher.py
--- old/pytest_httpx-0.35.0/pytest_httpx/_request_matcher.py 2024-11-28
20:16:16.000000000 +0100
+++ new/pytest_httpx-0.36.2/pytest_httpx/_request_matcher.py 2026-04-09
15:55:54.000000000 +0200
@@ -4,20 +4,24 @@
from re import Pattern
import httpx
+from httpx import QueryParams
from pytest_httpx._httpx_internals import _proxy_url
from pytest_httpx._options import _HTTPXMockOptions
def _url_match(
- url_to_match: Union[Pattern[str], httpx.URL], received: httpx.URL
+ url_to_match: Union[Pattern[str], httpx.URL],
+ received: httpx.URL,
+ params: Optional[dict[str, Union[str | list[str]]]],
) -> bool:
if isinstance(url_to_match, re.Pattern):
return url_to_match.match(str(received)) is not None
# Compare query parameters apart as order of parameters should not matter
- received_params = dict(received.params)
- params = dict(url_to_match.params)
+ received_params = to_params_dict(received.params)
+ if params is None:
+ params = to_params_dict(url_to_match.params)
# Remove the query parameters from the original URL to compare everything
besides query parameters
received_url = received.copy_with(query=None)
@@ -26,6 +30,15 @@
return (received_params == params) and (url == received_url)
+def to_params_dict(params: 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:
+ values = params.get_list(key)
+ d[key] = values if len(values) > 1 else values[0]
+ return d
+
+
class _RequestMatcher:
def __init__(
self,
@@ -39,6 +52,7 @@
match_data: Optional[dict[str, Any]] = None,
match_files: Optional[Any] = None,
match_extensions: Optional[dict[str, Any]] = None,
+ match_params: Optional[dict[str, Union[str | list[str]]]] = None,
is_optional: Optional[bool] = None,
is_reusable: Optional[bool] = None,
):
@@ -51,14 +65,23 @@
self.json = match_json
self.data = match_data
self.files = match_files
+ self.params = match_params
self.proxy_url = (
httpx.URL(proxy_url)
if proxy_url and isinstance(proxy_url, str)
else proxy_url
)
self.extensions = match_extensions
- self.is_optional = not options.assert_all_responses_were_requested if
is_optional is None else is_optional
- self.is_reusable = options.can_send_already_matched_responses if
is_reusable is None else is_reusable
+ self.is_optional = (
+ not options.assert_all_responses_were_requested
+ if is_optional is None
+ else is_optional
+ )
+ self.is_reusable = (
+ options.can_send_already_matched_responses
+ if is_reusable is None
+ else is_reusable
+ )
if self._is_matching_body_more_than_one_way():
raise ValueError(
"Only one way of matching against the body can be provided. "
@@ -66,6 +89,16 @@
"If you want to match against the multipart representation,
use match_files (and match_data). "
"Otherwise, use match_content."
)
+ if self.params and not self.url:
+ raise ValueError("URL must be provided when match_params is used.")
+ if self.params and isinstance(self.url, re.Pattern):
+ raise ValueError(
+ "match_params cannot be used in addition to regex URL. Request
this feature via
https://github.com/Colin-b/pytest_httpx/issues/new?title=Regex%20URL%20should%20allow%20match_params&body=Hi,%20I%20need%20a%20regex%20to%20match%20the%20non%20query%20part%20of%20the%20URL%20only"
+ )
+ if self._is_matching_params_more_than_one_way():
+ raise ValueError(
+ "Provided URL must not contain any query parameter when
match_params is used."
+ )
if self.data and not self.files:
raise ValueError(
"match_data is meant to be used for multipart matching (in
conjunction with match_files)."
@@ -88,6 +121,18 @@
]
return sum(matching_ways) > 1
+ def _is_matching_params_more_than_one_way(self) -> bool:
+ url_has_params = (
+ bool(self.url.params)
+ if (self.url and isinstance(self.url, httpx.URL))
+ else False
+ )
+ matching_ways = [
+ self.params is not None,
+ url_has_params,
+ ]
+ return sum(matching_ways) > 1
+
def match(
self,
real_transport: Union[httpx.HTTPTransport, httpx.AsyncHTTPTransport],
@@ -106,7 +151,7 @@
if not self.url:
return True
- return _url_match(self.url, request.url)
+ return _url_match(self.url, request.url, self.params)
def _method_match(self, request: httpx.Request) -> bool:
if not self.method:
@@ -168,7 +213,7 @@
return True
if real_proxy_url := _proxy_url(real_transport):
- return _url_match(self.proxy_url, real_proxy_url)
+ return _url_match(self.proxy_url, real_proxy_url, params=None)
return False
@@ -200,6 +245,8 @@
def _extra_description(self) -> str:
extra_description = []
+ if self.params:
+ extra_description.append(f"{self.params} query parameters")
if self.headers:
extra_description.append(f"{self.headers} headers")
if self.content is not None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpx-0.35.0/pytest_httpx/version.py
new/pytest_httpx-0.36.2/pytest_httpx/version.py
--- old/pytest_httpx-0.35.0/pytest_httpx/version.py 2024-11-28
20:16:16.000000000 +0100
+++ new/pytest_httpx-0.36.2/pytest_httpx/version.py 2026-04-09
15:55:54.000000000 +0200
@@ -3,4 +3,4 @@
# Major should be incremented in case there is a breaking change. (eg: 2.5.8
-> 3.0.0)
# Minor should be incremented in case there is an enhancement. (eg: 2.5.8 ->
2.6.0)
# Patch should be incremented in case there is a bug fix. (eg: 2.5.8 -> 2.5.9)
-__version__ = "0.35.0"
+__version__ = "0.36.2"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpx-0.35.0/tests/test_httpx_async.py
new/pytest_httpx-0.36.2/tests/test_httpx_async.py
--- old/pytest_httpx-0.35.0/tests/test_httpx_async.py 2024-11-28
20:16:16.000000000 +0100
+++ new/pytest_httpx-0.36.2/tests/test_httpx_async.py 2026-04-09
15:55:54.000000000 +0200
@@ -73,6 +73,189 @@
@pytest.mark.asyncio
+async def test_url_query_params_partial_matching(httpx_mock: HTTPXMock) ->
None:
+ httpx_mock.add_response(
+ url=httpx.URL("https://test_url"),
+ match_params={"a": ["1", "3"], "b": ANY, "c": "4", "d": ["5", ANY]},
+ is_reusable=True,
+ )
+
+ async with httpx.AsyncClient() as client:
+ response = await
client.post("https://test_url?a=1&b=2&a=3&c=4&d=5&d=6")
+ assert response.content == b""
+
+ # Parameters order should not matter
+ response = await client.get("https://test_url?b=9&a=1&a=3&c=4&d=5&d=7")
+ assert response.content == b""
+
+
[email protected]
+async def test_url_as_pattern_ignoring_query_parameters(httpx_mock: HTTPXMock):
+ httpx_mock.add_response(url=re.compile("https://test_url/something.*"))
+
+ async with httpx.AsyncClient() as client:
+ response = await client.get("https://test_url/something?a=1&b=2")
+ assert response.content == b""
+
+
[email protected]
[email protected]_mock(assert_all_requests_were_expected=False)
+async def test_url_query_params_with_single_value_list(httpx_mock: HTTPXMock)
-> None:
+ httpx_mock.add_response(
+ url="https://test_url",
+ match_params={"a": ["1"]},
+ is_optional=True,
+ )
+
+ async with httpx.AsyncClient() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ await client.post("https://test_url?a=1")
+ assert (
+ str(exception_info.value)
+ == """No response can be found for POST request on
https://test_url?a=1 amongst:
+- Match any request on https://test_url with {'a': ['1']} query parameters"""
+ )
+
+
[email protected]
[email protected]_mock(assert_all_requests_were_expected=False)
+async def test_url_query_params_with_non_str_value(httpx_mock: HTTPXMock) ->
None:
+ httpx_mock.add_response(
+ url="https://test_url",
+ match_params={"a": 1},
+ is_optional=True,
+ )
+
+ async with httpx.AsyncClient() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ await client.post("https://test_url?a=1")
+ assert (
+ str(exception_info.value)
+ == """No response can be found for POST request on
https://test_url?a=1 amongst:
+- Match any request on https://test_url with {'a': 1} query parameters"""
+ )
+
+
[email protected]
[email protected]_mock(assert_all_requests_were_expected=False)
+async def test_url_query_params_with_non_str_list_value(httpx_mock: HTTPXMock)
-> None:
+ httpx_mock.add_response(
+ url="https://test_url",
+ match_params={"a": [1, "2"]},
+ is_optional=True,
+ )
+
+ async with httpx.AsyncClient() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ await client.post("https://test_url?a=1&a=2")
+ assert (
+ str(exception_info.value)
+ == """No response can be found for POST request on
https://test_url?a=1&a=2 amongst:
+- Match any request on https://test_url with {'a': [1, '2']} query
parameters"""
+ )
+
+
[email protected]
[email protected]_mock(assert_all_requests_were_expected=False)
+async def test_url_query_params_with_non_str_name(httpx_mock: HTTPXMock) ->
None:
+ httpx_mock.add_response(
+ url="https://test_url",
+ match_params={1: "1"},
+ is_optional=True,
+ )
+
+ async with httpx.AsyncClient() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ await client.post("https://test_url?1=1")
+ assert (
+ str(exception_info.value)
+ == """No response can be found for POST request on
https://test_url?1=1 amongst:
+- Match any request on https://test_url with {1: '1'} query parameters"""
+ )
+
+
[email protected]
[email protected]_mock(assert_all_requests_were_expected=False)
+async def test_url_query_params_not_matching(httpx_mock: HTTPXMock) -> None:
+ httpx_mock.add_response(
+ url="https://test_url",
+ match_params={"a": "1"},
+ is_optional=True,
+ )
+
+ async with httpx.AsyncClient() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ await client.post("https://test_url?a=2")
+ assert (
+ str(exception_info.value)
+ == """No response can be found for POST request on
https://test_url?a=2 amongst:
+- Match any request on https://test_url with {'a': '1'} query parameters"""
+ )
+
+
[email protected]
+async def test_url_matching_with_more_than_one_value_on_same_param(
+ httpx_mock: HTTPXMock,
+) -> None:
+ httpx_mock.add_response(url="https://test_url?a=1&a=3", is_optional=True)
+
+ async with httpx.AsyncClient() as client:
+ response = await client.get("https://test_url", params={"a": [1, 3]})
+ assert response.content == b""
+
+
[email protected]
[email protected]_mock(assert_all_requests_were_expected=False)
+async def
test_url_not_matching_with_more_than_one_value_on_same_param_and_diff_value(
+ httpx_mock: HTTPXMock,
+) -> None:
+ httpx_mock.add_response(url="https://test_url?a=2&a=3", is_optional=True)
+
+ async with httpx.AsyncClient() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ await client.get("https://test_url", params={"a": [1, 3]})
+ assert (
+ str(exception_info.value)
+ == """No response can be found for GET request on
https://test_url?a=1&a=3 amongst:
+- Match any request on https://test_url?a=2&a=3"""
+ )
+
+
[email protected]
[email protected]_mock(assert_all_requests_were_expected=False)
+async def
test_url_not_matching_with_more_than_one_value_on_same_param_and_more_values(
+ httpx_mock: HTTPXMock,
+) -> None:
+ httpx_mock.add_response(url="https://test_url?a=1&a=3", is_optional=True)
+
+ async with httpx.AsyncClient() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ await client.get("https://test_url", params={"a": [1, 3, 4]})
+ assert (
+ str(exception_info.value)
+ == """No response can be found for GET request on
https://test_url?a=1&a=3&a=4 amongst:
+- Match any request on https://test_url?a=1&a=3"""
+ )
+
+
[email protected]
[email protected]_mock(assert_all_requests_were_expected=False)
+async def
test_url_not_matching_with_more_than_one_value_on_same_param_and_less_values(
+ httpx_mock: HTTPXMock,
+) -> None:
+ httpx_mock.add_response(url="https://test_url?a=1&a=3&a=4",
is_optional=True)
+
+ async with httpx.AsyncClient() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ await client.get("https://test_url", params={"a": [1, 3]})
+ assert (
+ str(exception_info.value)
+ == """No response can be found for GET request on
https://test_url?a=1&a=3 amongst:
+- Match any request on https://test_url?a=1&a=3&a=4"""
+ )
+
+
[email protected]
@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
async def test_url_not_matching(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(url="https://test_url", is_optional=True)
@@ -886,15 +1069,30 @@
@pytest.mark.asyncio
-async def test_non_request_exception_raising(httpx_mock: HTTPXMock) -> None:
- httpx_mock.add_exception(
- httpx.HTTPError("Unable to read within 5.0"), url="https://test_url"
- )
[email protected](
+ ("exception_type", "message"),
+ [
+ # httpx exception without request context
+ pytest.param(
+ httpx.HTTPError, "Unable to read within 5.0",
id="non_request_exception"
+ ),
+ # BaseException derived exception
+ pytest.param(
+ asyncio.CancelledError,
+ "Request was cancelled",
+ id="cancelled_exception",
+ ),
+ ],
+)
+async def test_non_request_exception_raising(
+ httpx_mock: HTTPXMock, exception_type: type, message: str
+) -> None:
+ httpx_mock.add_exception(exception_type(message), url="https://test_url")
async with httpx.AsyncClient() as client:
- with pytest.raises(httpx.HTTPError) as exception_info:
+ with pytest.raises(exception_type) as exception_info:
await client.get("https://test_url")
- assert str(exception_info.value) == "Unable to read within 5.0"
+ assert str(exception_info.value) == message
@pytest.mark.asyncio
@@ -1087,8 +1285,7 @@
"""
Single request cannot be returned if there is more than one matching.
"""
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -1102,8 +1299,7 @@
await client.get("https://test_url", headers={"X-TEST": "test
header 2"})
httpx_mock.get_request(url=httpx.URL("https://test_url"))
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(failed=1)
result.stdout.fnmatch_lines(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpx-0.35.0/tests/test_httpx_sync.py
new/pytest_httpx-0.36.2/tests/test_httpx_sync.py
--- old/pytest_httpx-0.35.0/tests/test_httpx_sync.py 2024-11-28
20:16:16.000000000 +0100
+++ new/pytest_httpx-0.36.2/tests/test_httpx_sync.py 2026-04-09
15:55:54.000000000 +0200
@@ -64,6 +64,207 @@
assert response.content == b""
+def test_url_query_params_partial_matching(httpx_mock: HTTPXMock) -> None:
+ httpx_mock.add_response(
+ url="https://test_url",
+ match_params={"a": ["1", "3"], "b": ANY, "c": "4", "d": ["5", ANY]},
+ is_reusable=True,
+ )
+
+ with httpx.Client() as client:
+ response = client.post("https://test_url?a=1&b=2&a=3&c=4&d=5&d=6")
+ assert response.content == b""
+
+ # Parameters order should not matter
+ response = client.get("https://test_url?b=9&a=1&a=3&c=4&d=5&d=7")
+ assert response.content == b""
+
+
+def test_url_as_pattern_ignoring_query_parameters(httpx_mock: HTTPXMock):
+ httpx_mock.add_response(url=re.compile("https://test_url/something.*"))
+
+ with httpx.Client() as client:
+ response = client.get("https://test_url/something?a=1&b=2")
+ assert response.content == b""
+
+
[email protected]_mock(assert_all_requests_were_expected=False)
+def test_url_query_params_with_single_value_list(httpx_mock: HTTPXMock) ->
None:
+ httpx_mock.add_response(
+ url="https://test_url",
+ match_params={"a": ["1"]},
+ is_optional=True,
+ )
+
+ with httpx.Client() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ client.post("https://test_url?a=1")
+ assert (
+ str(exception_info.value)
+ == """No response can be found for POST request on
https://test_url?a=1 amongst:
+- Match any request on https://test_url with {'a': ['1']} query parameters"""
+ )
+
+
[email protected]_mock(assert_all_requests_were_expected=False)
+def test_url_query_params_with_non_str_value(httpx_mock: HTTPXMock) -> None:
+ httpx_mock.add_response(
+ url="https://test_url",
+ match_params={"a": 1},
+ is_optional=True,
+ )
+
+ with httpx.Client() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ client.post("https://test_url?a=1")
+ assert (
+ str(exception_info.value)
+ == """No response can be found for POST request on
https://test_url?a=1 amongst:
+- Match any request on https://test_url with {'a': 1} query parameters"""
+ )
+
+
[email protected]_mock(assert_all_requests_were_expected=False)
+def test_url_query_params_with_non_str_list_value(httpx_mock: HTTPXMock) ->
None:
+ httpx_mock.add_response(
+ url="https://test_url",
+ match_params={"a": [1, "2"]},
+ is_optional=True,
+ )
+
+ with httpx.Client() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ client.post("https://test_url?a=1&a=2")
+ assert (
+ str(exception_info.value)
+ == """No response can be found for POST request on
https://test_url?a=1&a=2 amongst:
+- Match any request on https://test_url with {'a': [1, '2']} query
parameters"""
+ )
+
+
[email protected]_mock(assert_all_requests_were_expected=False)
+def test_url_query_params_with_non_str_name(httpx_mock: HTTPXMock) -> None:
+ httpx_mock.add_response(
+ url="https://test_url",
+ match_params={1: "1"},
+ is_optional=True,
+ )
+
+ with httpx.Client() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ client.post("https://test_url?1=1")
+ assert (
+ str(exception_info.value)
+ == """No response can be found for POST request on
https://test_url?1=1 amongst:
+- Match any request on https://test_url with {1: '1'} query parameters"""
+ )
+
+
+def test_match_params_without_url(httpx_mock: HTTPXMock) -> None:
+ with pytest.raises(ValueError) as exception_info:
+ httpx_mock.add_response(match_params={"a": "1"})
+
+ assert (
+ str(exception_info.value) == "URL must be provided when match_params
is used."
+ )
+
+
+def test_query_params_in_both_url_and_match_params(httpx_mock: HTTPXMock) ->
None:
+ with pytest.raises(ValueError) as exception_info:
+ httpx_mock.add_response(url="https://test_url?a=1", match_params={"a":
"1"})
+
+ assert (
+ str(exception_info.value)
+ == "Provided URL must not contain any query parameter when
match_params is used."
+ )
+
+
+def test_regex_url_and_match_params(httpx_mock: HTTPXMock) -> None:
+ with pytest.raises(ValueError) as exception_info:
+ httpx_mock.add_response(url=re.compile(".*test.*"), match_params={"a":
"1"})
+
+ assert (
+ str(exception_info.value)
+ == "match_params cannot be used in addition to regex URL. Request this
feature via
https://github.com/Colin-b/pytest_httpx/issues/new?title=Regex%20URL%20should%20allow%20match_params&body=Hi,%20I%20need%20a%20regex%20to%20match%20the%20non%20query%20part%20of%20the%20URL%20only"
+ )
+
+
[email protected]_mock(assert_all_requests_were_expected=False)
+def test_url_query_params_not_matching(httpx_mock: HTTPXMock) -> None:
+ httpx_mock.add_response(
+ url="https://test_url",
+ match_params={"a": "1"},
+ is_optional=True,
+ )
+
+ with httpx.Client() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ client.post("https://test_url?a=2")
+ assert (
+ str(exception_info.value)
+ == """No response can be found for POST request on
https://test_url?a=2 amongst:
+- Match any request on https://test_url with {'a': '1'} query parameters"""
+ )
+
+
+def test_url_matching_with_more_than_one_value_on_same_param(
+ httpx_mock: HTTPXMock,
+) -> None:
+ httpx_mock.add_response(url="https://test_url?a=1&a=3", is_optional=True)
+
+ with httpx.Client() as client:
+ response = client.get("https://test_url", params={"a": [1, 3]})
+ assert response.content == b""
+
+
[email protected]_mock(assert_all_requests_were_expected=False)
+def
test_url_not_matching_with_more_than_one_value_on_same_param_and_diff_value(
+ httpx_mock: HTTPXMock,
+) -> None:
+ httpx_mock.add_response(url="https://test_url?a=2&a=3", is_optional=True)
+
+ with httpx.Client() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ client.get("https://test_url", params={"a": [1, 3]})
+ assert (
+ str(exception_info.value)
+ == """No response can be found for GET request on
https://test_url?a=1&a=3 amongst:
+- Match any request on https://test_url?a=2&a=3"""
+ )
+
+
[email protected]_mock(assert_all_requests_were_expected=False)
+def
test_url_not_matching_with_more_than_one_value_on_same_param_and_more_values(
+ httpx_mock: HTTPXMock,
+) -> None:
+ httpx_mock.add_response(url="https://test_url?a=1&a=3", is_optional=True)
+
+ with httpx.Client() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ client.get("https://test_url", params={"a": [1, 3, 4]})
+ assert (
+ str(exception_info.value)
+ == """No response can be found for GET request on
https://test_url?a=1&a=3&a=4 amongst:
+- Match any request on https://test_url?a=1&a=3"""
+ )
+
+
[email protected]_mock(assert_all_requests_were_expected=False)
+def
test_url_not_matching_with_more_than_one_value_on_same_param_and_less_values(
+ httpx_mock: HTTPXMock,
+) -> None:
+ httpx_mock.add_response(url="https://test_url?a=1&a=3&a=4",
is_optional=True)
+
+ with httpx.Client() as client:
+ with pytest.raises(httpx.TimeoutException) as exception_info:
+ client.get("https://test_url", params={"a": [1, 3]})
+ assert (
+ str(exception_info.value)
+ == """No response can be found for GET request on
https://test_url?a=1&a=3 amongst:
+- Match any request on https://test_url?a=1&a=3&a=4"""
+ )
+
+
@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
def test_url_not_matching(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(url="https://test_url", is_optional=True)
@@ -862,8 +1063,7 @@
"""
Single request cannot be returned if there is more than one matching.
"""
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
@@ -875,8 +1075,7 @@
client.get("https://test_url", headers={"X-TEST": "test header
2"})
httpx_mock.get_request(url=httpx.URL("https://test_url"))
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(failed=1)
result.stdout.fnmatch_lines(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpx-0.35.0/tests/test_plugin.py
new/pytest_httpx-0.36.2/tests/test_plugin.py
--- old/pytest_httpx-0.35.0/tests/test_plugin.py 2024-11-28
20:16:16.000000000 +0100
+++ new/pytest_httpx-0.36.2/tests/test_plugin.py 2026-04-09
15:55:54.000000000 +0200
@@ -3,8 +3,7 @@
def test_fixture_is_available(testdir: Testdir) -> None:
# create a temporary pytest test file
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
@@ -13,8 +12,7 @@
r = httpx.get("https://foo.tld")
assert httpx_mock.get_request() is not None
- """
- )
+ """)
# run all tests with pytest
result = testdir.runpytest()
@@ -25,12 +23,10 @@
"""
Unused responses should fail test case.
"""
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
def test_httpx_mock_unused_response(httpx_mock):
httpx_mock.add_response()
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(errors=1, passed=1)
result.stdout.fnmatch_lines(
@@ -49,15 +45,13 @@
Unused responses should not fail test case if
assert_all_responses_were_requested option is set to False.
"""
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import pytest
@pytest.mark.httpx_mock(assert_all_responses_were_requested=False)
def test_httpx_mock_unused_response_without_assertion(httpx_mock):
httpx_mock.add_response()
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(passed=1)
@@ -66,16 +60,14 @@
"""
Unused callbacks should fail test case.
"""
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
def test_httpx_mock_unused_callback(httpx_mock):
def unused(*args, **kwargs):
pass
httpx_mock.add_callback(unused)
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(errors=1, passed=1)
result.stdout.fnmatch_lines(
@@ -94,8 +86,7 @@
Unused callbacks should not fail test case if
assert_all_responses_were_requested option is set to False.
"""
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import pytest
@pytest.mark.httpx_mock(assert_all_responses_were_requested=False)
@@ -105,8 +96,7 @@
httpx_mock.add_callback(unused)
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(passed=1)
@@ -116,8 +106,7 @@
Unexpected request should fail test case if
assert_all_requests_were_expected option is set to True (default).
"""
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -126,8 +115,7 @@
# Non mocked request
with pytest.raises(httpx.TimeoutException):
client.get("https://foo.tld")
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(errors=1, passed=1)
result.stdout.fnmatch_lines(
@@ -146,8 +134,7 @@
Unexpected request should not fail test case if
assert_all_requests_were_expected option is set to False.
"""
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -157,8 +144,7 @@
# Non mocked request
with pytest.raises(httpx.TimeoutException):
client.get("https://foo.tld")
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(passed=1)
@@ -168,8 +154,7 @@
Already matched response should fail test case if
can_send_already_matched_responses option is set to False (default).
"""
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -180,8 +165,7 @@
# Non mocked (already matched) request
with pytest.raises(httpx.TimeoutException):
client.get("https://foo.tld")
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(errors=1, passed=1)
result.stdout.fnmatch_lines(
@@ -200,8 +184,7 @@
Already matched response should not fail test case if
can_send_already_matched_responses option is set to True.
"""
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -212,8 +195,7 @@
client.get("https://foo.tld")
# Reusing response
client.get("https://foo.tld")
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(passed=1)
@@ -221,8 +203,7 @@
def test_httpx_mock_unmatched_request_without_responses(
testdir: Testdir,
) -> None:
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -232,8 +213,7 @@
client.get("https://foo22.tld")
# This code will not be reached
client.get("https://foo3.tld")
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(errors=1, failed=1)
# Assert the error that occurred
@@ -258,8 +238,7 @@
def test_httpx_mock_unmatched_request_with_only_unmatched_responses(
testdir: Testdir,
) -> None:
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -274,8 +253,7 @@
client.get("https://foo22.tld")
# This code will not be reached
client.get("https://foo3.tld")
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(errors=1, failed=1)
# Assert the error that occurred
@@ -303,8 +281,7 @@
def test_httpx_mock_unmatched_request_with_only_unmatched_reusable_responses(
testdir: Testdir,
) -> None:
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -320,8 +297,7 @@
client.get("https://foo22.tld")
# This code will not be reached
client.get("https://foo3.tld")
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(errors=1, failed=1)
# Assert the error that occurred
@@ -349,8 +325,7 @@
def test_httpx_mock_unmatched_request_with_only_matched_responses(
testdir: Testdir,
) -> None:
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -367,8 +342,7 @@
client.get("https://foo22.tld")
# This code will not be reached
client.get("https://foo3.tld")
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(errors=1, failed=1)
# Assert the error that occurred
@@ -397,8 +371,7 @@
def test_httpx_mock_unmatched_request_with_only_matched_reusable_responses(
testdir: Testdir,
) -> None:
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -417,8 +390,7 @@
client.get("https://foo22.tld")
# This code will not be reached
client.get("https://foo3.tld")
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(errors=1, failed=1)
# Assert the error that occurred
@@ -445,8 +417,7 @@
def test_httpx_mock_unmatched_request_with_matched_and_unmatched_responses(
testdir: Testdir,
) -> None:
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -467,8 +438,7 @@
client.get("https://foo22.tld")
# This code will not be reached
client.get("https://foo3.tld")
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(errors=1, failed=1)
# Assert the error that occurred
@@ -500,8 +470,7 @@
def
test_httpx_mock_unmatched_request_with_matched_and_unmatched_reusable_responses(
testdir: Testdir,
) -> None:
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -524,8 +493,7 @@
client.get("https://foo3.tld")
# This code will not be reached
client.get("https://foo2.tld")
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(errors=1, failed=1)
# Assert the error that occurred
@@ -556,8 +524,7 @@
"""
Non mocked requests should go through while other requests should be
mocked.
"""
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -576,8 +543,7 @@
# Assert that a single request was mocked
assert len(httpx_mock.get_requests()) == 1
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(passed=1)
@@ -586,8 +552,7 @@
"""
Non mocked requests should go through while other requests should be
mocked.
"""
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -607,8 +572,7 @@
# Assert that a single request was mocked
assert len(httpx_mock.get_requests()) == 1
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(passed=1)
@@ -621,18 +585,15 @@
module: assert_all_requests_were_expected (tested by not mocking one URL)
test: should_mock (tested by calling 3 URls, 2 mocked, the other one not)
"""
- testdir.makeconftest(
- """
+ testdir.makeconftest("""
import pytest
def pytest_collection_modifyitems(session, config, items):
for item in items:
item.add_marker(pytest.mark.httpx_mock(assert_all_responses_were_requested=False))
- """
- )
- testdir.makepyfile(
- """
+ """)
+ testdir.makepyfile("""
import httpx
import pytest
@@ -662,8 +623,7 @@
# Assert that 2 requests out of 3 were mocked
assert len(httpx_mock.get_requests()) == 2
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(passed=1)
@@ -672,16 +632,14 @@
"""
Unknown marker keyword arguments should raise a TypeError.
"""
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import pytest
@pytest.mark.httpx_mock(foo=123)
def test_invalid_marker(httpx_mock):
pass
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(errors=1)
result.stdout.re_match_lines([r".*got an unexpected keyword argument
'foo'"])
@@ -691,8 +649,7 @@
"""
is_optional MUST take precedence over assert_all_responses_were_requested.
"""
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
import pytest
@@ -703,8 +660,7 @@
# This response MUST be requested
httpx_mock.add_response(url="https://test_url2", is_optional=False)
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(errors=1, passed=1)
# Assert the teardown assertion failure
@@ -720,15 +676,13 @@
def test_reusable_response_not_matched(testdir: Testdir) -> None:
- testdir.makepyfile(
- """
+ testdir.makepyfile("""
import httpx
def test_reusable_response_not_matched(httpx_mock):
httpx_mock.add_response(url="https://test_url2", is_reusable=True)
- """
- )
+ """)
result = testdir.runpytest()
result.assert_outcomes(errors=1, passed=1)
# Assert the teardown assertion failure