Skip to content
Closed
145 changes: 145 additions & 0 deletions Doc/c-api/module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,151 @@ state:

.. versionadded:: 3.9

.. c:function:: PyTypeObject * PyModule_AddNewTypeFromSpec(PyObject *module, PyType_Spec *spec, PyObject *base)

Initialize a new type and add it to *module*.
The function is equivalent to :c:func:`PyType_FromModuleAndSpec` followed
by :c:func:`PyModule_AddType`. *base* must be either ``NULL``, a single
type object, or a tuple of types.
Return ``NULL`` on error; otherwise a ``PyTypeObject *``, which can
be assigned to a module state object.

.. versionadded:: 3.10

.. c:function:: PyObject * PyModule_AddNewException(PyObject *module, const char *name, const char *doc, PyObject *base, PyObject *dict)

Create a new exception and add it to *module*.
The function is equivalent to :c:func:`PyErr_NewExceptionWithDoc` followed
by :c:func:`PyModule_AddObjectRef`. The name of the exception object is
taken from the last component of *name* after dot.
Return ``NULL`` on error; otherwise ``PyObject *``, which can be assigned
to a module state object.

.. versionadded:: 3.10

.. c:function:: int PyModule_AddConstants(PyObject *module, PyModuleConst_Def *def)

Initialize module constants from a PyModuleConst_Def array. The function
provides a convenient way to declare module-level constants.
Return ``-1`` on error, ``0`` on success.

Example::

static PyObject*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: static PyObject * in here.

example_call(PyObject *module)
{
return PyBytes_FromString("23");
}

#define EXAMPLE_INT 23
#define EXAMPLE_STRING "world"

static PyModuleConst_Def example_constants[] = {
PyModuleConst_None("none_value"),
PyModuleConst_Long("integer", 42),
PyModuleConst_ULong("unsigned", 42UL),
PyModuleConst_Bool("false_value", 0),
PyModuleConst_Bool("true_value", 1),
#ifdef Py_MATH_PI
PyModuleConst_Double("pi", Py_MATH_PI),
#endif
PyModuleConst_String("somestring", "Hello"),
PyModuleConst_Call("call", example_call),
PyModuleConst_LongMacro(EXAMPLE_INT),
PyModuleConst_StringMacro(EXAMPLE_STRING),
{NULL},
}

static int
example_init_constants(PyObject *module)
{
return PyModule_AddConstants(module, example_constants);
}

static PyModuleDef_Slot example_slots[] = {
{Py_mod_exec, example_init_constants},
{0, NULL}
};


.. c:type:: PyModuleConst_Def

The values for *type* and the definition of the *value* union are
internal implementation details. Use any of the ``PyModuleConst_`` macros
to define entries. The array must be terminated by an entry with name
set to ``NULL``.

.. c:member:: const char *name

Attribute name.

.. c:member:: int type

Attribute type.

.. c:member:: void *value

Value of the module constant definition, whose meaning depends on
*type*.
Comment on lines +643 to +659
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The values for *type* and the definition of the *value* union are
internal implementation details. Use any of the ``PyModuleConst_`` macros
to define entries. The array must be terminated by an entry with name
set to ``NULL``.
.. c:member:: const char *name
Attribute name.
.. c:member:: int type
Attribute type.
.. c:member:: void *value
Value of the module constant definition, whose meaning depends on
*type*.
Definition of a module constant.
The members of this struct are internal implementation details.
To define entries, use only the ``PyModuleConst_`` macros below,
and ``{NULL}`` to mark the end of the array.


.. versionadded:: 3.10

.. c:macro:: PyModuleConst_None(name)

Add an entry for None.

.. versionadded:: 3.10

.. c:macro:: PyModuleConst_Long(name, value)

Add an entry for an integer constant.

.. versionadded:: 3.10

.. c:macro:: PyModuleConst_ULong(name, value)

Add an entry for an unsigned integer constant.

