* feat!: drop support for Python 3.8 The minimum supported version is now Python 3.9. pybind11 v3.0 was the last release that supports Python 3.8. The deprecation note said that support goes away in 3.1. Remove the code paths that this makes dead: - the `_PyObject_Vectorcall` fallback in `cast.h` - the `frame->f_code` and `frame->f_back` fallbacks in `pytypes.h` - the `PyFrame_FastToLocals` path in `get_type_override` - the conditional `Py_VISIT(Py_TYPE(self))` in `tp_traverse` - `PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX`, the pre-PyConfig interpreter init, and the `widen_chars` helpers in `embed.h` Assisted-by: ClaudeCode:claude-opus-5 * ci(appveyor): use Python 3.9 The AppVeyor job set `PYTHON: 38`, which makes the path `C:\Python38`. The image gives Python 3.9.13 as `C:\Python39`. Assisted-by: ClaudeCode:claude-opus-5 * feat!: require MSVC 2019 or newer Python 3.9 is the new minimum, so MSVC 2017 is no longer needed. Raise the compile-time floor to _MSC_VER 1920 and remove the workarounds that only applied below it: std::launder, fold expressions, weak_from_this, aligned new/delete, the C4100 warning helper, and the func_handle syntax error. AppVeyor now builds with Visual Studio 2019. Assisted-by: ClaudeCode:claude-opus-5 * fix(appveyor): build against the Python that has the test packages CMake 4 has no FindPythonLibs, so pybind11 uses FindPython. FindPython reads the registry before PATH and selected `C:\Python314-x64`, but the test packages go into the Python on PATH. Pass `Python_ROOT_DIR` to name the correct one. Also set `CMAKE_ARCH`. It was never set, so the architecture came from the generator. Visual Studio 2017 defaults to Win32, but Visual Studio 2019 defaults to x64, which made this x86 job build 64-bit code. Assisted-by: ClaudeCode:claude-opus-5 * fix(appveyor): give the linker Python's libs directory The build compiled but failed to link with LNK1104 on a bare `python39.lib`. That name comes from the `#pragma comment(lib, ...)` in pyconfig.h, so the linker needs the directory. CMake 4 has no FindPythonLibs, and FindPython does not add it for this Debug x86 build. Put it on LIB instead. The directory listing is temporary, to confirm the library is present. Assisted-by: ClaudeCode:claude-opus-5 * fix(appveyor): link the release Python library in Debug The image ships python39_d.lib next to python39.lib, so FindPython picks the debug import library for a Debug build. pybind11 undefines _DEBUG around Python.h, so pyconfig.h asks for python39.lib instead and the link failed with LNK1104. Name the release library for the debug slot. Setting LIB does not work, because MSBuild replaces it from the toolset, and it would link both import libraries. Assisted-by: ClaudeCode:claude-opus-5 * chore(appveyor): print link settings to debug LNK1104 Revert the two attempted fixes. Neither changed the failure: setting LIB does not survive MSBuild, and naming the release library for Python_LIBRARY_DEBUG had no effect. Print the Python cache entries and the link settings of a generated project file instead, to see what the linker really gets. Temporary. Assisted-by: ClaudeCode:claude-opus-5 * fix(appveyor): take the release Python library in Debug The generated project file linked C:\Python39\libs\python39_d.lib in the Debug configuration, because the image ships debug binaries next to the release ones. pybind11 undefines _DEBUG around Python.h, so pyconfig.h asks for python39.lib in a #pragma comment(lib), which nothing on the link line satisfies and no library directory holds. Map Debug to the release artifacts. Assisted-by: ClaudeCode:claude-opus-5 * chore(appveyor): drop the temporary link diagnostic Assisted-by: ClaudeCode:claude-opus-5 * fix(tests): guard the unraisable warning filter for pytest < 6 The distro pytest in the Clang and GCC Docker jobs has no PytestUnraisableExceptionWarning, so an unconditional filterwarnings mark makes pytest fail with an INTERNALERROR after the tests pass. Assisted-by: ClaudeCode:claude-opus-5 Claude-Session: https://claude.ai/code/session_01Aimf6HuSz1vLRwBnbxmCTc * docs: address review items on version hints, embed docs, and a PyPy xfail Extend Python_ADDITIONAL_VERSIONS through 3.15, describe the PyConfig behavior of initialize_interpreter, and drop the stale Python 3.8 wording from the PyPy xfail reason. Assisted-by: ClaudeCode:claude-opus-5 * Update README.rst Co-authored-by: Ralf W. Grosse-Kunstleve <rwgkio@gmail.com> --------- Co-authored-by: Ralf W. Grosse-Kunstleve <rwgkio@gmail.com>
6.6 KiB
Agent instructions
What this is
pybind11 v3 is a header-only C++ library that exposes C++ types to Python and vice versa. The "library" is entirely the headers under include/pybind11/; there is nothing to compile or link for a consumer. Everything else in the repo — the Python package, CMake tooling, and tests — exists to package, distribute, and verify those headers.
The version of record lives in include/pybind11/detail/common.h (as PYBIND11_VERSION_* macros); pybind11/_version.py parses it from there.
Build & test
The C++ tests need a compiler; the recommended flow uses CMake presets + uv:
cmake --workflow venv # set up a .venv and run all tests (config + build + check)
Break it apart to control Python version or target:
cmake --preset venv -DPYBIND11_CREATE_WITH_UV=3.13t # configure (e.g. free-threaded 3.13)
cmake --build --preset venv # build the test extension modules
cmake --build --preset venv -t cpptest # build/run just the C++ (Catch2) tests
The default preset uses an existing Python/venv rather than creating one. Presets default to Ninja, Debug, PYBIND11_WERROR=ON, DOWNLOAD_CATCH=ON, DOWNLOAD_EIGEN=ON, and export compile_commands.json.
Manual full setup (no presets):
cmake -S . -B build -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON
cmake --build build -j4
Test targets
Run via cmake --build build --target <name>:
check— everything (pytest + cpptest + cmake build tests)pytest— Python tests onlycpptest— C++ Catch2 tests onlytest_cmake_build— install / add_subdirectory integration tests
Tests come in pairs: tests/test_<name>.cpp (binds C++ test fixtures into a module) and tests/test_<name>.py (exercises them with pytest). To build only a subset, configure with -DPYBIND11_TEST_OVERRIDE="test_callbacks;test_pickling" (names without extension; empty = all).
Run a single Python test directly (the .so is not installed into the venv, so run from the build tree's tests/ dir):
cd build && source .venv/bin/activate && cd tests
python -m pytest test_callbacks.py -k some_test
Pass pytest flags via PYTEST_ADDOPTS, e.g. env PYTEST_ADDOPTS="-s -x" cmake --build build --target pytest.
nox shortcuts (minimal setup, slower)
nox -s lint, nox -s tests-3.9, nox -s docs -- serve, nox -s build. With uvx nox you don't even need nox installed.
Linting
Use prek -a --quiet (Rust version of pre-commit run -a) before committing. Pre-commit handles all formatting and most linting.
clang-tidy support is built into CMake (cmake --preset tidy), normally run in CI/Docker, not locally.
Architecture of the headers
include/pybind11/pybind11.h is the main entry point that most users include; it pulls in the core machinery. Key layers:
detail/— internal implementation, not part of the public API. This is where the hard parts live:type_caster_base.h,cast.h(include/pybind11/cast.h) — the type conversion system that maps C++ ↔ Python values. Most binding behavior ultimately routes through type casters.internals.h— the per-interpreter global state (registered types, instances, etc.), shared across modules via a capsule. ABI compatibility is gated by an ID; changing internals layout is an ABI break.struct_smart_holder.h/using_smart_holder.h— smart_holder was added in v3, enabling safe passing of objects betweenshared_ptr/unique_ptrand Python ownership. It is the recommended holder for most situations, but for backward compatibility it is not the default holder, and there are no plans to make it the default holder in the future.class.h,init.h— class registration and constructor (py::init) machinery.
pytypes.h— C++ wrappers for Python objects (py::object,py::dict,py::str, …) with reference counting.- Feature headers — opt-in includes:
stl.h/stl//stl_bind.h(STL conversions),numpy.h+eigen/(array/matrix interop),functional.h(std::function),chrono.h,complex.h,eval.h,embed.h(embedding the interpreter),iostream.h,gil.h/gil_safe_call_once.h,subinterpreter.h,native_enum.h,typing.h,warnings.h. conduit/—pybind11_conduit_v1, a stable cross-binding-framework protocol for sharing C++ objects between independently-built extension modules (even different pybind11 versions / other frameworks).
Because consumers can mix modules built against different pybind11 versions in one process, ABI stability is a first-class concern — be deliberate about anything touching internals.h, holders, or the platform ABI id (conduit/pybind11_platform_abi_id.h).
The Python package (pybind11/)
Separate from the C++ library: it ships the headers and CMake config so downstream projects can build extensions. Notable modules:
setup_helpers.py—Pybind11Extension/build_extand theintree_extensionshelper for setuptools-based builds; this file is intentionally standalone-copyable.commands.py/__main__.py—python -m pybind11 --includes/--cmakediretc., used by build systems to locate headers and CMake files.
Packaging produces two distributions: the normal pybind11 (headers live inside the package, found via the functions above) and pybind11-global (installs to <env>/include/pybind11 and <env>/share/cmake/pybind11 for system-wide CMake discovery). Build with nox -s build and nox -s build_global. The build backend is scikit-build-core. Packaging tests live in tests/extra_python_package (nox -s tests_packaging).
CMake tooling (tools/)
pybind11Common.cmake, pybind11Tools.cmake (classic FindPythonLibs path), and pybind11NewTools.cmake (CMake 3.12+ FindPython path, selected with -DPYBIND11_FINDPYTHON=ON) implement the pybind11_add_module() interface and the pybind11::* interface targets that downstream CMakeLists.txt files consume.
Conventions
- Small, self-contained PRs; this project deliberately favors minimal-code general solutions.
- Any new functionality needs a test (add to or create the paired
.cpp/.pyintests/, and register the test intests/CMakeLists.txt). - Bug fixes should be paired with new tests that fail without the fix.
- Try to add to an existing test file if possible (more files slow down tests)
- Default C++ standard follows the consumer's toolchain; CI exercises a wide matrix (CPython 3.9+, PyPy, GraalPy; multiple compilers and C++ standards). Keep changes portable across that matrix.
- PR descriptions follow a template, the key part is under "Suggested changelog entry", which is how we generate our changelog (with
nox -s make_changelog)