Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-hishel for openSUSE:Factory 
checked in at 2026-06-28 21:07:17
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-hishel (Old)
 and      /work/SRC/openSUSE:Factory/.python-hishel.new.11887 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-hishel"

Sun Jun 28 21:07:17 2026 rev:12 rq:1362047 version:1.3.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-hishel/python-hishel.changes      
2026-05-04 12:58:22.929493368 +0200
+++ /work/SRC/openSUSE:Factory/.python-hishel.new.11887/python-hishel.changes   
2026-06-28 21:07:57.396950012 +0200
@@ -1,0 +2,8 @@
+Sat Jun 27 20:59:34 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 1.3.0:
+  * remove egg folder from source by @karpetrosyan
+  * add readme in pyproject.toml by @karpetrosyan
+  * use weak ETag comparison when freshening stored responses
+
+-------------------------------------------------------------------

Old:
----
  hishel-1.2.1.tar.gz

New:
----
  hishel-1.3.0.tar.gz

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

Other differences:
------------------
++++++ python-hishel.spec ++++++
--- /var/tmp/diff_new_pack.ATaVOT/_old  2026-06-28 21:07:58.380983169 +0200
+++ /var/tmp/diff_new_pack.ATaVOT/_new  2026-06-28 21:07:58.380983169 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-hishel
-Version:        1.2.1
+Version:        1.3.0
 Release:        0
 Summary:        Persistent cache implementation for popular HTTP clients
 License:        BSD-3-Clause

++++++ hishel-1.2.1.tar.gz -> hishel-1.3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hishel-1.2.1/CHANGELOG.md 
new/hishel-1.3.0/CHANGELOG.md
--- old/hishel-1.2.1/CHANGELOG.md       2026-04-27 18:01:23.000000000 +0200
+++ new/hishel-1.3.0/CHANGELOG.md       2026-06-11 10:39:31.000000000 +0200
@@ -1,3 +1,17 @@
+## What's Changed in 1.3.0
+### ⚙️ Miscellaneous Tasks
+
+* remove egg folder from source by @karpetrosyan
+* add readme in pyproject.toml by @karpetrosyan
+### 🐛 Bug Fixes
+
+* use weak ETag comparison when freshening stored responses by @karpetrosyan
+
+### Contributors
+* @karpetrosyan
+
+**Full Changelog**: 
https://github.com/karpetrosyan/hishel/compare/1.2.1...1.3.0
+
 ## What's Changed in 1.2.1
 ### ⚙️ Miscellaneous Tasks
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hishel-1.2.1/docs/index.md 
new/hishel-1.3.0/docs/index.md
--- old/hishel-1.2.1/docs/index.md      2026-04-27 18:01:23.000000000 +0200
+++ new/hishel-1.3.0/docs/index.md      2026-06-11 10:39:31.000000000 +0200
@@ -4,7 +4,7 @@
 hero:
   name: "Hishel"
   text: An elegant HTTP caching library for Python
-  tagline: Clean HTTP semantics, bring your own transport
+  tagline: "HTTP caching for Python — RFC-compliant, transport-agnostic, 
streaming-friendly"
   actions:
     - theme: brand
       text: Get Started
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hishel-1.2.1/pyproject.toml 
new/hishel-1.3.0/pyproject.toml
--- old/hishel-1.2.1/pyproject.toml     2026-04-27 18:01:23.000000000 +0200
+++ new/hishel-1.3.0/pyproject.toml     2026-06-11 10:39:31.000000000 +0200
@@ -4,8 +4,9 @@
 
 [project]
 name = "hishel"
-version = "1.2.1"
+version = "1.3.0"
 description = " Elegant HTTP Caching for Python"
+readme = "README.md"
 requires-python = ">=3.10"
 authors = [{ name = "Kar Petrosyan", email = "[email protected]" }]
 classifiers = [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hishel-1.2.1/src/hishel/_core/_spec.py 
new/hishel-1.3.0/src/hishel/_core/_spec.py
--- old/hishel-1.2.1/src/hishel/_core/_spec.py  2026-04-27 18:01:23.000000000 
+0200
+++ new/hishel-1.3.0/src/hishel/_core/_spec.py  2026-06-11 10:39:31.000000000 
+0200
@@ -1839,9 +1839,26 @@
                 after_revalidation=True,
             ).next(revalidation_response)
 
