Skip to content

Commit e3f0eba

Browse files
committed
Add PyModule_Add() function
PyModule_AddObjectRef() now raises SystemError if the value is NULL and no exception is set.
1 parent 7515dae commit e3f0eba

File tree

4 files changed

+93
-15
lines changed

4 files changed

+93
-15
lines changed

docs/api.rst

+4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ Python 3.13
5151
5252
See `PyMapping_GetOptionalItemString() documentation <https://docs.python.org/dev/c-api/mapping.html#c.PyMapping_GetOptionalItemString>`__.
5353
54+
.. c:function:: int PyModule_Add(PyObject *module, const char *name, PyObject *value)
55+
56+
See `PyModule_Add() documentation <https://docs.python.org/dev/c-api/module.html#c.PyModule_Add>`__.
57+
5458
5559
Python 3.12
5660
-----------

docs/changelog.rst

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

4+
* 2023-07-18: Add ``PyModule_Add()`` function.
45
* 2023-07-12: Add ``PyObject_GetOptionalAttr()``,
56
``PyObject_GetOptionalAttrString()``,
67
``PyMapping_GetOptionalItem()``

pythoncapi_compat.h

+21
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,15 @@ PYCAPI_COMPAT_STATIC_INLINE(int)
422422
PyModule_AddObjectRef(PyObject *module, const char *name, PyObject *value)
423423
{
424424
int res;
425+
426+
if (!value && !PyErr_Occurred()) {
427+
// PyModule_AddObject() raises TypeError in this case
428+
PyErr_SetString(PyExc_SystemError,
429+
"PyModule_AddObjectRef() must be called "
430+
"with an exception raised if value is NULL");
431+
return -1;
432+
}
433+
425434
Py_XINCREF(value);
426435
res = PyModule_AddObject(module, name, value);
427436
if (res < 0) {
@@ -782,6 +791,18 @@ PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **resul
782791
#endif
783792

784793

794+
// gh-106307 added PyModule_Add() to Python 3.13.0a1
795+
#if PY_VERSION_HEX < 0x030D00A1
796+
PYCAPI_COMPAT_STATIC_INLINE(int)
797+
PyModule_Add(PyObject *mod, const char *name, PyObject *value)
798+
{
799+
int res = PyModule_AddObjectRef(mod, name, value);
800+
Py_XDECREF(value);
801+
return res;
802+
}
803+
#endif
804+
805+
785806
#ifdef __cplusplus
786807
}
787808
#endif

tests/test_pythoncapi_compat_cext.c

+67-15
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,23 @@ test_gc(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored))
386386
}
387387

388388

389+
static int
390+
check_module_attr(PyObject *module, const char *name, PyObject *expected)
391+
{
392+
PyObject *attr = PyObject_GetAttrString(module, name);
393+
if (attr == _Py_NULL) {
394+
return -1;
395+
}
396+
assert(attr == expected);
397+
Py_DECREF(attr);
398+
399+
if (PyObject_DelAttrString(module, name) < 0) {
400+
return -1;
401+
}
402+
return 0;
403+
}
404+
405+
389406
// test PyModule_AddType()
390407
static int
391408
test_module_add_type(PyObject *module)
@@ -407,14 +424,7 @@ test_module_add_type(PyObject *module)
407424
ASSERT_REFCNT(Py_REFCNT(type) == refcnt + 1);
408425
#endif
409426

410-
PyObject *attr = PyObject_GetAttrString(module, type_name);
411-
if (attr == _Py_NULL) {
412-
return -1;
413-
}
414-
assert(attr == _Py_CAST(PyObject*, type));
415-
Py_DECREF(attr);
416-
417-
if (PyObject_DelAttrString(module, type_name) < 0) {
427+
if (check_module_attr(module, type_name, _Py_CAST(PyObject*, type)) < 0) {
418428
return -1;
419429
}
420430
ASSERT_REFCNT(Py_REFCNT(type) == refcnt);
@@ -426,30 +436,70 @@ test_module_add_type(PyObject *module)
426436
static int
427437
test_module_addobjectref(PyObject *module)
428438
{
429-
PyObject *obj = Py_True;
430439
const char *name = "test_module_addobjectref";
440+
PyObject *obj = PyUnicode_FromString(name);
441+
assert(obj != NULL);
431442
#ifdef CHECK_REFCNT
432443
Py_ssize_t refcnt = Py_REFCNT(obj);
433444
#endif
434445

435446
if (PyModule_AddObjectRef(module, name, obj) < 0) {
436447
ASSERT_REFCNT(Py_REFCNT(obj) == refcnt);
448+
Py_DECREF(obj);
437449
return -1;
438450
}
439-
#ifndef IMMORTAL_OBJS
440451
ASSERT_REFCNT(Py_REFCNT(obj) == refcnt + 1);
441-
#endif
442452

443-
if (PyObject_DelAttrString(module, name) < 0) {
453+
if (check_module_attr(module, name, obj) < 0) {
454+
Py_DECREF(obj);
444455
return -1;
445456
}
446457
ASSERT_REFCNT(Py_REFCNT(obj) == refcnt);
447458

448459
// PyModule_AddObjectRef() with value=NULL must not crash
460+
assert(!PyErr_Occurred());
449461
int res = PyModule_AddObjectRef(module, name, _Py_NULL);
450462
assert(res < 0);
463+
assert(PyErr_ExceptionMatches(PyExc_SystemError));
464+
PyErr_Clear();
465+
466+
Py_DECREF(obj);
467+
return 0;
468+
}
469+
470+
471+
// test PyModule_Add()
472+
static int
473+
test_module_add(PyObject *module)
474+
{
475+
const char *name = "test_module_add";
476+
PyObject *obj = PyUnicode_FromString(name);
477+
assert(obj != NULL);
478+
#ifdef CHECK_REFCNT
479+
Py_ssize_t refcnt = Py_REFCNT(obj);
480+
#endif
481+
482+
if (PyModule_Add(module, name, Py_NewRef(obj)) < 0) {
483+
ASSERT_REFCNT(Py_REFCNT(obj) == refcnt);
484+
Py_DECREF(obj);
485+
return -1;
486+
}
487+
ASSERT_REFCNT(Py_REFCNT(obj) == refcnt + 1);
488+
489+
if (check_module_attr(module, name, obj) < 0) {
490+
Py_DECREF(obj);
491+
return -1;
492+
}
493+
ASSERT_REFCNT(Py_REFCNT(obj) == refcnt);
494+
495+
// PyModule_Add() with value=NULL must not crash
496+
assert(!PyErr_Occurred());
497+
int res = PyModule_Add(module, name, _Py_NULL);
498+
assert(res < 0);
499+
assert(PyErr_ExceptionMatches(PyExc_SystemError));
451500
PyErr_Clear();
452501

502+
Py_DECREF(obj);
453503
return 0;
454504
}
455505

@@ -458,18 +508,20 @@ static PyObject *
458508
test_module(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored))
459509
{
460510
PyObject *module = PyImport_ImportModule("sys");
461-
if (module == _Py_NULL) {
462-
return _Py_NULL;
511+
if (module == NULL) {
512+
return NULL;
463513
}
464514
assert(PyModule_Check(module));
465515

466516
if (test_module_add_type(module) < 0) {
467517
goto error;
468518
}
469-
470519
if (test_module_addobjectref(module) < 0) {
471520
goto error;
472521
}
522+
if (test_module_add(module) < 0) {
523+
goto error;
524+
}
473525

474526
Py_DECREF(module);
475527
Py_RETURN_NONE;

0 commit comments

Comments
 (0)