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-10-27 18:18:21
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-responses (Old)
 and      /work/SRC/openSUSE:Factory/.python-responses.new.2252680 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-responses"

Tue Oct 27 18:18:21 2026 rev:39 rq:1392582 version:0.26.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-responses/python-responses.changes        
2026-07-07 21:00:08.455982845 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-responses.new.2252680/python-responses.changes
   2026-10-27 18:18:28.995398543 +0100
@@ -1,0 +2,20 @@
+Sun Oct 25 21:41:18 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.26.3:
+  * Added a ``keep_headers`` argument to ``_recorder.record`` and
+   ``Recorder.dump_to_file`` to preserve named headers (for
+    example ``Date``) that are otherwise stripped as verbose
+    defaults, so a signed response can be recorded and later
+    verified. Matching is case-insensitive.
+  * Fixed the element type exposed by `CallList` so static type
+    checkers infer `Call` values when iterating, indexing, or
+    filtering recorded calls.
+  * Fixed `query_string_matcher` (and the query matching auto-
+    applied to a registered URL's own query string) discarding
+    blank-valued query params (``b=``), which caused requests with
+    an extra or missing blank param to match incorrectly. See #804
+  * Fixed `fragment_identifier_matcher` treating opaque fragments
+    (those without ``=``, e.g. ``/users/5``) as always equal, so a
+    required fragment matched a different one or none at all. See #806
+
+-------------------------------------------------------------------

Old:
----
  responses-0.26.2.tar.gz

New:
----
  responses-0.26.3.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-responses.spec ++++++
--- /var/tmp/diff_new_pack.O1yUeN/_old  2026-10-27 18:18:32.790556826 +0100
+++ /var/tmp/diff_new_pack.O1yUeN/_new  2026-10-27 18:18:32.792556909 +0100
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-responses
-Version:        0.26.2
+Version:        0.26.3
 Release:        0
 Summary:        A utility library for mocking out the `requests` Python library
 License:        Apache-2.0

++++++ responses-0.26.2.tar.gz -> responses-0.26.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/responses-0.26.2/CHANGES new/responses-0.26.3/CHANGES
--- old/responses-0.26.2/CHANGES        2026-07-03 18:42:35.000000000 +0200
+++ new/responses-0.26.3/CHANGES        2026-08-26 21:15:00.000000000 +0200
@@ -1,3 +1,20 @@
+0.26.3
+------
+
+* Added a ``keep_headers`` argument to ``_recorder.record`` and
+  ``Recorder.dump_to_file`` to preserve named headers (for example ``Date``)
+  that are otherwise stripped as verbose defaults, so a signed response can be
+  recorded and later verified. Matching is case-insensitive. See #763
+* Fixed the element type exposed by `CallList` so static type checkers infer
+  `Call` values when iterating, indexing, or filtering recorded calls. See #722
+* Fixed `query_string_matcher` (and the query matching auto-applied to a
+  registered URL's own query string) discarding blank-valued query params
+  (``b=``), which caused requests with an extra or missing blank param to
+  match incorrectly. See #804
+* Fixed `fragment_identifier_matcher` treating opaque fragments (those without
+  ``=``, e.g. ``/users/5``) as always equal, so a required fragment matched a
+  different one or none at all. See #806
+
 0.26.2
 ------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/responses-0.26.2/PKG-INFO 
new/responses-0.26.3/PKG-INFO
--- old/responses-0.26.2/PKG-INFO       2026-07-03 18:42:43.970410800 +0200
+++ new/responses-0.26.3/PKG-INFO       2026-08-26 21:15:08.727542200 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: responses
-Version: 0.26.2
+Version: 0.26.3
 Summary: A utility library for mocking out the `requests` Python library.
 Home-page: https://github.com/getsentry/responses
 Author: David Cramer
@@ -1455,6 +1455,17 @@
         status: 202
         url: https://httpstat.us/202
 
+Common headers such as ``Content-Type``, ``Date`` and ``Server`` are stripped
+from the recording to keep the file terse. If you need to keep one of them, for
+example a ``Date`` value that is part of a signed response you later verify,
+pass its name in ``keep_headers`` (matched case-insensitively):
+
+.. code-block:: python
+
+    @_recorder.record(file_path="out.yaml", keep_headers=["Date"])
+    def test_recorder():
+        ...
+
 If you are in the REPL, you can also activate the recorder for all following 
responses:
 
 .. code-block:: python
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/responses-0.26.2/README.rst 
new/responses-0.26.3/README.rst
--- old/responses-0.26.2/README.rst     2026-07-03 18:42:35.000000000 +0200
+++ new/responses-0.26.3/README.rst     2026-08-26 21:15:00.000000000 +0200
@@ -1401,6 +1401,17 @@
         status: 202
         url: https://httpstat.us/202
 
+Common headers such as ``Content-Type``, ``Date`` and ``Server`` are stripped
+from the recording to keep the file terse. If you need to keep one of them, for
+example a ``Date`` value that is part of a signed response you later verify,
+pass its name in ``keep_headers`` (matched case-insensitively):
+
+.. code-block:: python
+
+    @_recorder.record(file_path="out.yaml", keep_headers=["Date"])
+    def test_recorder():
+        ...
+
 If you are in the REPL, you can also activate the recorder for all following 
responses:
 
 .. code-block:: python
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/responses-0.26.2/responses/__init__.py 
new/responses-0.26.3/responses/__init__.py
--- old/responses-0.26.2/responses/__init__.py  2026-07-03 18:42:35.000000000 
+0200
+++ new/responses-0.26.3/responses/__init__.py  2026-08-26 21:15:00.000000000 
+0200
@@ -234,7 +234,7 @@
     return wrapper
 
 
-class CallList(Sequence[Any], Sized):
+class CallList(Sequence[Call], Sized):
     def __init__(self) -> None:
         self._calls: List[Call] = []
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/responses-0.26.2/responses/_recorder.py 
new/responses-0.26.3/responses/_recorder.py
--- old/responses-0.26.2/responses/_recorder.py 2026-07-03 18:42:35.000000000 
+0200
+++ new/responses-0.26.3/responses/_recorder.py 2026-08-26 21:15:00.000000000 
+0200
@@ -8,6 +8,7 @@
     from typing import BinaryIO
     from typing import Callable
     from typing import Dict
+    from typing import Iterable
     from typing import List
     from typing import Optional
     from typing import Type
@@ -38,11 +39,22 @@
     return d
 
 
-def _remove_default_headers(data: "Any") -> "Any":
+def _remove_default_headers(
+    data: "Any", keep_headers: "Optional[Iterable[str]]" = None
+) -> "Any":
     """
     It would be too verbose to store these headers in the file generated by the
     record functionality.
+
+    Header names passed in ``keep_headers`` are excluded from the default
+    stripping, so a caller can preserve an otherwise-removed header (for
+    example a ``Date`` value that is part of a signed response). Matching is
+    case-insensitive, consistent with the rest of this function.
     """
+    # A bare string is iterable over its characters, which would silently keep
+    # nothing useful; treat it as a single header name.
+    if isinstance(keep_headers, str):
+        keep_headers = [keep_headers]
     if isinstance(data, dict):
         keys_to_remove = [
             "Content-Length",
@@ -54,7 +66,8 @@
         ]
         # HTTP header names are case-insensitive, and HTTP/2 servers send them
         # lowercase, so match without regard to case.
-        keys_to_remove_lower = {key.lower() for key in keys_to_remove}
+        keep_lower = {key.lower() for key in keep_headers or ()}
+        keys_to_remove_lower = {key.lower() for key in keys_to_remove} - 
keep_lower
         for i, response in enumerate(data["responses"]):
             headers = data["responses"][i]["response"]["headers"]
             for key in list(headers):
@@ -69,6 +82,7 @@
     registered: "List[BaseResponse]",
     destination: "Union[BinaryIO, TextIOWrapper]",
     dumper: "Callable[[Union[Dict[Any, Any], List[Any]], Union[BinaryIO, 
TextIOWrapper]], Any]",
+    keep_headers: "Optional[Iterable[str]]" = None,
 ) -> None:
     data: Dict[str, Any] = {"responses": []}
     for rsp in registered:
