diff --git a/docs/api.rst b/docs/api.rst index 6efae15..ccd4d08 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -208,6 +208,9 @@ Python 3.14 See `PyConfig_GetInt() documentation `__. +.. c:function:: int PyUnstable_Object_IsUniquelyReferenced(PyObject *op) + + See `PyUnstable_Object_IsUniquelyReferenced() documentation `__. Not supported: diff --git a/docs/changelog.rst b/docs/changelog.rst index b8e3a86..fa9a35d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* 2025-09-01: Add ``PyUnstable_Object_IsUniquelyReferenced()`` function. * 2025-06-09: Add ``PyUnicodeWriter_WriteASCII()`` function. * 2025-06-03: Add functions: diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 3320f68..d14e90f 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -2211,6 +2211,24 @@ PyConfig_GetInt(const char *name, int *value) } #endif // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION) +// gh-133144 added PyUnstable_Object_IsUniquelyReferenced() to Python 3.14.0b1. +// Adapted from _PyObject_IsUniquelyReferenced() implementation. +#if PY_VERSION_HEX < 0x030E00B0 +static inline int PyUnstable_Object_IsUniquelyReferenced(PyObject *obj) +{ +#if !defined(Py_GIL_DISABLED) + return Py_REFCNT(obj) == 1; +#else + // NOTE: the entire ob_ref_shared field must be zero, including flags, to + // ensure that other threads cannot concurrently create new references to + // this object. + return (_Py_IsOwnedByCurrentThread(obj) && + _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local) == 1 && + _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared) == 0); +#endif +} +#endif + #if PY_VERSION_HEX < 0x030F0000 static inline PyObject* diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index c719d42..31386d5 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1972,6 +1972,25 @@ test_unicodewriter_format(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) } #endif +static PyObject * +test_uniquely_referenced(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + PyObject *obj = Py_BuildValue("(s, s)", "hello", "world"); + if (obj == NULL) { + return NULL; + } + + assert(PyUnstable_Object_IsUniquelyReferenced(obj)); + + Py_INCREF(obj); + + assert(!PyUnstable_Object_IsUniquelyReferenced(obj)); + + Py_DECREF(obj); + Py_DECREF(obj); + + Py_RETURN_NONE; +} static PyObject * test_bytes(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) @@ -2328,6 +2347,7 @@ static struct PyMethodDef methods[] = { {"test_config", test_config, METH_NOARGS, _Py_NULL}, #endif {"test_sys", test_sys, METH_NOARGS, _Py_NULL}, + {"test_uniquely_referenced", test_uniquely_referenced, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} };