Skip to content

Commit 6c49cf3

Browse files
authored
Add PyWeakref_GetRef() function (#61)
1 parent 9669348 commit 6c49cf3

File tree

4 files changed

+119
-6
lines changed

4 files changed

+119
-6
lines changed

docs/api.rst

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ Python 3.13
3131
3232
See `PyImport_AddModuleRef() documentation <https://docs.python.org/dev/c-api/import.html#c.PyImport_AddModuleRef>`__.
3333
34+
.. c:function:: int PyWeakref_GetRef(PyObject *ref, PyObject **pobj)
35+
36+
See `PyWeakref_GetRef() documentation <https://docs.python.org/dev/c-api/weakref.html#c.PyWeakref_GetRef>`__.
3437
3538
Python 3.12
3639
-----------

docs/changelog.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
Changelog
22
=========
33

4+
* 2023-06-21: Add ``PyWeakref_GetRef()`` function.
45
* 2023-06-20: Add ``PyImport_AddModuleRef()`` function.
56
* 2022-11-15: Add experimental operations to the ``upgrade_pythoncapi.py``
67
script: ``Py_NewRef``, ``Py_CLEAR`` and ``Py_SETREF``.

pythoncapi_compat.h

+28
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ PyCode_GetCellvars(PyCodeObject *code)
570570
# endif
571571
#endif
572572

573+
573574
// gh-105922 added PyImport_AddModuleRef() to Python 3.13.0a1
574575
#if PY_VERSION_HEX < 0x030D00A0
575576
PYCAPI_COMPAT_STATIC_INLINE(PyObject*)
@@ -580,6 +581,33 @@ PyImport_AddModuleRef(const char *name)
580581
#endif
581582

582583

584+
// gh-105927 added PyWeakref_GetRef() to Python 3.13.0a1
585+
#if PY_VERSION_HEX < 0x030D0000
586+
PYCAPI_COMPAT_STATIC_INLINE(int)
587+
PyWeakref_GetRef(PyObject *ref, PyObject **pobj)
588+
{
589+
PyObject *obj;
590+
if (ref != NULL && !PyWeakref_Check(ref)) {
591+
*pobj = NULL;
592+
PyErr_SetString(PyExc_TypeError, "expected a weakref");
593+
return -1;
594+
}
595+
obj = PyWeakref_GetObject(ref);
596+
if (obj == NULL) {
597+
// SystemError if ref is NULL
598+
*pobj = NULL;
599+
return -1;
600+
}
601+
if (obj == Py_None) {
602+
*pobj = NULL;
603+
return 0;
604+
}
605+
*pobj = Py_NewRef(obj);
606+
return 0;
607+
}
608+
#endif
609+
610+
583611
#ifdef __cplusplus
584612
}
585613
#endif

tests/test_pythoncapi_compat_cext.c

+87-6
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
#define MODULE_NAME_STR STR(MODULE_NAME)
3434

3535
// Ignore reference count checks on PyPy
36-
#if !defined(PYPY_VERSION)
36+
#ifndef PYPY_VERSION
3737
# define CHECK_REFCNT
3838
#endif
3939

@@ -148,7 +148,7 @@ test_py_is(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored))
148148
}
149149

150150

151-
#if !defined(PYPY_VERSION)
151+
#ifndef PYPY_VERSION
152152
static void
153153
test_frame_getvar(PyFrameObject *frame)
154154
{
@@ -279,7 +279,7 @@ test_thread_state(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored))
279279
PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate);
280280
assert(interp != _Py_NULL);
281281

282-
#if !defined(PYPY_VERSION)
282+
#ifndef PYPY_VERSION
283283
// test PyThreadState_GetFrame()
284284
PyFrameObject *frame = PyThreadState_GetFrame(tstate);
285285
if (frame != _Py_NULL) {
@@ -293,7 +293,7 @@ test_thread_state(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored))
293293
assert(id > 0);
294294
#endif
295295

296-
#if !defined(PYPY_VERSION)
296+
#ifndef PYPY_VERSION
297297
// PyThreadState_EnterTracing(), PyThreadState_LeaveTracing()
298298
PyThreadState_EnterTracing(tstate);
299299
PyThreadState_LeaveTracing(tstate);
@@ -668,10 +668,90 @@ test_import(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
668668
}
669669

670670