.. versionadded:: 3.10

.. c:macro:: PyModuleConst_Bool(name, value)

Add an entry for a bool constant. ``0`` is false, ``1`` is true.

.. versionadded:: 3.10

.. c:macro:: PyModuleConst_Double(name, value)

Add an entry for a float constant.

.. versionadded:: 3.10

.. c:macro:: PyModuleConst_String(name, value)

Add an entry for a string constant.

.. versionadded:: 3.10

.. c:macro:: PyModuleConst_Call(name, func)

Add an entry for a constant as returned by callback with signature
``PyObject* (*func)(PyObject *module)``.

.. versionadded:: 3.10

.. c:macro:: PyModuleConst_LongMacro(macro)

Add an entry for an int constant. The name and the value are taken from
*macro*.

.. versionadded:: 3.10

.. c:macro:: PyModuleConst_StringMacro(macro)

Add an entry for a string constant. The name and the value are taken from
*macro*.

.. versionadded:: 3.10

Module lookup
^^^^^^^^^^^^^
Expand Down
16 changes: 16 additions & 0 deletions Doc/data/refcounts.dat
Original file line number Diff line number Diff line change
Expand Up @@ -1325,6 +1325,10 @@ PyMethod_New:PyObject*:class:0:
PyMethod_Self:PyObject*::0:
PyMethod_Self:PyObject*:im:0:

PyModule_AddConstants:int:::
PyModule_AddConstants:PyObject*:module:0:
PyModule_AddConstants:PyModuleConst_Def*:def::

PyModule_AddFunctions:int:::
PyModule_AddFunctions:PyObject*:module:0:
PyModule_AddFunctions:PyMethodDef*:functions::
Expand All @@ -1343,6 +1347,18 @@ PyModule_AddObject:PyObject*:module:0:
PyModule_AddObject:const char*:name::
PyModule_AddObject:PyObject*:value:+1:

PyModule_AddNewException:PyObject*::+1:
PyModule_AddNewException:PyObject*:module:0:
PyModule_AddNewException:const char*:name::
PyModule_AddNewException:const char*:doc::
PyModule_AddNewException:PyObject*:base:0:
PyModule_AddNewException:PyObject*:dict:0:

PyModule_AddNewTypeFromSpec:PyObject*::+1:
PyModule_AddNewTypeFromSpec:PyObject*:module:0:
PyModule_AddNewTypeFromSpec:PyType_spec*:spec::
PyModule_AddNewTypeFromSpec:PyObject*:base:0:

PyModule_AddStringConstant:int:::
PyModule_AddStringConstant:PyObject*:module:0:
PyModule_AddStringConstant:const char*:name::
Expand Down
9 changes: 9 additions & 0 deletions Include/modsupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,15 @@ PyAPI_FUNC(int) PyModule_AddType(PyObject *module, PyTypeObject *type);
#define PyModule_AddIntMacro(m, c) PyModule_AddIntConstant(m, #c, c)
#define PyModule_AddStringMacro(m, c) PyModule_AddStringConstant(m, #c, c)

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03100000
/* New in 3.9 */
PyAPI_FUNC(PyTypeObject *) PyModule_AddNewTypeFromSpec(
PyObject *module, PyType_Spec *spec, PyObject *base);
PyAPI_FUNC(PyObject *) PyModule_AddNewException(
PyObject *module, const char *name, const char *doc,
PyObject *base, PyObject *dict);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you start by adding these two functions in a separated PR, to make this PR shorter?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really think that's necessary; the PR is not that big.

