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