671+
static void
672+
gc_collect(void)
673+
{
674+
#if defined(PYPY_VERSION)
675+
PyObject *mod = PyImport_ImportModule("gc");
676+
assert(mod != NULL);
677+
678+
PyObject *res = PyObject_CallMethod(mod, "collect", NULL);
679+
Py_DECREF(mod);
680+
assert(res != NULL);
681+
Py_DECREF(res);
682+
#else
683+
PyGC_Collect();
684+
#endif
685+
}
686+
687+
688+
static PyObject *
689+
test_weakref(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
690+
{
691+
// Create a new heap type, create an instance of this type, and delete the
692+
// type. This object supports weak references.
693+
PyObject *new_type = PyObject_CallFunction((PyObject*)&PyType_Type,
694+
"s(){}", "TypeName");
695+
if (new_type == NULL) {
696+
return NULL;
697+
}
698+
PyObject *obj = PyObject_CallNoArgs(new_type);
699+
Py_DECREF(new_type);
700+
if (obj == NULL) {
701+
return NULL;
702+
}
703+
Py_ssize_t refcnt = Py_REFCNT(obj);
704+
705+
// create a weak reference
706+
PyObject *weakref = PyWeakref_NewRef(obj, NULL);
707+
if (weakref == NULL) {
708+
return NULL;
709+
}
710+
711+
// test PyWeakref_GetRef(), reference is alive
712+
PyObject *ref = Py_True; // marker to check that value was set
713+
assert(PyWeakref_GetRef(weakref, &ref) == 0);
714+
assert(ref == obj);
715+
assert(Py_REFCNT(obj) == (refcnt + 1));
716+
Py_DECREF(ref);
717+
718+
// delete the referenced object: clear the weakref
719+
Py_DECREF(obj);
720+
gc_collect();
721+
722+
// test PyWeakref_GetRef(), reference is dead
723+
ref = Py_True;
724+
assert(PyWeakref_GetRef(weakref, &ref) == 0);
725+
assert(ref == NULL);
726+
727+
// test PyWeakref_GetRef(), invalid type
728+
PyObject *invalid_weakref = Py_None;
729+
assert(!PyErr_Occurred());
730+
ref = Py_True;
731+
assert(PyWeakref_GetRef(invalid_weakref, &ref) == -1);
732+
assert(PyErr_ExceptionMatches(PyExc_TypeError));
733+
assert(ref == NULL);
734+
PyErr_Clear();
735+
736+
#ifndef PYPY_VERSION
737+
// test PyWeakref_GetRef(NULL)
738+
ref = Py_True;
739+
assert(PyWeakref_GetRef(NULL, &ref) == -1);
740+
assert(PyErr_ExceptionMatches(PyExc_SystemError));
741+
assert(ref == NULL);
742+
PyErr_Clear();
743+
#endif
744+
745+
Py_DECREF(weakref);
746+
747+
Py_RETURN_NONE;
748+
}
749+
750+
671751
static struct PyMethodDef methods[] = {
672752
{"test_object", test_object, METH_NOARGS, _Py_NULL},
673753
{"test_py_is", test_py_is, METH_NOARGS, _Py_NULL},
674-
#if !defined(PYPY_VERSION)
754+
#ifndef PYPY_VERSION
675755
{"test_frame", test_frame, METH_NOARGS, _Py_NULL},
676756
#endif
677757
{"test_thread_state", test_thread_state, METH_NOARGS, _Py_NULL},
@@ -682,11 +762,12 @@ static struct PyMethodDef methods[] = {
682762
#if (PY_VERSION_HEX <= 0x030B00A1 || 0x030B00A7 <= PY_VERSION_HEX) && !defined(PYPY_VERSION)
683763
{"test_float_pack", test_float_pack, METH_NOARGS, _Py_NULL},
684764
#endif
685-
#if !defined(PYPY_VERSION)
765+
#ifndef PYPY_VERSION
686766
{"test_code", test_code, METH_NOARGS, _Py_NULL},
687767
#endif
688768
{"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL},
689769
{"test_import", test_import, METH_NOARGS, _Py_NULL},
770+
{"test_weakref", test_weakref, METH_NOARGS, _Py_NULL},
690771
{_Py_NULL, _Py_NULL, 0, _Py_NULL}
691772
};
692773

0 commit comments

Comments
 (0)