Skip to content

Commit 358fd67

Browse files
committed
pythongh-86542: New C-APIs to simplify module attribute declaration
1 parent 50b2261 commit 358fd67

16 files changed

+507
-130
lines changed

Doc/c-api/module.rst

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,141 @@ state:
577577
578578
.. versionadded:: 3.9
579579
580+
.. c:function:: PyTypeObject * PyModule_AddNewTypeFromSpec(PyObject *module, PyType_Spec *spec, PyObject *base)
581+
582+
Initialize a new type and add it to *module*.
583+
The function is equivalent to :c:func:`PyType_FromModuleAndSpec` followed
584+
by :c:func:`PyModule_AddType`. *base* must be either ``NULL``, a single
585+
type object, or a tuple of types.
586+
Return ``NULL`` on error; otherwise a ``PyTypeObject *``, which can
587+
be assigned to a module state object.
588+
589+
.. versionadded:: 3.12
590+
591+
.. c:function:: PyObject * PyModule_AddNewException(PyObject *module, const char *name, const char *doc, PyObject *base, PyObject *dict)
592+
593+
Create a new exception and add it to *module*.
594+
The function is equivalent to :c:func:`PyErr_NewExceptionWithDoc` followed
595+
by :c:func:`PyModule_AddObjectRef`. The name of the exception object is
596+
taken from the last component of *name* after dot.
597+
Return ``NULL`` on error; otherwise ``PyObject *``, which can be assigned
598+
to a module state object.
599+
600+
.. versionadded:: 3.12
601+
602+
.. c:function:: int PyModule_AddConstants(PyObject *module, PyModuleConst_Def *def)
603+
604+
Initialize module constants from a PyModuleConst_Def array. The function
605+
provides a convenient way to declare module-level constants.
606+
Return ``-1`` on error, ``0`` on success.
607+
608+
.. versionadded:: 3.12
609+
610+
Example::
611+
612+
static PyObject*
613+
example_call(PyObject *module)
614+
{
615+
return PyBytes_FromString("23");
616+
}
617+
618+
#define EXAMPLE_INT 23
619+
#define EXAMPLE_STRING "world"
620+
621+
static PyModuleConst_Def example_constants[] = {
622+
PyModuleConst_None("none_value"),
623+
PyModuleConst_Long("integer", 42),
624+
PyModuleConst_ULong("unsigned", 42UL),
625+
PyModuleConst_Bool("false_value", 0),
626+
PyModuleConst_Bool("true_value", 1),
627+
#ifdef Py_MATH_PI
628+
PyModuleConst_Double("pi", Py_MATH_PI),
629+
#endif
630+
PyModuleConst_String("somestring", "Hello"),
631+
PyModuleConst_Call("call", example_call),
632+
PyModuleConst_LongMacro(EXAMPLE_INT),
633+
PyModuleConst_StringMacro(EXAMPLE_STRING),
634+
{NULL},
635+
}
636+
637+
static int
638+
example_init_constants(PyObject *module)
639+
{
640+
return PyModule_AddConstants(module, example_constants);
641+
}
642+
643+
static PyModuleDef_Slot example_slots[] = {
644+
{Py_mod_exec, example_init_constants},
645+
{0, NULL}
646+
};
647+
648+
649+
.. c:type:: PyModuleConst_Def
650+
651+
Definition of a module constant.
652+
653+
The members of this struct are internal implementation details.
654+
To define entries, use only the ``PyModuleConst_`` macros below,
655+
and ``{NULL}`` to mark the end of the array.
656+
657+
.. versionadded:: 3.12
658+
659+
.. c:macro:: PyModuleConst_None(name)
660+
661+
Add an entry for None.
662+
663+
.. versionadded:: 3.10
664+
665+
.. c:macro:: PyModuleConst_Long(name, value)
666+
667+
Add an entry for an integer constant.
668+
669+
.. versionadded:: 3.12
670+
671+
.. c:macro:: PyModuleConst_ULong(name, value)
672+
673+
Add an entry for an unsigned integer constant.
674+
675+
.. versionadded:: 3.12
676+
677+
.. c:macro:: PyModuleConst_Bool(name, value)
678+
679+
Add an entry for a bool constant. ``0`` is false, ``1`` is true.
680+
681+
.. versionadded:: 3.12
682+
683+
.. c:macro:: PyModuleConst_Double(name, value)
684+
685+
Add an entry for a float constant.
686+
687+
.. versionadded:: 3.12
688+
689+
.. c:macro:: PyModuleConst_String(name, value)
690+
691+
Add an entry for a string constant.
692+
693+
.. versionadded:: 3.12
694+
695+
.. c:macro:: PyModuleConst_Call(name, func)
696+
697+
Add an entry for a constant as returned by callback with signature
698+
``PyObject* (*func)(PyObject *module)``.
699+
700+
.. versionadded:: 3.12
701+
702+
.. c:macro:: PyModuleConst_LongMacro(macro)
703+
704+
Add an entry for an int constant. The name and the value are taken from
705+
*macro*.
706+
707+
.. versionadded:: 3.12
708+
709+
.. c:macro:: PyModuleConst_StringMacro(macro)
710+
711+
Add an entry for a string constant. The name and the value are taken from
712+
*macro*.
713+
714+
.. versionadded:: 3.12
580715
581716
Module lookup
582717
^^^^^^^^^^^^^

