From 75ee0e84f2b61e32e65e28c8b4ce6b7adfd1d08c Mon Sep 17 00:00:00 2001 From: O01eg <397177+o01eg@users.noreply.github.com> Date: Sun, 20 Apr 2025 09:24:16 +0400 Subject: [PATCH 1/5] Add support for multi-phase module initialization --- include/boost/python/module.hpp | 3 ++ include/boost/python/module_init.hpp | 54 ++++++++++++++++++++++++++++ src/module.cpp | 12 +++++++ test/fabscript | 5 +++ test/module_multi_phase.cpp | 15 ++++++++ test/module_multi_phase.py | 23 ++++++++++++ 6 files changed, 112 insertions(+) create mode 100644 test/module_multi_phase.cpp create mode 100644 test/module_multi_phase.py diff --git a/include/boost/python/module.hpp b/include/boost/python/module.hpp index 8ad69f5a34..f6f9fa871f 100644 --- a/include/boost/python/module.hpp +++ b/include/boost/python/module.hpp @@ -9,5 +9,8 @@ # include # define BOOST_PYTHON_MODULE BOOST_PYTHON_MODULE_INIT +# if PY_VERSION_HEX >= 0x03050000 +# define BOOST_PYTHON_MODULE_MULTI_PHASE BOOST_PYTHON_MODULE_MULTI_PHASE_INIT +# endif #endif // MODULE_DWA20011221_HPP diff --git a/include/boost/python/module_init.hpp b/include/boost/python/module_init.hpp index 390db82cf4..f12550b221 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -47,6 +47,12 @@ namespace detail { BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef&, void(*)(), bool gil_not_used = false); +# if PY_VERSION_HEX >= 0x03050000 + +BOOST_PYTHON_DECL int exec_module(PyObject*, void(*)()); + +# endif + #else BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); @@ -115,6 +121,46 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); void BOOST_PP_CAT(init_module_, name)() # endif // HAS_CXX11 +# if PY_VERSION_HEX >= 0x03050000 + +# define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* module) \ + { \ + return boost::python::detail::exec_module( \ + module, BOOST_PP_CAT(init_module_, name) ); \ + } \ + extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ + { \ + static PyModuleDef_Base initial_m_base = { \ + PyObject_HEAD_INIT(NULL) \ + 0, /* m_init */ \ + 0, /* m_index */ \ + 0 /* m_copy */ }; \ + static PyMethodDef initial_methods[] = { { 0, 0, 0, 0 } }; \ + \ + static PyModuleDef_Slot slots[] = { \ + {Py_mod_exec, reinterpret_cast(reinterpret_cast(BOOST_PP_CAT(exec_module_, name)))}, \ + {0, NULL} \ + }; \ + \ + static struct PyModuleDef moduledef = { \ + initial_m_base, \ + BOOST_PP_STRINGIZE(name), \ + 0, /* m_doc */ \ + 0, /* m_size */ \ + initial_methods, \ + slots, /* m_slots */ \ + 0, /* m_traverse */ \ + 0, /* m_clear */ \ + 0, /* m_free */ \ + }; \ + \ + return PyModuleDef_Init(&moduledef); \ + } \ + void BOOST_PP_CAT(init_module_, name)() + +# endif + # else # define _BOOST_PYTHON_MODULE_INIT(name) \ @@ -137,6 +183,14 @@ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name, __VA_ARGS__) extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name) # endif // HAS_CXX11 && Python 3 +# if PY_VERSION_HEX >= 0x03050000 + +# define BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ + void BOOST_PP_CAT(init_module_,name)(); \ +extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) + +# endif + # endif #endif // MODULE_INIT_DWA20020722_HPP diff --git a/src/module.cpp b/src/module.cpp index c32f4187bc..ef83d31807 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -52,6 +52,18 @@ BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef& moduledef, init_function); } +# if PY_VERSION_HEX >= 0x03050000 + +BOOST_PYTHON_DECL int exec_module(PyObject* module, void(*init_function)()) +{ + PyObject* retval = init_module_in_scope( + module, + init_function); + return retval ? 0 : -1; +} + +# endif + #else namespace diff --git a/test/fabscript b/test/fabscript index 7cf22f9c09..362ff82d18 100644 --- a/test/fabscript +++ b/test/fabscript @@ -174,4 +174,9 @@ for t in ['numpy/dtype', tests.append(extension_test(t, numpy=True, condition=set.define.contains('HAS_NUMPY'))) +python_version_major, python_version_minor = map(int, python.instance().version.split('.')[:2]) + +tests.append(extension_test("module_multi_phase", + condition=python_version_major > 3 or (python_version_major == 3 and python_version_minor >= 5))) + default = report('report', tests, fail_on_failures=True) diff --git a/test/module_multi_phase.cpp b/test/module_multi_phase.cpp new file mode 100644 index 0000000000..dcbbe74305 --- /dev/null +++ b/test/module_multi_phase.cpp @@ -0,0 +1,15 @@ +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +using namespace boost::python; + +BOOST_PYTHON_MODULE_MULTI_PHASE(module_multi_phase_ext) +{ + scope().attr("x") = "x"; +} + +#include "module_tail.cpp" diff --git a/test/module_multi_phase.py b/test/module_multi_phase.py new file mode 100644 index 0000000000..138f5d9820 --- /dev/null +++ b/test/module_multi_phase.py @@ -0,0 +1,23 @@ +# Distributed under the Boost +# Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +""" +>>> import module_multi_phase_ext +>>> module_multi_phase_ext.x +'x' +""" + +def run(args = None): + import sys + import doctest + + if args is not None: + sys.argv = args + return doctest.testmod(sys.modules.get(__name__)) + +if __name__ == '__main__': + print("running...") + import sys + status = run()[0] + if (status == 0): print("Done.") + sys.exit(status) From 293744495592aedceb3103bf4155d83b59aa1aad Mon Sep 17 00:00:00 2001 From: O01eg <397177+o01eg@users.noreply.github.com> Date: Sat, 4 Apr 2026 22:34:27 +0400 Subject: [PATCH 2/5] Fix indent and add more remarks to make it more manageable --- include/boost/python/module_init.hpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/boost/python/module_init.hpp b/include/boost/python/module_init.hpp index f12550b221..ba62259bff 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -13,7 +13,7 @@ namespace boost { namespace python { -#ifdef HAS_CXX11 +# ifdef HAS_CXX11 // Use to activate the Py_MOD_GIL_NOT_USED flag. class mod_gil_not_used { public: @@ -39,7 +39,7 @@ inline bool gil_not_used_option(F &&, O &&...o) { } } -#endif // HAS_CXX11 +# endif // HAS_CXX11 namespace detail { @@ -51,13 +51,13 @@ BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef&, void(*)(), bool gil_not_us BOOST_PYTHON_DECL int exec_module(PyObject*, void(*)()); -# endif +# endif // PY_VERSION_HEX >= 0x03050000 -#else +# else // PY_VERSION_HEX >= 0x03000000 BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); -#endif +# endif // PY_VERSION_HEX >= 0x03000000 }}} @@ -159,9 +159,9 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); } \ void BOOST_PP_CAT(init_module_, name)() -# endif +# endif // PY_VERSION_HEX >= 0x03050000 -# else +# else // ! PY_VERSION_HEX >= 0x03000000 # define _BOOST_PYTHON_MODULE_INIT(name) \ void BOOST_PP_CAT(init,name)() \ @@ -171,7 +171,7 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); } \ void BOOST_PP_CAT(init_module_,name)() -# endif +# endif // PY_VERSION_HEX >= 0x03000000 # if defined(HAS_CXX11) && (PY_VERSION_HEX >= 0x03000000) # define BOOST_PYTHON_MODULE_INIT(name, ...) \ @@ -189,8 +189,8 @@ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name) void BOOST_PP_CAT(init_module_,name)(); \ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) -# endif +# endif // PY_VERSION_HEX >= 0x03050000 -# endif +# endif // BOOST_PYTHON_MODULE_INIT #endif // MODULE_INIT_DWA20020722_HPP From 4f49c834efd036cda6772b26a9fb5e451b155539 Mon Sep 17 00:00:00 2001 From: O01eg <397177+o01eg@users.noreply.github.com> Date: Sat, 4 Apr 2026 22:51:30 +0400 Subject: [PATCH 3/5] Add mod_gil_not_used option to BOOST_PYTHON_MODULE_MULTI_PHASE_INIT --- include/boost/python/module_init.hpp | 51 +++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/include/boost/python/module_init.hpp b/include/boost/python/module_init.hpp index ba62259bff..05ead85c71 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -123,7 +123,8 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); # if PY_VERSION_HEX >= 0x03050000 -# define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ +# if defined(HAS_CXX11) && (PY_VERSION_HEX >= 0x030D0000) +# define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name, ...) \ int BOOST_PP_CAT(exec_module_,name)(PyObject* module) \ { \ return boost::python::detail::exec_module( \ @@ -140,6 +141,7 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); \ static PyModuleDef_Slot slots[] = { \ {Py_mod_exec, reinterpret_cast(reinterpret_cast(BOOST_PP_CAT(exec_module_, name)))}, \ + {Py_mod_gil, boost::python::detail::gil_not_used_option(__VA_ARGS__) ? Py_MOD_GIL_NOT_USED : Py_MOD_GIL_USED}, \ {0, NULL} \ }; \ \ @@ -158,6 +160,43 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); return PyModuleDef_Init(&moduledef); \ } \ void BOOST_PP_CAT(init_module_, name)() +# else // ! HAS_CXX11 && Python 3.13+ +# define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* module) \ + { \ + return boost::python::detail::exec_module( \ + module, BOOST_PP_CAT(init_module_, name) ); \ + } \ + extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ + { \ + static PyModuleDef_Base initial_m_base = { \ + PyObject_HEAD_INIT(NULL) \ + 0, /* m_init */ \ + 0, /* m_index */ \ + 0 /* m_copy */ }; \ + static PyMethodDef initial_methods[] = { { 0, 0, 0, 0 } }; \ + \ + static PyModuleDef_Slot slots[] = { \ + {Py_mod_exec, reinterpret_cast(reinterpret_cast(BOOST_PP_CAT(exec_module_, name)))}, \ + {0, NULL} \ + }; \ + \ + static struct PyModuleDef moduledef = { \ + initial_m_base, \ + BOOST_PP_STRINGIZE(name), \ + 0, /* m_doc */ \ + 0, /* m_size */ \ + initial_methods, \ + slots, /* m_slots */ \ + 0, /* m_traverse */ \ + 0, /* m_clear */ \ + 0, /* m_free */ \ + }; \ + \ + return PyModuleDef_Init(&moduledef); \ + } \ + void BOOST_PP_CAT(init_module_, name)() +# endif // HAS_CXX11 && Python 3.13+ # endif // PY_VERSION_HEX >= 0x03050000 @@ -184,11 +223,15 @@ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name) # endif // HAS_CXX11 && Python 3 # if PY_VERSION_HEX >= 0x03050000 - -# define BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ +# if defined(HAS_CXX11) && (PY_VERSION_HEX >= 0x030D0000) +# define BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name, ...) \ + void BOOST_PP_CAT(init_module_,name)(); \ +extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name, __VA_ARGS__) +# else +# define BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ void BOOST_PP_CAT(init_module_,name)(); \ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) - +# endif // HAS_CXX11 && Python 3.13+ # endif // PY_VERSION_HEX >= 0x03050000 # endif // BOOST_PYTHON_MODULE_INIT From 40105f07b87ba2a2acead3bfb75ceab0532cf5cf Mon Sep 17 00:00:00 2001 From: O01eg <397177+o01eg@users.noreply.github.com> Date: Sat, 4 Apr 2026 22:53:54 +0400 Subject: [PATCH 4/5] Rename module to mod --- include/boost/python/module_init.hpp | 8 ++++---- src/module.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/boost/python/module_init.hpp b/include/boost/python/module_init.hpp index 05ead85c71..0d826be654 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -125,10 +125,10 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); # if defined(HAS_CXX11) && (PY_VERSION_HEX >= 0x030D0000) # define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name, ...) \ - int BOOST_PP_CAT(exec_module_,name)(PyObject* module) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ { \ return boost::python::detail::exec_module( \ - module, BOOST_PP_CAT(init_module_, name) ); \ + mod, BOOST_PP_CAT(init_module_, name) ); \ } \ extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ { \ @@ -162,10 +162,10 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); void BOOST_PP_CAT(init_module_, name)() # else // ! HAS_CXX11 && Python 3.13+ # define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ - int BOOST_PP_CAT(exec_module_,name)(PyObject* module) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ { \ return boost::python::detail::exec_module( \ - module, BOOST_PP_CAT(init_module_, name) ); \ + mod, BOOST_PP_CAT(init_module_, name) ); \ } \ extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ { \ diff --git a/src/module.cpp b/src/module.cpp index ef83d31807..22c4c5868a 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -54,10 +54,10 @@ BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef& moduledef, # if PY_VERSION_HEX >= 0x03050000 -BOOST_PYTHON_DECL int exec_module(PyObject* module, void(*init_function)()) +BOOST_PYTHON_DECL int exec_module(PyObject* mod, void(*init_function)()) { PyObject* retval = init_module_in_scope( - module, + mod, init_function); return retval ? 0 : -1; } From 542974f9445e1cf78247e4ac316a650167777ea5 Mon Sep 17 00:00:00 2001 From: O01eg <397177+o01eg@users.noreply.github.com> Date: Sat, 4 Apr 2026 23:12:47 +0400 Subject: [PATCH 5/5] Add tests --- test/fabscript | 2 ++ test/module_multi_phase_nogil.cpp | 25 +++++++++++++++++++++++++ test/module_multi_phase_nogil.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 test/module_multi_phase_nogil.cpp create mode 100644 test/module_multi_phase_nogil.py diff --git a/test/fabscript b/test/fabscript index 362ff82d18..f418ed3efb 100644 --- a/test/fabscript +++ b/test/fabscript @@ -178,5 +178,7 @@ python_version_major, python_version_minor = map(int, python.instance().version. tests.append(extension_test("module_multi_phase", condition=python_version_major > 3 or (python_version_major == 3 and python_version_minor >= 5))) +tests.append(extension_test("module_multi_phase_nogil", + condition=python_version_major > 3 or (python_version_major == 3 and python_version_minor >= 5))) default = report('report', tests, fail_on_failures=True) diff --git a/test/module_multi_phase_nogil.cpp b/test/module_multi_phase_nogil.cpp new file mode 100644 index 0000000000..6de74c29d7 --- /dev/null +++ b/test/module_multi_phase_nogil.cpp @@ -0,0 +1,25 @@ +// Test for BOOST_PYTHON_MODULE_MULTI_PHASE with optional mod_gil_not_used argument + +#include +#include + +// Simple function to export +int get_value() { + return 1234; +} + +#if defined(HAS_CXX11) && (PY_VERSION_HEX >= 0x030D0000) +// C++11 build with Python 3.13+: test with mod_gil_not_used option +BOOST_PYTHON_MODULE_MULTI_PHASE(module_multi_phase_nogil_ext, boost::python::mod_gil_not_used()) +{ + using namespace boost::python; + def("get_value", get_value); +} +#else +// C++98 build or Python 3.12-: test without optional arguments +BOOST_PYTHON_MODULE_MULTI_PHASE(module_multi_phase_nogil_ext) +{ + using namespace boost::python; + def("get_value", get_value); +} +#endif diff --git a/test/module_multi_phase_nogil.py b/test/module_multi_phase_nogil.py new file mode 100644 index 0000000000..30c6bcb984 --- /dev/null +++ b/test/module_multi_phase_nogil.py @@ -0,0 +1,29 @@ +""" +>>> from module_multi_phase_nogil_ext import * +>>> get_value() +1234 +>>> import sys, sysconfig +>>> Py_GIL_DISABLED = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) +>>> if Py_GIL_DISABLED and sys._is_gil_enabled(): +... print('GIL is enabled and should not be') +... else: +... print('okay') +okay +""" + +from __future__ import print_function + +def run(args = None): + import sys + import doctest + + if args is not None: + sys.argv = args + return doctest.testmod(sys.modules.get(__name__)) + +if __name__ == '__main__': + print("running...") + import sys + status = run()[0] + if (status == 0): print("Done.") + sys.exit(status)