From 8c3199591d0d978d0210ef84dcd21abf42f5e837 Mon Sep 17 00:00:00 2001 From: Stephen G Tuggy Date: Thu, 4 Jun 2026 00:15:55 +0000 Subject: [PATCH 1/8] Fix Windows CI build. --- .github/workflows/test-windows.yml | 77 ++++++++++++++++++++++-------- .github/workflows/vcpkg.json | 20 ++++++++ 2 files changed, 77 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/vcpkg.json diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 576e1f441..d13b6bf86 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -10,40 +10,77 @@ jobs: matrix: python-version: [3.7] + env: + VCPKG_ROOT: '${{ github.workspace }}/vcpkg' + VCPKG_ASSET_SOURCES: "${{ github.workspace }}/vcpkgAssets" + X_VCPKG_ASSET_SOURCES: clear;x-azurl,file:///${{ github.workspace }}/vcpkgAssets,,readwrite + VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}/vcpkgBin,readwrite + VCPKG_BUILD_TREES: "${{ github.workspace }}/vcpkgBuild" + VCPKG_INSTALLED_DIR: "${{ github.workspace }}/vcpkgInstalled" + VCPKG_INSTALL_OPTIONS: "--x-buildtrees-root=${{ github.workspace }}/vcpkgBuild" + VCPKG_DEFAULT_TRIPLET: 'x64-windows' + VCPKG_DEFAULT_HOST_TRIPLET: 'x64-windows' + steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + + - name: Restore vcpkg cache + id: cache-vcpkg-restore + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + "${{ github.workspace }}/vcpkgAssets" + "${{ github.workspace }}/vcpkgBCache" + "${{ github.workspace }}/vcpkgBin" + "${{ github.workspace }}/vcpkgBuild" + key: vcpkgCache-${{ github.repository_owner }}-${{ matrix.os }}-${{ matrix.triplet }}-${{ hashFiles('vcpkg.json') }} + + - name: Create directories on cache miss + run: | + mkdir -p "${{ github.workspace }}/vcpkgAssets" + mkdir -p "${{ github.workspace }}/vcpkgBCache" + mkdir -p "${{ github.workspace }}/vcpkgBin" + mkdir -p "${{ github.workspace }}/vcpkgBuild" + if: steps.cache-vcpkg-restore.outputs.cache-hit != 'true' + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - - uses: microsoft/setup-msbuild@v2 + + - uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0 + - name: setup boost prerequisites - uses: lukka/run-vcpkg@v6 + uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11.6 with: - vcpkgGitCommitId: '88b1071e39f13b632644d9d953738d345a4ac055' - vcpkgDirectory: '${{ runner.workspace }}/vcpkg' - vcpkgTriplet: x64-windows - vcpkgArguments: > - boost-config - boost-core - boost-function - boost-graph - boost-iterator - boost-lexical-cast - boost-mpl - boost-preprocessor - boost-smart-ptr - boost-static-assert - boost-align + vcpkgDirectory: '${{ github.workspace }}/vcpkg' + binaryCachePath: "${{ github.workspace }}/vcpkgBCache" + runVcpkgInstall: true + vcpkgJsonGlob: .github/workflows/vcpkg.json + - name: setup faber run: | python -m pip install --upgrade pip python -m pip install setuptools faber numpy faber --info=tools cxx + - name: build shell: cmd run: | - faber --builddir=build cxx.name=msvc --log=commands --log=output --with-boost-include=${{ runner.workspace }}\vcpkg\installed\x64-windows\include -j4 + faber --builddir=build cxx.name=msvc --log=commands --log=output --with-boost-include="${{ github.workspace }}/vcpkgInstalled/x64-windows/include" -j4 + - name: test shell: cmd run: | - faber --builddir=build cxx.name=msvc --with-boost-include=${{ runner.workspace }}\vcpkg\installed\x64-windows\include -j4 test.report + faber --builddir=build cxx.name=msvc --with-boost-include="${{ github.workspace }}/vcpkgInstalled/x64-windows/include" -j4 test.report + + - name: Save vcpkg cache + id: cache-vcpkg-save + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + "${{ github.workspace }}/vcpkgAssets" + "${{ github.workspace }}/vcpkgBCache" + "${{ github.workspace }}/vcpkgBin" + "${{ github.workspace }}/vcpkgBuild" + key: ${{ steps.cache-vcpkg-restore.outputs.cache-primary-key }} + if: steps.cache-vcpkg-restore.outputs.cache-hit != 'true' diff --git a/.github/workflows/vcpkg.json b/.github/workflows/vcpkg.json new file mode 100644 index 000000000..5a9e71d23 --- /dev/null +++ b/.github/workflows/vcpkg.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json", + "name": "boost-python", + "version-string": "1.91.0", + "builtin-baseline": "f3e10653cc27d62a37a3763cd84b38bca07c6075", + "dependencies": [ + "boost-config", + "boost-core", + "boost-function", + "boost-graph", + "boost-iterator", + "boost-lexical-cast", + "boost-mpl", + "boost-preprocessor", + "boost-smart-ptr", + "boost-static-assert", + "boost-align", + "python3" + ] +} From 29a44809c1ac474ea20cc18ca1fd0468efebd3cb Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Sun, 3 May 2026 15:55:57 +0900 Subject: [PATCH 2/8] refactor: use PyUnicode_AsUTF8 with Python 3.14+ _PyUnicode_AsString is deprecated since Python 3.14 https://docs.python.org/3.14/deprecations/index.html#id2 fix #512 --- src/converter/builtin_converters.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/converter/builtin_converters.cpp b/src/converter/builtin_converters.cpp index ee2d5b479..227fe2f80 100644 --- a/src/converter/builtin_converters.cpp +++ b/src/converter/builtin_converters.cpp @@ -40,22 +40,18 @@ namespace // An lvalue conversion function which extracts a char const* from a // Python String. -#if PY_VERSION_HEX < 0x03000000 void* convert_to_cstring(PyObject* obj) { +#if PY_VERSION_HEX < 0x03000000 return PyString_Check(obj) ? PyString_AsString(obj) : 0; - } #elif PY_VERSION_HEX < 0x03070000 - void* convert_to_cstring(PyObject* obj) - { return PyUnicode_Check(obj) ? _PyUnicode_AsString(obj) : 0; - } -#else - void* convert_to_cstring(PyObject* obj) - { +#elif PY_VERSION_HEX < 0x030E0000 return PyUnicode_Check(obj) ? const_cast(reinterpret_cast(_PyUnicode_AsString(obj))) : 0; - } +#else + return PyUnicode_Check(obj) ? const_cast(reinterpret_cast(PyUnicode_AsUTF8(obj))) : 0; #endif + } // Given a target type and a SlotPolicy describing how to perform a // given conversion, registers from_python converters which use the From 99f58c9b283c60f9cda54d667518c89fe9066b03 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 3/8] Add support for multi-phase module initialization --- include/boost/python/module.hpp | 4 + include/boost/python/module_init.hpp | 199 ++++++++++++++++++++++++++- src/module.cpp | 48 +++++++ test/fabscript | 9 ++ test/module_multi_phase.cpp | 15 ++ test/module_multi_phase.py | 23 ++++ test/module_multi_phase_nogil.cpp | 25 ++++ test/module_multi_phase_nogil.py | 29 ++++ test/module_multi_phase_state.cpp | 30 ++++ test/module_multi_phase_state.py | 23 ++++ 10 files changed, 398 insertions(+), 7 deletions(-) create mode 100644 test/module_multi_phase.cpp create mode 100644 test/module_multi_phase.py create mode 100644 test/module_multi_phase_nogil.cpp create mode 100644 test/module_multi_phase_nogil.py create mode 100644 test/module_multi_phase_state.cpp create mode 100644 test/module_multi_phase_state.py diff --git a/include/boost/python/module.hpp b/include/boost/python/module.hpp index 8ad69f5a3..4ec34868b 100644 --- a/include/boost/python/module.hpp +++ b/include/boost/python/module.hpp @@ -9,5 +9,9 @@ # 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 +# define BOOST_PYTHON_MODULE_WITH_STATE BOOST_PYTHON_MODULE_WITH_STATE_INIT +# endif #endif // MODULE_DWA20011221_HPP diff --git a/include/boost/python/module_init.hpp b/include/boost/python/module_init.hpp index 390db82cf..abf1ce252 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -9,11 +9,16 @@ # include # include +# if PY_VERSION_HEX >= 0x03050000 +# include +# include +# endif + # ifndef BOOST_PYTHON_MODULE_INIT 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 +44,7 @@ inline bool gil_not_used_option(F &&, O &&...o) { } } -#endif // HAS_CXX11 +# endif // HAS_CXX11 namespace detail { @@ -47,11 +52,19 @@ namespace detail { BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef&, void(*)(), bool gil_not_used = false); -#else +# if PY_VERSION_HEX >= 0x03050000 + +BOOST_PYTHON_DECL int exec_module(PyObject*, void(*)()); + +BOOST_PYTHON_DECL int exec_module_with_state(PyObject*, void(*)(void*)); + +# endif // PY_VERSION_HEX >= 0x03050000 + +# else // PY_VERSION_HEX >= 0x03000000 BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); -#endif +# endif // PY_VERSION_HEX >= 0x03000000 }}} @@ -115,7 +128,161 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); void BOOST_PP_CAT(init_module_, name)() # endif // HAS_CXX11 -# else +# if PY_VERSION_HEX >= 0x03050000 + +# if defined(HAS_CXX11) && (PY_VERSION_HEX >= 0x030D0000) +# define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name, ...) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ + { \ + return boost::python::detail::exec_module( \ + mod, 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)))}, \ + {Py_mod_gil, boost::python::detail::gil_not_used_option(__VA_ARGS__) ? Py_MOD_GIL_NOT_USED : Py_MOD_GIL_USED}, \ + {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)() +# define _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType, ...) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ + { \ + return boost::python::detail::exec_module_with_state( \ + mod, reinterpret_cast(BOOST_PP_CAT(init_module_, name)) ); \ + } \ + extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ + { \ + BOOST_STATIC_ASSERT_MSG(boost::is_pod::value, \ + "Module State MUST be a plain POD structure!"); \ + 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)))}, \ + {Py_mod_gil, boost::python::detail::gil_not_used_option(__VA_ARGS__) ? Py_MOD_GIL_NOT_USED : Py_MOD_GIL_USED}, \ + {0, NULL} \ + }; \ + \ + static struct PyModuleDef moduledef = { \ + initial_m_base, \ + BOOST_PP_STRINGIZE(name), \ + 0, /* m_doc */ \ + sizeof(StateType), /* 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)(StateType* state) +# else // ! HAS_CXX11 && Python 3.13+ +# define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ + { \ + return boost::python::detail::exec_module( \ + mod, 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)() +# define _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ + { \ + return boost::python::detail::exec_module_with_state( \ + mod, reinterpret_cast(BOOST_PP_CAT(init_module_, name)) ); \ + } \ + extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ + { \ + BOOST_STATIC_ASSERT_MSG(boost::is_pod::value, \ + "Module State MUST be a plain POD structure!"); \ + 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 */ \ + sizeof(StateType), /* 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)(StateType* state) +# endif // HAS_CXX11 && Python 3.13+ + +# endif // PY_VERSION_HEX >= 0x03050000 + +# else // ! PY_VERSION_HEX >= 0x03000000 # define _BOOST_PYTHON_MODULE_INIT(name) \ void BOOST_PP_CAT(init,name)() \ @@ -125,7 +292,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, ...) \ @@ -137,6 +304,24 @@ 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 -# endif +# if PY_VERSION_HEX >= 0x03050000 +# 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__) +# define BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType, ...) \ + void BOOST_PP_CAT(init_module_,name)(StateType* state); \ +extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType, __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) +# define BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType) \ + void BOOST_PP_CAT(init_module_,name)(StateType* state); \ +extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType) +# endif // HAS_CXX11 && Python 3.13+ +# endif // PY_VERSION_HEX >= 0x03050000 + +# endif // BOOST_PYTHON_MODULE_INIT #endif // MODULE_INIT_DWA20020722_HPP diff --git a/src/module.cpp b/src/module.cpp index c32f4187b..200b0bb78 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -26,6 +26,34 @@ namespace return m; } +# if PY_VERSION_HEX >= 0x03050000 + class init_function_with_state { + public: + init_function_with_state(void(*init_function)(void*), void* state) + : init_function_(init_function), state_(state) {} + + void operator()() const { init_function_(state_); } + private: + void(*const init_function_)(void*); + void* const state_; + }; + + PyObject* init_module_in_scope_with_state(PyObject* m, void(*init_function)(void*)) + { + if (m != 0) + { + // Create the current module scope + object m_obj(((borrowed_reference_t*)m)); + scope current_module(m_obj); + + void* state = PyModule_GetState(m); + + if (handle_exception(init_function_with_state(init_function, state))) return NULL; + } + + return m; + } +# endif } BOOST_PYTHON_DECL void scope_setattr_doc(char const* name, object const& x, char const* doc) @@ -52,6 +80,26 @@ BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef& moduledef, init_function); } +# if PY_VERSION_HEX >= 0x03050000 + +BOOST_PYTHON_DECL int exec_module(PyObject* mod, void(*init_function)()) +{ + PyObject* retval = init_module_in_scope( + mod, + init_function); + return retval ? 0 : -1; +} + +BOOST_PYTHON_DECL int exec_module_with_state(PyObject* mod, void(*init_function)(void*)) +{ + PyObject* retval = init_module_in_scope_with_state( + mod, + init_function); + return retval ? 0 : -1; +} + +# endif + #else namespace diff --git a/test/fabscript b/test/fabscript index 7cf22f9c0..c6d834aa0 100644 --- a/test/fabscript +++ b/test/fabscript @@ -174,4 +174,13 @@ 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))) +tests.append(extension_test("module_multi_phase_nogil", + condition=python_version_major > 3 or (python_version_major == 3 and python_version_minor >= 5))) +tests.append(extension_test("module_multi_phase_state", + 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 000000000..dcbbe7430 --- /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 000000000..138f5d982 --- /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) diff --git a/test/module_multi_phase_nogil.cpp b/test/module_multi_phase_nogil.cpp new file mode 100644 index 000000000..6de74c29d --- /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 000000000..30c6bcb98 --- /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) diff --git a/test/module_multi_phase_state.cpp b/test/module_multi_phase_state.cpp new file mode 100644 index 000000000..0e497fc9c --- /dev/null +++ b/test/module_multi_phase_state.cpp @@ -0,0 +1,30 @@ +// 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 +#include + +using namespace boost::python; + +struct TestState +{ + int x; +}; + +int get_state_x() { + TestState* state = reinterpret_cast(PyModule_GetState(import("module_multi_phase_state_ext").ptr())); + if (state != nullptr) { + return state->x; + } + return -1; +} + +BOOST_PYTHON_MODULE_WITH_STATE(module_multi_phase_state_ext, TestState) +{ + state->x = 42; + def("x", get_state_x); +} + +#include "module_tail.cpp" diff --git a/test/module_multi_phase_state.py b/test/module_multi_phase_state.py new file mode 100644 index 000000000..1df4b9623 --- /dev/null +++ b/test/module_multi_phase_state.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_state_ext +>>> module_multi_phase_state_ext.x() +42 +""" + +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 d7ff7199511c7b0116276c8fff5d05ba694949d1 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Sun, 5 Apr 2026 17:36:56 +0900 Subject: [PATCH 4/8] feat(vector_indexing_suite): implement __iadd__ --- .../boost/python/suite/indexing/vector_indexing_suite.hpp | 8 ++++++++ test/vector_indexing_suite.py | 3 +++ 2 files changed, 11 insertions(+) diff --git a/include/boost/python/suite/indexing/vector_indexing_suite.hpp b/include/boost/python/suite/indexing/vector_indexing_suite.hpp index 34c29ecc6..6ca31896a 100644 --- a/include/boost/python/suite/indexing/vector_indexing_suite.hpp +++ b/include/boost/python/suite/indexing/vector_indexing_suite.hpp @@ -61,6 +61,7 @@ namespace boost { namespace python { extension_def(Class& cl) { cl + .def("__iadd__", &base_iadd) .def("append", &base_append) .def("extend", &base_extend) ; @@ -235,6 +236,13 @@ namespace boost { namespace python { container_utils::extend_container(temp, v); DerivedPolicies::extend(container, temp.begin(), temp.end()); } + + static object + base_iadd(Container& container, object v) + { + base_extend(container, v); + return object(container); + } }; }} // namespace boost::python diff --git a/test/vector_indexing_suite.py b/test/vector_indexing_suite.py index 478cd0151..5e5e8f7a3 100644 --- a/test/vector_indexing_suite.py +++ b/test/vector_indexing_suite.py @@ -320,6 +320,9 @@ >>> v.extend(['f','g','h','i','j']) >>> print_xvec(v) [ a b c d e f g h i j ] +>>> v += ['k','l','m'] +>>> print_xvec(v) +[ a b c d e f g h i j k l m ] ##################################################################### # extend using a generator expression From c574084154f908a2d14c4c24866982d527c15111 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Sun, 5 Apr 2026 16:20:09 +0900 Subject: [PATCH 5/8] feat(indexing_suite): implement clear --- include/boost/python/suite/indexing/indexing_suite.hpp | 7 +++++++ include/boost/python/suite/indexing/map_indexing_suite.hpp | 6 ++++++ .../boost/python/suite/indexing/vector_indexing_suite.hpp | 6 ++++++ test/map_indexing_suite.py | 4 ++++ test/vector_indexing_suite.py | 4 ++++ 5 files changed, 27 insertions(+) diff --git a/include/boost/python/suite/indexing/indexing_suite.hpp b/include/boost/python/suite/indexing/indexing_suite.hpp index 3469a2a40..140e3a665 100644 --- a/include/boost/python/suite/indexing/indexing_suite.hpp +++ b/include/boost/python/suite/indexing/indexing_suite.hpp @@ -184,6 +184,7 @@ namespace boost { namespace python { .def("__getitem__", &base_get_item) .def("__contains__", &base_contains) .def("__iter__", def_iterator()) + .def("clear", &base_clear) ; DerivedPolicies::extension_def(cl); @@ -288,6 +289,12 @@ namespace boost { namespace python { return false; } } + + static void + base_clear(Container& container) + { + return DerivedPolicies::clear(container); + } }; }} // namespace boost::python diff --git a/include/boost/python/suite/indexing/map_indexing_suite.hpp b/include/boost/python/suite/indexing/map_indexing_suite.hpp index 7fbad4cac..567abe4e7 100644 --- a/include/boost/python/suite/indexing/map_indexing_suite.hpp +++ b/include/boost/python/suite/indexing/map_indexing_suite.hpp @@ -137,6 +137,12 @@ namespace boost { namespace python { container.erase(i); } + static void + clear(Container& container) + { + container.clear(); + } + static size_t size(Container& container) { diff --git a/include/boost/python/suite/indexing/vector_indexing_suite.hpp b/include/boost/python/suite/indexing/vector_indexing_suite.hpp index 6ca31896a..264eac013 100644 --- a/include/boost/python/suite/indexing/vector_indexing_suite.hpp +++ b/include/boost/python/suite/indexing/vector_indexing_suite.hpp @@ -135,6 +135,12 @@ namespace boost { namespace python { container.erase(container.begin()+from, container.begin()+to); } + static void + clear(Container& container) + { + container.clear(); + } + static size_t size(Container& container) { diff --git a/test/map_indexing_suite.py b/test/map_indexing_suite.py index 6d3e57a10..939d984c7 100644 --- a/test/map_indexing_suite.py +++ b/test/map_indexing_suite.py @@ -202,6 +202,10 @@ ... dom = el.data() joel kimpo +>>> tm.clear() +>>> print_xmap(tm) +[ ] + ##################################################################### # Test custom converter... ##################################################################### diff --git a/test/vector_indexing_suite.py b/test/vector_indexing_suite.py index 5e5e8f7a3..24d1caba4 100644 --- a/test/vector_indexing_suite.py +++ b/test/vector_indexing_suite.py @@ -337,6 +337,10 @@ >>> print_xvec(v) [ a b c d e f h i j ] +>>> v.clear() +>>> print_xvec(v) +[ ] + ##################################################################### # vector of strings ##################################################################### From 5762cb912f5889e47f3b390b2b0d8d87e6f728a5 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Sun, 5 Apr 2026 16:13:45 +0900 Subject: [PATCH 6/8] feat(vector_indexing_suite): implement count --- .../suite/indexing/vector_indexing_suite.hpp | 16 ++++++++++++++++ test/vector_indexing_suite.py | 7 +++++++ 2 files changed, 23 insertions(+) diff --git a/include/boost/python/suite/indexing/vector_indexing_suite.hpp b/include/boost/python/suite/indexing/vector_indexing_suite.hpp index 264eac013..7cbdd2c6e 100644 --- a/include/boost/python/suite/indexing/vector_indexing_suite.hpp +++ b/include/boost/python/suite/indexing/vector_indexing_suite.hpp @@ -63,6 +63,7 @@ namespace boost { namespace python { cl .def("__iadd__", &base_iadd) .def("append", &base_append) + .def("count", &base_count) .def("extend", &base_extend) ; } @@ -208,6 +209,21 @@ namespace boost { namespace python { } private: + + static size_t + base_count(Container& container, object v) + { + extract elem(v); + if (elem.check()) { + return std::count(container.begin(), container.end(), elem()); + } else { + extract elem(v); + if (!elem.check()) { + return 0; + } + return std::count(container.begin(), container.end(), elem()); + } + } static void base_append(Container& container, object v) diff --git a/test/vector_indexing_suite.py b/test/vector_indexing_suite.py index 24d1caba4..6abca6140 100644 --- a/test/vector_indexing_suite.py +++ b/test/vector_indexing_suite.py @@ -297,6 +297,13 @@ >>> assert not 12345 in v ##################################################################### +# Count +##################################################################### +>>> v.count('a') +1 +>>> v.count(12345) +0 + # Show that iteration allows mutable access to the elements ##################################################################### >>> v[:] = ['a','b','c','d','e'] # reset again From 30834f88715a89622589afad91579bcc7a316570 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Sun, 5 Apr 2026 17:50:35 +0900 Subject: [PATCH 7/8] feat(vector_indexing_suite): implement remove --- .../suite/indexing/vector_indexing_suite.hpp | 37 +++++++++++++++++++ test/vector_indexing_suite.py | 8 ++++ 2 files changed, 45 insertions(+) diff --git a/include/boost/python/suite/indexing/vector_indexing_suite.hpp b/include/boost/python/suite/indexing/vector_indexing_suite.hpp index 7cbdd2c6e..fd5d86c40 100644 --- a/include/boost/python/suite/indexing/vector_indexing_suite.hpp +++ b/include/boost/python/suite/indexing/vector_indexing_suite.hpp @@ -65,6 +65,7 @@ namespace boost { namespace python { .def("append", &base_append) .def("count", &base_count) .def("extend", &base_extend) + .def("remove", &base_remove) ; } @@ -265,6 +266,42 @@ namespace boost { namespace python { base_extend(container, v); return object(container); } + + static void + base_remove(Container& container, object v) + { + extract key(v); + if (key.check()) + { + auto i = std::find(container.begin(), container.end(), key()); + if (i == container.end()) + { + PyErr_SetString(PyExc_ValueError, "remove(x): x not in vector_indexing_suite"); + throw_error_already_set(); + } + container.erase(i); + } + else + { + extract key(v); + if (key.check()) + { + auto i = std::find(container.begin(), container.end(), key()); + if (i == container.end()) + { + PyErr_SetString(PyExc_ValueError, "remove(x): x not in vector_indexing_suite"); + throw_error_already_set(); + } + container.erase(i); + } + else + { + PyErr_SetString(PyExc_TypeError, + "Attempting to remove an invalid type"); + throw_error_already_set(); + } + } + } }; }} // namespace boost::python diff --git a/test/vector_indexing_suite.py b/test/vector_indexing_suite.py index 6abca6140..c0b72c6ab 100644 --- a/test/vector_indexing_suite.py +++ b/test/vector_indexing_suite.py @@ -78,6 +78,14 @@ >>> print_xvec(v) [ yaba c d e ] +>>> v2 = XVec() +>>> v2[:] = [X('b'), X('a'), X('c'), X('b'), X('a')] +>>> try: v2.remove("z") +... except ValueError: pass +>>> v2.remove("a") +>>> print_xvec(v2) +[ b c b a ] + ##################################################################### # Calling a mutating function of a container element ##################################################################### From ed1e5e9be00558ffe1196ab72eb0053e0e27d0c4 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Sun, 5 Apr 2026 16:29:32 +0900 Subject: [PATCH 8/8] feat(vector_indexing_suite): implement reverse --- .../python/suite/indexing/vector_indexing_suite.hpp | 10 ++++++++++ test/vector_indexing_suite.py | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/include/boost/python/suite/indexing/vector_indexing_suite.hpp b/include/boost/python/suite/indexing/vector_indexing_suite.hpp index fd5d86c40..f1b27c738 100644 --- a/include/boost/python/suite/indexing/vector_indexing_suite.hpp +++ b/include/boost/python/suite/indexing/vector_indexing_suite.hpp @@ -66,6 +66,7 @@ namespace boost { namespace python { .def("count", &base_count) .def("extend", &base_extend) .def("remove", &base_remove) + .def("reverse", &base_reverse) ; } @@ -302,6 +303,15 @@ namespace boost { namespace python { } } } + + static void + base_reverse(Container& container) + { + using std::swap; + const unsigned n = size(container); + for (unsigned i = 0; i < n / 2; i++) + swap(container[i], container[n - i - 1]); + } }; }} // namespace boost::python diff --git a/test/vector_indexing_suite.py b/test/vector_indexing_suite.py index c0b72c6ab..fb2b92a1e 100644 --- a/test/vector_indexing_suite.py +++ b/test/vector_indexing_suite.py @@ -312,6 +312,14 @@ >>> v.count(12345) 0 +##################################################################### +# Reverse +##################################################################### +>>> v.reverse() +>>> print_xvec(v) +[ e d c b a ] + +##################################################################### # Show that iteration allows mutable access to the elements ##################################################################### >>> v[:] = ['a','b','c','d','e'] # reset again