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";,

Reply via email to