Doc/data/refcounts.dat

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,6 +1325,10 @@ PyMethod_New:PyObject*:class:0:
13251325
PyMethod_Self:PyObject*::0:
13261326
PyMethod_Self:PyObject*:im:0:
13271327

1328+
PyModule_AddConstants:int:::
1329+
PyModule_AddConstants:PyObject*:module:0:
1330+
PyModule_AddConstants:PyModuleConst_Def*:def::
1331+
13281332
PyModule_AddFunctions:int:::
13291333
PyModule_AddFunctions:PyObject*:module:0:
13301334
PyModule_AddFunctions:PyMethodDef*:functions::
@@ -1343,6 +1347,18 @@ PyModule_AddObject:PyObject*:module:0:
13431347
PyModule_AddObject:const char*:name::
13441348
PyModule_AddObject:PyObject*:value:+1:
13451349

1350+
PyModule_AddNewException:PyObject*::+1:
1351+
PyModule_AddNewException:PyObject*:module:0:
1352+
PyModule_AddNewException:const char*:name::
1353+
PyModule_AddNewException:const char*:doc::
1354+
PyModule_AddNewException:PyObject*:base:0:
1355+
PyModule_AddNewException:PyObject*:dict:0:
1356+
1357+
PyModule_AddNewTypeFromSpec:PyObject*::+1:
1358+
PyModule_AddNewTypeFromSpec:PyObject*:module:0:
1359+
PyModule_AddNewTypeFromSpec:PyType_spec*:spec::
1360+
PyModule_AddNewTypeFromSpec:PyObject*:base:0:
1361+
13461362
PyModule_AddStringConstant:int:::
13471363
PyModule_AddStringConstant:PyObject*:module:0:
13481364
PyModule_AddStringConstant:const char*:name::

Include/modsupport.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ PyAPI_FUNC(int) PyModule_AddType(PyObject *module, PyTypeObject *type);
5959
#define PyModule_AddIntMacro(m, c) PyModule_AddIntConstant((m), #c, (c))
6060
#define PyModule_AddStringMacro(m, c) PyModule_AddStringConstant((m), #c, (c))
6161

62+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03100000
63+
/* New in 3.9 */
64+
PyAPI_FUNC(PyTypeObject *) PyModule_AddNewTypeFromSpec(
65+
PyObject *module, PyType_Spec *spec, PyObject *base);
66+
PyAPI_FUNC(PyObject *) PyModule_AddNewException(
67+
PyObject *module, const char *name, const char *doc,
68+
PyObject *base, PyObject *dict);
69+
#endif
70+
6271
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03050000
6372
/* New in 3.5 */
6473
PyAPI_FUNC(int) PyModule_SetDocString(PyObject *, const char *);

