Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-propcache for 
openSUSE:Factory checked in at 2026-04-13 23:18:00
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-propcache (Old)
 and      /work/SRC/openSUSE:Factory/.python-propcache.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-propcache"

Mon Apr 13 23:18:00 2026 rev:7 rq:1345291 version:0.4.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-propcache/python-propcache.changes        
2025-09-11 14:39:15.455586164 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-propcache.new.21863/python-propcache.changes 
    2026-04-13 23:18:04.394111118 +0200
@@ -1,0 +2,23 @@
+Wed Apr  8 21:24:40 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.4.1:
+  * Fixed reference leak caused by Py_INCREF because Cython has
+    its own reference counter systems -- by :user:`Vizonex`.
+    Related issues and pull requests on GitHub: :issue:`162`.
+  * Fixes the default value for the os parameter in reusable-
+    build-wheel.yml to be ubuntu-latest instead of ubuntu.
+    Related issues and pull requests on GitHub: :issue:`155`.
+  * Optimized propcache by replacing sentinel :py:class:`object`
+    for checking if the :py:class:`object` is NULL and changed
+    :py:class:`dict` API for Python C-API -- by :user:`Vizonex`.
+    Related issues and pull requests on GitHub: :issue:`121`.
+  * Builds have been added for arm64 Windows wheels and the
+    reusable-build-wheel.yml workflow has been modified to allow
+    for an OS value (windows-11-arm) which does not include the
+    -latest postfix -- by :user:`finnagin`. Related issues and
+    pull requests on GitHub: :issue:`133`.
+  * Added CI for CPython 3.14 -- by :user:`kumaraditya303`.
+    Related issues and pull requests on GitHub: :issue:`140`.
+- drop py314.patch (upstream)
+
+-------------------------------------------------------------------

Old:
----
  propcache-0.3.2.tar.gz
  py314.patch

New:
----
  propcache-0.4.1.tar.gz

----------(Old B)----------
  Old:    Related issues and pull requests on GitHub: :issue:`140`.
- drop py314.patch (upstream)
----------(Old E)----------

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

Other differences:
------------------
++++++ python-propcache.spec ++++++
--- /var/tmp/diff_new_pack.rIEqEe/_old  2026-04-13 23:18:05.042137839 +0200
+++ /var/tmp/diff_new_pack.rIEqEe/_new  2026-04-13 23:18:05.042137839 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-propcache
 #
-# Copyright (c) 2025 SUSE LLC and contributors
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,22 +18,20 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-propcache
-Version:        0.3.2
+Version:        0.4.1
 Release:        0
 Summary:        Accelerated property cache
 License:        Apache-2.0
 URL:            https://github.com/aio-libs/propcache
 Source:         
https://files.pythonhosted.org/packages/source/p/propcache/propcache-%{version}.tar.gz
 Patch0:         reproducible.patch
-# PATCH-FIX-UPSTREAM 
https://github.com/aio-libs/propcache/commit/b97e7e37cbe8329e2a4d8383166c094f471a0d6a
 Test on Python 3.14
-Patch1:         py314.patch
-BuildRequires:  %{python_module Cython >= 3.0.11}
+BuildRequires:  %{python_module Cython >= 3.1.4}
 BuildRequires:  %{python_module covdefaults}
 BuildRequires:  %{python_module expandvars}
 BuildRequires:  %{python_module pip}
+BuildRequires:  %{python_module pytest >= 8.4.2}
 BuildRequires:  %{python_module pytest-cov}
 BuildRequires:  %{python_module pytest-xdist}
-BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module setuptools >= 47}
 BuildRequires:  %{python_module wheel}
 BuildRequires:  fdupes

++++++ propcache-0.3.2.tar.gz -> propcache-0.4.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/.coveragerc 
new/propcache-0.4.1/.coveragerc
--- old/propcache-0.3.2/.coveragerc     2025-06-10 00:17:49.000000000 +0200
+++ new/propcache-0.4.1/.coveragerc     2025-10-08 20:09:05.000000000 +0200
@@ -25,6 +25,9 @@
 
 [run]
 branch = true