#endif

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03050000
/* New in 3.5 */
PyAPI_FUNC(int) PyModule_SetDocString(PyObject *, const char *);
Expand Down
47 changes: 47 additions & 0 deletions Include/moduleobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,53 @@ typedef struct PyModuleDef_Slot{

#endif /* New in 3.5 */

struct PyModuleConst_Def;
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03100000
/* New in 3.10 */
enum _PyModuleConst_type {
_PyModuleConst_none_type = 1,
_PyModuleConst_long_type = 2,
_PyModuleConst_ulong_type = 3,
_PyModuleConst_bool_type = 4,
_PyModuleConst_double_type = 5,
_PyModuleConst_string_type = 6,
_PyModuleConst_call_type = 7,
};

typedef struct PyModuleConst_Def {
const char *name;
enum _PyModuleConst_type type;
union {
const char *m_str;
long m_long;
unsigned long m_ulong;
double m_double;
PyObject* (*m_call)(PyObject *module);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to make this member future-proof in term of stable ABI.

We should try to put a max_align_t inside, but this type requires C11. GCC defines it with:

typedef struct {
  long long __max_align_ll __attribute__((__aligned__(__alignof__(long long))));
  long double __max_align_ld __attribute__((__aligned__(__alignof__(long double))));
  /* _Float128 is defined as a basic type, so max_align_t must be
     sufficiently aligned for it.  This code must work in C++, so we
     use __float128 here; that is only available on some
     architectures, but only on i386 is extra alignment needed for
     __float128.  */
#ifdef __i386__
  __float128 __max_align_f128 __attribute__((__aligned__(__alignof(__float128))));
#endif
} max_align_t;

Maybe we can at least put long long and long double:

// Unused members added to make PyModuleConst_Def large enough
// to get a stable ABI and support future additions.
long long m_long_long;
long double m_long_double;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only issue I found here :)
The stable ABI doesn't have a good story around evolving structs, and I think we should design a general mechanism for that rather than try to future-proof individual structs.
To move this PR forward, could we exclude PyModule_AddConstants & co. from the limited API for the time being?

} value;
} PyModuleConst_Def;

PyAPI_FUNC(int) PyModule_AddConstants(PyObject *, PyModuleConst_Def *);

#define PyModuleConst_None(name) \
{(name), _PyModuleConst_none_type, {.m_long=0}}
#define PyModuleConst_Long(name, value) \
{(name), _PyModuleConst_long_type, {.m_long=(value)}}
#define PyModuleConst_ULong(name, value) \
{(name), _PyModuleConst_ulong_type, {.m_ulong=(value)}}
#define PyModuleConst_Bool(name, value) \
{(name), _PyModuleConst_bool_type, {.m_long=(value)}}
#define PyModuleConst_Double(name, value) \
{(name), _PyModuleConst_double_type, {.m_double=(value)}}
#define PyModuleConst_String(name, value) \
{(name), _PyModuleConst_string_type, {.m_str=(value)}}
#define PyModuleConst_Call(name, value) \
{(name), _PyModuleConst_call_type, {.m_call=(value)}}

#define PyModuleConst_LongMacro(m) PyModuleConst_Long(#m, m)
#define PyModuleConst_StringMacro(m) PyModuleConst_String(#m, m)

#endif /* New in 3.10 */