Include/moduleobject.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,65 @@ struct PyModuleDef_Slot {
7171

7272
#endif /* New in 3.5 */
7373

74+
struct PyModuleConst_Def;
75+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03b00000
76+
/* New in 3.12 */
77+
enum _PyModuleConst_type {
78+
_PyModuleConst_none_type = 1,
79+
_PyModuleConst_long_type = 2,
80+
_PyModuleConst_ulong_type = 3,
81+
_PyModuleConst_bool_type = 4,
82+
_PyModuleConst_double_type = 5,
83+
_PyModuleConst_string_type = 6,
84+
_PyModuleConst_call_type = 7,
85+
};
86+
87+
typedef struct PyModuleConst_Def {
88+
const char *name;
89+
enum _PyModuleConst_type type;
90+
union {
91+
const char *m_str;
92+
long m_long;
93+
unsigned long m_ulong;
94+
double m_double;
95+
PyObject* (*m_call)(PyObject *module);
96+
// unused, included to force alignment and size for future use.
97+
long long m_long_long;
98+
#ifdef HAVE_LONG_DOUBLE
99+
long double m_long_double;
100+
#endif
101+
#ifdef HAVE_GCC_UINT128_T
102+
__uint128_t m_uint128;
103+
#endif
104+
#ifdef HAVE_GCC_FLOAT128_T
105+
__float128_t m_float128;
106+
#endif
107+
char __m[16]; // 128 bits
108+
} value;
109+
} PyModuleConst_Def;
110+
111+
PyAPI_FUNC(int) PyModule_AddConstants(PyObject *, PyModuleConst_Def *);
112+
113+
#define PyModuleConst_None(name) \
114+
{(name), _PyModuleConst_none_type, {.m_long=0}}
115+
#define PyModuleConst_Long(name, value) \
116+
{(name), _PyModuleConst_long_type, {.m_long=(value)}}
117+
#define PyModuleConst_ULong(name, value) \
118+
{(name), _PyModuleConst_ulong_type, {.m_ulong=(value)}}
119+
#define PyModuleConst_Bool(name, value) \
120+
{(name), _PyModuleConst_bool_type, {.m_long=(value)}}
121+
#define PyModuleConst_Double(name, value) \
122+
{(name), _PyModuleConst_double_type, {.m_double=(value)}}
123+
#define PyModuleConst_String(name, value) \
124+
{(name), _PyModuleConst_string_type, {.m_str=(value)}}
125+
#define PyModuleConst_Call(name, value) \
126+
{(name), _PyModuleConst_call_type, {.m_call=(value)}}
127+
128+
#define PyModuleConst_LongMacro(m) PyModuleConst_Long(#m, m)
129+
#define PyModuleConst_StringMacro(m) PyModuleConst_String(#m, m)
130+
131+
#endif /* New in 3.12 */
132+
74133
struct PyModuleDef {
75134
PyModuleDef_Base m_base;
76135
const char* m_name;

Lib/test/test_capi.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,5 +1203,19 @@ def func2(x=None):
12031203
self.do_test(func2)
12041204

12051205

1206+
class Test_PyModuleConst_Def(unittest.TestCase):
1207+
def test_constants(self):
1208+
self.assertIs(_testcapi.const_none, None)
1209+
self.assertEqual(_testcapi.const_int, 42)
1210+
self.assertEqual(_testcapi.const_uint, _testcapi.ULONG_MAX)
1211+
self.assertIs(_testcapi.const_true, True)
1212+
self.assertIs(_testcapi.const_false, False)
1213+
self.assertEqual(_testcapi.const_almost_tau, 6.2831)
1214+
self.assertEqual(_testcapi.const_str, "Hello")
1215+
self.assertEqual(_testcapi.const_call, b"23")
1216+
self.assertEqual(_testcapi.CONST_INT, 7)
1217+
self.assertEqual(_testcapi.CONST_STRING, "world")
1218+
1219+
12061220
if __name__ == "__main__":
12071221
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add new functions :c:func:`PyModule_AddConstants`,
2+
:c:func:`PyModule_AddNewTypeFromSpec`, :c:func:`PyModule_AddNewException` to
3+
simplify the declaration of attribute in modules.

Modules/_hashopenssl.c

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2132,14 +2132,9 @@ hashlib_init_evptype(PyObject *module)
21322132
{
21332133
_hashlibstate *state = get_hashlib_state(module);
21342134

2135-
state->EVPtype = (PyTypeObject *)PyType_FromSpec(&EVPtype_spec);
2136-
if (state->EVPtype == NULL) {
2137-
return -1;
2138-
}
2139-
if (PyModule_AddType(module, state->EVPtype) < 0) {
2140-
return -1;
2141-
}
2142-
return 0;
2135+
state->EVPtype = PyModule_AddNewTypeFromSpec(
2136+
module, &EVPtype_spec, NULL);
2137+
return state->EVPtype == NULL ? -1 : 0;
21432138
}
21442139

21452140
static int
@@ -2151,33 +2146,21 @@ hashlib_init_evpxoftype(PyObject *module)
21512146
if (state->EVPtype == NULL) {
21522147
return -1;
21532148
}
2154-
2155-
state->EVPXOFtype = (PyTypeObject *)PyType_FromSpecWithBases(
2156-
&EVPXOFtype_spec, (PyObject *)state->EVPtype
2157-
);
2158-
if (state->EVPXOFtype == NULL) {
2159-
return -1;
2160-
}
2161-
if (PyModule_AddType(module, state->EVPXOFtype) < 0) {
2162-
return -1;
2163-
}
2149+
state->EVPXOFtype = PyModule_AddNewTypeFromSpec(
2150+
module, &EVPXOFtype_spec, (PyObject *)state->EVPtype);
2151+
return state->EVPXOFtype == NULL ? -1 : 0;
21642152
#endif
21652153
return 0;
21662154
}
21672155

21682156
static int
21692157
hashlib_init_hmactype(PyObject *module)
21702158
{
2171-
_hashlibstate *state = get_hashlib_state(module);
2159+
_hashlibstate *state = get_hashlib_state(module);
21722160

2173-
state->HMACtype = (PyTypeObject *)PyType_FromSpec(&HMACtype_spec);
2174-
if (state->HMACtype == NULL) {
2175-
return -1;
2176-
}
2177-
if (PyModule_AddType(module, state->HMACtype) < 0) {
2178-
return -1;
2179-
}
2180-
return 0;
2161+
state->HMACtype = PyModule_AddNewTypeFromSpec(
2162+
module, &HMACtype_spec, NULL);
2163+
return state->HMACtype == NULL ? -1 : 0;
21812164
}
21822165

21832166
static int

Modules/_testcapimodule.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7530,6 +7530,29 @@ static PyTypeObject ContainerNoGC_type = {
75307530
.tp_new = ContainerNoGC_new,
75317531
};
75327532

7533+
static PyObject *
7534+
_testcapimodule_const_call(PyObject *module)
7535+
{
7536+
return PyBytes_FromString("23");
7537+
}
7538+
7539+
#define CONST_INT 7
7540+
#define CONST_STRING "world"
7541+
7542+
static PyModuleConst_Def _testcapimodule_consts[] = {
7543+
PyModuleConst_None("const_none"),
7544+
PyModuleConst_Long("const_int", 42),
7545+
PyModuleConst_ULong("const_uint", ULONG_MAX),
7546+
PyModuleConst_Bool("const_false", 0),
7547+
PyModuleConst_Bool("const_true", 1),
7548+
PyModuleConst_Double("const_almost_tau", 6.2831),
7549+
PyModuleConst_String("const_str", "Hello"),
7550+
PyModuleConst_Call("const_call", _testcapimodule_const_call),
7551+
PyModuleConst_LongMacro(CONST_INT),
7552+
PyModuleConst_StringMacro(CONST_STRING),
7553+
{NULL},
7554+
};
7555+
75337556

75347557
static struct PyModuleDef _testcapimodule = {
75357558
PyModuleDef_HEAD_INIT,
@@ -7555,6 +7578,11 @@ PyInit__testcapi(void)
75557578
if (m == NULL)
75567579
return NULL;
75577580

7581+
if (PyModule_AddConstants(m, _testcapimodule_consts) < 0) {
7582+
Py_DECREF(m);
7583+
return NULL;
7584+
}
7585+
75587586
Py_SET_TYPE(&_HashInheritanceTester_Type, &PyType_Type);
75597587

75607588
Py_SET_TYPE(&test_structmembersType, &PyType_Type);

0 commit comments

Comments
 (0)