Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-responses for
openSUSE:Factory checked in at 2026-05-28 23:07:45
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-responses (Old)
and /work/SRC/openSUSE:Factory/.python-responses.new.1937 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-responses"
Thu May 28 23:07:45 2026 rev:37 rq:1355106 version:0.26.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-responses/python-responses.changes
2026-03-05 17:15:24.843312821 +0100
+++
/work/SRC/openSUSE:Factory/.python-responses.new.1937/python-responses.changes
2026-05-28 23:08:00.225688984 +0200
@@ -1,0 +2,10 @@
+Mon May 25 20:10:22 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.26.1:
+ * Added Spanish translation of the README (``README.es.rst``)
+ * When both `content_type` and `headers['content-type']` are in
+ a response mock file, `content_type` is now used.
+ * Added strict_match to urlencoded_params_matcher, enabling
+ partial request parameter matching.
+
+-------------------------------------------------------------------
Old:
----
responses-0.26.0.tar.gz
New:
----
responses-0.26.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-responses.spec ++++++
--- /var/tmp/diff_new_pack.cLqlVE/_old 2026-05-28 23:08:03.177810529 +0200
+++ /var/tmp/diff_new_pack.cLqlVE/_new 2026-05-28 23:08:03.181810694 +0200
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-responses
-Version: 0.26.0
+Version: 0.26.1
Release: 0
Summary: A utility library for mocking out the `requests` Python library
License: Apache-2.0
++++++ responses-0.26.0.tar.gz -> responses-0.26.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/responses-0.26.0/CHANGES new/responses-0.26.1/CHANGES
--- old/responses-0.26.0/CHANGES 2026-02-19 15:36:42.000000000 +0100
+++ new/responses-0.26.1/CHANGES 2026-05-21 21:54:07.000000000 +0200
@@ -1,3 +1,12 @@
+0.26.1
+------
+
+* Added Spanish translation of the README (``README.es.rst``)
+* When both `content_type` and `headers['content-type']` are in a response
mock file,
+ `content_type` is now used.
+* Added strict_match to urlencoded_params_matcher, enabling partial request
parameter
+ matching.
+
0.26.0
------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/responses-0.26.0/PKG-INFO
new/responses-0.26.1/PKG-INFO
--- old/responses-0.26.0/PKG-INFO 2026-02-19 15:36:52.756099500 +0100
+++ new/responses-0.26.1/PKG-INFO 2026-05-21 21:54:17.008405000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: responses
-Version: 0.26.0
+Version: 0.26.1
Summary: A utility library for mocking out the `requests` Python library.
Home-page: https://github.com/getsentry/responses
Author: David Cramer
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/responses-0.26.0/responses/__init__.py
new/responses-0.26.1/responses/__init__.py
--- old/responses-0.26.0/responses/__init__.py 2026-02-19 15:36:42.000000000
+0100
+++ new/responses-0.26.1/responses/__init__.py 2026-05-21 21:54:07.000000000
+0200
@@ -39,7 +39,7 @@
try:
from typing_extensions import Literal
except ImportError: # pragma: no cover
- from typing import Literal # pragma: no cover
+ from typing import Literal
from io import BufferedReader
from io import BytesIO
@@ -249,7 +249,9 @@
"""Overload for scenario when index is provided."""
@overload
- def __getitem__(self, idx: "slice[int, int, Optional[int]]") -> List[Call]:
+ def __getitem__(
+ self, idx: "slice[Optional[int], Optional[int], Optional[int]]"
+ ) -> List[Call]:
"""Overload for scenario when slice is provided."""
def __getitem__(self, idx: Union[int, slice]) -> Union[Call, List[Call]]:
@@ -856,12 +858,21 @@
for rsp in data["responses"]:
rsp = rsp["response"]
+ headers = rsp["headers"] if "headers" in rsp else None
+
+ if headers is not None and "content_type" in rsp:
+ headers = {
+ k: v for k, v in headers.items() if k.lower() !=
"content-type"
+ }
+ if not headers:
+ headers = None
+
self.add(
method=rsp["method"],
url=rsp["url"],
body=rsp["body"],
status=rsp["status"],
- headers=rsp["headers"] if "headers" in rsp else None,
+ headers=headers,
content_type=rsp["content_type"],
auto_calculate_content_length=rsp["auto_calculate_content_length"],
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/responses-0.26.0/responses/matchers.py
new/responses-0.26.1/responses/matchers.py
--- old/responses-0.26.0/responses/matchers.py 2026-02-19 15:36:42.000000000
+0100
+++ new/responses-0.26.1/responses/matchers.py 2026-05-21 21:54:07.000000000
+0200
@@ -21,6 +21,13 @@
def _filter_dict_recursively(
dict1: Mapping[Any, Any], dict2: Mapping[Any, Any]
) -> Mapping[Any, Any]:
+ """
+ Make a new dictionary using only keys that exist in both
+ dictionary arguments. It will also work with deeply nested keys.
+ :param dict1: dictionary to filter
+ :param dict2: dictionary to filter
+ :return: new dictionary based on `dict1` and `dict2`
+ """
filtered_dict = {}
for k, val in dict1.items():
if k in dict2:
@@ -47,29 +54,53 @@
def urlencoded_params_matcher(
- params: Optional[Mapping[str, str]], *, allow_blank: bool = False
+ params: Optional[Mapping[str, str]],
+ *,
+ allow_blank: bool = False,
+ strict_match: bool = True,
) -> Callable[..., Any]:
"""
Matches URL encoded data
:param params: (dict) data provided to 'data' arg of request
+ :param allow_blank If true, blank values are accounted as empty strings
+ :param strict_match If true, all keys must match;
+ otherwise, partial matches allowed
:return: (func) matcher
"""
def match(request: PreparedRequest) -> Tuple[bool, str]:
reason = ""
request_body = request.body
- qsl_body = (
+ qsl_body: Mapping[Any, Any] = (
dict(parse_qsl(request_body, keep_blank_values=allow_blank)) #
type: ignore[type-var]
if request_body
else {}
)
- params_dict = params or {}
- valid = params is None if request_body is None else params_dict ==
qsl_body
+ request_params = qsl_body
+ match_params = params or {}
+
+ if not strict_match:
+ request_params = _filter_dict_recursively(qsl_body, match_params)
+
+ valid = (
+ params is None if request_body is None else match_params ==
request_params
+ )
+
+ # Prevents non-strict match of empty params with non-empty
+ # request body (due to dictionary filtering)
+ if not params and request_body:
+ valid = False
+
if not valid:
reason = (
- f"request.body doesn't match: {qsl_body} doesn't match
{params_dict}"
+ f"request.body doesn't match: {qsl_body} doesn't match
{match_params}"
)
+ if strict_match:
+ reason += (
+ "\nNote: You're using strict parameter check. "
+ "To try a partial match, use strict_match=False"
+ )
return valid, reason
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/responses-0.26.0/responses/tests/test_matchers.py
new/responses-0.26.1/responses/tests/test_matchers.py
--- old/responses-0.26.0/responses/tests/test_matchers.py 2026-02-19
15:36:42.000000000 +0100
+++ new/responses-0.26.1/responses/tests/test_matchers.py 2026-05-21
21:54:07.000000000 +0200
@@ -243,6 +243,117 @@
assert_reset()
+def test_urlencoded_params_matcher_strict():
+ """Test for partial urlencoded parameter matching"""
+
+ @responses.activate
+ def run():
+ responses.add(
+ method=responses.POST,
+ url="http://example.com/",
+ body="body1",
+ match=[
+ matchers.urlencoded_params_matcher(
+ {"key1": "value1"}, strict_match=False
+ )
+ ],
+ )
+ responses.add(
+ method=responses.POST,
+ url="http://example.com/",
+ body="body2",
+ match=[
+ matchers.urlencoded_params_matcher(
+ {"key2": "value2"}, strict_match=False
+ )
+ ],
+ )
+ # Fail for insufficient params and strict matching:
+ responses.add(
+ method=responses.POST,
+ url="http://example.com/",
+ body="body3",
+ match=[
+ matchers.urlencoded_params_matcher(
+ {"key3": "value3"}, strict_match=True # Note: Strict match
+ )
+ ],
+ )
+ # Fail for non-strict matching of empty params with non-empty body:
+ responses.add(
+ method=responses.POST,
+ url="http://example.com/",
+ body="body4",
+ match=[matchers.urlencoded_params_matcher({}, strict_match=False)],
+ )
+ # Test for successful strict matching:
+ responses.add(
+ method=responses.POST,
+ url="http://example.com/",
+ body="body5",
+ match=[
+ matchers.urlencoded_params_matcher(
+ {"key5": "value5", "type": "urlencoded"}, strict_match=True
+ )
+ ],
+ )
+
+ resp1 = requests.request(
+ "POST",
+ "http://example.com/",
+ headers={"Content-Type": "x-www-form-urlencoded"},
+ data={"key1": "value1", "type": "urlencoded"},
+ )
+ assert_response(resp1, "body1")
+
+ resp2 = requests.request(
+ "POST",
+ "http://example.com/",
+ headers={"Content-Type": "x-www-form-urlencoded"},
+ data={"key2": "value2", "type": "urlencoded"},
+ )
+ assert_response(resp2, "body2")
+
+ # The third request should NOT match, as it's strict
+ with pytest.raises(ConnectionError) as excinfo:
+ requests.request(
+ "POST",
+ "http://example.com/",
+ headers={"Content-Type": "x-www-form-urlencoded"},
+ data={"key3": "value3", "type": "urlencoded"},
+ )
+ msg = str(excinfo.value)
+ assert (
+ "request.body doesn't match: {'key3': 'value3', 'type':
'urlencoded'} "
+ "doesn't match {'key3': 'value3'}" in msg
+ )
+
+ # The fourth request should NOT match with empty params
+ with pytest.raises(ConnectionError) as excinfo:
+ requests.request(
+ "POST",
+ "http://example.com/",
+ headers={"Content-Type": "x-www-form-urlencoded"},
+ data={"key4": "value4", "type": "urlencoded"},
+ )
+ msg = str(excinfo.value)
+ assert (
+ "request.body doesn't match: {'key4': 'value4', 'type':
'urlencoded'} "
+ "doesn't match {}" in msg
+ )
+
+ resp5 = requests.request(
+ "POST",
+ "http://example.com/",
+ headers={"Content-Type": "x-www-form-urlencoded"},
+ data={"key5": "value5", "type": "urlencoded"},
+ )
+ assert_response(resp5, "body5")
+
+ run()
+ assert_reset()
+
+
def test_query_params_numbers():
@responses.activate
def run():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/responses-0.26.0/responses/tests/test_recorder.py
new/responses-0.26.1/responses/tests/test_recorder.py
--- old/responses-0.26.0/responses/tests/test_recorder.py 2026-02-19
15:36:42.000000000 +0100
+++ new/responses-0.26.1/responses/tests/test_recorder.py 2026-05-21
21:54:07.000000000 +0200
@@ -190,6 +190,12 @@
if self.out_file.exists():
self.out_file.unlink()
+ # Clean up any extension variants created by individual tests
+ for suffix in (".yaml", ".toml"):
+ p = Path(str(self.out_file) + suffix)
+ if p.exists():
+ p.unlink()
+
assert not self.out_file.exists()
@pytest.mark.parametrize("parser", (yaml, tomli_w))
@@ -204,6 +210,8 @@
@responses.activate
def run():
responses.patch("http://httpbin.org")
+
+ original_parser = responses.mock._parse_response_file
if parser == tomli_w:
def _parse_resp_f(file_path):
@@ -213,28 +221,104 @@
responses.mock._parse_response_file = _parse_resp_f # type:
ignore[method-assign]
- responses._add_from_file(file_path=self.out_file)
- responses.post("http://httpbin.org/form")
+ try:
+ responses._add_from_file(file_path=self.out_file)
+ responses.post("http://httpbin.org/form")
+
+ assert responses.registered()[0].url == "http://httpbin.org/"
+ assert responses.registered()[1].url ==
"http://example.com:8080/404"
+ assert (
+ responses.registered()[2].url
+ == "http://example.com:8080/status/wrong"
+ )
+ assert responses.registered()[3].url ==
"http://example.com:8080/500"
+ assert responses.registered()[4].url ==
"http://example.com:8080/202"
+ assert responses.registered()[5].url ==
"http://httpbin.org/form"
+
+ assert responses.registered()[0].method == "PATCH"
+ assert responses.registered()[2].method == "GET"
+ assert responses.registered()[4].method == "PUT"
+ assert responses.registered()[5].method == "POST"
+
+ assert responses.registered()[2].status == 400
+ assert responses.registered()[3].status == 500
+
+ assert responses.registered()[3].body == "500 Internal Server
Error"
+
+ assert responses.registered()[3].content_type == "text/plain"
+ finally:
+ responses.mock._parse_response_file = original_parser # type:
ignore[method-assign]
+
+ run()
- assert responses.registered()[0].url == "http://httpbin.org/"
- assert responses.registered()[1].url ==
"http://example.com:8080/404"
- assert (
- responses.registered()[2].url ==
"http://example.com:8080/status/wrong"
- )
- assert responses.registered()[3].url ==
"http://example.com:8080/500"
- assert responses.registered()[4].url ==
"http://example.com:8080/202"
- assert responses.registered()[5].url == "http://httpbin.org/form"
-
- assert responses.registered()[0].method == "PATCH"
- assert responses.registered()[2].method == "GET"
- assert responses.registered()[4].method == "PUT"
- assert responses.registered()[5].method == "POST"
+ def test_add_from_file_content_type_in_headers(self):
+ """Fixture files may contain Content-Type in both headers and
content_type.
- assert responses.registered()[2].status == 400
- assert responses.registered()[3].status == 500
+ The recorder captures ``Content-Type`` inside the ``headers`` dict
*and*
+ as the dedicated ``content_type`` field. Passing both to ``add()``
+ raises a ``RuntimeError`` because ``content_type`` and a
``Content-Type``
+ header conflict. ``_add_from_file`` should strip the duplicate header
+ entry so that the dedicated ``content_type`` kwarg wins.
+
+ Using mismatched values (``text/html`` in headers vs
``application/json``
+ in ``content_type``) ensures the assertion is non-trivial and confirms
+ that ``content_type`` takes precedence over the header value.
+
+ The fixture is saved as a ``.yaml`` file so that ``_add_from_file``
+ selects the YAML loader by extension.
+ """
+ data = {
+ "responses": [
+ {
+ "response": {
+ "method": "GET",
+ "url": "http://example.com/api",
+ "body": '{"status": "ok"}',
+ "status": 200,
+ # headers has a *different* Content-Type than
content_type
+ # to verify that content_type wins (not just that both
happen
+ # to be the same value).
+ "headers": {"Content-Type": "text/html"},
+ "content_type": "application/json",
+ "auto_calculate_content_length": False,
+ }
+ },
+ {
+ "response": {
+ "method": "POST",
+ "url": "http://example.com/submit",
+ "body": "created",
+ "status": 201,
+ "headers": {
+ "Content-Type": "text/html",
+ "X-Request-Id": "abc123",
+ },
+ "content_type": "text/plain",
+ "auto_calculate_content_length": False,
+ }
+ },
+ ]
+ }
+
+ yaml_file = Path(str(self.out_file) + ".yaml")
+ with open(yaml_file, "w") as f:
+ yaml.dump(data, f)
+
+ @responses.activate
+ def run():
+ responses._add_from_file(file_path=yaml_file)
- assert responses.registered()[3].body == "500 Internal Server
Error"
+ # Verify responses were registered without RuntimeError
+ assert len(responses.registered()) == 2
- assert responses.registered()[3].content_type == "text/plain"
+ # content_type must win over the conflicting Content-Type header
+ assert responses.registered()[0].url == "http://example.com/api"
+ assert responses.registered()[0].content_type == "application/json"
+
+ assert responses.registered()[1].url == "http://example.com/submit"
+ assert responses.registered()[1].content_type == "text/plain"
+ # Non-content-type headers should be preserved
+ resp = requests.post("http://example.com/submit")
+ assert resp.headers["X-Request-Id"] == "abc123"
run()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/responses-0.26.0/responses.egg-info/PKG-INFO
new/responses-0.26.1/responses.egg-info/PKG-INFO
--- old/responses-0.26.0/responses.egg-info/PKG-INFO 2026-02-19
15:36:52.000000000 +0100
+++ new/responses-0.26.1/responses.egg-info/PKG-INFO 2026-05-21
21:54:16.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: responses
-Version: 0.26.0
+Version: 0.26.1
Summary: A utility library for mocking out the `requests` Python library.
Home-page: https://github.com/getsentry/responses
Author: David Cramer
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/responses-0.26.0/setup.py
new/responses-0.26.1/setup.py
--- old/responses-0.26.0/setup.py 2026-02-19 15:36:42.000000000 +0100
+++ new/responses-0.26.1/setup.py 2026-05-21 21:54:07.000000000 +0200
@@ -36,7 +36,7 @@
setup(
name="responses",
- version="0.26.0",
+ version="0.26.1",
author="David Cramer",
description="A utility library for mocking out the `requests` Python
library.",
url="https://github.com/getsentry/responses",