+# Cython.Coverage plugin is not supported on sysmon core which is default on
+# Python 3.14+ so always use ctrace core
+core = ctrace
 cover_pylib = false
 # https://coverage.rtfd.io/en/latest/contexts.html#dynamic-contexts
 # dynamic_context = test_function  # conflicts with `pytest-cov` if set here
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/CHANGES.rst 
new/propcache-0.4.1/CHANGES.rst
--- old/propcache-0.3.2/CHANGES.rst     2025-06-10 00:17:49.000000000 +0200
+++ new/propcache-0.4.1/CHANGES.rst     2025-10-08 20:09:05.000000000 +0200
@@ -14,6 +14,75 @@
 
 .. towncrier release notes start
 
+0.4.1
+=====
+
+*(2025-10-08)*
+
+
+Bug fixes
+---------
+
+- Fixed reference leak caused by ``Py_INCREF`` because Cython has its own 
reference counter systems -- by :user:`Vizonex`.
+
+  *Related issues and pull requests on GitHub:*
+  :issue:`162`.
+
+
+Contributor-facing changes
+--------------------------
+
+- Fixes the default value for the ``os``
+  parameter in ``reusable-build-wheel.yml``
+  to be ``ubuntu-latest`` instead of
+  ``ubuntu``.
+
+  *Related issues and pull requests on GitHub:*
+  :issue:`155`.
+
+
+----
+
+
+0.4.0
+=====
+
+*(2025-10-04)*
+
+
+Features
+--------
+
+- Optimized propcache by replacing sentinel :py:class:`object` for checking if
+  the :py:class:`object` is ``NULL`` and changed :py:class:`dict` API for
+  Python C-API -- by :user:`Vizonex`.
+
+  *Related issues and pull requests on GitHub:*
+  :issue:`121`.
+
+
+Contributor-facing changes
+--------------------------
+
+- Builds have been added for arm64 Windows
+  wheels and the ``reusable-build-wheel.yml``
+  workflow has been modified to allow for
+  an OS value (``windows-11-arm``) which
+  does not include the ``-latest`` postfix
+  -- by :user:`finnagin`.
+
+  *Related issues and pull requests on GitHub:*
+  :issue:`133`.
+
+- Added CI for CPython 3.14 -- by :user:`kumaraditya303`.
+
+  *Related issues and pull requests on GitHub:*
+  :issue:`140`.
+
+
+----
+
+
 0.3.2
 =====
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/PKG-INFO new/propcache-0.4.1/PKG-INFO
--- old/propcache-0.3.2/PKG-INFO        2025-06-10 00:17:55.380904400 +0200
+++ new/propcache-0.4.1/PKG-INFO        2025-10-08 20:09:12.999469300 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: propcache
-Version: 0.3.2
+Version: 0.4.1
 Summary: Accelerated property cache
 Home-page: https://github.com/aio-libs/propcache
 Author: Andrew Svetlov
@@ -29,6 +29,7 @@
 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
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
 Requires-Python: >=3.9
@@ -152,6 +153,75 @@
 
 .. towncrier release notes start
 