+    def _opaque_tag(self, etag: str) -> str:
+        """
+        Return the opaque-tag portion of an entity-tag, dropping the weakness 
flag.
+
+        RFC 9110 Section 8.8.3:
+            entity-tag = [ weak ] opaque-tag
+            weak       = %s"W/"
+
+        Weak comparison (RFC 9110 Section 8.8.3.2): two entity-tags are 
equivalent
+        if their opaque-tags match character-by-character, regardless of 
either or
+        both being tagged as "weak". This is the comparison If-None-Match uses.
+        """
+        etag = etag.strip()
+        if etag.startswith(("W/", "w/")):
+            return etag[2:]
+        return etag
+
     def freshening_stored_responses(
         self, revalidation_response: Response
-    ) -> "NeedToBeUpdated" | "InvalidateEntries" | "CacheMiss":
+    ) -> "NeedToBeUpdated | InvalidateEntries | CacheMiss":
         """
         Freshens cached responses after receiving a 304 Not Modified response.
 
@@ -1852,82 +1869,25 @@
         3. Invalidates any cached responses that don't match
 
         Matching is done using validators in this priority order:
-        1. Strong ETag (if present and not weak)
+        1. ETag (weak comparison, see note below)
         2. Last-Modified (if present)
         3. Single response assumption (if only one cached response exists)
 
-        Parameters:
-        ----------
-        revalidation_response : Response
-            The 304 Not Modified response from the server, containing updated
-            metadata (Date, Cache-Control, ETag, etc.)
-
-        Returns:
-        -------
-        Union[NeedToBeUpdated, InvalidateEntries, CacheMiss]
-            - NeedToBeUpdated: When matching responses are found and updated
-            - InvalidateEntries: Wraps NeedToBeUpdated if non-matching 
responses exist
-            - CacheMiss: When no matching responses are found
-
         RFC 9111 Compliance:
         -------------------
-        From RFC 9111 Section 4.3.4:
-        "When a cache receives a 304 (Not Modified) response, it needs to 
identify
-        stored responses that are suitable for updating with the new 
information
-        provided, and then do so.
-
-        The initial set of stored responses to update are those that could have
-        been chosen for that request...
-
-        Then, that initial set of stored responses is further filtered by the
-        first match of:
-         - If the 304 response contains a strong entity tag: the stored 
responses
-           with the same strong entity tag.
-         - If the 304 response contains a Last-Modified value: the stored 
responses
-           with the same Last-Modified value.
-         - If there is only a single stored response: that response."
-
-        Implementation Notes:
-        --------------------
-        - Weak ETags (starting with "W/") are not used for matching
-        - Only strong ETags provide reliable validation
-        - If no validators match, all responses are invalidated
-        - Multiple responses can be freshened if they share the same validator
-
-        Examples:
-        --------
-        >>> # Matching by strong ETag
-        >>> cached_response = Response(headers=Headers({"etag": '"abc123"'}))
-        >>> revalidation_response = Response(
-        ...     status_code=304,
-        ...     headers=Headers({"etag": '"abc123"', "cache-control": 
"max-age=3600"})
-        ... )
-        >>> # Cached response will be freshened with new Cache-Control
-
-        >>> # Non-matching ETag
-        >>> cached_response = Response(headers=Headers({"etag": '"old123"'}))
-        >>> revalidation_response = Response(
-        ...     status_code=304,
-        ...     headers=Headers({"etag": '"new456"'})
-        ... )
-        >>> # Cached response will be invalidated (doesn't match)
+        RFC 9111 Section 4.3.4 filters the stored responses by the first match 
of:
+        - 304 contains a strong entity tag -> stored responses with the same
+            strong entity tag
+        - 304 contains a Last-Modified value -> stored responses with the same
+            Last-Modified value
+        - only a single stored response -> that response
         """
 
         # 
============================================================================
         # STEP 1: Identify Matching Responses Using Validators
         # 
============================================================================
-        # RFC 9111 Section 4.3.4: Freshening Stored Responses
-        # https://www.rfc-editor.org/rfc/rfc9111.html#section-4.3.4
-        #
-        # The 304 response tells us "the resource is unchanged", but we need to
-        # figure out WHICH of our cached responses match this confirmation.
-        #
-        # We use validators in priority order:
-        # Priority 1: Strong ETag (most reliable)
-        # Priority 2: Last-Modified timestamp
-        # Priority 3: Single response assumption
-
         identified_for_revalidation: list[Entry]
+        need_to_be_invalidated: list[Entry]
 
         # MATCHING STRATEGY 1: Strong ETag
         # RFC 9110 Section 8.8.3: ETag