@@ -93,7 +107,10 @@
                 "Probably you use custom Response object that is missing 
required attributes"
             ) from exc
 
-    dumper(_remove_default_headers(_remove_nones(data)), destination)
+    dumper(
+        _remove_default_headers(_remove_nones(data), 
keep_headers=keep_headers),
+        destination,
+    )
 
 
 class Recorder(RequestsMock):
@@ -109,7 +126,10 @@
         self._registry = OrderedRegistry()
 
     def record(
-        self, *, file_path: "Union[str, bytes, os.PathLike[Any]]" = 
"response.yaml"
+        self,
+        *,
+        file_path: "Union[str, bytes, os.PathLike[Any]]" = "response.yaml",
+        keep_headers: "Optional[Iterable[str]]" = None,
     ) -> "Union[Callable[[_F], _F], _F]":
         def deco_record(function: "_F") -> "Callable[..., Any]":
             @wraps(function)
@@ -117,7 +137,9 @@
                 with self:
                     ret = function(*args, **kwargs)
                     self.dump_to_file(
-                        file_path=file_path, 
registered=self.get_registry().registered
+                        file_path=file_path,
+                        registered=self.get_registry().registered,
+                        keep_headers=keep_headers,
                     )
 
                     return ret
@@ -131,12 +153,19 @@
         file_path: "Union[str, bytes, os.PathLike[Any]]",
         *,
         registered: "Optional[List[BaseResponse]]" = None,