typedef struct PyModuleDef{
PyModuleDef_Base m_base;
const char* m_name;
Expand Down
14 changes: 14 additions & 0 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -971,5 +971,19 @@ def test_state_access(self):
increment_count(1, 2, 3)


class Test_PyModuleConst_Def(unittest.TestCase):
def test_constants(self):
self.assertIs(_testcapi.const_none, None)
self.assertEqual(_testcapi.const_int, 42)
self.assertEqual(_testcapi.const_uint, _testcapi.ULONG_MAX)
self.assertIs(_testcapi.const_true, True)
self.assertIs(_testcapi.const_false, False)
self.assertEqual(_testcapi.const_almost_tau, 6.2831)
self.assertEqual(_testcapi.const_str, "Hello")
self.assertEqual(_testcapi.const_call, b"23")
self.assertEqual(_testcapi.CONST_INT, 7)
self.assertEqual(_testcapi.CONST_STRING, "world")


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add new functions :c:func:`PyModule_AddConstants`,
:c:func:`PyModule_AddNewTypeFromSpec`, :c:func:`PyModule_AddNewException` to
simplify the declaration of attribute in modules.
37 changes: 10 additions & 27 deletions Modules/_hashopenssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -2025,14 +2025,9 @@ hashlib_init_evptype(PyObject *module)
{
_hashlibstate *state = get_hashlib_state(module);

state->EVPtype = (PyTypeObject *)PyType_FromSpec(&EVPtype_spec);
if (state->EVPtype == NULL) {
return -1;
}
if (PyModule_AddType(module, state->EVPtype) < 0) {
return -1;
}
return 0;
state->EVPtype = PyModule_AddNewTypeFromSpec(
module, &EVPtype_spec, NULL);
return state->EVPtype == NULL ? -1 : 0;
}

static int
Expand All @@ -2044,33 +2039,21 @@ hashlib_init_evpxoftype(PyObject *module)
if (state->EVPtype == NULL) {
return -1;
}

state->EVPXOFtype = (PyTypeObject *)PyType_FromSpecWithBases(
&EVPXOFtype_spec, (PyObject *)state->EVPtype
);
if (state->EVPXOFtype == NULL) {
return -1;
}
if (PyModule_AddType(module, state->EVPXOFtype) < 0) {
return -1;
}
state->EVPXOFtype = PyModule_AddNewTypeFromSpec(
module, &EVPXOFtype_spec, (PyObject *)state->EVPtype);
return state->EVPXOFtype == NULL ? -1 : 0;
#endif
return 0;
}

static int
hashlib_init_hmactype(PyObject *module)
{
_hashlibstate *state = get_hashlib_state(module);
_hashlibstate *state = get_hashlib_state(module);

state->HMACtype = (PyTypeObject *)PyType_FromSpec(&HMACtype_spec);
if (state->HMACtype == NULL) {
return -1;
}
if (PyModule_AddType(module, state->HMACtype) < 0) {
return -1;
}
return 0;
state->HMACtype = PyModule_AddNewTypeFromSpec(
module, &HMACtype_spec, NULL);
return state->HMACtype == NULL ? -1 : 0;
}

static int
Expand Down
17 changes: 4 additions & 13 deletions Modules/_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -5459,39 +5459,30 @@ static PyMethodDef PySSL_methods[] = {
static int
sslmodule_init_types(PyObject *module)
{
PySSLContext_Type = (PyTypeObject *)PyType_FromModuleAndSpec(
PySSLContext_Type = PyModule_AddNewTypeFromSpec(
module, &PySSLContext_spec, NULL
);
if (PySSLContext_Type == NULL)
return -1;

PySSLSocket_Type = (PyTypeObject *)PyType_FromModuleAndSpec(
PySSLSocket_Type = PyModule_AddNewTypeFromSpec(
module, &PySSLSocket_spec, NULL
);
if (PySSLSocket_Type == NULL)
return -1;

PySSLMemoryBIO_Type = (PyTypeObject *)PyType_FromModuleAndSpec(
PySSLMemoryBIO_Type = PyModule_AddNewTypeFromSpec(
module, &PySSLMemoryBIO_spec, NULL
);
if (PySSLMemoryBIO_Type == NULL)
return -1;

PySSLSession_Type = (PyTypeObject *)PyType_FromModuleAndSpec(
PySSLSession_Type = PyModule_AddNewTypeFromSpec(
module, &PySSLSession_spec, NULL
);
if (PySSLSession_Type == NULL)
return -1;

if (PyModule_AddType(module, PySSLContext_Type))
return -1;
if (PyModule_AddType(module, PySSLSocket_Type))
return -1;
if (PyModule_AddType(module, PySSLMemoryBIO_Type))
return -1;
if (PyModule_AddType(module, PySSLSession_Type))
return -1;

return 0;
}

Expand Down
Loading