@@ -1946,24 +1906,21 @@
         if "etag" in revalidation_response.headers and (not 
revalidation_response.headers["etag"].startswith("W/")):
             # Found a strong ETag in the 304 response
             # Partition cached responses: matching vs non-matching ETags
+            # Use opaque-tag comparison so a stored weak ETag (W/"abc") matches
+            # a strong 304 ETag ("abc") with the same opaque-tag value.
             identified_for_revalidation, need_to_be_invalidated = partition(
                 self.revalidating_entries,
-                lambda pair: pair.response.headers.get("etag") == 
revalidation_response.headers.get("etag"),  # type: ignore[no-untyped-call]
+                lambda pair: (
+                    (stored_etag := pair.response.headers.get("etag")) is not 
None
+                    and self._opaque_tag(stored_etag) == 
revalidation_response.headers["etag"]
+                ),  # type: ignore[no-untyped-call]
             )
 
         # MATCHING STRATEGY 2: Last-Modified
-        # RFC 9110 Section 8.8.2: Last-Modified
-        # https://www.rfc-editor.org/rfc/rfc9110#section-8.8.2
         #
-        # "If the 304 response contains a Last-Modified value: the stored 
responses
-        # with the same Last-Modified value."
-        #
-        # Last-Modified is a timestamp indicating when the resource was last 
changed.
-        # It's less precise than ETags (1-second granularity) but widely 
supported.
-        # If the 304 has a Last-Modified, we can match it against cached 
responses.
+        # RFC 9111 Section 4.3.4: "If the 304 response contains a Last-Modified
+        # value: the stored responses with the same Last-Modified value."
         elif revalidation_response.headers.get("last-modified"):