+        keep_headers: "Optional[Iterable[str]]" = None,
     ) -> None:
-        """Dump the recorded responses to a file."""
+        """Dump the recorded responses to a file.
+
+        Header names listed in ``keep_headers`` are preserved in the output
+        even though they are normally stripped as verbose defaults (for
+        example ``Date`` when it is part of a signed response). Matching is
+        case-insensitive.
+        """
         if registered is None:
             registered = self.get_registry().registered
         with open(file_path, "w") as file:
-            _dump(registered, file, yaml.dump)
+            _dump(registered, file, yaml.dump, keep_headers=keep_headers)
 
     def _on_request(
         self,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/responses-0.26.2/responses/matchers.py 
new/responses-0.26.3/responses/matchers.py
--- old/responses-0.26.2/responses/matchers.py  2026-07-03 18:42:35.000000000 
+0200
+++ new/responses-0.26.3/responses/matchers.py  2026-08-26 21:15:00.000000000 
+0200
@@ -176,9 +176,16 @@
         reason = ""
         url_fragment = urlparse(request.url).fragment
         if identifier:
-            url_fragment_qsl = sorted(parse_qsl(url_fragment))  # type: 
ignore[type-var]
-            identifier_qsl = sorted(parse_qsl(identifier))
-            valid = identifier_qsl == url_fragment_qsl
+            if "=" in identifier:
+                # Query-string-style fragment: compare order-insensitively.
+                url_fragment_qsl = sorted(parse_qsl(url_fragment))  # type: 
ignore[type-var]
+                identifier_qsl = sorted(parse_qsl(identifier))
+                valid = identifier_qsl == url_fragment_qsl
+            else:
+                # Opaque fragment (e.g. "/users/5"): parse_qsl() yields no
+                # pairs, so any two opaque fragments would compare equal.
+                # Compare them verbatim instead.
+                valid = identifier == url_fragment
         else:
             valid = not url_fragment
 
@@ -259,8 +266,12 @@
         data = parse_url(request.url or "")
         request_query = data.query
 
-        request_qsl = sorted(parse_qsl(request_query)) if request_query else {}
-        matcher_qsl = sorted(parse_qsl(query)) if query else {}
+        request_qsl = (
+            sorted(parse_qsl(request_query, keep_blank_values=True))
+            if request_query
+            else {}
+        )
+        matcher_qsl = sorted(parse_qsl(query, keep_blank_values=True)) if 
query else {}
 
         valid = not query if request_query is None else request_qsl == 
matcher_qsl
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/responses-0.26.2/responses/tests/test_matchers.py 
new/responses-0.26.3/responses/tests/test_matchers.py
--- old/responses-0.26.2/responses/tests/test_matchers.py       2026-07-03 
18:42:35.000000000 +0200
+++ new/responses-0.26.3/responses/tests/test_matchers.py       2026-08-26 
21:15:00.000000000 +0200
@@ -69,6 +69,40 @@
     assert_reset()
 
 
+def test_query_string_matcher_blank_values():
+    """A blank-valued query param (``foo=``) is significant: it must be present
+    to match, and an extra blank param must not be ignored."""
+
+    def run():
+        with responses.RequestsMock(assert_all_requests_are_fired=False) as 
rsps:
+            rsps.add(
+                responses.GET,
+                "http://example.com";,
+                body=b"test",
+                match=[matchers.query_string_matcher("test=1&foo=")],
+            )
+            # Exact match, including the blank param, succeeds.
+            resp = requests.get("http://example.com?test=1&foo=";)
+            assert_response(resp, "test")
+            # Omitting the required blank param must not match.
+            with pytest.raises(ConnectionError):
+                requests.get("http://example.com?test=1";)
+
+        with responses.RequestsMock(assert_all_requests_are_fired=False) as 
rsps:
+            rsps.add(
+                responses.GET,
+                "http://example.com";,
+                body=b"test",
+                match=[matchers.query_string_matcher("test=1")],
+            )
+            # An extra blank param must not be silently ignored.
+            with pytest.raises(ConnectionError):
+                requests.get("http://example.com?test=1&foo=";)
+
+    run()
+    assert_reset()
+
+
 def test_request_matches_post_params():
     @responses.activate
     def run(deprecated):
@@ -937,6 +971,37 @@
         assert_response(resp, "test")
 
     run()
+    assert_reset()
+
+
+def test_fragment_identifier_matcher_opaque():
+    """Opaque (non key=value) fragments must compare verbatim.
+
+    Previously ``parse_qsl`` reduced any opaque fragment to ``[]``, so a
+    required ``#/users/5`` matched any other opaque fragment (or none).
+    """
+
+    @responses.activate
+    def run():
+        responses.add(
+            responses.GET,
+            "http://example.com/";,
+            match=[matchers.fragment_identifier_matcher("/users/5")],
+            body=b"test",
+        )
+
+        # A different opaque fragment, or no fragment, must not match.
+        with pytest.raises(ConnectionError) as excinfo:
+            requests.get("http://example.com/#/users/6";)
+        assert (
+            "URL fragment identifier is different: /users/5 doesn't match 
/users/6"
+        ) in str(excinfo.value)
+
+        # The exact opaque fragment matches.
+        resp = requests.get("http://example.com/#/users/5";)
+        assert_response(resp, "test")
+
+    run()
     assert_reset()
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/responses-0.26.2/responses/tests/test_recorder.py 
new/responses-0.26.3/responses/tests/test_recorder.py
--- old/responses-0.26.2/responses/tests/test_recorder.py       2026-07-03 
18:42:35.000000000 +0200
+++ new/responses-0.26.3/responses/tests/test_recorder.py       2026-08-26 
21:15:00.000000000 +0200
@@ -106,6 +106,63 @@
     assert "headers" not in result["responses"][1]["response"]
 
 
+def test_remove_default_headers_keeps_requested_headers():
+    """Headers named in ``keep_headers`` survive the default-header stripping.
+
+    Some consumers need a normally-stripped header in the recorded file, for
+    example a ``Date`` value that is part of a signed response they later
+    verify. Matching is case-insensitive so a lowercase HTTP/2 name is kept
+    too, while the other default headers are still removed.
+    """
+    data = {
+        "responses": [
+            {
+                "response": {
+                    "headers": {
+                        "date": "Mon, 01 Jan 2024 00:00:00 GMT",
+                        "Server": "nginx",
+                        "Content-Length": "12",
+                        "x-custom": "keep-me",
+                    }
+                }
+            },
+        ]
+    }
+
+    result = _remove_default_headers(data, keep_headers=["Date"])
+
+    assert result["responses"][0]["response"]["headers"] == {
+        "date": "Mon, 01 Jan 2024 00:00:00 GMT",
+        "x-custom": "keep-me",
+    }
+
+
+def test_remove_default_headers_accepts_a_single_string():
+    """A bare string is treated as one header name, not a set of characters."""
+    data = {
+        "responses": [
+            {"response": {"headers": {"Date": "d", "Server": "nginx"}}},
+        ]
+    }
+
+    result = _remove_default_headers(data, keep_headers="Date")
+
+    assert result["responses"][0]["response"]["headers"] == {"Date": "d"}
+
+
+def test_remove_default_headers_keep_non_default_is_noop():
+    """Naming a non-default header does not change which headers are 
stripped."""
+    data = {
+        "responses": [
+            {"response": {"headers": {"Date": "d", "x-custom": "keep-me"}}},
+        ]
+    }
+
+    result = _remove_default_headers(data, keep_headers=["x-custom"])
+
+    assert result["responses"][0]["response"]["headers"] == {"x-custom": 
"keep-me"}
+
+
 class TestRecord:
     def setup_method(self):
         self.out_file = Path("response_record")
@@ -133,12 +190,57 @@
             data = yaml.safe_load(file)
         assert data == get_data(httpserver.host, httpserver.port)
 
+    def test_recorder_keep_headers_preserves_date(self, httpserver):
+        """``keep_headers`` keeps a normally-stripped header in the record.
+
+        Reproduces the reported need to record the ``Date`` header so a signed
+        response can be verified against the recording (issue #763). Without
+        ``keep_headers`` the same header is stripped as a verbose default.
+        """
+        httpserver.expect_request("/signed").respond_with_data(
+            "ok",
+            status=200,
+            content_type="text/plain",
+            headers={"Date": "Mon, 01 Jan 2024 00:00:00 GMT"},
+        )
+        url = httpserver.url_for("/signed")
+
+        @_recorder.record(file_path=self.out_file, keep_headers=["Date"])
+        def run_kept():
+            requests.get(url)
+
+        run_kept()
+        with open(self.out_file) as file:
+            kept = yaml.safe_load(file)
+        kept_headers = kept["responses"][0]["response"]["headers"]
+        # The server owns the exact Date value, so assert the header survives
+        # rather than pinning its contents.
+        assert "Date" in kept_headers
+        assert "2024" in kept_headers["Date"]
+
+        self.out_file.unlink()
+
+        @_recorder.record(file_path=self.out_file)
+        def run_default():
+            requests.get(url)
+
+        run_default()
+        with open(self.out_file) as file:
+            default = yaml.safe_load(file)
+        default_headers = default["responses"][0]["response"].get("headers", 
{})
+        assert "Date" not in default_headers
+
     def test_recorder_toml(self, httpserver):
         custom_recorder = _recorder.Recorder()
 
-        def dump_to_file(file_path, registered):
+        def dump_to_file(file_path, registered, *, keep_headers=None):
             with open(file_path, "wb") as file:
-                _dump(registered, file, tomli_w.dump)  # type: ignore[arg-type]
+                _dump(
+                    registered,
+                    file,
+                    tomli_w.dump,  # type: ignore[arg-type]
+                    keep_headers=keep_headers,
+                )
 
         custom_recorder.dump_to_file = dump_to_file  # type: ignore[assignment]
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/responses-0.26.2/responses.egg-info/PKG-INFO 
new/responses-0.26.3/responses.egg-info/PKG-INFO
--- old/responses-0.26.2/responses.egg-info/PKG-INFO    2026-07-03 
18:42:43.000000000 +0200
+++ new/responses-0.26.3/responses.egg-info/PKG-INFO    2026-08-26 
21:15:08.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: responses
-Version: 0.26.2
+Version: 0.26.3
 Summary: A utility library for mocking out the `requests` Python library.
 Home-page: https://github.com/getsentry/responses
 Author: David Cramer
@@ -1455,6 +1455,17 @@
         status: 202
         url: https://httpstat.us/202
 
+Common headers such as ``Content-Type``, ``Date`` and ``Server`` are stripped
+from the recording to keep the file terse. If you need to keep one of them, for
+example a ``Date`` value that is part of a signed response you later verify,
+pass its name in ``keep_headers`` (matched case-insensitively):
+
+.. code-block:: python
+
+    @_recorder.record(file_path="out.yaml", keep_headers=["Date"])
+    def test_recorder():
+        ...
+
 If you are in the REPL, you can also activate the recorder for all following 
responses:
 
 .. code-block:: python
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/responses-0.26.2/setup.py 
new/responses-0.26.3/setup.py
--- old/responses-0.26.2/setup.py       2026-07-03 18:42:35.000000000 +0200
+++ new/responses-0.26.3/setup.py       2026-08-26 21:15:00.000000000 +0200
@@ -36,7 +36,7 @@
 
 setup(
     name="responses",
-    version="0.26.2",
+    version="0.26.3",
     author="David Cramer",
     description="A utility library for mocking out the `requests` Python 
library.",
     url="https://github.com/getsentry/responses";,

Reply via email to