+0.4.1
+=====
+
+*(2025-10-08)*
+
+
+Bug fixes
+---------
+
+- Fixed reference leak caused by ``Py_INCREF`` because Cython has its own 
reference counter systems -- by `@Vizonex 
<https://github.com/sponsors/Vizonex>`__.
+
+  *Related issues and pull requests on GitHub:*
+  `#162 <https://github.com/aio-libs/propcache/issues/162>`__.
+
+
+Contributor-facing changes
+--------------------------
+
+- Fixes the default value for the ``os``
+  parameter in ``reusable-build-wheel.yml``
+  to be ``ubuntu-latest`` instead of
+  ``ubuntu``.
+
+  *Related issues and pull requests on GitHub:*
+  `#155 <https://github.com/aio-libs/propcache/issues/155>`__.
+
+
+----
+
+
+0.4.0
+=====
+
+*(2025-10-04)*
+
+
+Features
+--------
+
+- Optimized propcache by replacing sentinel ``object`` for checking if
+  the ``object`` is ``NULL`` and changed ``dict`` API for
+  Python C-API -- by `@Vizonex <https://github.com/sponsors/Vizonex>`__.
+
+  *Related issues and pull requests on GitHub:*
+  `#121 <https://github.com/aio-libs/propcache/issues/121>`__.
+
+
+Contributor-facing changes
+--------------------------
+
+- Builds have been added for arm64 Windows
+  wheels and the ``reusable-build-wheel.yml``
+  workflow has been modified to allow for
+  an OS value (``windows-11-arm``) which
+  does not include the ``-latest`` postfix
+  -- by `@finnagin <https://github.com/sponsors/finnagin>`__.
+
+  *Related issues and pull requests on GitHub:*
+  `#133 <https://github.com/aio-libs/propcache/issues/133>`__.
+
+- Added CI for CPython 3.14 -- by `@kumaraditya303 
<https://github.com/sponsors/kumaraditya303>`__.
+
+  *Related issues and pull requests on GitHub:*
+  `#140 <https://github.com/aio-libs/propcache/issues/140>`__.
+
+
+----
+
+
 0.3.2
 =====
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/requirements/codspeed.txt 
new/propcache-0.4.1/requirements/codspeed.txt
--- old/propcache-0.3.2/requirements/codspeed.txt       2025-06-10 
00:17:49.000000000 +0200
+++ new/propcache-0.4.1/requirements/codspeed.txt       2025-10-08 
20:09:05.000000000 +0200
@@ -1,2 +1,3 @@
 -r test.txt
-pytest-codspeed==3.2.0
+cffi<2.0.0;python_version<"3.14"
+pytest-codspeed==4.1.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/requirements/cython.txt 
new/propcache-0.4.1/requirements/cython.txt
--- old/propcache-0.3.2/requirements/cython.txt 2025-06-10 00:17:49.000000000 
+0200
+++ new/propcache-0.4.1/requirements/cython.txt 2025-10-08 20:09:05.000000000 
+0200
@@ -1 +1 @@
-cython==3.1.2
+cython==3.1.4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/requirements/lint.txt 
new/propcache-0.4.1/requirements/lint.txt
--- old/propcache-0.3.2/requirements/lint.txt   2025-06-10 00:17:49.000000000 
+0200
+++ new/propcache-0.4.1/requirements/lint.txt   2025-10-08 20:09:05.000000000 
+0200
@@ -1 +1 @@
-pre-commit==4.2.0
+pre-commit==4.3.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/requirements/test.txt 
new/propcache-0.4.1/requirements/test.txt
--- old/propcache-0.3.2/requirements/test.txt   2025-06-10 00:17:49.000000000 
+0200
+++ new/propcache-0.4.1/requirements/test.txt   2025-10-08 20:09:05.000000000 
+0200
@@ -1,5 +1,5 @@
 -r cython.txt
 covdefaults