-            # Found Last-Modified in the 304 response
-            # Partition cached responses: matching vs non-matching timestamps
             identified_for_revalidation, need_to_be_invalidated = partition(
                 self.revalidating_entries,
                 lambda pair: (
@@ -1972,24 +1929,18 @@
             )
 
         # MATCHING STRATEGY 3: Single Response Assumption
-        # RFC 9111 Section 4.3.4:
-        #
-        # "If there is only a single stored response: that response."
         #
-        # If we only have one cached response and the server says "not 
modified",
-        # we can safely assume that single response is the one being confirmed.
-        # This handles cases where the server doesn't return validators in the 
304.
+        # RFC 9111 Section 4.3.4: "If there is only a single stored response:
+        # that response."
         else:
             if len(self.revalidating_entries) == 1:
-                # Only one cached response - it must be the matching one
                 identified_for_revalidation, need_to_be_invalidated = (
                     [self.revalidating_entries[0]],
                     [],
                 )
             else:
-                # Multiple cached responses but no validators to match them
-                # We cannot determine which (if any) are valid
-                # Conservative approach: invalidate all of them
+                # Multiple cached responses but no validators to match them.
+                # Conservative approach: invalidate all of them.
                 identified_for_revalidation, need_to_be_invalidated = (
                     [],
                     self.revalidating_entries,
@@ -1998,25 +1949,13 @@
         # 
============================================================================
         # STEP 2: Update Matching Responses or Create Cache Miss
         # 
============================================================================
-        # If we found matching responses, freshen them with new metadata.
-        # If we found no matches, treat it as a cache miss.
-
         next_state: "NeedToBeUpdated" | "CacheMiss"
 
         if identified_for_revalidation:
-            # We found responses that match the 304 confirmation
-            # Update their headers with fresh metadata from the 304 response
-            #
-            # RFC 9111 Section 3.2: Updating Stored Header Fields
-            # https://www.rfc-editor.org/rfc/rfc9111.html#section-3.2
-            #
-            # "When doing so, the cache MUST add each header field in the 
provided
-            # response to the stored response, replacing field values that are
-            # already present"
-            #
-            # The refresh_response_headers function handles this header merging
-            # while excluding certain headers that shouldn't be updated
-            # (Content-Encoding, Content-Type, Content-Range).
+            # RFC 9111 Section 3.2: add each header field in the 304 to the
+            # stored response, replacing values already present (handled by
+            # refresh_response_headers, which also excludes Content-Encoding,
+            # Content-Type, Content-Range).
             next_state = NeedToBeUpdated(
                 updating_entries=[
                     replace(
@@ -2029,10 +1968,8 @@
                 options=self.options,
             )
         else:
-            # No matching responses found
-            # This is unusual - the server said "not modified" but we can't 
figure
-            # out which cached response it's referring to.
-            # Treat this as a cache miss and let the normal flow handle it.
+            # The server said "not modified" but no stored response could be
+            # identified. Treat as a cache miss and let the normal flow handle 
it.
             next_state = CacheMiss(
                 options=self.options,
                 request=self.original_request,
@@ -2042,23 +1979,13 @@
         # 
============================================================================
         # STEP 3: Invalidate Non-Matching Responses (if any)
         # 
============================================================================
-        # If we had multiple cached responses and only some matched, we need to
-        # invalidate the non-matching ones. They're outdated or incorrect.
-        #
-        # For example:
-        # - Cached: Two responses with different ETags
-        # - 304 response: Matches only one ETag
-        # - Action: Update the matching one, invalidate the other
-
         if need_to_be_invalidated:
-            # Wrap the next state in an invalidation operation
             return InvalidateEntries(
                 options=self.options,
                 entry_ids=[entry.id for entry in need_to_be_invalidated],
                 next_state=next_state,
             )
 
-        # No invalidations needed, return the next state directly
         return next_state
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hishel-1.2.1/src/hishel.egg-info/PKG-INFO 
new/hishel-1.3.0/src/hishel.egg-info/PKG-INFO
--- old/hishel-1.2.1/src/hishel.egg-info/PKG-INFO       2026-04-27 
18:01:23.000000000 +0200
+++ new/hishel-1.3.0/src/hishel.egg-info/PKG-INFO       1970-01-01 
01:00:00.000000000 +0100
@@ -1,38 +0,0 @@
-Metadata-Version: 2.4
-Name: hishel
-Version: 1.1.10
-Summary:  Elegant HTTP Caching for Python
-Author-email: Kar Petrosyan <[email protected]>
-Project-URL: Homepage, https://hishel.com
-Project-URL: Source, https://github.com/karpetrosyan/hishel
-Classifier: Development Status :: 3 - Alpha
-Classifier: Environment :: Web Environment
-Classifier: Framework :: AsyncIO
-Classifier: Framework :: Trio
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3.11
-Classifier: Programming Language :: Python :: 3.12
-Classifier: Programming Language :: Python :: 3.13
-Classifier: Programming Language :: Python :: 3.14
-Classifier: Topic :: Internet :: WWW/HTTP
-Requires-Python: >=3.10
-License-File: LICENSE
-Requires-Dist: msgpack>=1.1.2
-Requires-Dist: typing-extensions>=4.14.1
-Provides-Extra: async
-Requires-Dist: anyio>=4.9.0; extra == "async"
-Requires-Dist: anysqlite>=0.0.5; extra == "async"
-Provides-Extra: requests
-Requires-Dist: requests>=2.32.5; extra == "requests"
-Provides-Extra: httpx
-Requires-Dist: httpx>=0.28.1; extra == "httpx"
-Requires-Dist: anyio>=4.9.0; extra == "httpx"
-Requires-Dist: anysqlite>=0.0.5; extra == "httpx"
-Provides-Extra: fastapi
-Requires-Dist: fastapi>=0.119.1; extra == "fastapi"
-Dynamic: license-file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hishel-1.2.1/src/hishel.egg-info/SOURCES.txt 
new/hishel-1.3.0/src/hishel.egg-info/SOURCES.txt
--- old/hishel-1.2.1/src/hishel.egg-info/SOURCES.txt    2026-04-27 
18:01:23.000000000 +0200
+++ new/hishel-1.3.0/src/hishel.egg-info/SOURCES.txt    1970-01-01 
01:00:00.000000000 +0100
@@ -1,32 +0,0 @@
-LICENSE
-README.md
-pyproject.toml
-src/hishel/__init__.py
-src/hishel/_async_cache.py
-src/hishel/_async_httpx.py
-src/hishel/_policies.py
-src/hishel/_sync_cache.py
-src/hishel/_sync_httpx.py
-src/hishel/_utils.py
-src/hishel/asgi.py
-src/hishel/fastapi.py
-src/hishel/httpx.py
-src/hishel/py.typed
-src/hishel/requests.py
-src/hishel.egg-info/PKG-INFO
-src/hishel.egg-info/SOURCES.txt
-src/hishel.egg-info/dependency_links.txt
-src/hishel.egg-info/requires.txt
-src/hishel.egg-info/top_level.txt
-src/hishel/_core/_headers.py
-src/hishel/_core/_spec.py
-src/hishel/_core/models.py
-src/hishel/_core/_storages/_async_base.py
-src/hishel/_core/_storages/_async_sqlite.py
-src/hishel/_core/_storages/_packing.py
-src/hishel/_core/_storages/_sync_base.py
-src/hishel/_core/_storages/_sync_sqlite.py
-tests/test_asgi.py
-tests/test_async_httpx.py
-tests/test_requests.py
-tests/test_sync_httpx.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/hishel-1.2.1/src/hishel.egg-info/dependency_links.txt 
new/hishel-1.3.0/src/hishel.egg-info/dependency_links.txt
--- old/hishel-1.2.1/src/hishel.egg-info/dependency_links.txt   2026-04-27 
18:01:23.000000000 +0200
+++ new/hishel-1.3.0/src/hishel.egg-info/dependency_links.txt   1970-01-01 
01:00:00.000000000 +0100
@@ -1 +0,0 @@
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hishel-1.2.1/src/hishel.egg-info/requires.txt 
new/hishel-1.3.0/src/hishel.egg-info/requires.txt
--- old/hishel-1.2.1/src/hishel.egg-info/requires.txt   2026-04-27 
18:01:23.000000000 +0200
+++ new/hishel-1.3.0/src/hishel.egg-info/requires.txt   1970-01-01 
01:00:00.000000000 +0100
@@ -1,17 +0,0 @@
-msgpack>=1.1.2
-typing-extensions>=4.14.1
-
-[async]
-anyio>=4.9.0
-anysqlite>=0.0.5
-
-[fastapi]
-fastapi>=0.119.1
-
-[httpx]
-httpx>=0.28.1
-anyio>=4.9.0
-anysqlite>=0.0.5
-
-[requests]
-requests>=2.32.5
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hishel-1.2.1/src/hishel.egg-info/top_level.txt 
new/hishel-1.3.0/src/hishel.egg-info/top_level.txt
--- old/hishel-1.2.1/src/hishel.egg-info/top_level.txt  2026-04-27 
18:01:23.000000000 +0200
+++ new/hishel-1.3.0/src/hishel.egg-info/top_level.txt  1970-01-01 
01:00:00.000000000 +0100
@@ -1 +0,0 @@
-hishel
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/hishel-1.2.1/tests/_core/spec/test_need_revalidation.py 
new/hishel-1.3.0/tests/_core/spec/test_need_revalidation.py
--- old/hishel-1.2.1/tests/_core/spec/test_need_revalidation.py 2026-04-27 
18:01:23.000000000 +0200
+++ new/hishel-1.3.0/tests/_core/spec/test_need_revalidation.py 2026-06-11 
10:39:31.000000000 +0200
@@ -313,6 +313,53 @@
         assert len(next_state.entry_ids) == 2
         assert isinstance(next_state.next_state, CacheMiss)
 
+    def test_304_with_strong_etag_matches_stored_weak_etag(self, 
default_options: CacheOptions) -> None:
+        """
+        Test: 304 response with strong ETag matches cached response with weak 
ETag
+        sharing the same opaque-tag, and identifies it for revalidation.
+
+        RFC 9110 Section 8.8.3.2 (weak comparison):
+        Two entity-tags are equivalent if their opaque-tags match regardless of
+        the W/ prefix on either side. The implementation uses weak comparison
+        so W/"abc123" (stored) must match "abc123" (304 response).
+        """
+        original_request = create_request()
+        conditional_request = create_request(headers={"if-none-match": 
'W/"abc123"'})
+
+        # Cached response has a WEAK ETag
+        cached_response = create_response(
+            headers={
+                "etag": 'W/"abc123"',
+                "cache-control": "max-age=3600",
+                "date": "Mon, 01 Jan 2024 00:00:00 GMT",
+            }
+        )
+        cached_pair = create_pair(request=original_request, 
response=cached_response)
+
+        need_revalidation = NeedRevalidation(
+            request=conditional_request,
+            original_request=original_request,
+            revalidating_entries=[cached_pair],
+            options=default_options,
+        )
+
+        # 304 response has the SAME tag but STRONG (no W/ prefix)
+        revalidation_response = create_response(
+            status_code=304,
+            headers={
+                "etag": '"abc123"',
+                "cache-control": "max-age=7200",
+                "date": "Mon, 01 Jan 2024 12:00:00 GMT",
+            },
+        )
+
+        next_state = need_revalidation.next(revalidation_response)
+
+        assert isinstance(next_state, NeedToBeUpdated)
+        assert len(next_state.updating_entries) == 1
+        updated_response = next_state.updating_entries[0].response
+        assert updated_response.headers["cache-control"] == "max-age=7200"
+
     def test_304_with_non_matching_etag_invalidates_response(self, 
default_options: CacheOptions) -> None:
         """
         Test: 304 with non-matching ETag invalidates cached response.

Reply via email to