-pytest==8.3.5
+pytest==8.4.2
 pytest-cov>=2.3.1
 pytest-xdist
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/requirements/towncrier.txt 
new/propcache-0.4.1/requirements/towncrier.txt
--- old/propcache-0.3.2/requirements/towncrier.txt      2025-06-10 
00:17:49.000000000 +0200
+++ new/propcache-0.4.1/requirements/towncrier.txt      2025-10-08 
20:09:05.000000000 +0200
@@ -1 +1 @@
-towncrier==23.11.0
+towncrier==25.8.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/setup.cfg 
new/propcache-0.4.1/setup.cfg
--- old/propcache-0.3.2/setup.cfg       2025-06-10 00:17:55.380904400 +0200
+++ new/propcache-0.4.1/setup.cfg       2025-10-08 20:09:13.000469200 +0200
@@ -41,6 +41,7 @@
        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 :: Libraries :: Python Modules
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/src/propcache/__init__.py 
new/propcache-0.4.1/src/propcache/__init__.py
--- old/propcache-0.3.2/src/propcache/__init__.py       2025-06-10 
00:17:49.000000000 +0200
+++ new/propcache-0.4.1/src/propcache/__init__.py       2025-10-08 
20:09:05.000000000 +0200
@@ -4,7 +4,7 @@
 
 _PUBLIC_API = ("cached_property", "under_cached_property")
 
-__version__ = "0.3.2"
+__version__ = "0.4.1"
 __all__ = ()
 
 # Imports have moved to `propcache.api` in 0.2.0+.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/src/propcache/_helpers_c.pyx 
new/propcache-0.4.1/src/propcache/_helpers_c.pyx
--- old/propcache-0.3.2/src/propcache/_helpers_c.pyx    2025-06-10 
00:17:49.000000000 +0200
+++ new/propcache-0.4.1/src/propcache/_helpers_c.pyx    2025-10-08 
20:09:05.000000000 +0200
@@ -1,8 +1,23 @@
 # cython: language_level=3, freethreading_compatible=True
 from types import GenericAlias
 
+from cpython.dict cimport PyDict_GetItem
+from cpython.object cimport PyObject
+
+
+cdef extern from "Python.h":
+    # Call a callable Python object callable with exactly
+    # 1 positional argument arg and no keyword arguments.
+    # Return the result of the call on success, or raise
+    # an exception and return NULL on failure.
+    PyObject* PyObject_CallOneArg(
+        object callable, object arg
+    ) except NULL
+    int PyDict_SetItem(
+        object dict, object key, PyObject* value
+    ) except -1
+    void Py_DECREF(PyObject*)
 
-cdef _sentinel = object()
 
 cdef class under_cached_property:
     """Use as a class method decorator.  It operates almost exactly like
@@ -16,7 +31,7 @@
     cdef readonly object wrapped
     cdef object name
 
-    def __init__(self, wrapped):
+    def __init__(self, object wrapped):
         self.wrapped = wrapped
         self.name = wrapped.__name__
 
@@ -24,15 +39,16 @@
     def __doc__(self):
         return self.wrapped.__doc__
 
-    def __get__(self, inst, owner):
+    def __get__(self, object inst, owner):
         if inst is None:
             return self
         cdef dict cache = inst._cache
-        val = cache.get(self.name, _sentinel)
-        if val is _sentinel:
-            val = self.wrapped(inst)
-            cache[self.name] = val
-        return val
+        cdef PyObject* val = PyDict_GetItem(cache, self.name)
+        if val == NULL:
+            val = PyObject_CallOneArg(self.wrapped, inst)
+            PyDict_SetItem(cache, self.name, val)
+            Py_DECREF(val)
+        return <object>val
 
     def __set__(self, inst, value):
         raise AttributeError("cached property is read-only")
@@ -60,7 +76,7 @@
     def __doc__(self):
         return self.func.__doc__
 
-    def __set_name__(self, owner, name):
+    def __set_name__(self, owner, object name):
         if self.name is None:
             self.name = name
         elif name != self.name:
@@ -77,10 +93,11 @@
                 "Cannot use cached_property instance"
                 " without calling __set_name__ on it.")
         cdef dict cache = inst.__dict__
-        val = cache.get(self.name, _sentinel)
-        if val is _sentinel:
-            val = self.func(inst)
-            cache[self.name] = val
-        return val
+        cdef PyObject* val = PyDict_GetItem(cache, self.name)
+        if val is NULL:
+            val = PyObject_CallOneArg(self.func, inst)
+            PyDict_SetItem(cache, self.name, val)
+            Py_DECREF(val)
+        return <object>val
 
     __class_getitem__ = classmethod(GenericAlias)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/src/propcache/_helpers_py.py 
new/propcache-0.4.1/src/propcache/_helpers_py.py
--- old/propcache-0.3.2/src/propcache/_helpers_py.py    2025-06-10 
00:17:49.000000000 +0200
+++ new/propcache-0.4.1/src/propcache/_helpers_py.py    2025-10-08 
20:09:05.000000000 +0200
@@ -42,7 +42,9 @@
     def __get__(self, inst: None, owner: Optional[type[object]] = None) -> 
Self: ...
 
     @overload
-    def __get__(self, inst: _CacheImpl[Any], owner: Optional[type[object]] = 
None) -> _T: ...  # type: ignore[misc]
+    def __get__(
+        self, inst: _CacheImpl[Any], owner: Optional[type[object]] = None
+    ) -> _T: ...
 
     def __get__(
         self, inst: Optional[_CacheImpl[Any]], owner: Optional[type[object]] = 
None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/src/propcache.egg-info/PKG-INFO 
new/propcache-0.4.1/src/propcache.egg-info/PKG-INFO
--- old/propcache-0.3.2/src/propcache.egg-info/PKG-INFO 2025-06-10 
00:17:55.000000000 +0200
+++ new/propcache-0.4.1/src/propcache.egg-info/PKG-INFO 2025-10-08 
20:09:12.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: propcache
-Version: 0.3.2
+Version: 0.4.1
 Summary: Accelerated property cache
 Home-page: https://github.com/aio-libs/propcache
 Author: Andrew Svetlov
@@ -29,6 +29,7 @@
 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
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
 Requires-Python: >=3.9
@@ -152,6 +153,75 @@
 
 .. towncrier release notes start
 
+0.4.1
+=====
+
+*(2025-10-08)*
+
+
+Bug fixes
+---------
+
+- Fixed reference leak caused by ``Py_INCREF`` because Cython has its own 
reference counter systems -- by `@Vizonex 
<https://github.com/sponsors/Vizonex>`__.
+
+  *Related issues and pull requests on GitHub:*
+  `#162 <https://github.com/aio-libs/propcache/issues/162>`__.
+
+
+Contributor-facing changes
+--------------------------
+
+- Fixes the default value for the ``os``
+  parameter in ``reusable-build-wheel.yml``
+  to be ``ubuntu-latest`` instead of
+  ``ubuntu``.
+
+  *Related issues and pull requests on GitHub:*
+  `#155 <https://github.com/aio-libs/propcache/issues/155>`__.
+
+
+----
+
+
+0.4.0
+=====
+
+*(2025-10-04)*
+
+
+Features
+--------
+
+- Optimized propcache by replacing sentinel ``object`` for checking if
+  the ``object`` is ``NULL`` and changed ``dict`` API for
+  Python C-API -- by `@Vizonex <https://github.com/sponsors/Vizonex>`__.
+
+  *Related issues and pull requests on GitHub:*
+  `#121 <https://github.com/aio-libs/propcache/issues/121>`__.
+
+
+Contributor-facing changes
+--------------------------
+
+- Builds have been added for arm64 Windows
+  wheels and the ``reusable-build-wheel.yml``
+  workflow has been modified to allow for
+  an OS value (``windows-11-arm``) which
+  does not include the ``-latest`` postfix
+  -- by `@finnagin <https://github.com/sponsors/finnagin>`__.
+
+  *Related issues and pull requests on GitHub:*
+  `#133 <https://github.com/aio-libs/propcache/issues/133>`__.
+
+- Added CI for CPython 3.14 -- by `@kumaraditya303 
<https://github.com/sponsors/kumaraditya303>`__.
+
+  *Related issues and pull requests on GitHub:*
+  `#140 <https://github.com/aio-libs/propcache/issues/140>`__.
+
+
+----
+
+
 0.3.2
 =====
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/tests/test_cached_property.py 
new/propcache-0.4.1/tests/test_cached_property.py
--- old/propcache-0.3.2/tests/test_cached_property.py   2025-06-10 
00:17:49.000000000 +0200
+++ new/propcache-0.4.1/tests/test_cached_property.py   2025-10-08 
20:09:05.000000000 +0200
@@ -1,3 +1,4 @@
+import gc
 import sys
 from collections.abc import Callable
 from operator import not_
@@ -7,6 +8,8 @@
 
 from propcache.api import cached_property
 
+IS_PYPY = hasattr(sys, "pypy_version_info")
+
 if sys.version_info >= (3, 11):
     from typing import assert_type
 
@@ -142,3 +145,82 @@
 
     a = A()
     assert A.prop.func(a) == 1
+
+
[email protected]_extension
[email protected](IS_PYPY, reason="PyPy has no C extension")
+def test_cached_property_no_refcount_leak(propcache_module: APIProtocol) -> 
None:
+    """Test that cached_property does not leak references."""
+
+    class CachedPropertySentinel:
+        """A unique object we can track."""
+
+    def count_sentinels() -> int:
+        """Count the number of CachedPropertySentinel instances in gc."""
+        gc.collect()
+        return sum(
+            1 for obj in gc.get_objects() if isinstance(obj, 
CachedPropertySentinel)
+        )
+
+    class A:
+        def __init__(self) -> None:
+            """Init."""
+
+        @propcache_module.cached_property
+        def prop(self) -> CachedPropertySentinel:
+            """Return a sentinel object."""
+            return CachedPropertySentinel()
+
+    initial_sentinel_count = count_sentinels()
+
+    a = A()
+
+    # First access - creates and caches the object
+    result = a.prop
+    # sys.getrefcount returns 1 higher than actual (the temp ref from the call)
+    # After first access: result owns 1, __dict__ owns 1, getrefcount call 
owns 1 = 3
+    initial_refcount = sys.getrefcount(result)
+
+    # Should have exactly 1 Sentinel instance now
+    assert count_sentinels() == initial_sentinel_count + 1
+
+    # Second access - should return the cached object without creating new refs
+    result2 = a.prop
+    assert result is result2
+    # After second access: result owns 1, result2 owns 1, __dict__ owns 1, 
getrefcount call owns 1 = 4
+    # Only result2 should add 1
+    second_refcount = sys.getrefcount(result)
+    assert second_refcount == initial_refcount + 1
+
+    # Still should have exactly 1 Sentinel instance
+    assert count_sentinels() == initial_sentinel_count + 1
+
+    # Third access
+    result3 = a.prop
+    assert result is result3
+    # result2 and result3 each add 1
+    third_refcount = sys.getrefcount(result)
+    assert third_refcount == initial_refcount + 2
+
+    # Clean up local refs - should be back to just result and __dict__
+    del result2
+    del result3
+    after_cleanup_refcount = sys.getrefcount(result)
+    assert after_cleanup_refcount == initial_refcount
+
+    # Clear the cache and verify no leak when re-fetching
+    # After clearing: only result owns it
+    del a.__dict__["prop"]
+    cleared_refcount = sys.getrefcount(result)
+    assert cleared_refcount == initial_refcount - 1  # No longer in __dict__
+
+    # Re-fetch - this should create a new object, not affect old one
+    result4 = a.prop
+    assert result4 is not result  # Should be a new object
+    refetch_refcount = sys.getrefcount(result)
+    assert refetch_refcount == cleared_refcount  # Original object refcount 
unchanged
+
+    # Now we should have 2 Sentinel instances:
+    # - original in `result`
+    # - new one in `result4`
+    assert count_sentinels() == initial_sentinel_count + 2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/propcache-0.3.2/tests/test_under_cached_property.py 
new/propcache-0.4.1/tests/test_under_cached_property.py
--- old/propcache-0.3.2/tests/test_under_cached_property.py     2025-06-10 
00:17:49.000000000 +0200
+++ new/propcache-0.4.1/tests/test_under_cached_property.py     2025-10-08 
20:09:05.000000000 +0200
@@ -1,3 +1,4 @@
+import gc
 import sys
 from collections.abc import Callable
 from typing import TYPE_CHECKING, Any, Protocol, TypedDict, TypeVar
@@ -6,6 +7,8 @@
 
 from propcache.api import under_cached_property
 
+IS_PYPY = hasattr(sys, "pypy_version_info")
+
 if sys.version_info >= (3, 11):
     from typing import assert_type
 
@@ -165,3 +168,85 @@
 
     a = A()
     assert A.prop.wrapped(a) == 1
+
+
[email protected]_extension
[email protected](IS_PYPY, reason="PyPy has no C extension")
+def test_under_cached_property_no_refcount_leak(propcache_module: APIProtocol) 
-> None:
+    """Test that under_cached_property does not leak references."""
+
+    class UnderCachedPropertySentinel:
+        """A unique object we can track."""
+
+    def count_sentinels() -> int:
+        """Count the number of UnderCachedPropertySentinel instances in gc."""
+        gc.collect()
+        return sum(
+            1
+            for obj in gc.get_objects()
+            if isinstance(obj, UnderCachedPropertySentinel)
+        )
+
+    class A:
+        def __init__(self) -> None:
+            """Init."""
+            self._cache: dict[str, Any] = {}
+
+        @propcache_module.under_cached_property
+        def prop(self) -> UnderCachedPropertySentinel:
+            """Return a sentinel object."""
+            return UnderCachedPropertySentinel()
+
+    initial_sentinel_count = count_sentinels()
+
+    a = A()
+
+    # First access - creates and caches the object
+    result = a.prop
+    # sys.getrefcount returns 1 higher than actual (the temp ref from the call)
+    # After first access: result owns 1, _cache owns 1, getrefcount call owns 
1 = 3
+    initial_refcount = sys.getrefcount(result)
+
+    # Should have exactly 1 Sentinel instance now
+    assert count_sentinels() == initial_sentinel_count + 1
+
+    # Second access - should return the cached object without creating new refs
+    result2 = a.prop
+    assert result is result2
+    # After second access: result owns 1, result2 owns 1, _cache owns 1, 
getrefcount call owns 1 = 4
+    # Only result2 should add 1
+    second_refcount = sys.getrefcount(result)
+    assert second_refcount == initial_refcount + 1
+
+    # Still should have exactly 1 Sentinel instance
+    assert count_sentinels() == initial_sentinel_count + 1
+
+    # Third access
+    result3 = a.prop
+    assert result is result3
+    # result2 and result3 each add 1
+    third_refcount = sys.getrefcount(result)
+    assert third_refcount == initial_refcount + 2
+
+    # Clean up local refs - should be back to just result and _cache
+    del result2
+    del result3
+    after_cleanup_refcount = sys.getrefcount(result)
+    assert after_cleanup_refcount == initial_refcount
+
+    # Clear the cache and verify no leak when re-fetching
+    # After clearing: only result owns it
+    del a._cache["prop"]
+    cleared_refcount = sys.getrefcount(result)
+    assert cleared_refcount == initial_refcount - 1  # No longer in _cache
+
+    # Re-fetch - this should create a new object, not affect old one
+    result4 = a.prop
+    assert result4 is not result  # Should be a new object
+    refetch_refcount = sys.getrefcount(result)
+    assert refetch_refcount == cleared_refcount  # Original object refcount 
unchanged
+
+    # Now we should have 2 Sentinel instances:
+    # - original in `result`
+    # - new one in `result4`
+    assert count_sentinels() == initial_sentinel_count + 2

Reply via email to