Merge branch 'master' into stable

This commit is contained in:
Henry Schreiner 2024-03-27 19:52:11 -04:00
commit 01ab935612
91 changed files with 2326 additions and 565 deletions

View File

@ -135,7 +135,7 @@ The valid options are:
* Use `-G` and the name of a generator to use something different. `cmake * Use `-G` and the name of a generator to use something different. `cmake
--help` lists the generators available. --help` lists the generators available.
- On Unix, setting `CMAKE_GENERATER=Ninja` in your environment will give - On Unix, setting `CMAKE_GENERATER=Ninja` in your environment will give
you automatic mulithreading on all your CMake projects! you automatic multithreading on all your CMake projects!
* Open the `CMakeLists.txt` with QtCreator to generate for that IDE. * Open the `CMakeLists.txt` with QtCreator to generate for that IDE.
* You can use `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to generate the `.json` file * You can use `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to generate the `.json` file
that some tools expect. that some tools expect.

View File

@ -4,4 +4,12 @@ updates:
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "weekly"
groups:
actions:
patterns:
- "*"
ignore:
- dependency-name: actions/checkout
versions:
- "<5"

View File

@ -69,13 +69,12 @@ jobs:
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Python ${{ matrix.python }} - name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
allow-prereleases: true
- name: Setup Boost (Linux) - name: Setup Boost (Linux)
# Can't use boost + define _ # Can't use boost + define _
@ -87,11 +86,11 @@ jobs:
run: brew install boost run: brew install boost
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.14 uses: jwlawson/actions-setup-cmake@v2.0
- name: Cache wheels - name: Cache wheels
if: runner.os == 'macOS' if: runner.os == 'macOS'
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
# This path is specific to macOS - we really only need it for PyPy NumPy wheels # This path is specific to macOS - we really only need it for PyPy NumPy wheels
# See https://github.com/actions/cache/blob/master/examples.md#python---pip # See https://github.com/actions/cache/blob/master/examples.md#python---pip
@ -109,12 +108,15 @@ jobs:
run: python -m pip install pytest-github-actions-annotate-failures run: python -m pip install pytest-github-actions-annotate-failures
# First build - C++11 mode and inplace # First build - C++11 mode and inplace
# More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON here. # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON here
# (same for PYBIND11_NUMPY_1_ONLY, but requires a NumPy 1.x at runtime).
- name: Configure C++11 ${{ matrix.args }} - name: Configure C++11 ${{ matrix.args }}
run: > run: >
cmake -S . -B . cmake -S . -B .
-DPYBIND11_WERROR=ON -DPYBIND11_WERROR=ON
-DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON
-DPYBIND11_NUMPY_1_ONLY=ON
-DDOWNLOAD_CATCH=ON -DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=11 -DCMAKE_CXX_STANDARD=11
@ -139,11 +141,13 @@ jobs:
# Second build - C++17 mode and in a build directory # Second build - C++17 mode and in a build directory
# More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF here. # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF here.
# (same for PYBIND11_NUMPY_1_ONLY, but requires a NumPy 1.x at runtime).
- name: Configure C++17 - name: Configure C++17
run: > run: >
cmake -S . -B build2 cmake -S . -B build2
-DPYBIND11_WERROR=ON -DPYBIND11_WERROR=ON
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
-DPYBIND11_NUMPY_1_ONLY=ON
-DDOWNLOAD_CATCH=ON -DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD=17
@ -195,9 +199,10 @@ jobs:
matrix: matrix:
include: include:
# TODO: Fails on 3.10, investigate # TODO: Fails on 3.10, investigate
- python-version: "3.9" # JOB DISABLED (NEEDS WORK): https://github.com/pybind/pybind11/issues/4889
python-debug: true # - python-version: "3.9"
valgrind: true # python-debug: true
# valgrind: true
- python-version: "3.11" - python-version: "3.11"
python-debug: false python-debug: false
@ -205,20 +210,20 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Python ${{ matrix.python-version }} (deadsnakes) - name: Setup Python ${{ matrix.python-version }} (deadsnakes)
uses: deadsnakes/action@v3.0.1 uses: deadsnakes/action@v3.1.0
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
debug: ${{ matrix.python-debug }} debug: ${{ matrix.python-debug }}
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.14 uses: jwlawson/actions-setup-cmake@v2.0
- name: Valgrind cache - name: Valgrind cache
if: matrix.valgrind if: matrix.valgrind
uses: actions/cache@v3 uses: actions/cache@v4
id: cache-valgrind id: cache-valgrind
with: with:
path: valgrind path: valgrind
@ -302,12 +307,15 @@ jobs:
- clang: 15 - clang: 15
std: 20 std: 20
container_suffix: "-bullseye" container_suffix: "-bullseye"
- clang: 16
std: 20
container_suffix: "-bullseye"
name: "🐍 3 • Clang ${{ matrix.clang }} • C++${{ matrix.std }} • x64" name: "🐍 3 • Clang ${{ matrix.clang }} • C++${{ matrix.std }} • x64"
container: "silkeh/clang:${{ matrix.clang }}${{ matrix.container_suffix }}" container: "silkeh/clang:${{ matrix.clang }}${{ matrix.container_suffix }}"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Add wget and python3 - name: Add wget and python3
run: apt-get update && apt-get install -y python3-dev python3-numpy python3-pytest libeigen3-dev run: apt-get update && apt-get install -y python3-dev python3-numpy python3-pytest libeigen3-dev
@ -341,7 +349,7 @@ jobs:
container: nvidia/cuda:12.2.0-devel-ubuntu22.04 container: nvidia/cuda:12.2.0-devel-ubuntu22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
# tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND # tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND
- name: Install 🐍 3 - name: Install 🐍 3
@ -365,7 +373,7 @@ jobs:
# container: centos:8 # container: centos:8
# #
# steps: # steps:
# - uses: actions/checkout@v3 # - uses: actions/checkout@v4
# #
# - name: Add Python 3 and a few requirements # - name: Add Python 3 and a few requirements
# run: yum update -y && yum install -y git python3-devel python3-numpy python3-pytest make environment-modules # run: yum update -y && yum install -y git python3-devel python3-numpy python3-pytest make environment-modules
@ -401,54 +409,55 @@ jobs:
# run: cmake --build build --target test_cmake_build # run: cmake --build build --target test_cmake_build
# Testing on CentOS 7 + PGI compilers, which seems to require more workarounds # Testing on Ubuntu + NVHPC (previous PGI) compilers, which seems to require more workarounds
centos-nvhpc7: ubuntu-nvhpc7:
if: ${{ false }} # JOB DISABLED (NEEDS WORK): https://github.com/pybind/pybind11/issues/4690 runs-on: ubuntu-20.04
runs-on: ubuntu-latest name: "🐍 3 • NVHPC 23.5 • C++17 • x64"
name: "🐍 3 • CentOS7 / PGI 22.9 • x64"
container: centos:7
env:
# tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND
DEBIAN_FRONTEND: 'noninteractive'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Add Python 3 and a few requirements - name: Add NVHPC Repo
run: yum update -y && yum install -y epel-release && yum install -y git python3-devel make environment-modules cmake3 yum-utils run: |
echo 'deb [trusted=yes] https://developer.download.nvidia.com/hpc-sdk/ubuntu/amd64 /' | \
sudo tee /etc/apt/sources.list.d/nvhpc.list
- name: Install NVidia HPC SDK - name: Install 🐍 3 & NVHPC
run: yum-config-manager --add-repo https://developer.download.nvidia.com/hpc-sdk/rhel/nvhpc.repo && yum -y install nvhpc-22.9 run: |
sudo apt-get update -y && \
sudo apt-get install -y cmake environment-modules git python3-dev python3-pip python3-numpy && \
sudo apt-get install -y --no-install-recommends nvhpc-23-5 && \
sudo rm -rf /var/lib/apt/lists/*
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade pytest
# On CentOS 7, we have to filter a few tests (compiler internal error) # On some systems, you many need further workarounds:
# and allow deeper template recursion (not needed on CentOS 8 with a newer
# standard library). On some systems, you many need further workarounds:
# https://github.com/pybind/pybind11/pull/2475 # https://github.com/pybind/pybind11/pull/2475
- name: Configure - name: Configure
shell: bash shell: bash
run: | run: |
source /etc/profile.d/modules.sh source /etc/profile.d/modules.sh
module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/22.9 module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/23.5
cmake3 -S . -B build -DDOWNLOAD_CATCH=ON \ cmake -S . -B build -DDOWNLOAD_CATCH=ON \
-DCMAKE_CXX_STANDARD=11 \ -DCMAKE_CXX_STANDARD=17 \
-DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \
-DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0" \ -DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0" \
-DPYBIND11_TEST_FILTER="test_smart_ptr.cpp" -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp"
# Building before installing Pip should produce a warning but not an error
- name: Build - name: Build
run: cmake3 --build build -j 2 --verbose run: cmake --build build -j 2 --verbose
- name: Install CMake with pip
run: |
python3 -m pip install --upgrade pip
python3 -m pip install pytest
- name: Python tests - name: Python tests
run: cmake3 --build build --target pytest run: cmake --build build --target pytest
- name: C++ tests - name: C++ tests
run: cmake3 --build build --target cpptest run: cmake --build build --target cpptest
- name: Interface test - name: Interface test
run: cmake3 --build build --target test_cmake_build run: cmake --build build --target test_cmake_build
# Testing on GCC using the GCC docker images (only recent images supported) # Testing on GCC using the GCC docker images (only recent images supported)
@ -465,12 +474,13 @@ jobs:
- { gcc: 10, std: 17 } - { gcc: 10, std: 17 }
- { gcc: 11, std: 20 } - { gcc: 11, std: 20 }
- { gcc: 12, std: 20 } - { gcc: 12, std: 20 }
- { gcc: 13, std: 20 }
name: "🐍 3 • GCC ${{ matrix.gcc }} • C++${{ matrix.std }}• x64" name: "🐍 3 • GCC ${{ matrix.gcc }} • C++${{ matrix.std }}• x64"
container: "gcc:${{ matrix.gcc }}" container: "gcc:${{ matrix.gcc }}"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Add Python 3 - name: Add Python 3
run: apt-get update; apt-get install -y python3-dev python3-numpy python3-pytest python3-pip libeigen3-dev run: apt-get update; apt-get install -y python3-dev python3-numpy python3-pytest python3-pip libeigen3-dev
@ -479,7 +489,7 @@ jobs:
run: python3 -m pip install --upgrade pip run: python3 -m pip install --upgrade pip
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.14 uses: jwlawson/actions-setup-cmake@v2.0
- name: Configure - name: Configure
shell: bash shell: bash
@ -524,13 +534,11 @@ jobs:
# Testing on ICC using the oneAPI apt repo # Testing on ICC using the oneAPI apt repo
icc: icc:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
strategy:
fail-fast: false
name: "🐍 3 • ICC latest • x64" name: "🐍 3 • ICC latest • x64"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Add apt repo - name: Add apt repo
run: | run: |
@ -634,7 +642,13 @@ jobs:
container: "${{ matrix.container }}" container: "${{ matrix.container }}"
steps: steps:
- uses: actions/checkout@v3 - name: Latest actions/checkout
uses: actions/checkout@v4
if: matrix.container != 'centos:7'
- name: Pin actions/checkout as required for centos:7
uses: actions/checkout@v3
if: matrix.container == 'centos:7'
- name: Add Python 3 (RHEL 7) - name: Add Python 3 (RHEL 7)
if: matrix.container == 'centos:7' if: matrix.container == 'centos:7'
@ -651,6 +665,11 @@ jobs:
run: | run: |
python3 -m pip install cmake -r tests/requirements.txt python3 -m pip install cmake -r tests/requirements.txt
- name: Ensure NumPy 2 is used (required Python >= 3.9)
if: matrix.container == 'almalinux:9'
run: |
python3 -m pip install 'numpy>=2.0.0b1' 'scipy>=1.13.0rc1'
- name: Configure - name: Configure
shell: bash shell: bash
run: > run: >
@ -682,7 +701,7 @@ jobs:
container: i386/debian:buster container: i386/debian:buster
steps: steps:
- uses: actions/checkout@v1 # Required to run inside docker - uses: actions/checkout@v1 # v1 is required to run inside docker
- name: Install requirements - name: Install requirements
run: | run: |
@ -725,9 +744,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: "3.x" python-version: "3.x"
@ -777,19 +796,19 @@ jobs:
runs-on: windows-2019 runs-on: windows-2019
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Python ${{ matrix.python }} - name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
architecture: x86 architecture: x86
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.14 uses: jwlawson/actions-setup-cmake@v2.0
- name: Prepare MSVC - name: Prepare MSVC
uses: ilammy/msvc-dev-cmd@v1.12.1 uses: ilammy/msvc-dev-cmd@v1.13.0
with: with:
arch: x86 arch: x86
@ -830,19 +849,19 @@ jobs:
runs-on: windows-2019 runs-on: windows-2019
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Python ${{ matrix.python }} - name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
architecture: x86 architecture: x86
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.14 uses: jwlawson/actions-setup-cmake@v2.0
- name: Prepare MSVC - name: Prepare MSVC
uses: ilammy/msvc-dev-cmd@v1.12.1 uses: ilammy/msvc-dev-cmd@v1.13.0
with: with:
arch: x86 arch: x86
@ -878,19 +897,21 @@ jobs:
runs-on: windows-2022 runs-on: windows-2022
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Python ${{ matrix.python }} - name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
- name: Prepare env - name: Prepare env
# Ensure use of NumPy 2 (via NumPy nightlies but can be changed soon)
run: | run: |
python3 -m pip install -r tests/requirements.txt python3 -m pip install -r tests/requirements.txt
python3 -m pip install 'numpy>=2.0.0b1' 'scipy>=1.13.0rc1'
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.14 uses: jwlawson/actions-setup-cmake@v2.0
- name: Configure C++20 - name: Configure C++20
run: > run: >
@ -948,7 +969,6 @@ jobs:
mingw-w64-${{matrix.env}}-gcc mingw-w64-${{matrix.env}}-gcc
mingw-w64-${{matrix.env}}-python-pip mingw-w64-${{matrix.env}}-python-pip
mingw-w64-${{matrix.env}}-python-numpy mingw-w64-${{matrix.env}}-python-numpy
mingw-w64-${{matrix.env}}-python-scipy
mingw-w64-${{matrix.env}}-cmake mingw-w64-${{matrix.env}}-cmake
mingw-w64-${{matrix.env}}-make mingw-w64-${{matrix.env}}-make
mingw-w64-${{matrix.env}}-python-pytest mingw-w64-${{matrix.env}}-python-pytest
@ -956,12 +976,23 @@ jobs:
mingw-w64-${{matrix.env}}-boost mingw-w64-${{matrix.env}}-boost
mingw-w64-${{matrix.env}}-catch mingw-w64-${{matrix.env}}-catch
- uses: actions/checkout@v3 - uses: msys2/setup-msys2@v2
if: matrix.sys == 'mingw64'
with:
msystem: ${{matrix.sys}}
install: >-
git
mingw-w64-${{matrix.env}}-python-scipy
- uses: actions/checkout@v4
- name: Configure C++11 - name: Configure C++11
# LTO leads to many undefined reference like # LTO leads to many undefined reference like
# `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&) # `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&)
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build run: >-
cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON
-DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)")
-S . -B build
- name: Build C++11 - name: Build C++11
run: cmake --build build -j 2 run: cmake --build build -j 2
@ -979,7 +1010,10 @@ jobs:
run: git clean -fdx run: git clean -fdx
- name: Configure C++14 - name: Configure C++14
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build2 run: >-
cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON
-DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)")
-S . -B build2
- name: Build C++14 - name: Build C++14
run: cmake --build build2 -j 2 run: cmake --build build2 -j 2
@ -997,7 +1031,10 @@ jobs:
run: git clean -fdx run: git clean -fdx
- name: Configure C++17 - name: Configure C++17
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build3 run: >-
cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON
-DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)")
-S . -B build3
- name: Build C++17 - name: Build C++17
run: cmake --build build3 -j 2 run: cmake --build build3 -j 2
@ -1027,21 +1064,21 @@ jobs:
run: env run: env
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up Clang - name: Set up Clang
uses: egor-tensin/setup-clang@v1 uses: egor-tensin/setup-clang@v1
- name: Setup Python ${{ matrix.python }} - name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.14 uses: jwlawson/actions-setup-cmake@v2.0
- name: Install ninja-build tool - name: Install ninja-build tool
uses: seanmiddleditch/gha-setup-ninja@v3 uses: seanmiddleditch/gha-setup-ninja@v4
- name: Run pip installs - name: Run pip installs
run: | run: |
@ -1096,7 +1133,7 @@ jobs:
run: env run: env
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Show Clang++ version before brew install llvm - name: Show Clang++ version before brew install llvm
run: clang++ --version run: clang++ --version
@ -1108,7 +1145,7 @@ jobs:
run: clang++ --version run: clang++ --version
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.14 uses: jwlawson/actions-setup-cmake@v2.0
- name: Run pip installs - name: Run pip installs
run: | run: |

View File

@ -49,10 +49,10 @@ jobs:
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Python 3.7 - name: Setup Python 3.7
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: 3.7 python-version: 3.7
architecture: ${{ matrix.arch }} architecture: ${{ matrix.arch }}
@ -63,7 +63,7 @@ jobs:
# An action for adding a specific version of CMake: # An action for adding a specific version of CMake:
# https://github.com/jwlawson/actions-setup-cmake # https://github.com/jwlawson/actions-setup-cmake
- name: Setup CMake ${{ matrix.cmake }} - name: Setup CMake ${{ matrix.cmake }}
uses: jwlawson/actions-setup-cmake@v1.14 uses: jwlawson/actions-setup-cmake@v2.0
with: with:
cmake-version: ${{ matrix.cmake }} cmake-version: ${{ matrix.cmake }}

View File

@ -25,13 +25,13 @@ jobs:
name: Format name: Format
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: "3.x" python-version: "3.x"
- name: Add matchers - name: Add matchers
run: echo "::add-matcher::$GITHUB_WORKSPACE/.github/matchers/pylint.json" run: echo "::add-matcher::$GITHUB_WORKSPACE/.github/matchers/pylint.json"
- uses: pre-commit/action@v3.0.0 - uses: pre-commit/action@v3.0.1
with: with:
# Slow hooks are marked with manual - slow is okay here, run them too # Slow hooks are marked with manual - slow is okay here, run them too
extra_args: --hook-stage manual --all-files extra_args: --hook-stage manual --all-files
@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: silkeh/clang:15-bullseye container: silkeh/clang:15-bullseye
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install requirements - name: Install requirements
run: apt-get update && apt-get install -y git python3-dev python3-pytest run: apt-get update && apt-get install -y git python3-dev python3-pytest

View File

@ -14,7 +14,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/labeler@main - uses: actions/labeler@v4
if: > if: >
github.event.pull_request.merged == true && github.event.pull_request.merged == true &&
!startsWith(github.event.pull_request.title, 'chore(deps):') && !startsWith(github.event.pull_request.title, 'chore(deps):') &&

View File

@ -28,10 +28,10 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup 🐍 3.6 - name: Setup 🐍 3.6
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: 3.6 python-version: 3.6
@ -50,10 +50,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup 🐍 3.8 - name: Setup 🐍 3.8
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: 3.8 python-version: 3.8
@ -73,13 +73,13 @@ jobs:
run: twine check dist/* run: twine check dist/*
- name: Save standard package - name: Save standard package
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: standard name: standard
path: dist/pybind11-* path: dist/pybind11-*
- name: Save global package - name: Save global package
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: global name: global
path: dist/pybind11_global-* path: dist/pybind11_global-*
@ -94,12 +94,12 @@ jobs:
needs: [packaging] needs: [packaging]
steps: steps:
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: "3.x" python-version: "3.x"
# Downloads all to directories matching the artifact names # Downloads all to directories matching the artifact names
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v4
- name: Publish standard package - name: Publish standard package
uses: pypa/gh-action-pypi-publish@release/v1 uses: pypa/gh-action-pypi-publish@release/v1

View File

@ -13,30 +13,30 @@ concurrency:
env: env:
PIP_BREAK_SYSTEM_PACKAGES: 1 PIP_BREAK_SYSTEM_PACKAGES: 1
PIP_ONLY_BINARY: ":all:"
# For cmake: # For cmake:
VERBOSE: 1 VERBOSE: 1
jobs: jobs:
standard: standard:
name: "🐍 3.12 latest • ubuntu-latest • x64" name: "🐍 3.13 latest • ubuntu-latest • x64"
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Only runs when the 'python dev' label is selected # Only runs when the 'python dev' label is selected
if: "contains(github.event.pull_request.labels.*.name, 'python dev')" if: "contains(github.event.pull_request.labels.*.name, 'python dev')"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Python 3.12 - name: Setup Python 3.13
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: "3.12-dev" python-version: "3.13"
allow-prereleases: true
- name: Setup Boost - name: Setup Boost
run: sudo apt-get install libboost-dev run: sudo apt-get install libboost-dev
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.14 uses: jwlawson/actions-setup-cmake@v2.0
- name: Run pip installs - name: Run pip installs
run: | run: |

View File

@ -25,27 +25,22 @@ repos:
# Clang format the codebase automatically # Clang format the codebase automatically
- repo: https://github.com/pre-commit/mirrors-clang-format - repo: https://github.com/pre-commit/mirrors-clang-format
rev: "v16.0.6" rev: "v17.0.6"
hooks: hooks:
- id: clang-format - id: clang-format
types_or: [c++, c, cuda] types_or: [c++, c, cuda]
# Black, the code formatter, natively supports pre-commit # Ruff, the Python auto-correcting linter/formatter written in Rust
- repo: https://github.com/psf/black
rev: "23.3.0" # Keep in sync with blacken-docs
hooks:
- id: black
# Ruff, the Python auto-correcting linter written in Rust
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.276 rev: v0.2.0
hooks: hooks:
- id: ruff - id: ruff
args: ["--fix", "--show-fixes"] args: ["--fix", "--show-fixes"]
- id: ruff-format
# Check static types with mypy # Check static types with mypy
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.4.1" rev: "v1.8.0"
hooks: hooks:
- id: mypy - id: mypy
args: [] args: []
@ -67,7 +62,7 @@ repos:
# Standard hooks # Standard hooks
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: "v4.4.0" rev: "v4.5.0"
hooks: hooks:
- id: check-added-large-files - id: check-added-large-files
- id: check-case-conflict - id: check-case-conflict
@ -84,21 +79,21 @@ repos:
# Also code format the docs # Also code format the docs
- repo: https://github.com/asottile/blacken-docs - repo: https://github.com/asottile/blacken-docs
rev: "1.14.0" rev: "1.16.0"
hooks: hooks:
- id: blacken-docs - id: blacken-docs
additional_dependencies: additional_dependencies:
- black==23.3.0 # keep in sync with black hook - black==23.*
# Changes tabs to spaces # Changes tabs to spaces
- repo: https://github.com/Lucas-C/pre-commit-hooks - repo: https://github.com/Lucas-C/pre-commit-hooks
rev: "v1.5.1" rev: "v1.5.4"
hooks: hooks:
- id: remove-tabs - id: remove-tabs
# Avoid directional quotes # Avoid directional quotes
- repo: https://github.com/sirosen/texthooks - repo: https://github.com/sirosen/texthooks
rev: "0.5.0" rev: "0.6.4"
hooks: hooks:
- id: fix-ligatures - id: fix-ligatures
- id: fix-smartquotes - id: fix-smartquotes
@ -124,7 +119,7 @@ repos:
# Use tools/codespell_ignore_lines_from_errors.py # Use tools/codespell_ignore_lines_from_errors.py
# to rebuild .codespell-ignore-lines # to rebuild .codespell-ignore-lines
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/codespell-project/codespell
rev: "v2.2.5" rev: "v2.2.6"
hooks: hooks:
- id: codespell - id: codespell
exclude: ".supp$" exclude: ".supp$"
@ -132,7 +127,7 @@ repos:
# Check for common shell mistakes # Check for common shell mistakes
- repo: https://github.com/shellcheck-py/shellcheck-py - repo: https://github.com/shellcheck-py/shellcheck-py
rev: "v0.9.0.5" rev: "v0.9.0.6"
hooks: hooks:
- id: shellcheck - id: shellcheck
@ -142,12 +137,19 @@ repos:
- id: disallow-caps - id: disallow-caps
name: Disallow improper capitalization name: Disallow improper capitalization
language: pygrep language: pygrep
entry: PyBind|Numpy|Cmake|CCache|PyTest entry: PyBind|\bNumpy\b|Cmake|CCache|PyTest
exclude: ^\.pre-commit-config.yaml$ exclude: ^\.pre-commit-config.yaml$
# PyLint has native support - not always usable, but works for us # PyLint has native support - not always usable, but works for us
- repo: https://github.com/PyCQA/pylint - repo: https://github.com/PyCQA/pylint
rev: "v3.0.0a6" rev: "v3.0.3"
hooks: hooks:
- id: pylint - id: pylint
files: ^pybind11 files: ^pybind11
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.28.0
hooks:
- id: check-readthedocs
- id: check-github-workflows
- id: check-dependabot

View File

@ -1,3 +1,20 @@
# https://blog.readthedocs.com/migrate-configuration-v2/
version: 2
build:
os: ubuntu-22.04
apt_packages:
- librsvg2-bin
tools:
python: "3.11"
sphinx:
configuration: docs/conf.py
python: python:
version: 3 install:
requirements_file: docs/requirements.txt - requirements: docs/requirements.txt
formats:
- pdf

View File

@ -5,15 +5,25 @@
# All rights reserved. Use of this source code is governed by a # All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file. # BSD-style license that can be found in the LICENSE file.
# Propagate this policy (FindPythonInterp removal) so it can be detected later
if(NOT CMAKE_VERSION VERSION_LESS "3.27")
cmake_policy(GET CMP0148 _pybind11_cmp0148)
endif()
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.27)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.26) if(${CMAKE_VERSION} VERSION_LESS 3.27)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.26) cmake_policy(VERSION 3.27)
endif()
if(_pybind11_cmp0148)
cmake_policy(SET CMP0148 ${_pybind11_cmp0148})
unset(_pybind11_cmp0148)
endif() endif()
# Avoid infinite recursion if tests include this as a subdirectory # Avoid infinite recursion if tests include this as a subdirectory
@ -82,33 +92,58 @@ if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
set(pybind11_system "") set(pybind11_system "")
set_property(GLOBAL PROPERTY USE_FOLDERS ON) set_property(GLOBAL PROPERTY USE_FOLDERS ON)
if(CMAKE_VERSION VERSION_LESS "3.18")
set(_pybind11_findpython_default OFF)
else()
set(_pybind11_findpython_default ON)
endif()
else() else()
set(PYBIND11_MASTER_PROJECT OFF) set(PYBIND11_MASTER_PROJECT OFF)
set(pybind11_system SYSTEM) set(pybind11_system SYSTEM)
set(_pybind11_findpython_default OFF)
endif() endif()
# Options # Options
option(PYBIND11_INSTALL "Install pybind11 header files?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_INSTALL "Install pybind11 header files?" ${PYBIND11_MASTER_PROJECT})
option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT})
option(PYBIND11_NOPYTHON "Disable search for Python" OFF) option(PYBIND11_NOPYTHON "Disable search for Python" OFF)
option(PYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION
"To enforce that a handle_type_name<> specialization exists" OFF)
option(PYBIND11_SIMPLE_GIL_MANAGEMENT option(PYBIND11_SIMPLE_GIL_MANAGEMENT
"Use simpler GIL management logic that does not support disassociation" OFF) "Use simpler GIL management logic that does not support disassociation" OFF)
option(PYBIND11_NUMPY_1_ONLY
"Disable NumPy 2 support to avoid changes to previous pybind11 versions." OFF)
set(PYBIND11_INTERNALS_VERSION set(PYBIND11_INTERNALS_VERSION
"" ""
CACHE STRING "Override the ABI version, may be used to enable the unstable ABI.") CACHE STRING "Override the ABI version, may be used to enable the unstable ABI.")
if(PYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION)
add_compile_definitions(PYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION)
endif()
if(PYBIND11_SIMPLE_GIL_MANAGEMENT) if(PYBIND11_SIMPLE_GIL_MANAGEMENT)
add_compile_definitions(PYBIND11_SIMPLE_GIL_MANAGEMENT) add_compile_definitions(PYBIND11_SIMPLE_GIL_MANAGEMENT)
endif() endif()
if(PYBIND11_NUMPY_1_ONLY)
add_compile_definitions(PYBIND11_NUMPY_1_ONLY)
endif()
cmake_dependent_option( cmake_dependent_option(
USE_PYTHON_INCLUDE_DIR USE_PYTHON_INCLUDE_DIR
"Install pybind11 headers in Python include directory instead of default installation prefix" "Install pybind11 headers in Python include directory instead of default installation prefix"
OFF "PYBIND11_INSTALL" OFF) OFF "PYBIND11_INSTALL" OFF)
cmake_dependent_option(PYBIND11_FINDPYTHON "Force new FindPython" OFF cmake_dependent_option(PYBIND11_FINDPYTHON "Force new FindPython" ${_pybind11_findpython_default}
"NOT CMAKE_VERSION VERSION_LESS 3.12" OFF) "NOT CMAKE_VERSION VERSION_LESS 3.12" OFF)
# Allow PYTHON_EXECUTABLE if in FINDPYTHON mode and building pybind11's tests
# (makes transition easier while we support both modes).
if(PYBIND11_MASTER_PROJECT
AND PYBIND11_FINDPYTHON
AND DEFINED PYTHON_EXECUTABLE
AND NOT DEFINED Python_EXECUTABLE)
set(Python_EXECUTABLE "${PYTHON_EXECUTABLE}")
endif()
# NB: when adding a header don't forget to also add it to setup.py # NB: when adding a header don't forget to also add it to setup.py
set(PYBIND11_HEADERS set(PYBIND11_HEADERS
include/pybind11/detail/class.h include/pybind11/detail/class.h
@ -132,6 +167,7 @@ set(PYBIND11_HEADERS
include/pybind11/embed.h include/pybind11/embed.h
include/pybind11/eval.h include/pybind11/eval.h
include/pybind11/gil.h include/pybind11/gil.h
include/pybind11/gil_safe_call_once.h
include/pybind11/iostream.h include/pybind11/iostream.h
include/pybind11/functional.h include/pybind11/functional.h
include/pybind11/numpy.h include/pybind11/numpy.h
@ -141,7 +177,8 @@ set(PYBIND11_HEADERS
include/pybind11/stl.h include/pybind11/stl.h
include/pybind11/stl_bind.h include/pybind11/stl_bind.h
include/pybind11/stl/filesystem.h include/pybind11/stl/filesystem.h
include/pybind11/type_caster_pyobject_ptr.h) include/pybind11/type_caster_pyobject_ptr.h
include/pybind11/typing.h)
# Compare with grep and warn if mismatched # Compare with grep and warn if mismatched
if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12) if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12)
@ -277,7 +314,21 @@ if(PYBIND11_INSTALL)
# pkg-config support # pkg-config support
if(NOT prefix_for_pc_file) if(NOT prefix_for_pc_file)
set(prefix_for_pc_file "${CMAKE_INSTALL_PREFIX}") if(IS_ABSOLUTE "${CMAKE_INSTALL_DATAROOTDIR}")
set(prefix_for_pc_file "${CMAKE_INSTALL_PREFIX}")
else()
set(pc_datarootdir "${CMAKE_INSTALL_DATAROOTDIR}")
if(CMAKE_VERSION VERSION_LESS 3.20)
set(prefix_for_pc_file "\${pcfiledir}/..")
while(pc_datarootdir)
get_filename_component(pc_datarootdir "${pc_datarootdir}" DIRECTORY)
string(APPEND prefix_for_pc_file "/..")
endwhile()
else()
cmake_path(RELATIVE_PATH CMAKE_INSTALL_PREFIX BASE_DIRECTORY CMAKE_INSTALL_DATAROOTDIR
OUTPUT_VARIABLE prefix_for_pc_file)
endif()
endif()
endif() endif()
join_paths(includedir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") join_paths(includedir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11.pc.in" configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11.pc.in"

View File

@ -36,10 +36,10 @@ with everything stripped away that isn't relevant for binding
generation. Without comments, the core header files only require ~4K generation. Without comments, the core header files only require ~4K
lines of code and depend on Python (3.6+, or PyPy) and the C++ lines of code and depend on Python (3.6+, or PyPy) and the C++
standard library. This compact implementation was possible thanks to standard library. This compact implementation was possible thanks to
some of the new C++11 language features (specifically: tuples, lambda some C++11 language features (specifically: tuples, lambda functions and
functions and variadic templates). Since its creation, this library has variadic templates). Since its creation, this library has grown beyond
grown beyond Boost.Python in many ways, leading to dramatically simpler Boost.Python in many ways, leading to dramatically simpler binding code in many
binding code in many common situations. common situations.
Tutorial and reference documentation is provided at Tutorial and reference documentation is provided at
`pybind11.readthedocs.io <https://pybind11.readthedocs.io/en/latest>`_. `pybind11.readthedocs.io <https://pybind11.readthedocs.io/en/latest>`_.
@ -71,6 +71,7 @@ pybind11 can map the following core C++ features to Python:
- Internal references with correct reference counting - Internal references with correct reference counting
- C++ classes with virtual (and pure virtual) methods can be extended - C++ classes with virtual (and pure virtual) methods can be extended
in Python in Python
- Integrated NumPy support (NumPy 2 requires pybind11 2.12+)
Goodies Goodies
------- -------

View File

@ -18,7 +18,7 @@ information, see :doc:`/compiling`.
.. code-block:: cmake .. code-block:: cmake
cmake_minimum_required(VERSION 3.5...3.26) cmake_minimum_required(VERSION 3.5...3.27)
project(example) project(example)
find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)` find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)`

View File

@ -127,8 +127,7 @@ before a global translator is tried.
Inside the translator, ``std::rethrow_exception`` should be used within Inside the translator, ``std::rethrow_exception`` should be used within
a try block to re-throw the exception. One or more catch clauses to catch a try block to re-throw the exception. One or more catch clauses to catch
the appropriate exceptions should then be used with each clause using the appropriate exceptions should then be used with each clause using
``PyErr_SetString`` to set a Python exception or ``ex(string)`` to set ``py::set_error()`` (see below).
the python exception to a custom exception type (see below).
To declare a custom Python exception type, declare a ``py::exception`` variable To declare a custom Python exception type, declare a ``py::exception`` variable
and use this in the associated exception translator (note: it is often useful and use this in the associated exception translator (note: it is often useful
@ -142,14 +141,16 @@ standard python RuntimeError:
.. code-block:: cpp .. code-block:: cpp
static py::exception<MyCustomException> exc(m, "MyCustomError"); PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> exc_storage;
exc_storage.call_once_and_store_result(
[&]() { return py::exception<MyCustomException>(m, "MyCustomError"); });
py::register_exception_translator([](std::exception_ptr p) { py::register_exception_translator([](std::exception_ptr p) {
try { try {
if (p) std::rethrow_exception(p); if (p) std::rethrow_exception(p);
} catch (const MyCustomException &e) { } catch (const MyCustomException &e) {
exc(e.what()); py::set_error(exc_storage.get_stored(), e.what());
} catch (const OtherException &e) { } catch (const OtherException &e) {
PyErr_SetString(PyExc_RuntimeError, e.what()); py::set_error(PyExc_RuntimeError, e.what());
} }
}); });
@ -168,8 +169,7 @@ section.
.. note:: .. note::
Call either ``PyErr_SetString`` or a custom exception's call Call ``py::set_error()`` for every exception caught in a custom exception
operator (``exc(string)``) for every exception caught in a custom exception
translator. Failure to do so will cause Python to crash with ``SystemError: translator. Failure to do so will cause Python to crash with ``SystemError:
error return without exception set``. error return without exception set``.
@ -200,7 +200,7 @@ If module1 has the following translator:
try { try {
if (p) std::rethrow_exception(p); if (p) std::rethrow_exception(p);
} catch (const std::invalid_argument &e) { } catch (const std::invalid_argument &e) {
PyErr_SetString("module1 handled this") py::set_error(PyExc_ArgumentError, "module1 handled this");
} }
} }
@ -212,7 +212,7 @@ and module2 has the following similar translator:
try { try {
if (p) std::rethrow_exception(p); if (p) std::rethrow_exception(p);
} catch (const std::invalid_argument &e) { } catch (const std::invalid_argument &e) {
PyErr_SetString("module2 handled this") py::set_error(PyExc_ArgumentError, "module2 handled this");
} }
} }
@ -312,11 +312,11 @@ error protocol, which is outlined here.
After calling the Python C API, if Python returns an error, After calling the Python C API, if Python returns an error,
``throw py::error_already_set();``, which allows pybind11 to deal with the ``throw py::error_already_set();``, which allows pybind11 to deal with the
exception and pass it back to the Python interpreter. This includes calls to exception and pass it back to the Python interpreter. This includes calls to
the error setting functions such as ``PyErr_SetString``. the error setting functions such as ``py::set_error()``.
.. code-block:: cpp .. code-block:: cpp
PyErr_SetString(PyExc_TypeError, "C API type error demo"); py::set_error(PyExc_TypeError, "C API type error demo");
throw py::error_already_set(); throw py::error_already_set();
// But it would be easier to simply... // But it would be easier to simply...

View File

@ -16,7 +16,7 @@ lifetime of objects managed by them. This can lead to issues when creating
bindings for functions that return a non-trivial type. Just by looking at the bindings for functions that return a non-trivial type. Just by looking at the
type information, it is not clear whether Python should take charge of the type information, it is not clear whether Python should take charge of the
returned value and eventually free its resources, or if this is handled on the returned value and eventually free its resources, or if this is handled on the
C++ side. For this reason, pybind11 provides a several *return value policy* C++ side. For this reason, pybind11 provides several *return value policy*
annotations that can be passed to the :func:`module_::def` and annotations that can be passed to the :func:`module_::def` and
:func:`class_::def` functions. The default policy is :func:`class_::def` functions. The default policy is
:enum:`return_value_policy::automatic`. :enum:`return_value_policy::automatic`.

View File

@ -398,3 +398,32 @@ before they are used as a parameter or return type of a function:
pyFoo.def(py::init<const ns::Bar&>()); pyFoo.def(py::init<const ns::Bar&>());
pyBar.def(py::init<const ns::Foo&>()); pyBar.def(py::init<const ns::Foo&>());
} }
Setting inner type hints in docstrings
======================================
When you use pybind11 wrappers for ``list``, ``dict``, and other generic python
types, the docstring will just display the generic type. You can convey the
inner types in the docstring by using a special 'typed' version of the generic
type.
.. code-block:: cpp
PYBIND11_MODULE(example, m) {
m.def("pass_list_of_str", [](py::typing::List<py::str> arg) {
// arg can be used just like py::list
));
}
The resulting docstring will be ``pass_list_of_str(arg0: list[str]) -> None``.
The following special types are available in ``pybind11/typing.h``:
* ``py::Tuple<Args...>``
* ``py::Dict<K, V>``
* ``py::List<V>``
* ``py::Set<V>``
* ``py::Callable<Signature>``
.. warning:: Just like in python, these are merely hints. They don't actually
enforce the types of their contents at runtime or compile time.

View File

@ -70,7 +70,7 @@ def generate_dummy_code_boost(nclasses=10):
for codegen in [generate_dummy_code_pybind11, generate_dummy_code_boost]: for codegen in [generate_dummy_code_pybind11, generate_dummy_code_boost]:
print("{") print("{")
for i in range(0, 10): for i in range(10):
nclasses = 2**i nclasses = 2**i
with open("test.cpp", "w") as f: with open("test.cpp", "w") as f:
f.write(codegen(nclasses)) f.write(codegen(nclasses))

View File

@ -10,8 +10,188 @@ Changes will be added here periodically from the "Suggested changelog entry"
block in pull request descriptions. block in pull request descriptions.
IN DEVELOPMENT
--------------
Changes will be summarized here periodically.
Version 2.12.0 (March 27, 2025)
-------------------------------
New Features:
* ``pybind11`` now supports compiling for
`NumPy 2 <https://numpy.org/devdocs/numpy_2_0_migration_guide.html>`_. Most
code shouldn't change (see :ref:`upgrade-guide-2.12` for details). However,
if you experience issues you can define ``PYBIND11_NUMPY_1_ONLY`` to disable
the new support for now, but this will be removed in the future.
`#5050 <https://github.com/pybind/pybind11/pull/5050>`_
* ``pybind11/gil_safe_call_once.h`` was added (it needs to be included
explicitly). The primary use case is GIL-safe initialization of C++
``static`` variables.
`#4877 <https://github.com/pybind/pybind11/pull/4877>`_
* Support move-only iterators in ``py::make_iterator``,
``py::make_key_iterator``, ``py::make_value_iterator``.
`#4834 <https://github.com/pybind/pybind11/pull/4834>`_
* Two simple ``py::set_error()`` functions were added and the documentation was
updated accordingly. In particular, ``py::exception<>::operator()`` was
deprecated (use one of the new functions instead). The documentation for
``py::exception<>`` was further updated to not suggest code that may result
in undefined behavior.
`#4772 <https://github.com/pybind/pybind11/pull/4772>`_
Bug fixes:
* Removes potential for Undefined Behavior during process teardown.
`#4897 <https://github.com/pybind/pybind11/pull/4897>`_
* Improve compatibility with the nvcc compiler (especially CUDA 12.1/12.2).
`#4893 <https://github.com/pybind/pybind11/pull/4893>`_
* ``pybind11/numpy.h`` now imports NumPy's ``multiarray`` and ``_internal``
submodules with paths depending on the installed version of NumPy (for
compatibility with NumPy 2).
`#4857 <https://github.com/pybind/pybind11/pull/4857>`_
* Builtins collections names in docstrings are now consistently rendered in
lowercase (list, set, dict, tuple), in accordance with PEP 585.
`#4833 <https://github.com/pybind/pybind11/pull/4833>`_
* Added ``py::typing::Iterator<T>``, ``py::typing::Iterable<T>``.
`#4832 <https://github.com/pybind/pybind11/pull/4832>`_
* Render ``py::function`` as ``Callable`` in docstring.
`#4829 <https://github.com/pybind/pybind11/pull/4829>`_
* Also bump ``PYBIND11_INTERNALS_VERSION`` for MSVC, which unlocks two new
features without creating additional incompatibilities.
`#4819 <https://github.com/pybind/pybind11/pull/4819>`_
* Guard against crashes/corruptions caused by modules built with different MSVC
versions.
`#4779 <https://github.com/pybind/pybind11/pull/4779>`_
* A long-standing bug in the handling of Python multiple inheritance was fixed.
See PR #4762 for the rather complex details.
`#4762 <https://github.com/pybind/pybind11/pull/4762>`_
* Fix ``bind_map`` with ``using`` declarations.
`#4952 <https://github.com/pybind/pybind11/pull/4952>`_
* Qualify ``py::detail::concat`` usage to avoid ADL selecting one from
somewhere else, such as modernjson's concat.
`#4955 <https://github.com/pybind/pybind11/pull/4955>`_
* Use new PyCode API on Python 3.12+.
`#4916 <https://github.com/pybind/pybind11/pull/4916>`_
* Minor cleanup from warnings reported by Clazy.
`#4988 <https://github.com/pybind/pybind11/pull/4988>`_
* Remove typing and duplicate ``class_`` for ``KeysView``/``ValuesView``/``ItemsView``.
`#4985 <https://github.com/pybind/pybind11/pull/4985>`_
* Use ``PyObject_VisitManagedDict()`` and ``PyObject_ClearManagedDict()`` on Python 3.13 and newer.
`#4973 <https://github.com/pybind/pybind11/pull/4973>`_
* Update ``make_static_property_type()`` to make it compatible with Python 3.13.
`#4971 <https://github.com/pybind/pybind11/pull/4971>`_
.. fix(types)
* Render typed iterators for ``make_iterator``, ``make_key_iterator``,
``make_value_iterator``.
`#4876 <https://github.com/pybind/pybind11/pull/4876>`_
* Add several missing type name specializations.
`#5073 <https://github.com/pybind/pybind11/pull/5073>`_
* Change docstring render for ``py::buffer``, ``py::sequence`` and
``py::handle`` (to ``Buffer``, ``Sequence``, ``Any``).
`#4831 <https://github.com/pybind/pybind11/pull/4831>`_
* Fixed ``base_enum.__str__`` docstring.
`#4827 <https://github.com/pybind/pybind11/pull/4827>`_
* Enforce single line docstring signatures.
`#4735 <https://github.com/pybind/pybind11/pull/4735>`_
* Special 'typed' wrappers now available in ``typing.h`` to annotate tuple, dict,
list, set, and function.
`#4259 <https://github.com/pybind/pybind11/pull/4259>`_
* Create ``handle_type_name`` specialization to type-hint variable length tuples.
`#5051 <https://github.com/pybind/pybind11/pull/5051>`_
.. fix(build)
* Setting ``PYBIND11_FINDPYTHON`` to OFF will force the old FindPythonLibs mechanism to be used.
`#5042 <https://github.com/pybind/pybind11/pull/5042>`_
* Skip empty ``PYBIND11_PYTHON_EXECUTABLE_LAST`` for the first cmake run.
`#4856 <https://github.com/pybind/pybind11/pull/4856>`_
* Fix FindPython mode exports & avoid ``pkg_resources`` if
``importlib.metadata`` available.
`#4941 <https://github.com/pybind/pybind11/pull/4941>`_
* ``Python_ADDITIONAL_VERSIONS`` (classic search) now includes 3.12.
`#4909 <https://github.com/pybind/pybind11/pull/4909>`_
* ``pybind11.pc`` is now relocatable by default as long as install destinations
are not absolute paths.
`#4830 <https://github.com/pybind/pybind11/pull/4830>`_
* Correctly detect CMake FindPython removal when used as a subdirectory.
`#4806 <https://github.com/pybind/pybind11/pull/4806>`_
* Don't require the libs component on CMake 3.18+ when using
PYBIND11_FINDPYTHON (fixes manylinux builds).
`#4805 <https://github.com/pybind/pybind11/pull/4805>`_
* ``pybind11_strip`` is no longer automatically applied when
``CMAKE_BUILD_TYPE`` is unset.
`#4780 <https://github.com/pybind/pybind11/pull/4780>`_
* Support ``DEBUG_POSFIX`` correctly for debug builds.
`#4761 <https://github.com/pybind/pybind11/pull/4761>`_
* Hardcode lto/thin lto for Emscripten cross-compiles.
`#4642 <https://github.com/pybind/pybind11/pull/4642>`_
* Upgrade maximum supported CMake version to 3.27 to fix CMP0148 warnings.
`#4786 <https://github.com/pybind/pybind11/pull/4786>`_
Documentation:
* Small fix to grammar in ``functions.rst``.
`#4791 <https://github.com/pybind/pybind11/pull/4791>`_
* Remove upper bound in example pyproject.toml for setuptools.
`#4774 <https://github.com/pybind/pybind11/pull/4774>`_
CI:
* CI: Update NVHPC to 23.5 and Ubuntu 20.04.
`#4764 <https://github.com/pybind/pybind11/pull/4764>`_
* Test on PyPy 3.10.
`#4714 <https://github.com/pybind/pybind11/pull/4714>`_
Other:
* Use Ruff formatter instead of Black.
`#4912 <https://github.com/pybind/pybind11/pull/4912>`_
* An ``assert()`` was added to help Coverty avoid generating a false positive.
`#4817 <https://github.com/pybind/pybind11/pull/4817>`_
Version 2.11.1 (July 17, 2023) Version 2.11.1 (July 17, 2023)
----------------------------- ------------------------------
Changes: Changes:
@ -26,7 +206,7 @@ Changes:
Version 2.11.0 (July 14, 2023) Version 2.11.0 (July 14, 2023)
----------------------------- ------------------------------
New features: New features:

View File

@ -143,7 +143,7 @@ Your ``pyproject.toml`` file will likely look something like this:
.. code-block:: toml .. code-block:: toml
[build-system] [build-system]
requires = ["setuptools>=42", "wheel", "pybind11~=2.6.1"] requires = ["setuptools>=42", "pybind11>=2.6.1"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
.. note:: .. note::
@ -241,7 +241,7 @@ extension module can be created with just a few lines of code:
.. code-block:: cmake .. code-block:: cmake
cmake_minimum_required(VERSION 3.5...3.26) cmake_minimum_required(VERSION 3.5...3.27)
project(example LANGUAGES CXX) project(example LANGUAGES CXX)
add_subdirectory(pybind11) add_subdirectory(pybind11)
@ -498,7 +498,7 @@ You can use these targets to build complex applications. For example, the
.. code-block:: cmake .. code-block:: cmake
cmake_minimum_required(VERSION 3.5...3.26) cmake_minimum_required(VERSION 3.5...3.27)
project(example LANGUAGES CXX) project(example LANGUAGES CXX)
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)
@ -556,7 +556,7 @@ information about usage in C++, see :doc:`/advanced/embedding`.
.. code-block:: cmake .. code-block:: cmake
cmake_minimum_required(VERSION 3.5...3.26) cmake_minimum_required(VERSION 3.5...3.27)
project(example LANGUAGES CXX) project(example LANGUAGES CXX)
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)
@ -639,3 +639,11 @@ cross-project dependency management. Additionally, it is able to autogenerate
customizable pybind11-based wrappers by parsing C++ header files. customizable pybind11-based wrappers by parsing C++ header files.
.. [robotpy-build] https://robotpy-build.readthedocs.io .. [robotpy-build] https://robotpy-build.readthedocs.io
[litgen]_ is an automatic python bindings generator with a focus on generating
documented and discoverable bindings: bindings will nicely reproduce the documentation
found in headers. It is is based on srcML (srcml.org), a highly scalable, multi-language
parsing tool with a developer centric approach. The API that you want to expose to python
must be C++14 compatible (but your implementation can use more modern constructs).
.. [litgen] https://pthom.github.io/litgen

View File

@ -15,8 +15,8 @@ For example:
For beta, ``PYBIND11_VERSION_PATCH`` should be ``Z.b1``. RC's can be ``Z.rc1``. For beta, ``PYBIND11_VERSION_PATCH`` should be ``Z.b1``. RC's can be ``Z.rc1``.
Always include the dot (even though PEP 440 allows it to be dropped). For a Always include the dot (even though PEP 440 allows it to be dropped). For a
final release, this must be a simple integer. There is also a HEX version of final release, this must be a simple integer. There is also
the version just below. ``PYBIND11_VERSION_HEX`` just below that needs to be updated.
To release a new version of pybind11: To release a new version of pybind11:
@ -26,53 +26,93 @@ If you don't have nox, you should either use ``pipx run nox`` instead, or use
``pipx install nox`` or ``brew install nox`` (Unix). ``pipx install nox`` or ``brew install nox`` (Unix).
- Update the version number - Update the version number
- Update ``PYBIND11_VERSION_MAJOR`` etc. in
``include/pybind11/detail/common.h``. PATCH should be a simple integer. - Update ``PYBIND11_VERSION_MAJOR`` etc. in
- Update the version HEX just below, as well. ``include/pybind11/detail/common.h``. PATCH should be a simple integer.
- Update ``pybind11/_version.py`` (match above)
- Run ``nox -s tests_packaging`` to ensure this was done correctly. - Update ``PYBIND11_VERSION_HEX`` just below as well.
- Ensure that all the information in ``setup.cfg`` is up-to-date, like
supported Python versions. - Update ``pybind11/_version.py`` (match above).
- Add release date in ``docs/changelog.rst`` and integrate the output of
``nox -s make_changelog``. - Run ``nox -s tests_packaging`` to ensure this was done correctly.
- Note that the ``make_changelog`` command inspects
`needs changelog <https://github.com/pybind/pybind11/pulls?q=is%3Apr+is%3Aclosed+label%3A%22needs+changelog%22>`_. - Ensure that all the information in ``setup.cfg`` is up-to-date, like
- Manually clear the ``needs changelog`` labels using the GitHub web supported Python versions.
interface (very easy: start by clicking the link above).
- ``git add`` and ``git commit``, ``git push``. **Ensure CI passes**. (If it - Add release date in ``docs/changelog.rst`` and integrate the output of
fails due to a known flake issue, either ignore or restart CI.) ``nox -s make_changelog``.
- Add a release branch if this is a new minor version, or update the existing release branch if it is a patch version
- New branch: ``git checkout -b vX.Y``, ``git push -u origin vX.Y`` - Note that the ``nox -s make_changelog`` command inspects
- Update branch: ``git checkout vX.Y``, ``git merge <release branch>``, ``git push`` `needs changelog <https://github.com/pybind/pybind11/pulls?q=is%3Apr+is%3Aclosed+label%3A%22needs+changelog%22>`_.
- Manually clear the ``needs changelog`` labels using the GitHub web
interface (very easy: start by clicking the link above).
- ``git add`` and ``git commit``, ``git push``. **Ensure CI passes**. (If it
fails due to a known flake issue, either ignore or restart CI.)
- Add a release branch if this is a new MINOR version, or update the existing
release branch if it is a patch version
- New branch: ``git checkout -b vX.Y``, ``git push -u origin vX.Y``
- Update branch: ``git checkout vX.Y``, ``git merge <release branch>``, ``git push``
- Update tags (optional; if you skip this, the GitHub release makes a - Update tags (optional; if you skip this, the GitHub release makes a
non-annotated tag for you) non-annotated tag for you)
- ``git tag -a vX.Y.Z -m 'vX.Y.Z release'``.
- ``git push --tags``. - ``git tag -a vX.Y.Z -m 'vX.Y.Z release'``
- ``grep ^__version__ pybind11/_version.py``
- Last-minute consistency check: same as tag?
- ``git push --tags``
- Update stable - Update stable
- ``git checkout stable``
- ``git merge master`` - ``git checkout stable``
- ``git push``
- ``git merge -X theirs vX.Y.Z``
- ``git diff vX.Y.Z``
- Carefully review and reconcile any diffs. There should be none.
- ``git push``
- Make a GitHub release (this shows up in the UI, sends new release - Make a GitHub release (this shows up in the UI, sends new release
notifications to users watching releases, and also uploads PyPI packages). notifications to users watching releases, and also uploads PyPI packages).
(Note: if you do not use an existing tag, this creates a new lightweight tag (Note: if you do not use an existing tag, this creates a new lightweight tag
for you, so you could skip the above step.) for you, so you could skip the above step.)
- GUI method: Under `releases <https://github.com/pybind/pybind11/releases>`_
click "Draft a new release" on the far right, fill in the tag name - GUI method: Under `releases <https://github.com/pybind/pybind11/releases>`_
(if you didn't tag above, it will be made here), fill in a release name click "Draft a new release" on the far right, fill in the tag name
like "Version X.Y.Z", and copy-and-paste the markdown-formatted (!) changelog (if you didn't tag above, it will be made here), fill in a release name
into the description (usually ``cat docs/changelog.rst | pandoc -f rst -t gfm``). like "Version X.Y.Z", and copy-and-paste the markdown-formatted (!) changelog
Check "pre-release" if this is a beta/RC. into the description. You can use ``cat docs/changelog.rst | pandoc -f rst -t gfm``,
- CLI method: with ``gh`` installed, run ``gh release create vX.Y.Z -t "Version X.Y.Z"`` then manually remove line breaks and strip links to PRs and issues,
If this is a pre-release, add ``-p``. e.g. to a bare ``#1234``, without the surrounding ``<...>_`` hyperlink markup.
Check "pre-release" if this is a beta/RC.
- CLI method: with ``gh`` installed, run ``gh release create vX.Y.Z -t "Version X.Y.Z"``
If this is a pre-release, add ``-p``.
- Get back to work - Get back to work
- Make sure you are on master, not somewhere else: ``git checkout master``
- Update version macros in ``include/pybind11/detail/common.h`` (set PATCH to - Make sure you are on master, not somewhere else: ``git checkout master``
``0.dev1`` and increment MINOR).
- Update ``_version.py`` to match - Update version macros in ``include/pybind11/detail/common.h`` (set PATCH to
- Run ``nox -s tests_packaging`` to ensure this was done correctly. ``0.dev1`` and increment MINOR).
- Add a spot for in-development updates in ``docs/changelog.rst``.
- ``git add``, ``git commit``, ``git push`` - Update ``pybind11/_version.py`` to match.
- Run ``nox -s tests_packaging`` to ensure this was done correctly.
- If the release was a new MINOR version, add a new ``IN DEVELOPMENT``
section in ``docs/changelog.rst``.
- ``git add``, ``git commit``, ``git push``
If a version branch is updated, remember to set PATCH to ``1.dev1``. If a version branch is updated, remember to set PATCH to ``1.dev1``.
@ -89,7 +129,11 @@ merge it if there are no issues.
Manual packaging Manual packaging
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
If you need to manually upload releases, you can download the releases from the job artifacts and upload them with twine. You can also make the files locally (not recommended in general, as your local directory is more likely to be "dirty" and SDists love picking up random unrelated/hidden files); this is the procedure: If you need to manually upload releases, you can download the releases from
the job artifacts and upload them with twine. You can also make the files
locally (not recommended in general, as your local directory is more likely
to be "dirty" and SDists love picking up random unrelated/hidden files);
this is the procedure:
.. code-block:: bash .. code-block:: bash

View File

@ -8,6 +8,34 @@ to a new version. But it goes into more detail. This includes things like
deprecated APIs and their replacements, build system changes, general code deprecated APIs and their replacements, build system changes, general code
modernization and other useful information. modernization and other useful information.
.. _upgrade-guide-2.12:
v2.12
=====
NumPy support has been upgraded to support the 2.x series too. The two relevant
changes are that:
* ``dtype.flags()`` is now a ``uint64`` and ``dtype.alignment()`` an
``ssize_t`` (and NumPy may return an larger than integer value for
``itemsize()`` in NumPy 2.x).
* The long deprecated NumPy function ``PyArray_GetArrayParamsFromObject``
function is not available anymore.
Due to NumPy changes, you may experience difficulties updating to NumPy 2.
Please see the [NumPy 2 migration guide](https://numpy.org/devdocs/numpy_2_0_migration_guide.html) for details.
For example, a more direct change could be that the default integer ``"int_"``
(and ``"uint"``) is now ``ssize_t`` and not ``long`` (affects 64bit windows).
If you want to only support NumPy 1.x for now and are having problems due to
the two internal changes listed above, you can define
``PYBIND11_NUMPY_1_ONLY`` to disable the new support for now. Make sure you
define this on all pybind11 compile units, since it could be a source of ODR
violations if used inconsistently. This option will be removed in the future,
so adapting your code is highly recommended.
.. _upgrade-guide-2.11: .. _upgrade-guide-2.11:
v2.11 v2.11

View File

@ -42,13 +42,15 @@ using make_caster = type_caster<intrinsic_t<type>>;
// Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T // Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T
template <typename T> template <typename T>
typename make_caster<T>::template cast_op_type<T> cast_op(make_caster<T> &caster) { typename make_caster<T>::template cast_op_type<T> cast_op(make_caster<T> &caster) {
return caster.operator typename make_caster<T>::template cast_op_type<T>(); using result_t = typename make_caster<T>::template cast_op_type<T>; // See PR #4893
return caster.operator result_t();
} }
template <typename T> template <typename T>
typename make_caster<T>::template cast_op_type<typename std::add_rvalue_reference<T>::type> typename make_caster<T>::template cast_op_type<typename std::add_rvalue_reference<T>::type>
cast_op(make_caster<T> &&caster) { cast_op(make_caster<T> &&caster) {
return std::move(caster).operator typename make_caster<T>:: using result_t = typename make_caster<T>::template cast_op_type<
template cast_op_type<typename std::add_rvalue_reference<T>::type>(); typename std::add_rvalue_reference<T>::type>; // See PR #4893
return std::move(caster).operator result_t();
} }
template <typename type> template <typename type>
@ -325,8 +327,9 @@ public:
value = false; value = false;
return true; return true;
} }
if (convert || (std::strcmp("numpy.bool_", Py_TYPE(src.ptr())->tp_name) == 0)) { if (convert || is_numpy_bool(src)) {
// (allow non-implicit conversion for numpy booleans) // (allow non-implicit conversion for numpy booleans), use strncmp
// since NumPy 1.x had an additional trailing underscore.
Py_ssize_t res = -1; Py_ssize_t res = -1;
if (src.is_none()) { if (src.is_none()) {
@ -358,6 +361,15 @@ public:
return handle(src ? Py_True : Py_False).inc_ref(); return handle(src ? Py_True : Py_False).inc_ref();
} }
PYBIND11_TYPE_CASTER(bool, const_name("bool")); PYBIND11_TYPE_CASTER(bool, const_name("bool"));
private:
// Test if an object is a NumPy boolean (without fetching the type).
static inline bool is_numpy_bool(handle object) {
const char *type_name = Py_TYPE(object.ptr())->tp_name;
// Name changed to `numpy.bool` in NumPy 2, `numpy.bool_` is needed for 1.x support
return std::strcmp("numpy.bool", type_name) == 0
|| std::strcmp("numpy.bool_", type_name) == 0;
}
}; };
// Helper class for UTF-{8,16,32} C++ stl strings: // Helper class for UTF-{8,16,32} C++ stl strings:
@ -660,8 +672,9 @@ public:
return cast(*src, policy, parent); return cast(*src, policy, parent);
} }
static constexpr auto name static constexpr auto name = const_name("tuple[")
= const_name("Tuple[") + concat(make_caster<Ts>::name...) + const_name("]"); + ::pybind11::detail::concat(make_caster<Ts>::name...)
+ const_name("]");
template <typename T> template <typename T>
using cast_op_type = type; using cast_op_type = type;
@ -869,10 +882,53 @@ struct is_holder_type
template <typename base, typename deleter> template <typename base, typename deleter>
struct is_holder_type<base, std::unique_ptr<base, deleter>> : std::true_type {}; struct is_holder_type<base, std::unique_ptr<base, deleter>> : std::true_type {};
#ifdef PYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION // See PR #4888
// This leads to compilation errors if a specialization is missing.
template <typename T>
struct handle_type_name;
#else
template <typename T> template <typename T>
struct handle_type_name { struct handle_type_name {
static constexpr auto name = const_name<T>(); static constexpr auto name = const_name<T>();
}; };
#endif
template <>
struct handle_type_name<object> {
static constexpr auto name = const_name("object");
};
template <>
struct handle_type_name<list> {
static constexpr auto name = const_name("list");
};
template <>
struct handle_type_name<dict> {
static constexpr auto name = const_name("dict");
};
template <>
struct handle_type_name<anyset> {
static constexpr auto name = const_name("Union[set, frozenset]");
};
template <>
struct handle_type_name<set> {
static constexpr auto name = const_name("set");
};
template <>
struct handle_type_name<frozenset> {
static constexpr auto name = const_name("frozenset");
};
template <>
struct handle_type_name<str> {
static constexpr auto name = const_name("str");
};
template <>
struct handle_type_name<tuple> {
static constexpr auto name = const_name("tuple");
};
template <> template <>
struct handle_type_name<bool_> { struct handle_type_name<bool_> {
static constexpr auto name = const_name("bool"); static constexpr auto name = const_name("bool");
@ -882,6 +938,10 @@ struct handle_type_name<bytes> {
static constexpr auto name = const_name(PYBIND11_BYTES_NAME); static constexpr auto name = const_name(PYBIND11_BYTES_NAME);
}; };
template <> template <>
struct handle_type_name<buffer> {
static constexpr auto name = const_name("Buffer");
};
template <>
struct handle_type_name<int_> { struct handle_type_name<int_> {
static constexpr auto name = const_name("int"); static constexpr auto name = const_name("int");
}; };
@ -898,10 +958,50 @@ struct handle_type_name<float_> {
static constexpr auto name = const_name("float"); static constexpr auto name = const_name("float");
}; };
template <> template <>
struct handle_type_name<function> {
static constexpr auto name = const_name("Callable");
};
template <>
struct handle_type_name<handle> {
static constexpr auto name = handle_type_name<object>::name;
};
template <>
struct handle_type_name<none> { struct handle_type_name<none> {
static constexpr auto name = const_name("None"); static constexpr auto name = const_name("None");
}; };
template <> template <>
struct handle_type_name<sequence> {
static constexpr auto name = const_name("Sequence");
};
template <>
struct handle_type_name<bytearray> {
static constexpr auto name = const_name("bytearray");
};
template <>
struct handle_type_name<memoryview> {
static constexpr auto name = const_name("memoryview");
};
template <>
struct handle_type_name<slice> {
static constexpr auto name = const_name("slice");
};
template <>
struct handle_type_name<type> {
static constexpr auto name = const_name("type");
};
template <>
struct handle_type_name<capsule> {
static constexpr auto name = const_name("capsule");
};
template <>
struct handle_type_name<ellipsis> {
static constexpr auto name = const_name("ellipsis");
};
template <>
struct handle_type_name<weakref> {
static constexpr auto name = const_name("weakref");
};
template <>
struct handle_type_name<args> { struct handle_type_name<args> {
static constexpr auto name = const_name("*args"); static constexpr auto name = const_name("*args");
}; };
@ -909,6 +1009,30 @@ template <>
struct handle_type_name<kwargs> { struct handle_type_name<kwargs> {
static constexpr auto name = const_name("**kwargs"); static constexpr auto name = const_name("**kwargs");
}; };
template <>
struct handle_type_name<obj_attr_accessor> {
static constexpr auto name = const_name<obj_attr_accessor>();
};
template <>
struct handle_type_name<str_attr_accessor> {
static constexpr auto name = const_name<str_attr_accessor>();
};
template <>
struct handle_type_name<item_accessor> {
static constexpr auto name = const_name<item_accessor>();
};
template <>
struct handle_type_name<sequence_accessor> {
static constexpr auto name = const_name<sequence_accessor>();
};
template <>
struct handle_type_name<list_accessor> {
static constexpr auto name = const_name<list_accessor>();
};
template <>
struct handle_type_name<tuple_accessor> {
static constexpr auto name = const_name<tuple_accessor>();
};
template <typename type> template <typename type>
struct pyobject_caster { struct pyobject_caster {
@ -1377,7 +1501,15 @@ inline namespace literals {
/** \rst /** \rst
String literal version of `arg` String literal version of `arg`
\endrst */ \endrst */
constexpr arg operator"" _a(const char *name, size_t) { return arg(name); } constexpr arg
#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5
operator"" _a // gcc 4.8.5 insists on having a space (hard error).
#else
operator""_a // clang 17 generates a deprecation warning if there is a space.
#endif
(const char *name, size_t) {
return arg(name);
}
} // namespace literals } // namespace literals
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
@ -1438,7 +1570,8 @@ public:
static_assert(args_pos == -1 || args_pos == constexpr_first<argument_is_args, Args...>(), static_assert(args_pos == -1 || args_pos == constexpr_first<argument_is_args, Args...>(),
"py::args cannot be specified more than once"); "py::args cannot be specified more than once");
static constexpr auto arg_names = concat(type_descr(make_caster<Args>::name)...); static constexpr auto arg_names
= ::pybind11::detail::concat(type_descr(make_caster<Args>::name)...);
bool load_args(function_call &call) { return load_impl_sequence(call, indices{}); } bool load_args(function_call &call) { return load_impl_sequence(call, indices{}); }

View File

@ -86,17 +86,16 @@ inline PyTypeObject *make_static_property_type() {
type->tp_descr_get = pybind11_static_get; type->tp_descr_get = pybind11_static_get;
type->tp_descr_set = pybind11_static_set; type->tp_descr_set = pybind11_static_set;
if (PyType_Ready(type) < 0) {
pybind11_fail("make_static_property_type(): failure in PyType_Ready()!");
}
# if PY_VERSION_HEX >= 0x030C0000 # if PY_VERSION_HEX >= 0x030C0000
// PRE 3.12 FEATURE FREEZE. PLEASE REVIEW AFTER FREEZE.
// Since Python-3.12 property-derived types are required to // Since Python-3.12 property-derived types are required to
// have dynamic attributes (to set `__doc__`) // have dynamic attributes (to set `__doc__`)
enable_dynamic_attributes(heap_type); enable_dynamic_attributes(heap_type);
# endif # endif
if (PyType_Ready(type) < 0) {
pybind11_fail("make_static_property_type(): failure in PyType_Ready()!");
}
setattr((PyObject *) type, "__module__", str("pybind11_builtins")); setattr((PyObject *) type, "__module__", str("pybind11_builtins"));
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
@ -189,12 +188,10 @@ extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, P
return nullptr; return nullptr;
} }
// This must be a pybind11 instance
auto *instance = reinterpret_cast<detail::instance *>(self);
// Ensure that the base __init__ function(s) were called // Ensure that the base __init__ function(s) were called
for (const auto &vh : values_and_holders(instance)) { values_and_holders vhs(self);
if (!vh.holder_constructed()) { for (const auto &vh : vhs) {
if (!vh.holder_constructed() && !vhs.is_redundant_value_and_holder(vh)) {
PyErr_Format(PyExc_TypeError, PyErr_Format(PyExc_TypeError,
"%.200s.__init__() must be called when overriding __init__", "%.200s.__init__() must be called when overriding __init__",
get_fully_qualified_tp_name(vh.type->type).c_str()); get_fully_qualified_tp_name(vh.type->type).c_str());
@ -375,7 +372,7 @@ extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *,
extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject *) { extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject *) {
PyTypeObject *type = Py_TYPE(self); PyTypeObject *type = Py_TYPE(self);
std::string msg = get_fully_qualified_tp_name(type) + ": No constructor defined!"; std::string msg = get_fully_qualified_tp_name(type) + ": No constructor defined!";
PyErr_SetString(PyExc_TypeError, msg.c_str()); set_error(PyExc_TypeError, msg.c_str());
return -1; return -1;
} }
@ -522,8 +519,12 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) {
/// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`. /// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`.
extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) { extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) {
#if PY_VERSION_HEX >= 0x030D0000
PyObject_VisitManagedDict(self, visit, arg);
#else
PyObject *&dict = *_PyObject_GetDictPtr(self); PyObject *&dict = *_PyObject_GetDictPtr(self);
Py_VISIT(dict); Py_VISIT(dict);
#endif
// https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_traverse // https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_traverse
#if PY_VERSION_HEX >= 0x03090000 #if PY_VERSION_HEX >= 0x03090000
Py_VISIT(Py_TYPE(self)); Py_VISIT(Py_TYPE(self));
@ -533,8 +534,12 @@ extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *a
/// dynamic_attr: Allow the GC to clear the dictionary. /// dynamic_attr: Allow the GC to clear the dictionary.
extern "C" inline int pybind11_clear(PyObject *self) { extern "C" inline int pybind11_clear(PyObject *self) {
#if PY_VERSION_HEX >= 0x030D0000
PyObject_ClearManagedDict(self);
#else
PyObject *&dict = *_PyObject_GetDictPtr(self); PyObject *&dict = *_PyObject_GetDictPtr(self);
Py_CLEAR(dict); Py_CLEAR(dict);
#endif
return 0; return 0;
} }
@ -579,7 +584,7 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
if (view) { if (view) {
view->obj = nullptr; view->obj = nullptr;
} }
PyErr_SetString(PyExc_BufferError, "pybind11_getbuffer(): Internal error"); set_error(PyExc_BufferError, "pybind11_getbuffer(): Internal error");
return -1; return -1;
} }
std::memset(view, 0, sizeof(Py_buffer)); std::memset(view, 0, sizeof(Py_buffer));
@ -587,7 +592,7 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) { if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) {
delete info; delete info;
// view->obj = nullptr; // Was just memset to 0, so not necessary // view->obj = nullptr; // Was just memset to 0, so not necessary
PyErr_SetString(PyExc_BufferError, "Writable buffer requested for readonly storage"); set_error(PyExc_BufferError, "Writable buffer requested for readonly storage");
return -1; return -1;
} }
view->obj = obj; view->obj = obj;

View File

@ -10,12 +10,12 @@
#pragma once #pragma once
#define PYBIND11_VERSION_MAJOR 2 #define PYBIND11_VERSION_MAJOR 2
#define PYBIND11_VERSION_MINOR 11 #define PYBIND11_VERSION_MINOR 12
#define PYBIND11_VERSION_PATCH 1 #define PYBIND11_VERSION_PATCH 0
// Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html // Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html
// Additional convention: 0xD = dev // Additional convention: 0xD = dev
#define PYBIND11_VERSION_HEX 0x020B0100 #define PYBIND11_VERSION_HEX 0x020C0000
// Define some generic pybind11 helper macros for warning management. // Define some generic pybind11 helper macros for warning management.
// //
@ -118,6 +118,14 @@
# endif # endif
#endif #endif
#if defined(PYBIND11_CPP20)
# define PYBIND11_CONSTINIT constinit
# define PYBIND11_DTOR_CONSTEXPR constexpr
#else
# define PYBIND11_CONSTINIT
# define PYBIND11_DTOR_CONSTEXPR
#endif
// Compiler version assertions // Compiler version assertions
#if defined(__INTEL_COMPILER) #if defined(__INTEL_COMPILER)
# if __INTEL_COMPILER < 1800 # if __INTEL_COMPILER < 1800
@ -288,6 +296,10 @@ PYBIND11_WARNING_DISABLE_MSVC(4505)
# undef copysign # undef copysign
#endif #endif
#if defined(PYBIND11_NUMPY_1_ONLY)
# define PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED
#endif
#if defined(PYPY_VERSION) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) #if defined(PYPY_VERSION) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
# define PYBIND11_SIMPLE_GIL_MANAGEMENT # define PYBIND11_SIMPLE_GIL_MANAGEMENT
#endif #endif
@ -399,7 +411,7 @@ PYBIND11_WARNING_POP
return nullptr; \ return nullptr; \
} \ } \
catch (const std::exception &e) { \ catch (const std::exception &e) { \
PyErr_SetString(PyExc_ImportError, e.what()); \ ::pybind11::set_error(PyExc_ImportError, e.what()); \
return nullptr; \ return nullptr; \
} }

View File

@ -65,7 +65,7 @@ constexpr bool is_alias(void *) {
} }
// Constructs and returns a new object; if the given arguments don't map to a constructor, we fall // Constructs and returns a new object; if the given arguments don't map to a constructor, we fall
// back to brace aggregate initiailization so that for aggregate initialization can be used with // back to brace aggregate initialization so that for aggregate initialization can be used with
// py::init, e.g. `py::init<int, int>` to initialize a `struct T { int a; int b; }`. For // py::init, e.g. `py::init<int, int>` to initialize a `struct T { int a; int b; }`. For
// non-aggregate types, we need to use an ordinary T(...) constructor (invoking as `T{...}` usually // non-aggregate types, we need to use an ordinary T(...) constructor (invoking as `T{...}` usually
// works, but will not do the expected thing when `T` has an `initializer_list<T>` constructor). // works, but will not do the expected thing when `T` has an `initializer_list<T>` constructor).

View File

@ -34,8 +34,9 @@
/// further ABI-incompatible changes may be made before the ABI is officially /// further ABI-incompatible changes may be made before the ABI is officially
/// changed to the new version. /// changed to the new version.
#ifndef PYBIND11_INTERNALS_VERSION #ifndef PYBIND11_INTERNALS_VERSION
# if PY_VERSION_HEX >= 0x030C0000 # if PY_VERSION_HEX >= 0x030C0000 || defined(_MSC_VER)
// Version bump for Python 3.12+, before first 3.12 beta release. // Version bump for Python 3.12+, before first 3.12 beta release.
// Version bump for MSVC piggy-backed on PR #4779. See comments there.
# define PYBIND11_INTERNALS_VERSION 5 # define PYBIND11_INTERNALS_VERSION 5
# else # else
# define PYBIND11_INTERNALS_VERSION 4 # define PYBIND11_INTERNALS_VERSION 4
@ -66,9 +67,14 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass);
// `Py_LIMITED_API` anyway. // `Py_LIMITED_API` anyway.
# if PYBIND11_INTERNALS_VERSION > 4 # if PYBIND11_INTERNALS_VERSION > 4
# define PYBIND11_TLS_KEY_REF Py_tss_t & # define PYBIND11_TLS_KEY_REF Py_tss_t &
# if defined(__GNUC__) && !defined(__INTEL_COMPILER) # if defined(__clang__)
// Clang on macOS warns due to `Py_tss_NEEDS_INIT` not specifying an initializer # define PYBIND11_TLS_KEY_INIT(var) \
// for every field. _Pragma("clang diagnostic push") /**/ \
_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
Py_tss_t var \
= Py_tss_NEEDS_INIT; \
_Pragma("clang diagnostic pop")
# elif defined(__GNUC__) && !defined(__INTEL_COMPILER)
# define PYBIND11_TLS_KEY_INIT(var) \ # define PYBIND11_TLS_KEY_INIT(var) \
_Pragma("GCC diagnostic push") /**/ \ _Pragma("GCC diagnostic push") /**/ \
_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \ _Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
@ -291,9 +297,12 @@ struct type_info {
#endif #endif
/// On Linux/OSX, changes in __GXX_ABI_VERSION__ indicate ABI incompatibility. /// On Linux/OSX, changes in __GXX_ABI_VERSION__ indicate ABI incompatibility.
/// On MSVC, changes in _MSC_VER may indicate ABI incompatibility (#2898).
#ifndef PYBIND11_BUILD_ABI #ifndef PYBIND11_BUILD_ABI
# if defined(__GXX_ABI_VERSION) # if defined(__GXX_ABI_VERSION)
# define PYBIND11_BUILD_ABI "_cxxabi" PYBIND11_TOSTRING(__GXX_ABI_VERSION) # define PYBIND11_BUILD_ABI "_cxxabi" PYBIND11_TOSTRING(__GXX_ABI_VERSION)
# elif defined(_MSC_VER)
# define PYBIND11_BUILD_ABI "_mscver" PYBIND11_TOSTRING(_MSC_VER)
# else # else
# define PYBIND11_BUILD_ABI "" # define PYBIND11_BUILD_ABI ""
# endif # endif
@ -352,7 +361,7 @@ inline bool raise_err(PyObject *exc_type, const char *msg) {
raise_from(exc_type, msg); raise_from(exc_type, msg);
return true; return true;
} }
PyErr_SetString(exc_type, msg); set_error(exc_type, msg);
return false; return false;
} }
@ -447,6 +456,7 @@ inline object get_python_state_dict() {
#endif #endif
if (!state_dict) { if (!state_dict) {
raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED"); raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED");
throw error_already_set();
} }
return state_dict; return state_dict;
} }
@ -459,6 +469,7 @@ inline internals **get_internals_pp_from_capsule(handle obj) {
void *raw_ptr = PyCapsule_GetPointer(obj.ptr(), /*name=*/nullptr); void *raw_ptr = PyCapsule_GetPointer(obj.ptr(), /*name=*/nullptr);
if (raw_ptr == nullptr) { if (raw_ptr == nullptr) {
raise_from(PyExc_SystemError, "pybind11::detail::get_internals_pp_from_capsule() FAILED"); raise_from(PyExc_SystemError, "pybind11::detail::get_internals_pp_from_capsule() FAILED");
throw error_already_set();
} }
return static_cast<internals **>(raw_ptr); return static_cast<internals **>(raw_ptr);
} }

View File

@ -102,8 +102,22 @@ public:
inline std::pair<decltype(internals::registered_types_py)::iterator, bool> inline std::pair<decltype(internals::registered_types_py)::iterator, bool>
all_type_info_get_cache(PyTypeObject *type); all_type_info_get_cache(PyTypeObject *type);
// Band-aid workaround to fix a subtle but serious bug in a minimalistic fashion. See PR #4762.
inline void all_type_info_add_base_most_derived_first(std::vector<type_info *> &bases,
type_info *addl_base) {
for (auto it = bases.begin(); it != bases.end(); it++) {
type_info *existing_base = *it;
if (PyType_IsSubtype(addl_base->type, existing_base->type) != 0) {
bases.insert(it, addl_base);
return;
}
}
bases.push_back(addl_base);
}
// Populates a just-created cache entry. // Populates a just-created cache entry.
PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_info *> &bases) { PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_info *> &bases) {
assert(bases.empty());
std::vector<PyTypeObject *> check; std::vector<PyTypeObject *> check;
for (handle parent : reinterpret_borrow<tuple>(t->tp_bases)) { for (handle parent : reinterpret_borrow<tuple>(t->tp_bases)) {
check.push_back((PyTypeObject *) parent.ptr()); check.push_back((PyTypeObject *) parent.ptr());
@ -136,7 +150,7 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_
} }
} }
if (!found) { if (!found) {
bases.push_back(tinfo); all_type_info_add_base_most_derived_first(bases, tinfo);
} }
} }
} else if (type->tp_bases) { } else if (type->tp_bases) {
@ -322,18 +336,29 @@ public:
explicit values_and_holders(instance *inst) explicit values_and_holders(instance *inst)
: inst{inst}, tinfo(all_type_info(Py_TYPE(inst))) {} : inst{inst}, tinfo(all_type_info(Py_TYPE(inst))) {}
explicit values_and_holders(PyObject *obj)
: inst{nullptr}, tinfo(all_type_info(Py_TYPE(obj))) {
if (!tinfo.empty()) {
inst = reinterpret_cast<instance *>(obj);
}
}
struct iterator { struct iterator {
private: private:
instance *inst = nullptr; instance *inst = nullptr;
const type_vec *types = nullptr; const type_vec *types = nullptr;
value_and_holder curr; value_and_holder curr;
friend struct values_and_holders; friend struct values_and_holders;
iterator(instance *inst, const type_vec *tinfo) iterator(instance *inst, const type_vec *tinfo) : inst{inst}, types{tinfo} {
: inst{inst}, types{tinfo}, if (inst != nullptr) {
curr(inst /* instance */, assert(!types->empty());
types->empty() ? nullptr : (*types)[0] /* type info */, curr = value_and_holder(
0, /* vpos: (non-simple types only): the first vptr comes first */ inst /* instance */,
0 /* index */) {} (*types)[0] /* type info */,
0, /* vpos: (non-simple types only): the first vptr comes first */
0 /* index */);
}
}
// Past-the-end iterator: // Past-the-end iterator:
explicit iterator(size_t end) : curr(end) {} explicit iterator(size_t end) : curr(end) {}
@ -364,6 +389,16 @@ public:
} }
size_t size() { return tinfo.size(); } size_t size() { return tinfo.size(); }
// Band-aid workaround to fix a subtle but serious bug in a minimalistic fashion. See PR #4762.
bool is_redundant_value_and_holder(const value_and_holder &vh) {
for (size_t i = 0; i < vh.index; i++) {
if (PyType_IsSubtype(tinfo[i]->type, tinfo[vh.index]->type) != 0) {
return true;
}
}
return false;
}
}; };
/** /**
@ -486,8 +521,10 @@ PYBIND11_NOINLINE handle get_object_handle(const void *ptr, const detail::type_i
inline PyThreadState *get_thread_state_unchecked() { inline PyThreadState *get_thread_state_unchecked() {
#if defined(PYPY_VERSION) #if defined(PYPY_VERSION)
return PyThreadState_GET(); return PyThreadState_GET();
#else #elif PY_VERSION_HEX < 0x030D0000
return _PyThreadState_UncheckedGet(); return _PyThreadState_UncheckedGet();
#else
return PyThreadState_GetUnchecked();
#endif #endif
} }
@ -786,7 +823,7 @@ public:
std::string tname = rtti_type ? rtti_type->name() : cast_type.name(); std::string tname = rtti_type ? rtti_type->name() : cast_type.name();
detail::clean_type_id(tname); detail::clean_type_id(tname);
std::string msg = "Unregistered type : " + tname; std::string msg = "Unregistered type : " + tname;
PyErr_SetString(PyExc_TypeError, msg.c_str()); set_error(PyExc_TypeError, msg.c_str());
return {nullptr, nullptr}; return {nullptr, nullptr};
} }
@ -1164,13 +1201,17 @@ protected:
static Constructor make_move_constructor(...) { return nullptr; } static Constructor make_move_constructor(...) { return nullptr; }
}; };
inline std::string quote_cpp_type_name(const std::string &cpp_type_name) {
return cpp_type_name; // No-op for now. See PR #4888
}
PYBIND11_NOINLINE std::string type_info_description(const std::type_info &ti) { PYBIND11_NOINLINE std::string type_info_description(const std::type_info &ti) {
if (auto *type_data = get_type_info(ti)) { if (auto *type_data = get_type_info(ti)) {
handle th((PyObject *) type_data->type); handle th((PyObject *) type_data->type);
return th.attr("__module__").cast<std::string>() + '.' return th.attr("__module__").cast<std::string>() + '.'
+ th.attr("__qualname__").cast<std::string>(); + th.attr("__qualname__").cast<std::string>();
} }
return clean_type_id(ti.name()); return quote_cpp_type_name(clean_type_id(ti.name()));
} }
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)

View File

@ -70,7 +70,7 @@ struct eigen_tensor_helper<Eigen::Tensor<Scalar_, NumIndices_, Options_, IndexTy
template <size_t... Is> template <size_t... Is>
struct helper<index_sequence<Is...>> { struct helper<index_sequence<Is...>> {
static constexpr auto value = concat(const_name(((void) Is, "?"))...); static constexpr auto value = ::pybind11::detail::concat(const_name(((void) Is, "?"))...);
}; };
static constexpr auto dimensions_descriptor static constexpr auto dimensions_descriptor
@ -104,7 +104,8 @@ struct eigen_tensor_helper<
return get_shape() == shape; return get_shape() == shape;
} }
static constexpr auto dimensions_descriptor = concat(const_name<Indices>()...); static constexpr auto dimensions_descriptor
= ::pybind11::detail::concat(const_name<Indices>()...);
template <typename... Args> template <typename... Args>
static Type *alloc(Args &&...args) { static Type *alloc(Args &&...args) {

View File

@ -128,7 +128,8 @@ public:
} }
PYBIND11_TYPE_CASTER(type, PYBIND11_TYPE_CASTER(type,
const_name("Callable[[") + concat(make_caster<Args>::name...) const_name("Callable[[")
+ ::pybind11::detail::concat(make_caster<Args>::name...)
+ const_name("], ") + make_caster<retval_type>::name + const_name("], ") + make_caster<retval_type>::name
+ const_name("]")); + const_name("]"));
}; };

View File

@ -11,6 +11,8 @@
#include "detail/common.h" #include "detail/common.h"
#include <cassert>
#if defined(WITH_THREAD) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) #if defined(WITH_THREAD) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
# include "detail/internals.h" # include "detail/internals.h"
#endif #endif
@ -137,7 +139,9 @@ private:
class gil_scoped_release { class gil_scoped_release {
public: public:
// PRECONDITION: The GIL must be held when this constructor is called.
explicit gil_scoped_release(bool disassoc = false) : disassoc(disassoc) { explicit gil_scoped_release(bool disassoc = false) : disassoc(disassoc) {
assert(PyGILState_Check());
// `get_internals()` must be called here unconditionally in order to initialize // `get_internals()` must be called here unconditionally in order to initialize
// `internals.tstate` for subsequent `gil_scoped_acquire` calls. Otherwise, an // `internals.tstate` for subsequent `gil_scoped_acquire` calls. Otherwise, an
// initialization race could occur as multiple threads try `gil_scoped_acquire`. // initialization race could occur as multiple threads try `gil_scoped_acquire`.
@ -201,7 +205,11 @@ class gil_scoped_release {
PyThreadState *state; PyThreadState *state;
public: public:
gil_scoped_release() : state{PyEval_SaveThread()} {} // PRECONDITION: The GIL must be held when this constructor is called.
gil_scoped_release() {
assert(PyGILState_Check());
state = PyEval_SaveThread();
}
gil_scoped_release(const gil_scoped_release &) = delete; gil_scoped_release(const gil_scoped_release &) = delete;
gil_scoped_release &operator=(const gil_scoped_release &) = delete; gil_scoped_release &operator=(const gil_scoped_release &) = delete;
~gil_scoped_release() { PyEval_RestoreThread(state); } ~gil_scoped_release() { PyEval_RestoreThread(state); }

View File

@ -0,0 +1,91 @@
// Copyright (c) 2023 The pybind Community.
#pragma once
#include "detail/common.h"
#include "gil.h"
#include <cassert>
#include <mutex>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
// Use the `gil_safe_call_once_and_store` class below instead of the naive
//
// static auto imported_obj = py::module_::import("module_name"); // BAD, DO NOT USE!
//
// which has two serious issues:
//
// 1. Py_DECREF() calls potentially after the Python interpreter was finalized already, and
// 2. deadlocks in multi-threaded processes (because of missing lock ordering).
//
// The following alternative avoids both problems:
//
// PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
// auto &imported_obj = storage // Do NOT make this `static`!
// .call_once_and_store_result([]() {
// return py::module_::import("module_name");
// })
// .get_stored();
//
// The parameter of `call_once_and_store_result()` must be callable. It can make
// CPython API calls, and in particular, it can temporarily release the GIL.
//
// `T` can be any C++ type, it does not have to involve CPython API types.
//
// The behavior with regard to signals, e.g. `SIGINT` (`KeyboardInterrupt`),
// is not ideal. If the main thread is the one to actually run the `Callable`,
// then a `KeyboardInterrupt` will interrupt it if it is running normal Python
// code. The situation is different if a non-main thread runs the
// `Callable`, and then the main thread starts waiting for it to complete:
// a `KeyboardInterrupt` will not interrupt the non-main thread, but it will
// get processed only when it is the main thread's turn again and it is running
// normal Python code. However, this will be unnoticeable for quick call-once
// functions, which is usually the case.
template <typename T>
class gil_safe_call_once_and_store {
public:
// PRECONDITION: The GIL must be held when `call_once_and_store_result()` is called.
template <typename Callable>
gil_safe_call_once_and_store &call_once_and_store_result(Callable &&fn) {
if (!is_initialized_) { // This read is guarded by the GIL.
// Multiple threads may enter here, because the GIL is released in the next line and
// CPython API calls in the `fn()` call below may release and reacquire the GIL.
gil_scoped_release gil_rel; // Needed to establish lock ordering.
std::call_once(once_flag_, [&] {
// Only one thread will ever enter here.
gil_scoped_acquire gil_acq;
::new (storage_) T(fn()); // fn may release, but will reacquire, the GIL.
is_initialized_ = true; // This write is guarded by the GIL.
});
// All threads will observe `is_initialized_` as true here.
}
// Intentionally not returning `T &` to ensure the calling code is self-documenting.
return *this;
}
// This must only be called after `call_once_and_store_result()` was called.
T &get_stored() {
assert(is_initialized_);
PYBIND11_WARNING_PUSH
#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5
// Needed for gcc 4.8.5
PYBIND11_WARNING_DISABLE_GCC("-Wstrict-aliasing")
#endif
return *reinterpret_cast<T *>(storage_);
PYBIND11_WARNING_POP
}
constexpr gil_safe_call_once_and_store() = default;
PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() = default;
private:
alignas(T) char storage_[sizeof(T)] = {};
std::once_flag once_flag_ = {};
bool is_initialized_ = false;
// The `is_initialized_`-`storage_` pair is very similar to `std::optional`,
// but the latter does not have the triviality properties of former,
// therefore `std::optional` is not a viable alternative here.
};
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -10,7 +10,10 @@
#pragma once #pragma once
#include "pybind11.h" #include "pybind11.h"
#include "detail/common.h"
#include "complex.h" #include "complex.h"
#include "gil_safe_call_once.h"
#include "pytypes.h"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
@ -26,10 +29,15 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#if defined(PYBIND11_NUMPY_1_ONLY) && !defined(PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED)
# error PYBIND11_NUMPY_1_ONLY must be defined before any pybind11 header is included.
#endif
/* This will be true on all flat address space platforms and allows us to reduce the /* This will be true on all flat address space platforms and allows us to reduce the
whole npy_intp / ssize_t / Py_intptr_t business down to just ssize_t for all size whole npy_intp / ssize_t / Py_intptr_t business down to just ssize_t for all size
and dimension types (e.g. shape, strides, indexing), instead of inflicting this and dimension types (e.g. shape, strides, indexing), instead of inflicting this
upon the library user. */ upon the library user.
Note that NumPy 2 now uses ssize_t for `npy_intp` to simplify this. */
static_assert(sizeof(::pybind11::ssize_t) == sizeof(Py_intptr_t), "ssize_t != Py_intptr_t"); static_assert(sizeof(::pybind11::ssize_t) == sizeof(Py_intptr_t), "ssize_t != Py_intptr_t");
static_assert(std::is_signed<Py_intptr_t>::value, "Py_intptr_t must be signed"); static_assert(std::is_signed<Py_intptr_t>::value, "Py_intptr_t must be signed");
// We now can reinterpret_cast between py::ssize_t and Py_intptr_t (MSVC + PyPy cares) // We now can reinterpret_cast between py::ssize_t and Py_intptr_t (MSVC + PyPy cares)
@ -38,10 +46,16 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127) PYBIND11_WARNING_DISABLE_MSVC(4127)
class dtype; // Forward declaration
class array; // Forward declaration class array; // Forward declaration
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
template <>
struct handle_type_name<dtype> {
static constexpr auto name = const_name("numpy.dtype");
};
template <> template <>
struct handle_type_name<array> { struct handle_type_name<array> {
static constexpr auto name = const_name("numpy.ndarray"); static constexpr auto name = const_name("numpy.ndarray");
@ -50,7 +64,8 @@ struct handle_type_name<array> {
template <typename type, typename SFINAE = void> template <typename type, typename SFINAE = void>
struct npy_format_descriptor; struct npy_format_descriptor;
struct PyArrayDescr_Proxy { /* NumPy 1 proxy (always includes legacy fields) */
struct PyArrayDescr1_Proxy {
PyObject_HEAD PyObject_HEAD
PyObject *typeobj; PyObject *typeobj;
char kind; char kind;
@ -65,6 +80,43 @@ struct PyArrayDescr_Proxy {
PyObject *names; PyObject *names;
}; };
#ifndef PYBIND11_NUMPY_1_ONLY
struct PyArrayDescr_Proxy {
PyObject_HEAD
PyObject *typeobj;
char kind;
char type;
char byteorder;
char _former_flags;
int type_num;
/* Additional fields are NumPy version specific. */
};
#else
/* NumPy 1.x only, we can expose all fields */
using PyArrayDescr_Proxy = PyArrayDescr1_Proxy;
#endif
/* NumPy 2 proxy, including legacy fields */
struct PyArrayDescr2_Proxy {
PyObject_HEAD
PyObject *typeobj;
char kind;
char type;
char byteorder;
char _former_flags;
int type_num;
std::uint64_t flags;
ssize_t elsize;
ssize_t alignment;
PyObject *metadata;
Py_hash_t hash;
void *reserved_null[2];
/* The following fields only exist if 0 <= type_num < 2056 */
char *subarray;
PyObject *fields;
PyObject *names;
};
struct PyArray_Proxy { struct PyArray_Proxy {
PyObject_HEAD PyObject_HEAD
char *data; char *data;
@ -120,6 +172,28 @@ inline numpy_internals &get_numpy_internals() {
return *ptr; return *ptr;
} }
PYBIND11_NOINLINE module_ import_numpy_core_submodule(const char *submodule_name) {
module_ numpy = module_::import("numpy");
str version_string = numpy.attr("__version__");
module_ numpy_lib = module_::import("numpy.lib");
object numpy_version = numpy_lib.attr("NumpyVersion")(version_string);
int major_version = numpy_version.attr("major").cast<int>();
#ifdef PYBIND11_NUMPY_1_ONLY
if (major_version >= 2) {
throw std::runtime_error(
"This extension was built with PYBIND11_NUMPY_1_ONLY defined, "
"but NumPy 2 is used in this process. For NumPy2 compatibility, "
"this extension needs to be rebuilt without the PYBIND11_NUMPY_1_ONLY define.");
}
#endif
/* `numpy.core` was renamed to `numpy._core` in NumPy 2.0 as it officially
became a private module. */
std::string numpy_core_path = major_version >= 2 ? "numpy._core" : "numpy.core";
return module_::import((numpy_core_path + "." + submodule_name).c_str());
}
template <typename T> template <typename T>
struct same_size { struct same_size {
template <typename U> template <typename U>
@ -186,14 +260,16 @@ struct npy_api {
NPY_ULONG_, NPY_ULONGLONG_, NPY_UINT_), NPY_ULONG_, NPY_ULONGLONG_, NPY_UINT_),
}; };
unsigned int PyArray_RUNTIME_VERSION_;
struct PyArray_Dims { struct PyArray_Dims {
Py_intptr_t *ptr; Py_intptr_t *ptr;
int len; int len;
}; };
static npy_api &get() { static npy_api &get() {
static npy_api api = lookup(); PYBIND11_CONSTINIT static gil_safe_call_once_and_store<npy_api> storage;
return api; return storage.call_once_and_store_result(lookup).get_stored();
} }
bool PyArray_Check_(PyObject *obj) const { bool PyArray_Check_(PyObject *obj) const {
@ -224,6 +300,7 @@ struct npy_api {
PyObject *(*PyArray_FromAny_)(PyObject *, PyObject *, int, int, int, PyObject *); PyObject *(*PyArray_FromAny_)(PyObject *, PyObject *, int, int, int, PyObject *);
int (*PyArray_DescrConverter_)(PyObject *, PyObject **); int (*PyArray_DescrConverter_)(PyObject *, PyObject **);
bool (*PyArray_EquivTypes_)(PyObject *, PyObject *); bool (*PyArray_EquivTypes_)(PyObject *, PyObject *);
#ifdef PYBIND11_NUMPY_1_ONLY
int (*PyArray_GetArrayParamsFromObject_)(PyObject *, int (*PyArray_GetArrayParamsFromObject_)(PyObject *,
PyObject *, PyObject *,
unsigned char, unsigned char,
@ -232,6 +309,7 @@ struct npy_api {
Py_intptr_t *, Py_intptr_t *,
PyObject **, PyObject **,
PyObject *); PyObject *);
#endif
PyObject *(*PyArray_Squeeze_)(PyObject *); PyObject *(*PyArray_Squeeze_)(PyObject *);
// Unused. Not removed because that affects ABI of the class. // Unused. Not removed because that affects ABI of the class.
int (*PyArray_SetBaseObject_)(PyObject *, PyObject *); int (*PyArray_SetBaseObject_)(PyObject *, PyObject *);
@ -249,7 +327,8 @@ private:
API_PyArray_DescrFromScalar = 57, API_PyArray_DescrFromScalar = 57,
API_PyArray_FromAny = 69, API_PyArray_FromAny = 69,
API_PyArray_Resize = 80, API_PyArray_Resize = 80,
API_PyArray_CopyInto = 82, // CopyInto was slot 82 and 50 was effectively an alias. NumPy 2 removed 82.
API_PyArray_CopyInto = 50,
API_PyArray_NewCopy = 85, API_PyArray_NewCopy = 85,
API_PyArray_NewFromDescr = 94, API_PyArray_NewFromDescr = 94,
API_PyArray_DescrNewFromType = 96, API_PyArray_DescrNewFromType = 96,
@ -258,18 +337,25 @@ private:
API_PyArray_View = 137, API_PyArray_View = 137,
API_PyArray_DescrConverter = 174, API_PyArray_DescrConverter = 174,
API_PyArray_EquivTypes = 182, API_PyArray_EquivTypes = 182,
#ifdef PYBIND11_NUMPY_1_ONLY
API_PyArray_GetArrayParamsFromObject = 278, API_PyArray_GetArrayParamsFromObject = 278,
#endif
API_PyArray_SetBaseObject = 282 API_PyArray_SetBaseObject = 282
}; };
static npy_api lookup() { static npy_api lookup() {
module_ m = module_::import("numpy.core.multiarray"); module_ m = detail::import_numpy_core_submodule("multiarray");
auto c = m.attr("_ARRAY_API"); auto c = m.attr("_ARRAY_API");
void **api_ptr = (void **) PyCapsule_GetPointer(c.ptr(), nullptr); void **api_ptr = (void **) PyCapsule_GetPointer(c.ptr(), nullptr);
if (api_ptr == nullptr) {
raise_from(PyExc_SystemError, "FAILURE obtaining numpy _ARRAY_API pointer.");
throw error_already_set();
}
npy_api api; npy_api api;
#define DECL_NPY_API(Func) api.Func##_ = (decltype(api.Func##_)) api_ptr[API_##Func]; #define DECL_NPY_API(Func) api.Func##_ = (decltype(api.Func##_)) api_ptr[API_##Func];
DECL_NPY_API(PyArray_GetNDArrayCFeatureVersion); DECL_NPY_API(PyArray_GetNDArrayCFeatureVersion);
if (api.PyArray_GetNDArrayCFeatureVersion_() < 0x7) { api.PyArray_RUNTIME_VERSION_ = api.PyArray_GetNDArrayCFeatureVersion_();
if (api.PyArray_RUNTIME_VERSION_ < 0x7) {
pybind11_fail("pybind11 numpy support requires numpy >= 1.7.0"); pybind11_fail("pybind11 numpy support requires numpy >= 1.7.0");
} }
DECL_NPY_API(PyArray_Type); DECL_NPY_API(PyArray_Type);
@ -288,7 +374,9 @@ private:
DECL_NPY_API(PyArray_View); DECL_NPY_API(PyArray_View);
DECL_NPY_API(PyArray_DescrConverter); DECL_NPY_API(PyArray_DescrConverter);
DECL_NPY_API(PyArray_EquivTypes); DECL_NPY_API(PyArray_EquivTypes);
#ifdef PYBIND11_NUMPY_1_ONLY
DECL_NPY_API(PyArray_GetArrayParamsFromObject); DECL_NPY_API(PyArray_GetArrayParamsFromObject);
#endif
DECL_NPY_API(PyArray_SetBaseObject); DECL_NPY_API(PyArray_SetBaseObject);
#undef DECL_NPY_API #undef DECL_NPY_API
@ -310,6 +398,14 @@ inline const PyArrayDescr_Proxy *array_descriptor_proxy(const PyObject *ptr) {
return reinterpret_cast<const PyArrayDescr_Proxy *>(ptr); return reinterpret_cast<const PyArrayDescr_Proxy *>(ptr);
} }
inline const PyArrayDescr1_Proxy *array_descriptor1_proxy(const PyObject *ptr) {
return reinterpret_cast<const PyArrayDescr1_Proxy *>(ptr);
}
inline const PyArrayDescr2_Proxy *array_descriptor2_proxy(const PyObject *ptr) {
return reinterpret_cast<const PyArrayDescr2_Proxy *>(ptr);
}
inline bool check_flags(const void *ptr, int flag) { inline bool check_flags(const void *ptr, int flag) {
return (flag == (array_proxy(ptr)->flags & flag)); return (flag == (array_proxy(ptr)->flags & flag));
} }
@ -350,7 +446,7 @@ struct array_info<std::array<T, N>> {
} }
static constexpr auto extents = const_name<array_info<T>::is_array>( static constexpr auto extents = const_name<array_info<T>::is_array>(
concat(const_name<N>(), array_info<T>::extents), const_name<N>()); ::pybind11::detail::concat(const_name<N>(), array_info<T>::extents), const_name<N>());
}; };
// For numpy we have special handling for arrays of characters, so we don't include // For numpy we have special handling for arrays of characters, so we don't include
// the size in the array extents. // the size in the array extents.
@ -589,10 +685,32 @@ public:
} }
/// Size of the data type in bytes. /// Size of the data type in bytes.
#ifdef PYBIND11_NUMPY_1_ONLY
ssize_t itemsize() const { return detail::array_descriptor_proxy(m_ptr)->elsize; } ssize_t itemsize() const { return detail::array_descriptor_proxy(m_ptr)->elsize; }
#else
ssize_t itemsize() const {
if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) {
return detail::array_descriptor1_proxy(m_ptr)->elsize;
}
return detail::array_descriptor2_proxy(m_ptr)->elsize;
}
#endif
/// Returns true for structured data types. /// Returns true for structured data types.
#ifdef PYBIND11_NUMPY_1_ONLY
bool has_fields() const { return detail::array_descriptor_proxy(m_ptr)->names != nullptr; } bool has_fields() const { return detail::array_descriptor_proxy(m_ptr)->names != nullptr; }
#else
bool has_fields() const {
if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) {
return detail::array_descriptor1_proxy(m_ptr)->names != nullptr;
}
const auto *proxy = detail::array_descriptor2_proxy(m_ptr);
if (proxy->type_num < 0 || proxy->type_num >= 2056) {
return false;
}
return proxy->names != nullptr;
}
#endif
/// Single-character code for dtype's kind. /// Single-character code for dtype's kind.
/// For example, floating point types are 'f' and integral types are 'i'. /// For example, floating point types are 'f' and integral types are 'i'.
@ -618,20 +736,39 @@ public:
/// Single character for byteorder /// Single character for byteorder
char byteorder() const { return detail::array_descriptor_proxy(m_ptr)->byteorder; } char byteorder() const { return detail::array_descriptor_proxy(m_ptr)->byteorder; }
/// Alignment of the data type /// Alignment of the data type
#ifdef PYBIND11_NUMPY_1_ONLY
int alignment() const { return detail::array_descriptor_proxy(m_ptr)->alignment; } int alignment() const { return detail::array_descriptor_proxy(m_ptr)->alignment; }
#else
ssize_t alignment() const {
if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) {
return detail::array_descriptor1_proxy(m_ptr)->alignment;
}
return detail::array_descriptor2_proxy(m_ptr)->alignment;
}
#endif
/// Flags for the array descriptor /// Flags for the array descriptor
#ifdef PYBIND11_NUMPY_1_ONLY
char flags() const { return detail::array_descriptor_proxy(m_ptr)->flags; } char flags() const { return detail::array_descriptor_proxy(m_ptr)->flags; }
#else
std::uint64_t flags() const {
if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) {
return (unsigned char) detail::array_descriptor1_proxy(m_ptr)->flags;
}
return detail::array_descriptor2_proxy(m_ptr)->flags;
}
#endif
private: private:
static object _dtype_from_pep3118() { static object &_dtype_from_pep3118() {
static PyObject *obj = module_::import("numpy.core._internal") PYBIND11_CONSTINIT static gil_safe_call_once_and_store<object> storage;
.attr("_dtype_from_pep3118") return storage
.cast<object>() .call_once_and_store_result([]() {
.release() return detail::import_numpy_core_submodule("_internal")
.ptr(); .attr("_dtype_from_pep3118");
return reinterpret_borrow<object>(obj); })
.get_stored();
} }
dtype strip_padding(ssize_t itemsize) { dtype strip_padding(ssize_t itemsize) {
@ -788,9 +925,7 @@ public:
} }
/// Byte size of a single element /// Byte size of a single element
ssize_t itemsize() const { ssize_t itemsize() const { return dtype().itemsize(); }
return detail::array_descriptor_proxy(detail::array_proxy(m_ptr)->descr)->elsize;
}
/// Total number of bytes /// Total number of bytes
ssize_t nbytes() const { return size() * itemsize(); } ssize_t nbytes() const { return size() * itemsize(); }
@ -1008,7 +1143,7 @@ protected:
/// Create array from any object -- always returns a new reference /// Create array from any object -- always returns a new reference
static PyObject *raw_array(PyObject *ptr, int ExtraFlags = 0) { static PyObject *raw_array(PyObject *ptr, int ExtraFlags = 0) {
if (ptr == nullptr) { if (ptr == nullptr) {
PyErr_SetString(PyExc_ValueError, "cannot create a pybind11::array from a nullptr"); set_error(PyExc_ValueError, "cannot create a pybind11::array from a nullptr");
return nullptr; return nullptr;
} }
return detail::npy_api::get().PyArray_FromAny_( return detail::npy_api::get().PyArray_FromAny_(
@ -1155,7 +1290,7 @@ protected:
/// Create array from any object -- always returns a new reference /// Create array from any object -- always returns a new reference
static PyObject *raw_array_t(PyObject *ptr) { static PyObject *raw_array_t(PyObject *ptr) {
if (ptr == nullptr) { if (ptr == nullptr) {
PyErr_SetString(PyExc_ValueError, "cannot create a pybind11::array_t from a nullptr"); set_error(PyExc_ValueError, "cannot create a pybind11::array_t from a nullptr");
return nullptr; return nullptr;
} }
return detail::npy_api::get().PyArray_FromAny_(ptr, return detail::npy_api::get().PyArray_FromAny_(ptr,

View File

@ -14,7 +14,9 @@
#include "detail/init.h" #include "detail/init.h"
#include "attr.h" #include "attr.h"
#include "gil.h" #include "gil.h"
#include "gil_safe_call_once.h"
#include "options.h" #include "options.h"
#include "typing.h"
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
@ -52,6 +54,47 @@ PYBIND11_WARNING_DISABLE_MSVC(4127)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
inline std::string replace_newlines_and_squash(const char *text) {
const char *whitespaces = " \t\n\r\f\v";
std::string result(text);
bool previous_is_whitespace = false;
if (result.size() >= 2) {
// Do not modify string representations
char first_char = result[0];
char last_char = result[result.size() - 1];
if (first_char == last_char && first_char == '\'') {
return result;
}
}
result.clear();
// Replace characters in whitespaces array with spaces and squash consecutive spaces
while (*text != '\0') {
if (std::strchr(whitespaces, *text)) {
if (!previous_is_whitespace) {
result += ' ';
previous_is_whitespace = true;
}
} else {
result += *text;
previous_is_whitespace = false;
}
++text;
}
// Strip leading and trailing whitespaces
const size_t str_begin = result.find_first_not_of(whitespaces);
if (str_begin == std::string::npos) {
return "";
}
const size_t str_end = result.find_last_not_of(whitespaces);
const size_t str_range = str_end - str_begin + 1;
return result.substr(str_begin, str_range);
}
// Apply all the extensions translators from a list // Apply all the extensions translators from a list
// Return true if one of the translators completed without raising an exception // Return true if one of the translators completed without raising an exception
// itself. Return of false indicates that if there are other translators // itself. Return of false indicates that if there are other translators
@ -424,7 +467,7 @@ protected:
// Write default value if available. // Write default value if available.
if (!is_starred && arg_index < rec->args.size() && rec->args[arg_index].descr) { if (!is_starred && arg_index < rec->args.size() && rec->args[arg_index].descr) {
signature += " = "; signature += " = ";
signature += rec->args[arg_index].descr; signature += detail::replace_newlines_and_squash(rec->args[arg_index].descr);
} }
// Separator for positional-only arguments (placed after the // Separator for positional-only arguments (placed after the
// argument, rather than before like * // argument, rather than before like *
@ -449,9 +492,7 @@ protected:
signature += rec->scope.attr("__module__").cast<std::string>() + "." signature += rec->scope.attr("__module__").cast<std::string>() + "."
+ rec->scope.attr("__qualname__").cast<std::string>(); + rec->scope.attr("__qualname__").cast<std::string>();
} else { } else {
std::string tname(t->name()); signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name()));
detail::clean_type_id(tname);
signature += tname;
} }
} else { } else {
signature += c; signature += c;
@ -680,7 +721,7 @@ protected:
/* Iterator over the list of potentially admissible overloads */ /* Iterator over the list of potentially admissible overloads */
const function_record *overloads = reinterpret_cast<function_record *>( const function_record *overloads = reinterpret_cast<function_record *>(
PyCapsule_GetPointer(self, get_function_record_capsule_name())), PyCapsule_GetPointer(self, get_function_record_capsule_name())),
*it = overloads; *current_overload = overloads;
assert(overloads != nullptr); assert(overloads != nullptr);
/* Need to know how many arguments + keyword arguments there are to pick the right /* Need to know how many arguments + keyword arguments there are to pick the right
@ -694,9 +735,8 @@ protected:
if (overloads->is_constructor) { if (overloads->is_constructor) {
if (!parent if (!parent
|| !PyObject_TypeCheck(parent.ptr(), (PyTypeObject *) overloads->scope.ptr())) { || !PyObject_TypeCheck(parent.ptr(), (PyTypeObject *) overloads->scope.ptr())) {
PyErr_SetString( set_error(PyExc_TypeError,
PyExc_TypeError, "__init__(self, ...) called with invalid or missing `self` argument");
"__init__(self, ...) called with invalid or missing `self` argument");
return nullptr; return nullptr;
} }
@ -719,9 +759,10 @@ protected:
std::vector<function_call> second_pass; std::vector<function_call> second_pass;
// However, if there are no overloads, we can just skip the no-convert pass entirely // However, if there are no overloads, we can just skip the no-convert pass entirely
const bool overloaded = it != nullptr && it->next != nullptr; const bool overloaded
= current_overload != nullptr && current_overload->next != nullptr;
for (; it != nullptr; it = it->next) { for (; current_overload != nullptr; current_overload = current_overload->next) {
/* For each overload: /* For each overload:
1. Copy all positional arguments we were given, also checking to make sure that 1. Copy all positional arguments we were given, also checking to make sure that
@ -742,7 +783,7 @@ protected:
a result other than PYBIND11_TRY_NEXT_OVERLOAD. a result other than PYBIND11_TRY_NEXT_OVERLOAD.
*/ */
const function_record &func = *it; const function_record &func = *current_overload;
size_t num_args = func.nargs; // Number of positional arguments that we need size_t num_args = func.nargs; // Number of positional arguments that we need
if (func.has_args) { if (func.has_args) {
--num_args; // (but don't count py::args --num_args; // (but don't count py::args
@ -980,10 +1021,10 @@ protected:
} }
if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD) { if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD) {
// The error reporting logic below expects 'it' to be valid, as it would be // The error reporting logic below expects 'current_overload' to be valid,
// if we'd encountered this failure in the first-pass loop. // as it would be if we'd encountered this failure in the first-pass loop.
if (!result) { if (!result) {
it = &call.func; current_overload = &call.func;
} }
break; break;
} }
@ -1007,7 +1048,7 @@ protected:
A translator may choose to do one of the following: A translator may choose to do one of the following:
- catch the exception and call PyErr_SetString or PyErr_SetObject - catch the exception and call py::set_error()
to set a standard (or custom) Python exception, or to set a standard (or custom) Python exception, or
- do nothing and let the exception fall through to the next translator, or - do nothing and let the exception fall through to the next translator, or
- delegate translation to the next translator by throwing a new type of exception. - delegate translation to the next translator by throwing a new type of exception.
@ -1023,8 +1064,7 @@ protected:
return nullptr; return nullptr;
} }
PyErr_SetString(PyExc_SystemError, set_error(PyExc_SystemError, "Exception escaped from default exception translator!");
"Exception escaped from default exception translator!");
return nullptr; return nullptr;
} }
@ -1102,7 +1142,7 @@ protected:
} }
msg += "kwargs: "; msg += "kwargs: ";
bool first = true; bool first = true;
for (auto kwarg : kwargs) { for (const auto &kwarg : kwargs) {
if (first) { if (first) {
first = false; first = false;
} else { } else {
@ -1125,20 +1165,21 @@ protected:
raise_from(PyExc_TypeError, msg.c_str()); raise_from(PyExc_TypeError, msg.c_str());
return nullptr; return nullptr;
} }
PyErr_SetString(PyExc_TypeError, msg.c_str()); set_error(PyExc_TypeError, msg.c_str());
return nullptr; return nullptr;
} }
if (!result) { if (!result) {
std::string msg = "Unable to convert function return value to a " std::string msg = "Unable to convert function return value to a "
"Python type! The signature was\n\t"; "Python type! The signature was\n\t";
msg += it->signature; assert(current_overload != nullptr);
msg += current_overload->signature;
append_note_if_missing_header_is_suspected(msg); append_note_if_missing_header_is_suspected(msg);
// Attach additional error info to the exception if supported // Attach additional error info to the exception if supported
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
raise_from(PyExc_TypeError, msg.c_str()); raise_from(PyExc_TypeError, msg.c_str());
return nullptr; return nullptr;
} }
PyErr_SetString(PyExc_TypeError, msg.c_str()); set_error(PyExc_TypeError, msg.c_str());
return nullptr; return nullptr;
} }
if (overloads->is_constructor && !self_value_and_holder.holder_constructed()) { if (overloads->is_constructor && !self_value_and_holder.holder_constructed()) {
@ -1149,6 +1190,15 @@ protected:
} }
}; };
PYBIND11_NAMESPACE_BEGIN(detail)
template <>
struct handle_type_name<cpp_function> {
static constexpr auto name = const_name("Callable");
};
PYBIND11_NAMESPACE_END(detail)
/// Wrapper for Python extension modules /// Wrapper for Python extension modules
class module_ : public object { class module_ : public object {
public: public:
@ -1276,6 +1326,15 @@ public:
} }
}; };
PYBIND11_NAMESPACE_BEGIN(detail)
template <>
struct handle_type_name<module_> {
static constexpr auto name = const_name("module");
};
PYBIND11_NAMESPACE_END(detail)
// When inside a namespace (or anywhere as long as it's not the first item on a line), // When inside a namespace (or anywhere as long as it's not the first item on a line),
// C++20 allows "module" to be used. This is provided for backward compatibility, and for // C++20 allows "module" to be used. This is provided for backward compatibility, and for
// simplicity, if someone wants to use py::module for example, that is perfectly safe. // simplicity, if someone wants to use py::module for example, that is perfectly safe.
@ -1977,7 +2036,7 @@ struct enum_base {
object type_name = type::handle_of(arg).attr("__name__"); object type_name = type::handle_of(arg).attr("__name__");
return pybind11::str("{}.{}").format(std::move(type_name), enum_name(arg)); return pybind11::str("{}.{}").format(std::move(type_name), enum_name(arg));
}, },
name("name"), name("__str__"),
is_method(m_base)); is_method(m_base));
if (options::show_enum_members_docstring()) { if (options::show_enum_members_docstring()) {
@ -2395,7 +2454,7 @@ iterator make_iterator_impl(Iterator first, Sentinel last, Extra &&...extra) {
Policy); Policy);
} }
return cast(state{first, last, true}); return cast(state{std::forward<Iterator>(first), std::forward<Sentinel>(last), true});
} }
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
@ -2406,13 +2465,15 @@ template <return_value_policy Policy = return_value_policy::reference_internal,
typename Sentinel, typename Sentinel,
typename ValueType = typename detail::iterator_access<Iterator>::result_type, typename ValueType = typename detail::iterator_access<Iterator>::result_type,
typename... Extra> typename... Extra>
iterator make_iterator(Iterator first, Sentinel last, Extra &&...extra) { typing::Iterator<ValueType> make_iterator(Iterator first, Sentinel last, Extra &&...extra) {
return detail::make_iterator_impl<detail::iterator_access<Iterator>, return detail::make_iterator_impl<detail::iterator_access<Iterator>,
Policy, Policy,
Iterator, Iterator,
Sentinel, Sentinel,
ValueType, ValueType,
Extra...>(first, last, std::forward<Extra>(extra)...); Extra...>(std::forward<Iterator>(first),
std::forward<Sentinel>(last),
std::forward<Extra>(extra)...);
} }
/// Makes a python iterator over the keys (`.first`) of a iterator over pairs from a /// Makes a python iterator over the keys (`.first`) of a iterator over pairs from a
@ -2422,13 +2483,15 @@ template <return_value_policy Policy = return_value_policy::reference_internal,
typename Sentinel, typename Sentinel,
typename KeyType = typename detail::iterator_key_access<Iterator>::result_type, typename KeyType = typename detail::iterator_key_access<Iterator>::result_type,
typename... Extra> typename... Extra>
iterator make_key_iterator(Iterator first, Sentinel last, Extra &&...extra) { typing::Iterator<KeyType> make_key_iterator(Iterator first, Sentinel last, Extra &&...extra) {
return detail::make_iterator_impl<detail::iterator_key_access<Iterator>, return detail::make_iterator_impl<detail::iterator_key_access<Iterator>,
Policy, Policy,
Iterator, Iterator,
Sentinel, Sentinel,
KeyType, KeyType,
Extra...>(first, last, std::forward<Extra>(extra)...); Extra...>(std::forward<Iterator>(first),
std::forward<Sentinel>(last),
std::forward<Extra>(extra)...);
} }
/// Makes a python iterator over the values (`.second`) of a iterator over pairs from a /// Makes a python iterator over the values (`.second`) of a iterator over pairs from a
@ -2438,21 +2501,25 @@ template <return_value_policy Policy = return_value_policy::reference_internal,
typename Sentinel, typename Sentinel,
typename ValueType = typename detail::iterator_value_access<Iterator>::result_type, typename ValueType = typename detail::iterator_value_access<Iterator>::result_type,
typename... Extra> typename... Extra>
iterator make_value_iterator(Iterator first, Sentinel last, Extra &&...extra) { typing::Iterator<ValueType> make_value_iterator(Iterator first, Sentinel last, Extra &&...extra) {
return detail::make_iterator_impl<detail::iterator_value_access<Iterator>, return detail::make_iterator_impl<detail::iterator_value_access<Iterator>,
Policy, Policy,
Iterator, Iterator,
Sentinel, Sentinel,
ValueType, ValueType,
Extra...>(first, last, std::forward<Extra>(extra)...); Extra...>(std::forward<Iterator>(first),
std::forward<Sentinel>(last),
std::forward<Extra>(extra)...);
} }
/// Makes an iterator over values of an stl container or other container supporting /// Makes an iterator over values of an stl container or other container supporting
/// `std::begin()`/`std::end()` /// `std::begin()`/`std::end()`
template <return_value_policy Policy = return_value_policy::reference_internal, template <return_value_policy Policy = return_value_policy::reference_internal,
typename Type, typename Type,
typename ValueType = typename detail::iterator_access<
decltype(std::begin(std::declval<Type &>()))>::result_type,
typename... Extra> typename... Extra>
iterator make_iterator(Type &value, Extra &&...extra) { typing::Iterator<ValueType> make_iterator(Type &value, Extra &&...extra) {
return make_iterator<Policy>( return make_iterator<Policy>(
std::begin(value), std::end(value), std::forward<Extra>(extra)...); std::begin(value), std::end(value), std::forward<Extra>(extra)...);
} }
@ -2461,8 +2528,10 @@ iterator make_iterator(Type &value, Extra &&...extra) {
/// `std::begin()`/`std::end()` /// `std::begin()`/`std::end()`
template <return_value_policy Policy = return_value_policy::reference_internal, template <return_value_policy Policy = return_value_policy::reference_internal,
typename Type, typename Type,
typename KeyType = typename detail::iterator_key_access<
decltype(std::begin(std::declval<Type &>()))>::result_type,
typename... Extra> typename... Extra>
iterator make_key_iterator(Type &value, Extra &&...extra) { typing::Iterator<KeyType> make_key_iterator(Type &value, Extra &&...extra) {
return make_key_iterator<Policy>( return make_key_iterator<Policy>(
std::begin(value), std::end(value), std::forward<Extra>(extra)...); std::begin(value), std::end(value), std::forward<Extra>(extra)...);
} }
@ -2471,8 +2540,10 @@ iterator make_key_iterator(Type &value, Extra &&...extra) {
/// `std::begin()`/`std::end()` /// `std::begin()`/`std::end()`
template <return_value_policy Policy = return_value_policy::reference_internal, template <return_value_policy Policy = return_value_policy::reference_internal,
typename Type, typename Type,
typename ValueType = typename detail::iterator_value_access<
decltype(std::begin(std::declval<Type &>()))>::result_type,
typename... Extra> typename... Extra>
iterator make_value_iterator(Type &value, Extra &&...extra) { typing::Iterator<ValueType> make_value_iterator(Type &value, Extra &&...extra) {
return make_value_iterator<Policy>( return make_value_iterator<Policy>(
std::begin(value), std::end(value), std::forward<Extra>(extra)...); std::begin(value), std::end(value), std::forward<Extra>(extra)...);
} }
@ -2528,7 +2599,7 @@ inline void register_local_exception_translator(ExceptionTranslator &&translator
/** /**
* Wrapper to generate a new Python exception type. * Wrapper to generate a new Python exception type.
* *
* This should only be used with PyErr_SetString for now. * This should only be used with py::set_error() for now.
* It is not (yet) possible to use as a py::base. * It is not (yet) possible to use as a py::base.
* Template type argument is reserved for future use. * Template type argument is reserved for future use.
*/ */
@ -2549,27 +2620,25 @@ public:
} }
// Sets the current python exception to this exception object with the given message // Sets the current python exception to this exception object with the given message
void operator()(const char *message) { PyErr_SetString(m_ptr, message); } PYBIND11_DEPRECATED("Please use py::set_error() instead "
"(https://github.com/pybind/pybind11/pull/4772)")
void operator()(const char *message) const { set_error(*this, message); }
}; };
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
// Returns a reference to a function-local static exception object used in the simple
// register_exception approach below. (It would be simpler to have the static local variable template <>
// directly in register_exception, but that makes clang <3.5 segfault - issue #1349). struct handle_type_name<exception<void>> {
template <typename CppException> static constexpr auto name = const_name("Exception");
exception<CppException> &get_exception_object() { };
static exception<CppException> ex;
return ex;
}
// Helper function for register_exception and register_local_exception // Helper function for register_exception and register_local_exception
template <typename CppException> template <typename CppException>
exception<CppException> & exception<CppException> &
register_exception_impl(handle scope, const char *name, handle base, bool isLocal) { register_exception_impl(handle scope, const char *name, handle base, bool isLocal) {
auto &ex = detail::get_exception_object<CppException>(); PYBIND11_CONSTINIT static gil_safe_call_once_and_store<exception<CppException>> exc_storage;
if (!ex) { exc_storage.call_once_and_store_result(
ex = exception<CppException>(scope, name, base); [&]() { return exception<CppException>(scope, name, base); });
}
auto register_func auto register_func
= isLocal ? &register_local_exception_translator : &register_exception_translator; = isLocal ? &register_local_exception_translator : &register_exception_translator;
@ -2581,10 +2650,10 @@ register_exception_impl(handle scope, const char *name, handle base, bool isLoca
try { try {
std::rethrow_exception(p); std::rethrow_exception(p);
} catch (const CppException &e) { } catch (const CppException &e) {
detail::get_exception_object<CppException>()(e.what()); set_error(exc_storage.get_stored(), e.what());
} }
}); });
return ex; return exc_storage.get_stored();
} }
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
@ -2703,7 +2772,11 @@ get_type_override(const void *this_ptr, const type_info *this_type, const char *
if ((std::string) str(f_code->co_name) == name && f_code->co_argcount > 0) { if ((std::string) str(f_code->co_name) == name && f_code->co_argcount > 0) {
PyObject *locals = PyEval_GetLocals(); PyObject *locals = PyEval_GetLocals();
if (locals != nullptr) { if (locals != nullptr) {
# if PY_VERSION_HEX >= 0x030b0000
PyObject *co_varnames = PyCode_GetVarnames(f_code);
# else
PyObject *co_varnames = PyObject_GetAttrString((PyObject *) f_code, "co_varnames"); PyObject *co_varnames = PyObject_GetAttrString((PyObject *) f_code, "co_varnames");
# endif
PyObject *self_arg = PyTuple_GET_ITEM(co_varnames, 0); PyObject *self_arg = PyTuple_GET_ITEM(co_varnames, 0);
Py_DECREF(co_varnames); Py_DECREF(co_varnames);
PyObject *self_caller = dict_getitem(locals, self_arg); PyObject *self_caller = dict_getitem(locals, self_arg);

View File

@ -59,6 +59,7 @@ struct sequence_item;
struct list_item; struct list_item;
struct tuple_item; struct tuple_item;
} // namespace accessor_policies } // namespace accessor_policies
// PLEASE KEEP handle_type_name SPECIALIZATIONS IN SYNC.
using obj_attr_accessor = accessor<accessor_policies::obj_attr>; using obj_attr_accessor = accessor<accessor_policies::obj_attr>;
using str_attr_accessor = accessor<accessor_policies::str_attr>; using str_attr_accessor = accessor<accessor_policies::str_attr>;
using item_accessor = accessor<accessor_policies::generic_item>; using item_accessor = accessor<accessor_policies::generic_item>;
@ -305,19 +306,19 @@ private:
"https://pybind11.readthedocs.io/en/stable/advanced/" "https://pybind11.readthedocs.io/en/stable/advanced/"
"misc.html#common-sources-of-global-interpreter-lock-errors for debugging advice.\n" "misc.html#common-sources-of-global-interpreter-lock-errors for debugging advice.\n"
"If you are convinced there is no bug in your code, you can #define " "If you are convinced there is no bug in your code, you can #define "
"PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF" "PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF "
"to disable this check. In that case you have to ensure this #define is consistently " "to disable this check. In that case you have to ensure this #define is consistently "
"used for all translation units linked into a given pybind11 extension, otherwise " "used for all translation units linked into a given pybind11 extension, otherwise "
"there will be ODR violations.", "there will be ODR violations.",
function_name.c_str()); function_name.c_str());
fflush(stderr);
if (Py_TYPE(m_ptr)->tp_name != nullptr) { if (Py_TYPE(m_ptr)->tp_name != nullptr) {
fprintf(stderr, fprintf(stderr,
"The failing %s call was triggered on a %s object.\n", " The failing %s call was triggered on a %s object.",
function_name.c_str(), function_name.c_str(),
Py_TYPE(m_ptr)->tp_name); Py_TYPE(m_ptr)->tp_name);
fflush(stderr);
} }
fprintf(stderr, "\n");
fflush(stderr);
throw std::runtime_error(function_name + " PyGILState_Check() failure."); throw std::runtime_error(function_name + " PyGILState_Check() failure.");
} }
#endif #endif
@ -334,6 +335,14 @@ public:
#endif #endif
}; };
inline void set_error(const handle &type, const char *message) {
PyErr_SetString(type.ptr(), message);
}
inline void set_error(const handle &type, const handle &value) {
PyErr_SetObject(type.ptr(), value.ptr());
}
/** \rst /** \rst
Holds a reference to a Python object (with reference counting) Holds a reference to a Python object (with reference counting)
@ -1612,7 +1621,15 @@ inline namespace literals {
/** \rst /** \rst
String literal version of `str` String literal version of `str`
\endrst */ \endrst */
inline str operator"" _s(const char *s, size_t size) { return {s, size}; } inline str
#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5
operator"" _s // gcc 4.8.5 insists on having a space (hard error).
#else
operator""_s // clang 17 generates a deprecation warning if there is a space.
#endif
(const char *s, size_t size) {
return {s, size};
}
} // namespace literals } // namespace literals
/// \addtogroup pytypes /// \addtogroup pytypes

View File

@ -100,7 +100,7 @@ public:
return s.release(); return s.release();
} }
PYBIND11_TYPE_CASTER(type, const_name("Set[") + key_conv::name + const_name("]")); PYBIND11_TYPE_CASTER(type, const_name("set[") + key_conv::name + const_name("]"));
}; };
template <typename Type, typename Key, typename Value> template <typename Type, typename Key, typename Value>
@ -157,7 +157,7 @@ public:
} }
PYBIND11_TYPE_CASTER(Type, PYBIND11_TYPE_CASTER(Type,
const_name("Dict[") + key_conv::name + const_name(", ") + value_conv::name const_name("dict[") + key_conv::name + const_name(", ") + value_conv::name
+ const_name("]")); + const_name("]"));
}; };
@ -172,7 +172,7 @@ struct list_caster {
auto s = reinterpret_borrow<sequence>(src); auto s = reinterpret_borrow<sequence>(src);
value.clear(); value.clear();
reserve_maybe(s, &value); reserve_maybe(s, &value);
for (auto it : s) { for (const auto &it : s) {
value_conv conv; value_conv conv;
if (!conv.load(it, convert)) { if (!conv.load(it, convert)) {
return false; return false;
@ -208,7 +208,7 @@ public:
return l.release(); return l.release();
} }
PYBIND11_TYPE_CASTER(Type, const_name("List[") + value_conv::name + const_name("]")); PYBIND11_TYPE_CASTER(Type, const_name("list[") + value_conv::name + const_name("]"));
}; };
template <typename Type, typename Alloc> template <typename Type, typename Alloc>
@ -247,7 +247,7 @@ public:
return false; return false;
} }
size_t ctr = 0; size_t ctr = 0;
for (auto it : l) { for (const auto &it : l) {
value_conv conv; value_conv conv;
if (!conv.load(it, convert)) { if (!conv.load(it, convert)) {
return false; return false;
@ -274,7 +274,7 @@ public:
PYBIND11_TYPE_CASTER(ArrayType, PYBIND11_TYPE_CASTER(ArrayType,
const_name<Resizable>(const_name(""), const_name("Annotated[")) const_name<Resizable>(const_name(""), const_name("Annotated["))
+ const_name("List[") + value_conv::name + const_name("]") + const_name("list[") + value_conv::name + const_name("]")
+ const_name<Resizable>(const_name(""), + const_name<Resizable>(const_name(""),
const_name(", FixedSize(") const_name(", FixedSize(")
+ const_name<Size>() + const_name(")]"))); + const_name<Size>() + const_name(")]")));
@ -421,7 +421,8 @@ struct variant_caster<V<Ts...>> {
using Type = V<Ts...>; using Type = V<Ts...>;
PYBIND11_TYPE_CASTER(Type, PYBIND11_TYPE_CASTER(Type,
const_name("Union[") + detail::concat(make_caster<Ts>::name...) const_name("Union[")
+ ::pybind11::detail::concat(make_caster<Ts>::name...)
+ const_name("]")); + const_name("]"));
}; };

View File

@ -525,7 +525,7 @@ class_<Vector, holder_type> bind_vector(handle scope, std::string const &name, A
[](const Vector &v) -> bool { return !v.empty(); }, [](const Vector &v) -> bool { return !v.empty(); },
"Check whether the list is nonempty"); "Check whether the list is nonempty");
cl.def("__len__", &Vector::size); cl.def("__len__", [](const Vector &vec) { return vec.size(); });
#if 0 #if 0
// C++ style functions deprecated, leaving it here as an example // C++ style functions deprecated, leaving it here as an example
@ -645,49 +645,50 @@ auto map_if_insertion_operator(Class_ &cl, std::string const &name)
"Return the canonical string representation of this map."); "Return the canonical string representation of this map.");
} }
template <typename KeyType>
struct keys_view { struct keys_view {
virtual size_t len() = 0; virtual size_t len() = 0;
virtual iterator iter() = 0; virtual iterator iter() = 0;
virtual bool contains(const KeyType &k) = 0; virtual bool contains(const handle &k) = 0;
virtual bool contains(const object &k) = 0;
virtual ~keys_view() = default; virtual ~keys_view() = default;
}; };
template <typename MappedType>
struct values_view { struct values_view {
virtual size_t len() = 0; virtual size_t len() = 0;
virtual iterator iter() = 0; virtual iterator iter() = 0;
virtual ~values_view() = default; virtual ~values_view() = default;
}; };
template <typename KeyType, typename MappedType>
struct items_view { struct items_view {
virtual size_t len() = 0; virtual size_t len() = 0;
virtual iterator iter() = 0; virtual iterator iter() = 0;
virtual ~items_view() = default; virtual ~items_view() = default;
}; };
template <typename Map, typename KeysView> template <typename Map>
struct KeysViewImpl : public KeysView { struct KeysViewImpl : public detail::keys_view {
explicit KeysViewImpl(Map &map) : map(map) {} explicit KeysViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); } size_t len() override { return map.size(); }
iterator iter() override { return make_key_iterator(map.begin(), map.end()); } iterator iter() override { return make_key_iterator(map.begin(), map.end()); }
bool contains(const typename Map::key_type &k) override { return map.find(k) != map.end(); } bool contains(const handle &k) override {
bool contains(const object &) override { return false; } try {
return map.find(k.template cast<typename Map::key_type>()) != map.end();
} catch (const cast_error &) {
return false;
}
}
Map &map; Map &map;
}; };
template <typename Map, typename ValuesView> template <typename Map>
struct ValuesViewImpl : public ValuesView { struct ValuesViewImpl : public detail::values_view {
explicit ValuesViewImpl(Map &map) : map(map) {} explicit ValuesViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); } size_t len() override { return map.size(); }
iterator iter() override { return make_value_iterator(map.begin(), map.end()); } iterator iter() override { return make_value_iterator(map.begin(), map.end()); }
Map &map; Map &map;
}; };
template <typename Map, typename ItemsView> template <typename Map>
struct ItemsViewImpl : public ItemsView { struct ItemsViewImpl : public detail::items_view {
explicit ItemsViewImpl(Map &map) : map(map) {} explicit ItemsViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); } size_t len() override { return map.size(); }
iterator iter() override { return make_iterator(map.begin(), map.end()); } iterator iter() override { return make_iterator(map.begin(), map.end()); }
@ -700,11 +701,9 @@ template <typename Map, typename holder_type = std::unique_ptr<Map>, typename...
class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&...args) { class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&...args) {
using KeyType = typename Map::key_type; using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type; using MappedType = typename Map::mapped_type;
using StrippedKeyType = detail::remove_cvref_t<KeyType>; using KeysView = detail::keys_view;
using StrippedMappedType = detail::remove_cvref_t<MappedType>; using ValuesView = detail::values_view;
using KeysView = detail::keys_view<StrippedKeyType>; using ItemsView = detail::items_view;
using ValuesView = detail::values_view<StrippedMappedType>;
using ItemsView = detail::items_view<StrippedKeyType, StrippedMappedType>;
using Class_ = class_<Map, holder_type>; using Class_ = class_<Map, holder_type>;
// If either type is a non-module-local bound type then make the map binding non-local as well; // If either type is a non-module-local bound type then make the map binding non-local as well;
@ -718,39 +717,20 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&
} }
Class_ cl(scope, name.c_str(), pybind11::module_local(local), std::forward<Args>(args)...); Class_ cl(scope, name.c_str(), pybind11::module_local(local), std::forward<Args>(args)...);
static constexpr auto key_type_descr = detail::make_caster<KeyType>::name;
static constexpr auto mapped_type_descr = detail::make_caster<MappedType>::name;
std::string key_type_name(key_type_descr.text), mapped_type_name(mapped_type_descr.text);
// If key type isn't properly wrapped, fall back to C++ names // Wrap KeysView if it wasn't already wrapped
if (key_type_name == "%") {
key_type_name = detail::type_info_description(typeid(KeyType));
}
// Similarly for value type:
if (mapped_type_name == "%") {
mapped_type_name = detail::type_info_description(typeid(MappedType));
}
// Wrap KeysView[KeyType] if it wasn't already wrapped
if (!detail::get_type_info(typeid(KeysView))) { if (!detail::get_type_info(typeid(KeysView))) {
class_<KeysView> keys_view( class_<KeysView> keys_view(scope, "KeysView", pybind11::module_local(local));
scope, ("KeysView[" + key_type_name + "]").c_str(), pybind11::module_local(local));
keys_view.def("__len__", &KeysView::len); keys_view.def("__len__", &KeysView::len);
keys_view.def("__iter__", keys_view.def("__iter__",
&KeysView::iter, &KeysView::iter,
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
); );
keys_view.def("__contains__", keys_view.def("__contains__", &KeysView::contains);
static_cast<bool (KeysView::*)(const KeyType &)>(&KeysView::contains));
// Fallback for when the object is not of the key type
keys_view.def("__contains__",
static_cast<bool (KeysView::*)(const object &)>(&KeysView::contains));
} }
// Similarly for ValuesView: // Similarly for ValuesView:
if (!detail::get_type_info(typeid(ValuesView))) { if (!detail::get_type_info(typeid(ValuesView))) {
class_<ValuesView> values_view(scope, class_<ValuesView> values_view(scope, "ValuesView", pybind11::module_local(local));
("ValuesView[" + mapped_type_name + "]").c_str(),
pybind11::module_local(local));
values_view.def("__len__", &ValuesView::len); values_view.def("__len__", &ValuesView::len);
values_view.def("__iter__", values_view.def("__iter__",
&ValuesView::iter, &ValuesView::iter,
@ -759,10 +739,7 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&
} }
// Similarly for ItemsView: // Similarly for ItemsView:
if (!detail::get_type_info(typeid(ItemsView))) { if (!detail::get_type_info(typeid(ItemsView))) {
class_<ItemsView> items_view( class_<ItemsView> items_view(scope, "ItemsView", pybind11::module_local(local));
scope,
("ItemsView[" + key_type_name + ", ").append(mapped_type_name + "]").c_str(),
pybind11::module_local(local));
items_view.def("__len__", &ItemsView::len); items_view.def("__len__", &ItemsView::len);
items_view.def("__iter__", items_view.def("__iter__",
&ItemsView::iter, &ItemsView::iter,
@ -788,25 +765,19 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&
cl.def( cl.def(
"keys", "keys",
[](Map &m) { [](Map &m) { return std::unique_ptr<KeysView>(new detail::KeysViewImpl<Map>(m)); },
return std::unique_ptr<KeysView>(new detail::KeysViewImpl<Map, KeysView>(m));
},
keep_alive<0, 1>() /* Essential: keep map alive while view exists */ keep_alive<0, 1>() /* Essential: keep map alive while view exists */
); );
cl.def( cl.def(
"values", "values",
[](Map &m) { [](Map &m) { return std::unique_ptr<ValuesView>(new detail::ValuesViewImpl<Map>(m)); },
return std::unique_ptr<ValuesView>(new detail::ValuesViewImpl<Map, ValuesView>(m));
},
keep_alive<0, 1>() /* Essential: keep map alive while view exists */ keep_alive<0, 1>() /* Essential: keep map alive while view exists */
); );
cl.def( cl.def(
"items", "items",
[](Map &m) { [](Map &m) { return std::unique_ptr<ItemsView>(new detail::ItemsViewImpl<Map>(m)); },
return std::unique_ptr<ItemsView>(new detail::ItemsViewImpl<Map, ItemsView>(m));
},
keep_alive<0, 1>() /* Essential: keep map alive while view exists */ keep_alive<0, 1>() /* Essential: keep map alive while view exists */
); );
@ -843,7 +814,8 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&
m.erase(it); m.erase(it);
}); });
cl.def("__len__", &Map::size); // Always use a lambda in case of `using` declaration
cl.def("__len__", [](const Map &m) { return m.size(); });
return cl; return cl;
} }

125
include/pybind11/typing.h Normal file
View File

@ -0,0 +1,125 @@
/*
pybind11/typing.h: Convenience wrapper classes for basic Python types
with more explicit annotations.
Copyright (c) 2023 Dustin Spicuzza <dustin@virtualroadside.com>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "detail/common.h"
#include "cast.h"
#include "pytypes.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(typing)
/*
The following types can be used to direct pybind11-generated docstrings
to have have more explicit types (e.g., `list[str]` instead of `list`).
Just use these in place of existing types.
There is no additional enforcement of types at runtime.
*/
template <typename... Types>
class Tuple : public tuple {
using tuple::tuple;
};
template <typename K, typename V>
class Dict : public dict {
using dict::dict;
};
template <typename T>
class List : public list {
using list::list;
};
template <typename T>
class Set : public set {
using set::set;
};
template <typename T>
class Iterable : public iterable {
using iterable::iterable;
};
template <typename T>
class Iterator : public iterator {
using iterator::iterator;
};
template <typename Signature>
class Callable;
template <typename Return, typename... Args>
class Callable<Return(Args...)> : public function {
using function::function;
};
PYBIND11_NAMESPACE_END(typing)
PYBIND11_NAMESPACE_BEGIN(detail)
template <typename... Types>
struct handle_type_name<typing::Tuple<Types...>> {
static constexpr auto name = const_name("tuple[")
+ ::pybind11::detail::concat(make_caster<Types>::name...)
+ const_name("]");
};
template <>
struct handle_type_name<typing::Tuple<>> {
// PEP 484 specifies this syntax for an empty tuple
static constexpr auto name = const_name("tuple[()]");
};
template <typename T>
struct handle_type_name<typing::Tuple<T, ellipsis>> {
// PEP 484 specifies this syntax for a variable-length tuple
static constexpr auto name
= const_name("tuple[") + make_caster<T>::name + const_name(", ...]");
};
template <typename K, typename V>
struct handle_type_name<typing::Dict<K, V>> {
static constexpr auto name = const_name("dict[") + make_caster<K>::name + const_name(", ")
+ make_caster<V>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::List<T>> {
static constexpr auto name = const_name("list[") + make_caster<T>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::Set<T>> {
static constexpr auto name = const_name("set[") + make_caster<T>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::Iterable<T>> {
static constexpr auto name = const_name("Iterable[") + make_caster<T>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::Iterator<T>> {
static constexpr auto name = const_name("Iterator[") + make_caster<T>::name + const_name("]");
};
template <typename Return, typename... Args>
struct handle_type_name<typing::Callable<Return(Args...)>> {
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
static constexpr auto name
= const_name("Callable[[") + ::pybind11::detail::concat(make_caster<Args>::name...)
+ const_name("], ") + make_caster<retval_type>::name + const_name("]");
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -57,7 +57,7 @@ def tests_packaging(session: nox.Session) -> None:
Run the packaging tests. Run the packaging tests.
""" """
session.install("-r", "tests/requirements.txt", "--prefer-binary") session.install("-r", "tests/requirements.txt")
session.run("pytest", "tests/extra_python_package", *session.posargs) session.run("pytest", "tests/extra_python_package", *session.posargs)

View File

@ -8,5 +8,5 @@ def _to_int(s: str) -> Union[int, str]:
return s return s
__version__ = "2.11.1" __version__ = "2.12.0"
version_info = tuple(_to_int(s) for s in __version__.split(".")) version_info = tuple(_to_int(s) for s in __version__.split("."))

View File

@ -66,7 +66,9 @@ try:
from setuptools import Extension as _Extension from setuptools import Extension as _Extension
from setuptools.command.build_ext import build_ext as _build_ext from setuptools.command.build_ext import build_ext as _build_ext
except ImportError: except ImportError:
from distutils.command.build_ext import build_ext as _build_ext # type: ignore[assignment] from distutils.command.build_ext import ( # type: ignore[assignment]
build_ext as _build_ext,
)
from distutils.extension import Extension as _Extension # type: ignore[assignment] from distutils.extension import Extension as _Extension # type: ignore[assignment]
import distutils.ccompiler import distutils.ccompiler

View File

@ -19,7 +19,7 @@ ignore = [
[tool.mypy] [tool.mypy]
files = ["pybind11"] files = ["pybind11"]
python_version = "3.6" python_version = "3.7"
strict = true strict = true
show_error_codes = true show_error_codes = true
enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
@ -57,10 +57,12 @@ messages_control.disable = [
"unused-argument", # covered by Ruff ARG "unused-argument", # covered by Ruff ARG
] ]
[tool.ruff] [tool.ruff]
select = [ target-version = "py37"
"E", "F", "W", # flake8 src = ["src"]
[tool.ruff.lint]
extend-select = [
"B", # flake8-bugbear "B", # flake8-bugbear
"I", # isort "I", # isort
"N", # pep8-naming "N", # pep8-naming
@ -68,7 +70,6 @@ select = [
"C4", # flake8-comprehensions "C4", # flake8-comprehensions
"EM", # flake8-errmsg "EM", # flake8-errmsg
"ICN", # flake8-import-conventions "ICN", # flake8-import-conventions
"ISC", # flake8-implicit-str-concat
"PGH", # pygrep-hooks "PGH", # pygrep-hooks
"PIE", # flake8-pie "PIE", # flake8-pie
"PL", # pylint "PL", # pylint
@ -86,13 +87,9 @@ ignore = [
"PT004", # Fixture that doesn't return needs underscore (no, it is fine) "PT004", # Fixture that doesn't return needs underscore (no, it is fine)
"SIM118", # iter(x) is not always the same as iter(x.keys()) "SIM118", # iter(x) is not always the same as iter(x.keys())
] ]
target-version = "py37"
src = ["src"]
unfixable = ["T20"] unfixable = ["T20"]
exclude = []
line-length = 120
isort.known-first-party = ["env", "pybind11_cross_module_tests", "pybind11_tests"] isort.known-first-party = ["env", "pybind11_cross_module_tests", "pybind11_tests"]
[tool.ruff.per-file-ignores] [tool.ruff.lint.per-file-ignores]
"tests/**" = ["EM", "N"] "tests/**" = ["EM", "N", "E721"]
"tests/test_call_policies.py" = ["PLC1901"] "tests/test_call_policies.py" = ["PLC1901"]

View File

@ -7,13 +7,13 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.27)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.26) if(${CMAKE_VERSION} VERSION_LESS 3.27)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.26) cmake_policy(VERSION 3.27)
endif() endif()
# Filter out items; print an optional message if any items filtered. This ignores extensions. # Filter out items; print an optional message if any items filtered. This ignores extensions.
@ -144,6 +144,7 @@ set(PYBIND11_TEST_FILES
test_opaque_types test_opaque_types
test_operator_overloading test_operator_overloading
test_pickling test_pickling
test_python_multiple_inheritance
test_pytypes test_pytypes
test_sequences_and_iterators test_sequences_and_iterators
test_smart_ptr test_smart_ptr
@ -519,11 +520,15 @@ set(PYBIND11_TEST_PREFIX_COMMAND
"" ""
CACHE STRING "Put this before pytest, use for checkers and such") CACHE STRING "Put this before pytest, use for checkers and such")
set(PYBIND11_PYTEST_ARGS
""
CACHE STRING "Extra arguments for pytest")
# A single command to compile and run the tests # A single command to compile and run the tests
add_custom_target( add_custom_target(
pytest pytest
COMMAND ${PYBIND11_TEST_PREFIX_COMMAND} ${PYTHON_EXECUTABLE} -m pytest COMMAND ${PYBIND11_TEST_PREFIX_COMMAND} ${PYTHON_EXECUTABLE} -m pytest
${PYBIND11_ABS_PYTEST_FILES} ${PYBIND11_ABS_PYTEST_FILES} ${PYBIND11_PYTEST_ARGS}
DEPENDS ${test_targets} DEPENDS ${test_targets}
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
USES_TERMINAL) USES_TERMINAL)

View File

@ -218,4 +218,5 @@ def pytest_report_header(config):
f" {pybind11_tests.cpp_std}" f" {pybind11_tests.cpp_std}"
f" {pybind11_tests.PYBIND11_INTERNALS_ID}" f" {pybind11_tests.PYBIND11_INTERNALS_ID}"
f" PYBIND11_SIMPLE_GIL_MANAGEMENT={pybind11_tests.PYBIND11_SIMPLE_GIL_MANAGEMENT}" f" PYBIND11_SIMPLE_GIL_MANAGEMENT={pybind11_tests.PYBIND11_SIMPLE_GIL_MANAGEMENT}"
f" PYBIND11_NUMPY_1_ONLY={pybind11_tests.PYBIND11_NUMPY_1_ONLY}"
) )

View File

@ -16,12 +16,12 @@ namespace {
namespace py = pybind11; namespace py = pybind11;
void interleaved_error_already_set() { void interleaved_error_already_set() {
PyErr_SetString(PyExc_RuntimeError, "1st error."); py::set_error(PyExc_RuntimeError, "1st error.");
try { try {
throw py::error_already_set(); throw py::error_already_set();
} catch (const py::error_already_set &) { } catch (const py::error_already_set &) {
// The 2nd error could be conditional in a real application. // The 2nd error could be conditional in a real application.
PyErr_SetString(PyExc_RuntimeError, "2nd error."); py::set_error(PyExc_RuntimeError, "2nd error.");
} // Here the 1st error is destroyed before the 2nd error is fetched. } // Here the 1st error is destroyed before the 2nd error is fetched.
// The error_already_set dtor triggers a pybind11::detail::get_internals() // The error_already_set dtor triggers a pybind11::detail::get_internals()
// call via pybind11::gil_scoped_acquire. // call via pybind11::gil_scoped_acquire.

View File

@ -35,6 +35,7 @@ main_headers = {
"include/pybind11/eval.h", "include/pybind11/eval.h",
"include/pybind11/functional.h", "include/pybind11/functional.h",
"include/pybind11/gil.h", "include/pybind11/gil.h",
"include/pybind11/gil_safe_call_once.h",
"include/pybind11/iostream.h", "include/pybind11/iostream.h",
"include/pybind11/numpy.h", "include/pybind11/numpy.h",
"include/pybind11/operators.h", "include/pybind11/operators.h",
@ -44,6 +45,7 @@ main_headers = {
"include/pybind11/stl.h", "include/pybind11/stl.h",
"include/pybind11/stl_bind.h", "include/pybind11/stl_bind.h",
"include/pybind11/type_caster_pyobject_ptr.h", "include/pybind11/type_caster_pyobject_ptr.h",
"include/pybind11/typing.h",
} }
detail_headers = { detail_headers = {

View File

@ -31,11 +31,11 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
// test_exceptions.py // test_exceptions.py
py::register_local_exception<LocalSimpleException>(m, "LocalSimpleException"); py::register_local_exception<LocalSimpleException>(m, "LocalSimpleException");
m.def("raise_runtime_error", []() { m.def("raise_runtime_error", []() {
PyErr_SetString(PyExc_RuntimeError, "My runtime error"); py::set_error(PyExc_RuntimeError, "My runtime error");
throw py::error_already_set(); throw py::error_already_set();
}); });
m.def("raise_value_error", []() { m.def("raise_value_error", []() {
PyErr_SetString(PyExc_ValueError, "My value error"); py::set_error(PyExc_ValueError, "My value error");
throw py::error_already_set(); throw py::error_already_set();
}); });
m.def("throw_pybind_value_error", []() { throw py::value_error("pybind11 value error"); }); m.def("throw_pybind_value_error", []() { throw py::value_error("pybind11 value error"); });
@ -49,7 +49,7 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
std::rethrow_exception(p); std::rethrow_exception(p);
} }
} catch (const shared_exception &e) { } catch (const shared_exception &e) {
PyErr_SetString(PyExc_KeyError, e.what()); py::set_error(PyExc_KeyError, e.what());
} }
}); });
@ -60,7 +60,7 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
std::rethrow_exception(p); std::rethrow_exception(p);
} }
} catch (const LocalException &e) { } catch (const LocalException &e) {
PyErr_SetString(PyExc_KeyError, e.what()); py::set_error(PyExc_KeyError, e.what());
} }
}); });

View File

@ -80,10 +80,10 @@ PYBIND11_MODULE(pybind11_tests, m) {
// Intentionally kept minimal to not create a maintenance chore // Intentionally kept minimal to not create a maintenance chore
// ("just enough" to be conclusive). // ("just enough" to be conclusive).
#if defined(_MSC_FULL_VER) #if defined(__VERSION__)
m.attr("compiler_info") = "MSVC " PYBIND11_TOSTRING(_MSC_FULL_VER);
#elif defined(__VERSION__)
m.attr("compiler_info") = __VERSION__; m.attr("compiler_info") = __VERSION__;
#elif defined(_MSC_FULL_VER)
m.attr("compiler_info") = "MSVC " PYBIND11_TOSTRING(_MSC_FULL_VER);
#else #else
m.attr("compiler_info") = py::none(); m.attr("compiler_info") = py::none();
#endif #endif
@ -95,6 +95,12 @@ PYBIND11_MODULE(pybind11_tests, m) {
#else #else
false; false;
#endif #endif
m.attr("PYBIND11_NUMPY_1_ONLY") =
#if defined(PYBIND11_NUMPY_1_ONLY)
true;
#else
false;
#endif
bind_ConstructorStats(m); bind_ConstructorStats(m);

View File

@ -1,9 +1,15 @@
build==0.8.0 --only-binary=:all:
numpy==1.21.5; platform_python_implementation=="PyPy" and sys_platform=="linux" and python_version=="3.7" build~=0.9; python_version=="3.6"
numpy==1.19.3; platform_python_implementation!="PyPy" and python_version=="3.6" build~=1.0; python_version>="3.7"
numpy==1.21.5; platform_python_implementation!="PyPy" and python_version>="3.7" and python_version<"3.10" numpy~=1.20.0; python_version=="3.7" and platform_python_implementation=="PyPy"
numpy==1.22.2; platform_python_implementation!="PyPy" and python_version>="3.10" and python_version<"3.11" numpy~=1.23.0; python_version=="3.8" and platform_python_implementation=="PyPy"
pytest==7.0.0 numpy~=1.25.0; python_version=="3.9" and platform_python_implementation=='PyPy'
numpy~=1.19.3; platform_python_implementation!="PyPy" and python_version=="3.6"
numpy~=1.21.5; platform_python_implementation!="PyPy" and python_version>="3.7" and python_version<"3.10"
numpy~=1.22.2; platform_python_implementation!="PyPy" and python_version=="3.10"
numpy~=1.26.0; platform_python_implementation!="PyPy" and python_version>="3.11" and python_version<"3.13"
pytest~=7.0
pytest-timeout pytest-timeout
scipy==1.5.4; platform_python_implementation!="PyPy" and python_version<"3.10" scipy~=1.5.4; platform_python_implementation!="PyPy" and python_version<"3.10"
scipy==1.10.0; platform_python_implementation!="PyPy" and python_version=="3.10" scipy~=1.8.0; platform_python_implementation!="PyPy" and python_version=="3.10"
scipy~=1.11.1; platform_python_implementation!="PyPy" and python_version>="3.11" and python_version<"3.13"

View File

@ -219,3 +219,10 @@ def test_ctypes_from_buffer():
assert cinfo.shape == pyinfo.shape assert cinfo.shape == pyinfo.shape
assert cinfo.strides == pyinfo.strides assert cinfo.strides == pyinfo.strides
assert not cinfo.readonly assert not cinfo.readonly
def test_buffer_docstring():
assert (
m.get_buffer_info.__doc__.strip()
== "get_buffer_info(arg0: Buffer) -> pybind11_tests.buffers.buffer_info"
)

View File

@ -352,7 +352,7 @@ def test_tuple(doc):
assert ( assert (
doc(m.pair_passthrough) doc(m.pair_passthrough)
== """ == """
pair_passthrough(arg0: Tuple[bool, str]) -> Tuple[str, bool] pair_passthrough(arg0: tuple[bool, str]) -> tuple[str, bool]
Return a pair in reversed order Return a pair in reversed order
""" """
@ -360,7 +360,7 @@ def test_tuple(doc):
assert ( assert (
doc(m.tuple_passthrough) doc(m.tuple_passthrough)
== """ == """
tuple_passthrough(arg0: Tuple[bool, str, int]) -> Tuple[int, str, bool] tuple_passthrough(arg0: tuple[bool, str, int]) -> tuple[int, str, bool]
Return a triple in reversed order Return a triple in reversed order
""" """

View File

@ -216,3 +216,10 @@ def test_custom_func():
def test_custom_func2(): def test_custom_func2():
assert m.custom_function2(3) == 27 assert m.custom_function2(3) == 27
assert m.roundtrip(m.custom_function2)(3) == 27 assert m.roundtrip(m.custom_function2)(3) == 27
def test_callback_docstring():
assert (
m.test_tuple_unpacking.__doc__.strip()
== "test_tuple_unpacking(arg0: Callable) -> object"
)

View File

@ -1,3 +1,5 @@
from unittest import mock
import pytest import pytest
import env import env
@ -203,6 +205,18 @@ def test_inheritance_init(msg):
assert msg(exc_info.value) == expected assert msg(exc_info.value) == expected
@pytest.mark.parametrize(
"mock_return_value", [None, (1, 2, 3), m.Pet("Polly", "parrot"), m.Dog("Molly")]
)
def test_mock_new(mock_return_value):
with mock.patch.object(
m.Pet, "__new__", return_value=mock_return_value
) as mock_new:
obj = m.Pet("Noname", "Nospecies")
assert obj is mock_return_value
mock_new.assert_called_once_with(m.Pet, "Noname", "Nospecies")
def test_automatic_upcasting(): def test_automatic_upcasting():
assert type(m.return_class_1()).__name__ == "DerivedClass1" assert type(m.return_class_1()).__name__ == "DerivedClass1"
assert type(m.return_class_2()).__name__ == "DerivedClass2" assert type(m.return_class_2()).__name__ == "DerivedClass2"

View File

@ -5,9 +5,8 @@ function(pybind11_add_build_test name)
set(build_options "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}") set(build_options "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}")
list(APPEND build_options "-DPYBIND11_FINDPYTHON=${PYBIND11_FINDPYTHON}")
if(PYBIND11_FINDPYTHON) if(PYBIND11_FINDPYTHON)
list(APPEND build_options "-DPYBIND11_FINDPYTHON=${PYBIND11_FINDPYTHON}")
if(DEFINED Python_ROOT_DIR) if(DEFINED Python_ROOT_DIR)
list(APPEND build_options "-DPython_ROOT_DIR=${Python_ROOT_DIR}") list(APPEND build_options "-DPython_ROOT_DIR=${Python_ROOT_DIR}")
endif() endif()

View File

@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.27)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.26) if(${CMAKE_VERSION} VERSION_LESS 3.27)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.26) cmake_policy(VERSION 3.27)
endif() endif()
project(test_installed_embed CXX) project(test_installed_embed CXX)

View File

@ -1,13 +1,13 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
project(test_installed_module CXX) project(test_installed_module CXX)
# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.27)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.26) if(${CMAKE_VERSION} VERSION_LESS 3.27)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.26) cmake_policy(VERSION 3.27)
endif() endif()
project(test_installed_function CXX) project(test_installed_function CXX)

View File

@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.27)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.26) if(${CMAKE_VERSION} VERSION_LESS 3.27)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.26) cmake_policy(VERSION 3.27)
endif() endif()
project(test_installed_target CXX) project(test_installed_target CXX)

View File

@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.27)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.26) if(${CMAKE_VERSION} VERSION_LESS 3.27)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.26) cmake_policy(VERSION 3.27)
endif() endif()
project(test_subdirectory_embed CXX) project(test_subdirectory_embed CXX)
@ -16,6 +16,12 @@ set(PYBIND11_INSTALL
CACHE BOOL "") CACHE BOOL "")
set(PYBIND11_EXPORT_NAME test_export) set(PYBIND11_EXPORT_NAME test_export)
# Allow PYTHON_EXECUTABLE if in FINDPYTHON mode and building pybind11's tests
# (makes transition easier while we support both modes).
if(DEFINED PYTHON_EXECUTABLE AND NOT DEFINED Python_EXECUTABLE)
set(Python_EXECUTABLE "${PYTHON_EXECUTABLE}")
endif()
add_subdirectory("${pybind11_SOURCE_DIR}" pybind11) add_subdirectory("${pybind11_SOURCE_DIR}" pybind11)
# Test basic target functionality # Test basic target functionality

View File

@ -1,16 +1,22 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.27)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.26) if(${CMAKE_VERSION} VERSION_LESS 3.27)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.26) cmake_policy(VERSION 3.27)
endif() endif()
project(test_subdirectory_function CXX) project(test_subdirectory_function CXX)
# Allow PYTHON_EXECUTABLE if in FINDPYTHON mode and building pybind11's tests
# (makes transition easier while we support both modes).
if(DEFINED PYTHON_EXECUTABLE AND NOT DEFINED Python_EXECUTABLE)
set(Python_EXECUTABLE "${PYTHON_EXECUTABLE}")
endif()
add_subdirectory("${pybind11_SOURCE_DIR}" pybind11) add_subdirectory("${pybind11_SOURCE_DIR}" pybind11)
pybind11_add_module(test_subdirectory_function ../main.cpp) pybind11_add_module(test_subdirectory_function ../main.cpp)
set_target_properties(test_subdirectory_function PROPERTIES OUTPUT_NAME test_cmake_build) set_target_properties(test_subdirectory_function PROPERTIES OUTPUT_NAME test_cmake_build)

View File

@ -1,16 +1,22 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.27)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate # some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround: # the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.26) if(${CMAKE_VERSION} VERSION_LESS 3.27)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.26) cmake_policy(VERSION 3.27)
endif() endif()
project(test_subdirectory_target CXX) project(test_subdirectory_target CXX)
# Allow PYTHON_EXECUTABLE if in FINDPYTHON mode and building pybind11's tests
# (makes transition easier while we support both modes).
if(DEFINED PYTHON_EXECUTABLE AND NOT DEFINED Python_EXECUTABLE)
set(Python_EXECUTABLE "${PYTHON_EXECUTABLE}")
endif()
add_subdirectory("${pybind11_SOURCE_DIR}" pybind11) add_subdirectory("${pybind11_SOURCE_DIR}" pybind11)
add_library(test_subdirectory_target MODULE ../main.cpp) add_library(test_subdirectory_target MODULE ../main.cpp)

View File

@ -54,7 +54,11 @@ int f2(int x) noexcept(true) { return x + 2; }
int f3(int x) noexcept(false) { return x + 3; } int f3(int x) noexcept(false) { return x + 3; }
PYBIND11_WARNING_PUSH PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_GCC("-Wdeprecated") PYBIND11_WARNING_DISABLE_GCC("-Wdeprecated")
#if defined(__clang_major__) && __clang_major__ >= 5
PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated-dynamic-exception-spec")
#else
PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated") PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated")
#endif
// NOLINTNEXTLINE(modernize-use-noexcept) // NOLINTNEXTLINE(modernize-use-noexcept)
int f4(int x) throw() { return x + 4; } // Deprecated equivalent to noexcept(true) int f4(int x) throw() { return x + 4; } // Deprecated equivalent to noexcept(true)
PYBIND11_WARNING_POP PYBIND11_WARNING_POP

View File

@ -134,6 +134,16 @@ struct type_caster<other_lib::MyType> : public other_lib::my_caster {};
} // namespace detail } // namespace detail
} // namespace PYBIND11_NAMESPACE } // namespace PYBIND11_NAMESPACE
// This simply is required to compile
namespace ADL_issue {
template <typename OutStringType = std::string, typename... Args>
OutStringType concat(Args &&...) {
return OutStringType();
}
struct test {};
} // namespace ADL_issue
TEST_SUBMODULE(custom_type_casters, m) { TEST_SUBMODULE(custom_type_casters, m) {
// test_custom_type_casters // test_custom_type_casters
@ -206,4 +216,6 @@ TEST_SUBMODULE(custom_type_casters, m) {
py::return_value_policy::reference); py::return_value_policy::reference);
m.def("other_lib_type", [](other_lib::MyType x) { return x; }); m.def("other_lib_type", [](other_lib::MyType x) { return x; });
m.def("_adl_issue", [](const ADL_issue::test &) {});
} }

View File

@ -330,6 +330,23 @@ TEST_SUBMODULE(eigen_matrix, m) {
m.def("dense_c", [mat]() -> DenseMatrixC { return DenseMatrixC(mat); }); m.def("dense_c", [mat]() -> DenseMatrixC { return DenseMatrixC(mat); });
m.def("dense_copy_r", [](const DenseMatrixR &m) -> DenseMatrixR { return m; }); m.def("dense_copy_r", [](const DenseMatrixR &m) -> DenseMatrixR { return m; });
m.def("dense_copy_c", [](const DenseMatrixC &m) -> DenseMatrixC { return m; }); m.def("dense_copy_c", [](const DenseMatrixC &m) -> DenseMatrixC { return m; });
// test_defaults
bool have_numpy = true;
try {
py::module_::import("numpy");
} catch (const py::error_already_set &) {
have_numpy = false;
}
if (have_numpy) {
py::module_::import("numpy");
Eigen::Matrix<double, 3, 3> defaultMatrix = Eigen::Matrix3d::Identity();
m.def(
"defaults_mat", [](const Eigen::Matrix3d &) {}, py::arg("mat") = defaultMatrix);
Eigen::VectorXd defaultVector = Eigen::VectorXd::Ones(32);
m.def(
"defaults_vec", [](const Eigen::VectorXd &) {}, py::arg("vec") = defaultMatrix);
}
// test_sparse, test_sparse_signature // test_sparse, test_sparse_signature
m.def("sparse_r", [mat]() -> SparseMatrixR { m.def("sparse_r", [mat]() -> SparseMatrixR {
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)

View File

@ -608,7 +608,9 @@ def test_both_ref_mutators():
def test_nocopy_wrapper(): def test_nocopy_wrapper():
# get_elem requires a column-contiguous matrix reference, but should be # get_elem requires a column-contiguous matrix reference, but should be
# callable with other types of matrix (via copying): # callable with other types of matrix (via copying):
int_matrix_colmajor = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], order="F") int_matrix_colmajor = np.array(
[[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype="l", order="F"
)
dbl_matrix_colmajor = np.array( dbl_matrix_colmajor = np.array(
int_matrix_colmajor, dtype="double", order="F", copy=True int_matrix_colmajor, dtype="double", order="F", copy=True
) )
@ -716,6 +718,11 @@ def test_dense_signature(doc):
) )
def test_defaults(doc):
assert "\n" not in str(doc(m.defaults_mat))
assert "\n" not in str(doc(m.defaults_vec))
def test_named_arguments(): def test_named_arguments():
a = np.array([[1.0, 2], [3, 4], [5, 6]]) a = np.array([[1.0, 2], [3, 4], [5, 6]])
b = np.ones((2, 1)) b = np.ones((2, 1))

View File

@ -60,9 +60,7 @@ Members:
ETwo : Docstring for ETwo ETwo : Docstring for ETwo
EThree : Docstring for EThree""".split( EThree : Docstring for EThree""".split("\n"):
"\n"
):
assert docstring_line in m.UnscopedEnum.__doc__ assert docstring_line in m.UnscopedEnum.__doc__
# Unscoped enums will accept ==/!= int comparisons # Unscoped enums will accept ==/!= int comparisons
@ -264,3 +262,8 @@ def test_docstring_signatures():
for attr in enum_type.__dict__.values(): for attr in enum_type.__dict__.values():
# Issue #2623/PR #2637: Add argument names to enum_ methods # Issue #2623/PR #2637: Add argument names to enum_ methods
assert "arg0" not in (attr.__doc__ or "") assert "arg0" not in (attr.__doc__ or "")
def test_str_signature():
for enum_type in [m.ScopedEnum, m.UnscopedEnum]:
assert enum_type.__str__.__doc__.startswith("__str__")

View File

@ -6,6 +6,8 @@
All rights reserved. Use of this source code is governed by a All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file. BSD-style license that can be found in the LICENSE file.
*/ */
#include <pybind11/gil_safe_call_once.h>
#include "test_exceptions.h" #include "test_exceptions.h"
#include "local_bindings.h" #include "local_bindings.h"
@ -25,6 +27,10 @@ private:
std::string message = ""; std::string message = "";
}; };
class MyExceptionUseDeprecatedOperatorCall : public MyException {
using MyException::MyException;
};
// A type that should be translated to a standard Python exception // A type that should be translated to a standard Python exception
class MyException2 : public std::exception { class MyException2 : public std::exception {
public: public:
@ -109,8 +115,10 @@ TEST_SUBMODULE(exceptions, m) {
m.def("throw_std_exception", m.def("throw_std_exception",
[]() { throw std::runtime_error("This exception was intentionally thrown."); }); []() { throw std::runtime_error("This exception was intentionally thrown."); });
// make a new custom exception and use it as a translation target // PLEASE KEEP IN SYNC with docs/advanced/exceptions.rst
static py::exception<MyException> ex(m, "MyException"); PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> ex_storage;
ex_storage.call_once_and_store_result(
[&]() { return py::exception<MyException>(m, "MyException"); });
py::register_exception_translator([](std::exception_ptr p) { py::register_exception_translator([](std::exception_ptr p) {
try { try {
if (p) { if (p) {
@ -118,7 +126,32 @@ TEST_SUBMODULE(exceptions, m) {
} }
} catch (const MyException &e) { } catch (const MyException &e) {
// Set MyException as the active python error // Set MyException as the active python error
ex(e.what()); py::set_error(ex_storage.get_stored(), e.what());
}
});
// Same as above, but using the deprecated `py::exception<>::operator()`
// We want to be sure it still works, until it's removed.
static const auto *const exd = new py::exception<MyExceptionUseDeprecatedOperatorCall>(
m, "MyExceptionUseDeprecatedOperatorCall");
py::register_exception_translator([](std::exception_ptr p) {
try {
if (p) {
std::rethrow_exception(p);
}
} catch (const MyExceptionUseDeprecatedOperatorCall &e) {
#if defined(__INTEL_COMPILER) || defined(__NVCOMPILER)
// It is not worth the trouble dealing with warning suppressions for these compilers.
// Falling back to the recommended approach to keep the test code simple.
py::set_error(*exd, e.what());
#else
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
PYBIND11_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
PYBIND11_WARNING_DISABLE_MSVC(4996)
(*exd)(e.what());
PYBIND11_WARNING_POP
#endif
} }
}); });
@ -132,7 +165,7 @@ TEST_SUBMODULE(exceptions, m) {
} }
} catch (const MyException2 &e) { } catch (const MyException2 &e) {
// Translate this exception to a standard RuntimeError // Translate this exception to a standard RuntimeError
PyErr_SetString(PyExc_RuntimeError, e.what()); py::set_error(PyExc_RuntimeError, e.what());
} }
}); });
@ -162,11 +195,16 @@ TEST_SUBMODULE(exceptions, m) {
std::rethrow_exception(p); std::rethrow_exception(p);
} }
} catch (const MyException6 &e) { } catch (const MyException6 &e) {
PyErr_SetString(PyExc_RuntimeError, e.what()); py::set_error(PyExc_RuntimeError, e.what());
} }
}); });
m.def("throws1", []() { throw MyException("this error should go to a custom type"); }); m.def("throws1",
[]() { throw MyException("this error should go to py::exception<MyException>"); });
m.def("throws1d", []() {
throw MyExceptionUseDeprecatedOperatorCall(
"this error should go to py::exception<MyExceptionUseDeprecatedOperatorCall>");
});
m.def("throws2", m.def("throws2",
[]() { throw MyException2("this error should go to a standard Python exception"); }); []() { throw MyException2("this error should go to a standard Python exception"); });
m.def("throws3", []() { throw MyException3("this error cannot be translated"); }); m.def("throws3", []() { throw MyException3("this error cannot be translated"); });
@ -222,7 +260,7 @@ TEST_SUBMODULE(exceptions, m) {
m.def("throw_already_set", [](bool err) { m.def("throw_already_set", [](bool err) {
if (err) { if (err) {
PyErr_SetString(PyExc_ValueError, "foo"); py::set_error(PyExc_ValueError, "foo");
} }
try { try {
throw py::error_already_set(); throw py::error_already_set();
@ -238,7 +276,7 @@ TEST_SUBMODULE(exceptions, m) {
} }
PyErr_Clear(); PyErr_Clear();
if (err) { if (err) {
PyErr_SetString(PyExc_ValueError, "foo"); py::set_error(PyExc_ValueError, "foo");
} }
throw py::error_already_set(); throw py::error_already_set();
}); });
@ -247,7 +285,7 @@ TEST_SUBMODULE(exceptions, m) {
bool retval = false; bool retval = false;
try { try {
PythonCallInDestructor set_dict_in_destructor(d); PythonCallInDestructor set_dict_in_destructor(d);
PyErr_SetString(PyExc_ValueError, "foo"); py::set_error(PyExc_ValueError, "foo");
throw py::error_already_set(); throw py::error_already_set();
} catch (const py::error_already_set &) { } catch (const py::error_already_set &) {
retval = true; retval = true;
@ -282,14 +320,14 @@ TEST_SUBMODULE(exceptions, m) {
m.def("throw_should_be_translated_to_key_error", []() { throw shared_exception(); }); m.def("throw_should_be_translated_to_key_error", []() { throw shared_exception(); });
m.def("raise_from", []() { m.def("raise_from", []() {
PyErr_SetString(PyExc_ValueError, "inner"); py::set_error(PyExc_ValueError, "inner");
py::raise_from(PyExc_ValueError, "outer"); py::raise_from(PyExc_ValueError, "outer");
throw py::error_already_set(); throw py::error_already_set();
}); });
m.def("raise_from_already_set", []() { m.def("raise_from_already_set", []() {
try { try {
PyErr_SetString(PyExc_ValueError, "inner"); py::set_error(PyExc_ValueError, "inner");
throw py::error_already_set(); throw py::error_already_set();
} catch (py::error_already_set &e) { } catch (py::error_already_set &e) {
py::raise_from(e, PyExc_ValueError, "outer"); py::raise_from(e, PyExc_ValueError, "outer");
@ -306,7 +344,7 @@ TEST_SUBMODULE(exceptions, m) {
}); });
m.def("error_already_set_what", [](const py::object &exc_type, const py::object &exc_value) { m.def("error_already_set_what", [](const py::object &exc_type, const py::object &exc_value) {
PyErr_SetObject(exc_type.ptr(), exc_value.ptr()); py::set_error(exc_type, exc_value);
std::string what = py::error_already_set().what(); std::string what = py::error_already_set().what();
bool py_err_set_after_what = (PyErr_Occurred() != nullptr); bool py_err_set_after_what = (PyErr_Occurred() != nullptr);
PyErr_Clear(); PyErr_Clear();
@ -321,7 +359,7 @@ TEST_SUBMODULE(exceptions, m) {
}); });
m.def("test_error_already_set_double_restore", [](bool dry_run) { m.def("test_error_already_set_double_restore", [](bool dry_run) {
PyErr_SetString(PyExc_ValueError, "Random error."); py::set_error(PyExc_ValueError, "Random error.");
py::error_already_set e; py::error_already_set e;
e.restore(); e.restore();
PyErr_Clear(); PyErr_Clear();
@ -344,4 +382,7 @@ TEST_SUBMODULE(exceptions, m) {
// function returns None instead of int, should give a useful error message // function returns None instead of int, should give a useful error message
fn().cast<int>(); fn().cast<int>();
}); });
// m.def("pass_exception_void", [](const py::exception<void>&) {}); // Does not compile.
m.def("return_exception_void", []() { return py::exception<void>(); });
} }

View File

@ -9,5 +9,5 @@ class PYBIND11_EXPORT_EXCEPTION shared_exception : public pybind11::builtin_exce
public: public:
using builtin_exception::builtin_exception; using builtin_exception::builtin_exception;
explicit shared_exception() : shared_exception("") {} explicit shared_exception() : shared_exception("") {}
void set_error() const override { PyErr_SetString(PyExc_RuntimeError, what()); } void set_error() const override { py::set_error(PyExc_RuntimeError, what()); }
}; };

View File

@ -4,7 +4,7 @@ import pytest
import env import env
import pybind11_cross_module_tests as cm import pybind11_cross_module_tests as cm
import pybind11_tests # noqa: F401 import pybind11_tests
from pybind11_tests import exceptions as m from pybind11_tests import exceptions as m
@ -139,7 +139,15 @@ def test_custom(msg):
# Can we catch a MyException? # Can we catch a MyException?
with pytest.raises(m.MyException) as excinfo: with pytest.raises(m.MyException) as excinfo:
m.throws1() m.throws1()
assert msg(excinfo.value) == "this error should go to a custom type" assert msg(excinfo.value) == "this error should go to py::exception<MyException>"
# Can we catch a MyExceptionUseDeprecatedOperatorCall?
with pytest.raises(m.MyExceptionUseDeprecatedOperatorCall) as excinfo:
m.throws1d()
assert (
msg(excinfo.value)
== "this error should go to py::exception<MyExceptionUseDeprecatedOperatorCall>"
)
# Can we translate to standard Python exceptions? # Can we translate to standard Python exceptions?
with pytest.raises(RuntimeError) as excinfo: with pytest.raises(RuntimeError) as excinfo:
@ -240,6 +248,11 @@ def test_nested_throws(capture):
assert str(excinfo.value) == "this is a helper-defined translated exception" assert str(excinfo.value) == "this is a helper-defined translated exception"
# TODO: Investigate this crash, see pybind/pybind11#5062 for background
@pytest.mark.skipif(
sys.platform.startswith("win32") and "Clang" in pybind11_tests.compiler_info,
reason="Started segfaulting February 2024",
)
def test_throw_nested_exception(): def test_throw_nested_exception():
with pytest.raises(RuntimeError) as excinfo: with pytest.raises(RuntimeError) as excinfo:
m.throw_nested_exception() m.throw_nested_exception()
@ -411,3 +424,9 @@ def test_fn_cast_int_exception():
assert str(excinfo.value).startswith( assert str(excinfo.value).startswith(
"Unable to cast Python instance of type <class 'NoneType'> to C++ type" "Unable to cast Python instance of type <class 'NoneType'> to C++ type"
) )
def test_return_exception_void():
with pytest.raises(TypeError) as excinfo:
m.return_exception_void()
assert "Exception" in str(excinfo.value)

View File

@ -77,7 +77,7 @@ def test_init_factory_signature(msg):
1. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int) 1. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int)
2. m.factory_constructors.TestFactory1(arg0: str) 2. m.factory_constructors.TestFactory1(arg0: str)
3. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.pointer_tag) 3. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.pointer_tag)
4. m.factory_constructors.TestFactory1(arg0: handle, arg1: int, arg2: handle) 4. m.factory_constructors.TestFactory1(arg0: object, arg1: int, arg2: object)
Invoked with: 'invalid', 'constructor', 'arguments' Invoked with: 'invalid', 'constructor', 'arguments'
""" """
@ -95,7 +95,7 @@ def test_init_factory_signature(msg):
3. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.pointer_tag) -> None 3. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.pointer_tag) -> None
4. __init__(self: m.factory_constructors.TestFactory1, arg0: handle, arg1: int, arg2: handle) -> None 4. __init__(self: m.factory_constructors.TestFactory1, arg0: object, arg1: int, arg2: object) -> None
""" """
) )

View File

@ -42,6 +42,52 @@ TEST_SUBMODULE(kwargs_and_defaults, m) {
m.def("kw_func_udl", kw_func, "x"_a, "y"_a = 300); m.def("kw_func_udl", kw_func, "x"_a, "y"_a = 300);
m.def("kw_func_udl_z", kw_func, "x"_a, "y"_a = 0); m.def("kw_func_udl_z", kw_func, "x"_a, "y"_a = 0);
// test line breaks in default argument representation
struct CustomRepr {
std::string repr_string;
explicit CustomRepr(const std::string &repr) : repr_string(repr) {}
std::string __repr__() const { return repr_string; }
};
py::class_<CustomRepr>(m, "CustomRepr")
.def(py::init<const std::string &>())
.def("__repr__", &CustomRepr::__repr__);
m.def(
"kw_lb_func0",
[](const CustomRepr &) {},
py::arg("custom") = CustomRepr(" array([[A, B], [C, D]]) "));
m.def(
"kw_lb_func1",
[](const CustomRepr &) {},
py::arg("custom") = CustomRepr(" array([[A, B],\n[C, D]]) "));
m.def(
"kw_lb_func2",
[](const CustomRepr &) {},
py::arg("custom") = CustomRepr("\v\n array([[A, B], [C, D]])"));
m.def(
"kw_lb_func3",
[](const CustomRepr &) {},
py::arg("custom") = CustomRepr("array([[A, B], [C, D]]) \f\n"));
m.def(
"kw_lb_func4",
[](const CustomRepr &) {},
py::arg("custom") = CustomRepr("array([[A, B],\n\f\n[C, D]])"));
m.def(
"kw_lb_func5",
[](const CustomRepr &) {},
py::arg("custom") = CustomRepr("array([[A, B],\r [C, D]])"));
m.def(
"kw_lb_func6", [](const CustomRepr &) {}, py::arg("custom") = CustomRepr(" \v\t "));
m.def(
"kw_lb_func7",
[](const std::string &) {},
py::arg("str_arg") = "First line.\n Second line.");
m.def(
"kw_lb_func8", [](const CustomRepr &) {}, py::arg("custom") = CustomRepr(""));
// test_args_and_kwargs // test_args_and_kwargs
m.def("args_function", [](py::args args) -> py::tuple { m.def("args_function", [](py::args args) -> py::tuple {
PYBIND11_WARNING_PUSH PYBIND11_WARNING_PUSH

View File

@ -8,7 +8,7 @@ def test_function_signatures(doc):
assert doc(m.kw_func1) == "kw_func1(x: int, y: int) -> str" assert doc(m.kw_func1) == "kw_func1(x: int, y: int) -> str"
assert doc(m.kw_func2) == "kw_func2(x: int = 100, y: int = 200) -> str" assert doc(m.kw_func2) == "kw_func2(x: int = 100, y: int = 200) -> str"
assert doc(m.kw_func3) == "kw_func3(data: str = 'Hello world!') -> None" assert doc(m.kw_func3) == "kw_func3(data: str = 'Hello world!') -> None"
assert doc(m.kw_func4) == "kw_func4(myList: List[int] = [13, 17]) -> str" assert doc(m.kw_func4) == "kw_func4(myList: list[int] = [13, 17]) -> str"
assert doc(m.kw_func_udl) == "kw_func_udl(x: int, y: int = 300) -> str" assert doc(m.kw_func_udl) == "kw_func_udl(x: int, y: int = 300) -> str"
assert doc(m.kw_func_udl_z) == "kw_func_udl_z(x: int, y: int = 0) -> str" assert doc(m.kw_func_udl_z) == "kw_func_udl_z(x: int, y: int = 0) -> str"
assert doc(m.args_function) == "args_function(*args) -> tuple" assert doc(m.args_function) == "args_function(*args) -> tuple"
@ -23,6 +23,42 @@ def test_function_signatures(doc):
doc(m.KWClass.foo1) doc(m.KWClass.foo1)
== "foo1(self: m.kwargs_and_defaults.KWClass, x: int, y: float) -> None" == "foo1(self: m.kwargs_and_defaults.KWClass, x: int, y: float) -> None"
) )
assert (
doc(m.kw_lb_func0)
== "kw_lb_func0(custom: m.kwargs_and_defaults.CustomRepr = array([[A, B], [C, D]])) -> None"
)
assert (
doc(m.kw_lb_func1)
== "kw_lb_func1(custom: m.kwargs_and_defaults.CustomRepr = array([[A, B], [C, D]])) -> None"
)
assert (
doc(m.kw_lb_func2)
== "kw_lb_func2(custom: m.kwargs_and_defaults.CustomRepr = array([[A, B], [C, D]])) -> None"
)
assert (
doc(m.kw_lb_func3)
== "kw_lb_func3(custom: m.kwargs_and_defaults.CustomRepr = array([[A, B], [C, D]])) -> None"
)
assert (
doc(m.kw_lb_func4)
== "kw_lb_func4(custom: m.kwargs_and_defaults.CustomRepr = array([[A, B], [C, D]])) -> None"
)
assert (
doc(m.kw_lb_func5)
== "kw_lb_func5(custom: m.kwargs_and_defaults.CustomRepr = array([[A, B], [C, D]])) -> None"
)
assert (
doc(m.kw_lb_func6)
== "kw_lb_func6(custom: m.kwargs_and_defaults.CustomRepr = ) -> None"
)
assert (
doc(m.kw_lb_func7)
== "kw_lb_func7(str_arg: str = 'First line.\\n Second line.') -> None"
)
assert (
doc(m.kw_lb_func8)
== "kw_lb_func8(custom: m.kwargs_and_defaults.CustomRepr = ) -> None"
)
def test_named_arguments(): def test_named_arguments():

View File

@ -232,25 +232,29 @@ def test_no_mixed_overloads():
with pytest.raises(RuntimeError) as excinfo: with pytest.raises(RuntimeError) as excinfo:
m.ExampleMandA.add_mixed_overloads1() m.ExampleMandA.add_mixed_overloads1()
assert str( assert (
excinfo.value str(excinfo.value)
) == "overloading a method with both static and instance methods is not supported; " + ( == "overloading a method with both static and instance methods is not supported; "
"#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details" + (
if not detailed_error_messages_enabled "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details"
else "error while attempting to bind static method ExampleMandA.overload_mixed1" if not detailed_error_messages_enabled
"(arg0: float) -> str" else "error while attempting to bind static method ExampleMandA.overload_mixed1"
"(arg0: float) -> str"
)
) )
with pytest.raises(RuntimeError) as excinfo: with pytest.raises(RuntimeError) as excinfo:
m.ExampleMandA.add_mixed_overloads2() m.ExampleMandA.add_mixed_overloads2()
assert str( assert (
excinfo.value str(excinfo.value)
) == "overloading a method with both static and instance methods is not supported; " + ( == "overloading a method with both static and instance methods is not supported; "
"#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details" + (
if not detailed_error_messages_enabled "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details"
else "error while attempting to bind instance method ExampleMandA.overload_mixed2" if not detailed_error_messages_enabled
"(self: pybind11_tests.methods_and_attributes.ExampleMandA, arg0: int, arg1: int)" else "error while attempting to bind instance method ExampleMandA.overload_mixed2"
" -> str" "(self: pybind11_tests.methods_and_attributes.ExampleMandA, arg0: int, arg1: int)"
" -> str"
)
) )

View File

@ -298,7 +298,7 @@ def test_constructors():
results = m.converting_constructors([1, 2, 3]) results = m.converting_constructors([1, 2, 3])
for a in results.values(): for a in results.values():
np.testing.assert_array_equal(a, [1, 2, 3]) np.testing.assert_array_equal(a, [1, 2, 3])
assert results["array"].dtype == np.int_ assert results["array"].dtype == np.dtype(int)
assert results["array_t<int32>"].dtype == np.int32 assert results["array_t<int32>"].dtype == np.int32
assert results["array_t<double>"].dtype == np.float64 assert results["array_t<double>"].dtype == np.float64
@ -536,7 +536,12 @@ def test_format_descriptors_for_floating_point_types(test_func):
@pytest.mark.parametrize("contiguity", [None, "C", "F"]) @pytest.mark.parametrize("contiguity", [None, "C", "F"])
@pytest.mark.parametrize("noconvert", [False, True]) @pytest.mark.parametrize("noconvert", [False, True])
@pytest.mark.filterwarnings( @pytest.mark.filterwarnings(
"ignore:Casting complex values to real discards the imaginary part:numpy.ComplexWarning" "ignore:Casting complex values to real discards the imaginary part:"
+ (
"numpy.exceptions.ComplexWarning"
if hasattr(np, "exceptions")
else "numpy.ComplexWarning"
)
) )
def test_argument_conversions(forcecast, contiguity, noconvert): def test_argument_conversions(forcecast, contiguity, noconvert):
function_name = "accept_double" function_name = "accept_double"
@ -583,7 +588,8 @@ def test_argument_conversions(forcecast, contiguity, noconvert):
def test_dtype_refcount_leak(): def test_dtype_refcount_leak():
from sys import getrefcount from sys import getrefcount
dtype = np.dtype(np.float_) # Was np.float_ but that alias for float64 was removed in NumPy 2.
dtype = np.dtype(np.float64)
a = np.array([1], dtype=dtype) a = np.array([1], dtype=dtype)
before = getrefcount(dtype) before = getrefcount(dtype)
m.ndim(a) m.ndim(a)

View File

@ -157,7 +157,7 @@ py::array mkarray_via_buffer(size_t n) {
do { \ do { \
(s).bool_ = (i) % 2 != 0; \ (s).bool_ = (i) % 2 != 0; \
(s).uint_ = (uint32_t) (i); \ (s).uint_ = (uint32_t) (i); \
(s).float_ = (float) (i) *1.5f; \ (s).float_ = (float) (i) * 1.5f; \
(s).ldbl_ = (long double) (i) * -2.5L; \ (s).ldbl_ = (long double) (i) * -2.5L; \
} while (0) } while (0)
@ -405,10 +405,35 @@ TEST_SUBMODULE(numpy_dtypes, m) {
}); });
// test_dtype // test_dtype
// Below we use `L` for unsigned long as unfortunately the only name that
// works reliably on Both NumPy 2.x and old NumPy 1.x.
std::vector<const char *> dtype_names{ std::vector<const char *> dtype_names{
"byte", "short", "intc", "int_", "longlong", "ubyte", "ushort", "byte",
"uintc", "uint", "ulonglong", "half", "single", "double", "longdouble", "short",
"csingle", "cdouble", "clongdouble", "bool_", "datetime64", "timedelta64", "object_"}; "intc",
"long",
"longlong",
"ubyte",
"ushort",
"uintc",
"L",
"ulonglong",
"half",
"single",
"double",
"longdouble",
"csingle",
"cdouble",
"clongdouble",
"bool_",
"datetime64",
"timedelta64",
"object_",
// platform dependent aliases (int_ and uint are also NumPy version dependent on windows)
"int_",
"uint",
"intp",
"uintp"};
m.def("print_dtypes", []() { m.def("print_dtypes", []() {
py::list l; py::list l;

View File

@ -3,6 +3,7 @@ import re
import pytest import pytest
import env # noqa: F401 import env # noqa: F401
from pybind11_tests import PYBIND11_NUMPY_1_ONLY
from pybind11_tests import numpy_dtypes as m from pybind11_tests import numpy_dtypes as m
np = pytest.importorskip("numpy") np = pytest.importorskip("numpy")
@ -172,13 +173,20 @@ def test_dtype(simple_dtype):
np.zeros(1, m.trailing_padding_dtype()) np.zeros(1, m.trailing_padding_dtype())
) )
expected_chars = "bhilqBHILQefdgFDG?MmO" expected_chars = list("bhilqBHILQefdgFDG?MmO")
assert m.test_dtype_kind() == list("iiiiiuuuuuffffcccbMmO") # Note that int_ and uint size and mapping is NumPy version dependent:
expected_chars += [np.dtype(_).char for _ in ("int_", "uint", "intp", "uintp")]
assert m.test_dtype_kind() == list("iiiiiuuuuuffffcccbMmOiuiu")
assert m.test_dtype_char_() == list(expected_chars) assert m.test_dtype_char_() == list(expected_chars)
assert m.test_dtype_num() == [np.dtype(ch).num for ch in expected_chars] assert m.test_dtype_num() == [np.dtype(ch).num for ch in expected_chars]
assert m.test_dtype_byteorder() == [np.dtype(ch).byteorder for ch in expected_chars] assert m.test_dtype_byteorder() == [np.dtype(ch).byteorder for ch in expected_chars]
assert m.test_dtype_alignment() == [np.dtype(ch).alignment for ch in expected_chars] assert m.test_dtype_alignment() == [np.dtype(ch).alignment for ch in expected_chars]
assert m.test_dtype_flags() == [chr(np.dtype(ch).flags) for ch in expected_chars] if not PYBIND11_NUMPY_1_ONLY:
assert m.test_dtype_flags() == [np.dtype(ch).flags for ch in expected_chars]
else:
assert m.test_dtype_flags() == [
chr(np.dtype(ch).flags) for ch in expected_chars
]
def test_recarray(simple_dtype, packed_dtype): def test_recarray(simple_dtype, packed_dtype):

View File

@ -0,0 +1,45 @@
#include "pybind11_tests.h"
namespace test_python_multiple_inheritance {
// Copied from:
// https://github.com/google/clif/blob/5718e4d0807fd3b6a8187dde140069120b81ecef/clif/testing/python_multiple_inheritance.h
struct CppBase {
explicit CppBase(int value) : base_value(value) {}
int get_base_value() const { return base_value; }
void reset_base_value(int new_value) { base_value = new_value; }
private:
int base_value;
};
struct CppDrvd : CppBase {
explicit CppDrvd(int value) : CppBase(value), drvd_value(value * 3) {}
int get_drvd_value() const { return drvd_value; }
void reset_drvd_value(int new_value) { drvd_value = new_value; }
int get_base_value_from_drvd() const { return get_base_value(); }
void reset_base_value_from_drvd(int new_value) { reset_base_value(new_value); }
private:
int drvd_value;
};
} // namespace test_python_multiple_inheritance
TEST_SUBMODULE(python_multiple_inheritance, m) {
using namespace test_python_multiple_inheritance;
py::class_<CppBase>(m, "CppBase")
.def(py::init<int>())
.def("get_base_value", &CppBase::get_base_value)
.def("reset_base_value", &CppBase::reset_base_value);
py::class_<CppDrvd, CppBase>(m, "CppDrvd")
.def(py::init<int>())
.def("get_drvd_value", &CppDrvd::get_drvd_value)
.def("reset_drvd_value", &CppDrvd::reset_drvd_value)
.def("get_base_value_from_drvd", &CppDrvd::get_base_value_from_drvd)
.def("reset_base_value_from_drvd", &CppDrvd::reset_base_value_from_drvd);
}

View File

@ -0,0 +1,35 @@
# Adapted from:
# https://github.com/google/clif/blob/5718e4d0807fd3b6a8187dde140069120b81ecef/clif/testing/python/python_multiple_inheritance_test.py
from pybind11_tests import python_multiple_inheritance as m
class PC(m.CppBase):
pass
class PPCC(PC, m.CppDrvd):
pass
def test_PC():
d = PC(11)
assert d.get_base_value() == 11
d.reset_base_value(13)
assert d.get_base_value() == 13
def test_PPCC():
d = PPCC(11)
assert d.get_drvd_value() == 33
d.reset_drvd_value(55)
assert d.get_drvd_value() == 55
assert d.get_base_value() == 11
assert d.get_base_value_from_drvd() == 11
d.reset_base_value(20)
assert d.get_base_value() == 20
assert d.get_base_value_from_drvd() == 20
d.reset_base_value_from_drvd(30)
assert d.get_base_value() == 30
assert d.get_base_value_from_drvd() == 30

View File

@ -7,6 +7,8 @@
BSD-style license that can be found in the LICENSE file. BSD-style license that can be found in the LICENSE file.
*/ */
#include <pybind11/typing.h>
#include "pybind11_tests.h" #include "pybind11_tests.h"
#include <utility> #include <utility>
@ -23,7 +25,7 @@ PyObject *conv(PyObject *o) {
ret = PyFloat_FromDouble(v); ret = PyFloat_FromDouble(v);
} }
} else { } else {
PyErr_SetString(PyExc_TypeError, "Unexpected type"); py::set_error(PyExc_TypeError, "Unexpected type");
} }
return ret; return ret;
} }
@ -39,6 +41,15 @@ class float_ : public py::object {
}; };
} // namespace external } // namespace external
namespace pybind11 {
namespace detail {
template <>
struct handle_type_name<external::float_> {
static constexpr auto name = const_name("float");
};
} // namespace detail
} // namespace pybind11
namespace implicit_conversion_from_0_to_handle { namespace implicit_conversion_from_0_to_handle {
// Uncomment to trigger compiler error. Note: Before PR #4008 this used to compile successfully. // Uncomment to trigger compiler error. Note: Before PR #4008 this used to compile successfully.
// void expected_to_trigger_compiler_error() { py::handle(0); } // void expected_to_trigger_compiler_error() { py::handle(0); }
@ -660,8 +671,8 @@ TEST_SUBMODULE(pytypes, m) {
// This is "most correct" and enforced on these platforms. // This is "most correct" and enforced on these platforms.
# define PYBIND11_AUTO_IT auto it # define PYBIND11_AUTO_IT auto it
#else #else
// This works on many platforms and is (unfortunately) reflective of existing user code. // This works on many platforms and is (unfortunately) reflective of existing user code.
// NOLINTNEXTLINE(bugprone-macro-parentheses) // NOLINTNEXTLINE(bugprone-macro-parentheses)
# define PYBIND11_AUTO_IT auto &it # define PYBIND11_AUTO_IT auto &it
#endif #endif
@ -820,4 +831,16 @@ TEST_SUBMODULE(pytypes, m) {
a >>= b; a >>= b;
return a; return a;
}); });
m.def("annotate_tuple_float_str", [](const py::typing::Tuple<py::float_, py::str> &) {});
m.def("annotate_tuple_empty", [](const py::typing::Tuple<> &) {});
m.def("annotate_tuple_variable_length",
[](const py::typing::Tuple<py::float_, py::ellipsis> &) {});
m.def("annotate_dict_str_int", [](const py::typing::Dict<py::str, int> &) {});
m.def("annotate_list_int", [](const py::typing::List<int> &) {});
m.def("annotate_set_str", [](const py::typing::Set<std::string> &) {});
m.def("annotate_iterable_str", [](const py::typing::Iterable<std::string> &) {});
m.def("annotate_iterator_int", [](const py::typing::Iterator<int> &) {});
m.def("annotate_fn",
[](const py::typing::Callable<int(py::typing::List<py::str>, py::str)> &) {});
} }

View File

@ -121,7 +121,7 @@ def test_set(capture, doc):
assert m.anyset_contains({"foo"}, "foo") assert m.anyset_contains({"foo"}, "foo")
assert doc(m.get_set) == "get_set() -> set" assert doc(m.get_set) == "get_set() -> set"
assert doc(m.print_anyset) == "print_anyset(arg0: anyset) -> None" assert doc(m.print_anyset) == "print_anyset(arg0: Union[set, frozenset]) -> None"
def test_frozenset(capture, doc): def test_frozenset(capture, doc):
@ -896,3 +896,59 @@ def test_inplace_lshift(a, b):
def test_inplace_rshift(a, b): def test_inplace_rshift(a, b):
expected = a >> b expected = a >> b
assert m.inplace_rshift(a, b) == expected assert m.inplace_rshift(a, b) == expected
def test_tuple_nonempty_annotations(doc):
assert (
doc(m.annotate_tuple_float_str)
== "annotate_tuple_float_str(arg0: tuple[float, str]) -> None"
)
def test_tuple_empty_annotations(doc):
assert (
doc(m.annotate_tuple_empty) == "annotate_tuple_empty(arg0: tuple[()]) -> None"
)
def test_tuple_variable_length_annotations(doc):
assert (
doc(m.annotate_tuple_variable_length)
== "annotate_tuple_variable_length(arg0: tuple[float, ...]) -> None"
)
def test_dict_annotations(doc):
assert (
doc(m.annotate_dict_str_int)
== "annotate_dict_str_int(arg0: dict[str, int]) -> None"
)
def test_list_annotations(doc):
assert doc(m.annotate_list_int) == "annotate_list_int(arg0: list[int]) -> None"
def test_set_annotations(doc):
assert doc(m.annotate_set_str) == "annotate_set_str(arg0: set[str]) -> None"
def test_iterable_annotations(doc):
assert (
doc(m.annotate_iterable_str)
== "annotate_iterable_str(arg0: Iterable[str]) -> None"
)
def test_iterator_annotations(doc):
assert (
doc(m.annotate_iterator_int)
== "annotate_iterator_int(arg0: Iterator[int]) -> None"
)
def test_fn_annotations(doc):
assert (
doc(m.annotate_fn)
== "annotate_fn(arg0: Callable[[list[str], str], int]) -> None"
)

View File

@ -28,6 +28,13 @@ class NonZeroIterator {
public: public:
explicit NonZeroIterator(const T *ptr) : ptr_(ptr) {} explicit NonZeroIterator(const T *ptr) : ptr_(ptr) {}
// Make the iterator non-copyable and movable
NonZeroIterator(const NonZeroIterator &) = delete;
NonZeroIterator(NonZeroIterator &&) noexcept = default;
NonZeroIterator &operator=(const NonZeroIterator &) = delete;
NonZeroIterator &operator=(NonZeroIterator &&) noexcept = default;
const T &operator*() const { return *ptr_; } const T &operator*() const { return *ptr_; }
NonZeroIterator &operator++() { NonZeroIterator &operator++() {
++ptr_; ++ptr_;
@ -78,6 +85,7 @@ private:
int value_; int value_;
}; };
using NonCopyableIntPair = std::pair<NonCopyableInt, NonCopyableInt>; using NonCopyableIntPair = std::pair<NonCopyableInt, NonCopyableInt>;
PYBIND11_MAKE_OPAQUE(std::vector<NonCopyableInt>); PYBIND11_MAKE_OPAQUE(std::vector<NonCopyableInt>);
PYBIND11_MAKE_OPAQUE(std::vector<NonCopyableIntPair>); PYBIND11_MAKE_OPAQUE(std::vector<NonCopyableIntPair>);
@ -375,6 +383,17 @@ TEST_SUBMODULE(sequences_and_iterators, m) {
private: private:
std::vector<std::pair<int, int>> data_; std::vector<std::pair<int, int>> data_;
}; };
{
// #4383 : Make sure `py::make_*iterator` functions work with move-only iterators
using iterator_t = NonZeroIterator<std::pair<int, int>>;
static_assert(std::is_move_assignable<iterator_t>::value, "");
static_assert(std::is_move_constructible<iterator_t>::value, "");
static_assert(!std::is_copy_assignable<iterator_t>::value, "");
static_assert(!std::is_copy_constructible<iterator_t>::value, "");
}
py::class_<IntPairs>(m, "IntPairs") py::class_<IntPairs>(m, "IntPairs")
.def(py::init<std::vector<std::pair<int, int>>>()) .def(py::init<std::vector<std::pair<int, int>>>())
.def( .def(

View File

@ -58,6 +58,15 @@ def test_generalized_iterators_simple():
assert list(m.IntPairs([(1, 2), (3, 4), (0, 5)]).simple_values()) == [2, 4, 5] assert list(m.IntPairs([(1, 2), (3, 4), (0, 5)]).simple_values()) == [2, 4, 5]
def test_iterator_doc_annotations():
assert m.IntPairs.nonref.__doc__.endswith("-> Iterator[tuple[int, int]]\n")
assert m.IntPairs.nonref_keys.__doc__.endswith("-> Iterator[int]\n")
assert m.IntPairs.nonref_values.__doc__.endswith("-> Iterator[int]\n")
assert m.IntPairs.simple_iterator.__doc__.endswith("-> Iterator[tuple[int, int]]\n")
assert m.IntPairs.simple_keys.__doc__.endswith("-> Iterator[int]\n")
assert m.IntPairs.simple_values.__doc__.endswith("-> Iterator[int]\n")
def test_iterator_referencing(): def test_iterator_referencing():
"""Test that iterators reference rather than copy their referents.""" """Test that iterators reference rather than copy their referents."""
vec = m.VectorNonCopyableInt() vec = m.VectorNonCopyableInt()
@ -171,6 +180,10 @@ def test_sequence_length():
assert m.sequence_length("hello") == 5 assert m.sequence_length("hello") == 5
def test_sequence_doc():
assert m.sequence_length.__doc__.strip() == "sequence_length(arg0: Sequence) -> int"
def test_map_iterator(): def test_map_iterator():
sm = m.StringMap({"hi": "bye", "black": "white"}) sm = m.StringMap({"hi": "bye", "black": "white"})
assert sm["hi"] == "bye" assert sm["hi"] == "bye"

View File

@ -103,21 +103,26 @@ private:
int value; int value;
}; };
template <typename T>
std::unordered_set<T *> &pointer_set() {
// https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables
static auto singleton = new std::unordered_set<T *>();
return *singleton;
}
// test_unique_nodelete // test_unique_nodelete
// Object with a private destructor // Object with a private destructor
class MyObject4;
std::unordered_set<MyObject4 *> myobject4_instances;
class MyObject4 { class MyObject4 {
public: public:
explicit MyObject4(int value) : value{value} { explicit MyObject4(int value) : value{value} {
print_created(this); print_created(this);
myobject4_instances.insert(this); pointer_set<MyObject4>().insert(this);
} }
int value; int value;
static void cleanupAllInstances() { static void cleanupAllInstances() {
auto tmp = std::move(myobject4_instances); auto tmp = std::move(pointer_set<MyObject4>());
myobject4_instances.clear(); pointer_set<MyObject4>().clear();
for (auto *o : tmp) { for (auto *o : tmp) {
delete o; delete o;
} }
@ -125,7 +130,7 @@ public:
private: private:
~MyObject4() { ~MyObject4() {
myobject4_instances.erase(this); pointer_set<MyObject4>().erase(this);
print_destroyed(this); print_destroyed(this);
} }
}; };
@ -133,19 +138,17 @@ private:
// test_unique_deleter // test_unique_deleter
// Object with std::unique_ptr<T, D> where D is not matching the base class // Object with std::unique_ptr<T, D> where D is not matching the base class
// Object with a protected destructor // Object with a protected destructor
class MyObject4a;
std::unordered_set<MyObject4a *> myobject4a_instances;
class MyObject4a { class MyObject4a {
public: public:
explicit MyObject4a(int i) : value{i} { explicit MyObject4a(int i) : value{i} {
print_created(this); print_created(this);
myobject4a_instances.insert(this); pointer_set<MyObject4a>().insert(this);
}; };
int value; int value;
static void cleanupAllInstances() { static void cleanupAllInstances() {
auto tmp = std::move(myobject4a_instances); auto tmp = std::move(pointer_set<MyObject4a>());
myobject4a_instances.clear(); pointer_set<MyObject4a>().clear();
for (auto *o : tmp) { for (auto *o : tmp) {
delete o; delete o;
} }
@ -153,7 +156,7 @@ public:
protected: protected:
virtual ~MyObject4a() { virtual ~MyObject4a() {
myobject4a_instances.erase(this); pointer_set<MyObject4a>().erase(this);
print_destroyed(this); print_destroyed(this);
} }
}; };

View File

@ -16,8 +16,8 @@ def test_vector(doc):
assert m.load_bool_vector([True, False]) assert m.load_bool_vector([True, False])
assert m.load_bool_vector((True, False)) assert m.load_bool_vector((True, False))
assert doc(m.cast_vector) == "cast_vector() -> List[int]" assert doc(m.cast_vector) == "cast_vector() -> list[int]"
assert doc(m.load_vector) == "load_vector(arg0: List[int]) -> bool" assert doc(m.load_vector) == "load_vector(arg0: list[int]) -> bool"
# Test regression caused by 936: pointers to stl containers weren't castable # Test regression caused by 936: pointers to stl containers weren't castable
assert m.cast_ptr_vector() == ["lvalue", "lvalue"] assert m.cast_ptr_vector() == ["lvalue", "lvalue"]
@ -39,10 +39,10 @@ def test_array(doc):
assert m.load_array(lst) assert m.load_array(lst)
assert m.load_array(tuple(lst)) assert m.load_array(tuple(lst))
assert doc(m.cast_array) == "cast_array() -> Annotated[List[int], FixedSize(2)]" assert doc(m.cast_array) == "cast_array() -> Annotated[list[int], FixedSize(2)]"
assert ( assert (
doc(m.load_array) doc(m.load_array)
== "load_array(arg0: Annotated[List[int], FixedSize(2)]) -> bool" == "load_array(arg0: Annotated[list[int], FixedSize(2)]) -> bool"
) )
@ -53,8 +53,8 @@ def test_valarray(doc):
assert m.load_valarray(lst) assert m.load_valarray(lst)
assert m.load_valarray(tuple(lst)) assert m.load_valarray(tuple(lst))
assert doc(m.cast_valarray) == "cast_valarray() -> List[int]" assert doc(m.cast_valarray) == "cast_valarray() -> list[int]"
assert doc(m.load_valarray) == "load_valarray(arg0: List[int]) -> bool" assert doc(m.load_valarray) == "load_valarray(arg0: list[int]) -> bool"
def test_map(doc): def test_map(doc):
@ -66,8 +66,8 @@ def test_map(doc):
assert "key2" in d assert "key2" in d
assert m.load_map(d) assert m.load_map(d)
assert doc(m.cast_map) == "cast_map() -> Dict[str, str]" assert doc(m.cast_map) == "cast_map() -> dict[str, str]"
assert doc(m.load_map) == "load_map(arg0: Dict[str, str]) -> bool" assert doc(m.load_map) == "load_map(arg0: dict[str, str]) -> bool"
def test_set(doc): def test_set(doc):
@ -78,8 +78,8 @@ def test_set(doc):
assert m.load_set(s) assert m.load_set(s)
assert m.load_set(frozenset(s)) assert m.load_set(frozenset(s))
assert doc(m.cast_set) == "cast_set() -> Set[str]" assert doc(m.cast_set) == "cast_set() -> set[str]"
assert doc(m.load_set) == "load_set(arg0: Set[str]) -> bool" assert doc(m.load_set) == "load_set(arg0: set[str]) -> bool"
def test_recursive_casting(): def test_recursive_casting():
@ -303,7 +303,7 @@ def test_stl_pass_by_pointer(msg):
msg(excinfo.value) msg(excinfo.value)
== """ == """
stl_pass_by_pointer(): incompatible function arguments. The following argument types are supported: stl_pass_by_pointer(): incompatible function arguments. The following argument types are supported:
1. (v: List[int] = None) -> List[int] 1. (v: list[int] = None) -> list[int]
Invoked with: Invoked with:
""" """
@ -315,7 +315,7 @@ def test_stl_pass_by_pointer(msg):
msg(excinfo.value) msg(excinfo.value)
== """ == """
stl_pass_by_pointer(): incompatible function arguments. The following argument types are supported: stl_pass_by_pointer(): incompatible function arguments. The following argument types are supported:
1. (v: List[int] = None) -> List[int] 1. (v: list[int] = None) -> list[int]
Invoked with: None Invoked with: None
""" """

View File

@ -15,6 +15,7 @@
#include <deque> #include <deque>
#include <map> #include <map>
#include <unordered_map> #include <unordered_map>
#include <vector>
class El { class El {
public: public:
@ -83,6 +84,71 @@ struct RecursiveMap : std::map<int, RecursiveMap> {
using Parent::Parent; using Parent::Parent;
}; };
class UserVectorLike : private std::vector<int> {
public:
// This is only a subset of the member functions, as needed at the time.
using Base = std::vector<int>;
using typename Base::const_iterator;
using typename Base::difference_type;
using typename Base::iterator;
using typename Base::size_type;
using typename Base::value_type;
using Base::at;
using Base::back;
using Base::Base;
using Base::begin;
using Base::cbegin;
using Base::cend;
using Base::clear;
using Base::empty;
using Base::end;
using Base::erase;
using Base::front;
using Base::insert;
using Base::pop_back;
using Base::push_back;
using Base::reserve;
using Base::shrink_to_fit;
using Base::swap;
using Base::operator[];
using Base::capacity;
using Base::size;
};
bool operator==(UserVectorLike const &, UserVectorLike const &) { return true; }
bool operator!=(UserVectorLike const &, UserVectorLike const &) { return false; }
class UserMapLike : private std::map<int, int> {
public:
// This is only a subset of the member functions, as needed at the time.
using Base = std::map<int, int>;
using typename Base::const_iterator;
using typename Base::iterator;
using typename Base::key_type;
using typename Base::mapped_type;
using typename Base::size_type;
using typename Base::value_type;
using Base::at;
using Base::Base;
using Base::begin;
using Base::cbegin;
using Base::cend;
using Base::clear;
using Base::emplace;
using Base::emplace_hint;
using Base::empty;
using Base::end;
using Base::erase;
using Base::find;
using Base::insert;
using Base::max_size;
using Base::swap;
using Base::operator[];
using Base::size;
};
/* /*
* Pybind11 does not catch more complicated recursion schemes, such as mutual * Pybind11 does not catch more complicated recursion schemes, such as mutual
* recursion. * recursion.
@ -126,6 +192,16 @@ TEST_SUBMODULE(stl_binders, m) {
py::bind_map<std::unordered_map<std::string, double const>>(m, py::bind_map<std::unordered_map<std::string, double const>>(m,
"UnorderedMapStringDoubleConst"); "UnorderedMapStringDoubleConst");
// test_map_view_types
py::bind_map<std::map<std::string, float>>(m, "MapStringFloat");
py::bind_map<std::unordered_map<std::string, float>>(m, "UnorderedMapStringFloat");
py::bind_map<std::map<std::pair<double, int>, int32_t>>(m, "MapPairDoubleIntInt32");
py::bind_map<std::map<std::pair<double, int>, int64_t>>(m, "MapPairDoubleIntInt64");
py::bind_map<std::map<int, py::object>>(m, "MapIntObject");
py::bind_map<std::map<std::string, py::object>>(m, "MapStringObject");
py::class_<E_nc>(m, "ENC").def(py::init<int>()).def_readwrite("value", &E_nc::value); py::class_<E_nc>(m, "ENC").def(py::init<int>()).def_readwrite("value", &E_nc::value);
// test_noncopyable_containers // test_noncopyable_containers
@ -173,6 +249,10 @@ TEST_SUBMODULE(stl_binders, m) {
py::bind_map<MutuallyRecursiveContainerPairMV>(m, "MutuallyRecursiveContainerPairMV"); py::bind_map<MutuallyRecursiveContainerPairMV>(m, "MutuallyRecursiveContainerPairMV");
py::bind_vector<MutuallyRecursiveContainerPairVM>(m, "MutuallyRecursiveContainerPairVM"); py::bind_vector<MutuallyRecursiveContainerPairVM>(m, "MutuallyRecursiveContainerPairVM");
// Bind with private inheritance + `using` directives.
py::bind_vector<UserVectorLike>(m, "UserVectorLike");
py::bind_map<UserMapLike>(m, "UserMapLike");
// The rest depends on numpy: // The rest depends on numpy:
try { try {
py::module_::import("numpy"); py::module_::import("numpy");

View File

@ -209,7 +209,7 @@ def test_map_string_double_const():
def test_noncopyable_containers(): def test_noncopyable_containers():
# std::vector # std::vector
vnc = m.get_vnc(5) vnc = m.get_vnc(5)
for i in range(0, 5): for i in range(5):
assert vnc[i].value == i + 1 assert vnc[i].value == i + 1
for i, j in enumerate(vnc, start=1): for i, j in enumerate(vnc, start=1):
@ -217,7 +217,7 @@ def test_noncopyable_containers():
# std::deque # std::deque
dnc = m.get_dnc(5) dnc = m.get_dnc(5)
for i in range(0, 5): for i in range(5):
assert dnc[i].value == i + 1 assert dnc[i].value == i + 1
i = 1 i = 1
@ -252,7 +252,7 @@ def test_noncopyable_containers():
# nested std::map<std::vector> # nested std::map<std::vector>
nvnc = m.get_nvnc(5) nvnc = m.get_nvnc(5)
for i in range(1, 6): for i in range(1, 6):
for j in range(0, 5): for j in range(5):
assert nvnc[i][j].value == j + 1 assert nvnc[i][j].value == j + 1
# Note: maps do not have .values() # Note: maps do not have .values()
@ -317,9 +317,9 @@ def test_map_view_types():
map_string_double_const = m.MapStringDoubleConst() map_string_double_const = m.MapStringDoubleConst()
unordered_map_string_double_const = m.UnorderedMapStringDoubleConst() unordered_map_string_double_const = m.UnorderedMapStringDoubleConst()
assert map_string_double.keys().__class__.__name__ == "KeysView[str]" assert map_string_double.keys().__class__.__name__ == "KeysView"
assert map_string_double.values().__class__.__name__ == "ValuesView[float]" assert map_string_double.values().__class__.__name__ == "ValuesView"
assert map_string_double.items().__class__.__name__ == "ItemsView[str, float]" assert map_string_double.items().__class__.__name__ == "ItemsView"
keys_type = type(map_string_double.keys()) keys_type = type(map_string_double.keys())
assert type(unordered_map_string_double.keys()) is keys_type assert type(unordered_map_string_double.keys()) is keys_type
@ -336,6 +336,30 @@ def test_map_view_types():
assert type(map_string_double_const.items()) is items_type assert type(map_string_double_const.items()) is items_type
assert type(unordered_map_string_double_const.items()) is items_type assert type(unordered_map_string_double_const.items()) is items_type
map_string_float = m.MapStringFloat()
unordered_map_string_float = m.UnorderedMapStringFloat()
assert type(map_string_float.keys()) is keys_type
assert type(unordered_map_string_float.keys()) is keys_type
assert type(map_string_float.values()) is values_type
assert type(unordered_map_string_float.values()) is values_type
assert type(map_string_float.items()) is items_type
assert type(unordered_map_string_float.items()) is items_type
map_pair_double_int_int32 = m.MapPairDoubleIntInt32()
map_pair_double_int_int64 = m.MapPairDoubleIntInt64()
assert type(map_pair_double_int_int32.values()) is values_type
assert type(map_pair_double_int_int64.values()) is values_type
map_int_object = m.MapIntObject()
map_string_object = m.MapStringObject()
assert type(map_int_object.keys()) is keys_type
assert type(map_string_object.keys()) is keys_type
assert type(map_int_object.items()) is items_type
assert type(map_string_object.items()) is items_type
def test_recursive_vector(): def test_recursive_vector():
recursive_vector = m.RecursiveVector() recursive_vector = m.RecursiveVector()
@ -353,3 +377,17 @@ def test_recursive_map():
recursive_map[100][101] = m.RecursiveMap() recursive_map[100][101] = m.RecursiveMap()
recursive_map[100][102] = m.RecursiveMap() recursive_map[100][102] = m.RecursiveMap()
assert list(recursive_map[100].keys()) == [101, 102] assert list(recursive_map[100].keys()) == [101, 102]
def test_user_vector_like():
vec = m.UserVectorLike()
vec.append(2)
assert vec[0] == 2
assert len(vec) == 1
def test_user_like_map():
map = m.UserMapLike()
map[33] = 44
assert map[33] == 44
assert len(map) == 1

View File

@ -78,14 +78,14 @@ TEST_SUBMODULE(type_caster_pyobject_ptr, m) {
m.def("cast_to_pyobject_ptr_nullptr", [](bool set_error) { m.def("cast_to_pyobject_ptr_nullptr", [](bool set_error) {
if (set_error) { if (set_error) {
PyErr_SetString(PyExc_RuntimeError, "Reflective of healthy error handling."); py::set_error(PyExc_RuntimeError, "Reflective of healthy error handling.");
} }
PyObject *ptr = nullptr; PyObject *ptr = nullptr;
py::cast(ptr); py::cast(ptr);
}); });
m.def("cast_to_pyobject_ptr_non_nullptr_with_error_set", []() { m.def("cast_to_pyobject_ptr_non_nullptr_with_error_set", []() {
PyErr_SetString(PyExc_RuntimeError, "Reflective of unhealthy error handling."); py::set_error(PyExc_RuntimeError, "Reflective of unhealthy error handling.");
py::cast(Py_None); py::cast(Py_None);
}); });

View File

@ -95,6 +95,22 @@ if(NOT PythonLibsNew_FIND_VERSION)
set(PythonLibsNew_FIND_VERSION "3.6") set(PythonLibsNew_FIND_VERSION "3.6")
endif() endif()
if(NOT CMAKE_VERSION VERSION_LESS "3.27")
cmake_policy(GET CMP0148 _pybind11_cmp0148)
if(NOT _pybind11_cmp0148)
message(
AUTHOR_WARNING
"Policy CMP0148 is not set: The FindPythonInterp and FindPythonLibs "
"modules are removed. Run \"cmake --help-policy CMP0148\" for policy "
"details. Use the cmake_policy command to set the policy and suppress "
"this warning, or preferably upgrade to using FindPython, either by "
"calling it explicitly before pybind11, or by setting "
"PYBIND11_FINDPYTHON ON before pybind11.")
endif()
cmake_policy(SET CMP0148 OLD)
unset(_pybind11_cmp0148)
endif()
find_package(PythonInterp ${PythonLibsNew_FIND_VERSION} ${_pythonlibs_required} find_package(PythonInterp ${PythonLibsNew_FIND_VERSION} ${_pythonlibs_required}
${_pythonlibs_quiet}) ${_pythonlibs_quiet})
@ -172,13 +188,20 @@ _pybind11_get_if_undef(_PYTHON_VALUES 0 _PYTHON_VERSION_LIST)
_pybind11_get_if_undef(_PYTHON_VALUES 1 PYTHON_PREFIX) _pybind11_get_if_undef(_PYTHON_VALUES 1 PYTHON_PREFIX)
_pybind11_get_if_undef(_PYTHON_VALUES 2 PYTHON_INCLUDE_DIR) _pybind11_get_if_undef(_PYTHON_VALUES 2 PYTHON_INCLUDE_DIR)
_pybind11_get_if_undef(_PYTHON_VALUES 3 PYTHON_SITE_PACKAGES) _pybind11_get_if_undef(_PYTHON_VALUES 3 PYTHON_SITE_PACKAGES)
_pybind11_get_if_undef(_PYTHON_VALUES 4 PYTHON_MODULE_EXTENSION)
_pybind11_get_if_undef(_PYTHON_VALUES 5 PYTHON_IS_DEBUG) _pybind11_get_if_undef(_PYTHON_VALUES 5 PYTHON_IS_DEBUG)
_pybind11_get_if_undef(_PYTHON_VALUES 6 PYTHON_SIZEOF_VOID_P) _pybind11_get_if_undef(_PYTHON_VALUES 6 PYTHON_SIZEOF_VOID_P)
_pybind11_get_if_undef(_PYTHON_VALUES 7 PYTHON_LIBRARY_SUFFIX) _pybind11_get_if_undef(_PYTHON_VALUES 7 PYTHON_LIBRARY_SUFFIX)
_pybind11_get_if_undef(_PYTHON_VALUES 8 PYTHON_LIBDIR) _pybind11_get_if_undef(_PYTHON_VALUES 8 PYTHON_LIBDIR)
_pybind11_get_if_undef(_PYTHON_VALUES 9 PYTHON_MULTIARCH) _pybind11_get_if_undef(_PYTHON_VALUES 9 PYTHON_MULTIARCH)
list(GET _PYTHON_VALUES 4 _PYTHON_MODULE_EXT_SUFFIX)
if(PYBIND11_PYTHONLIBS_OVERWRITE OR NOT DEFINED PYTHON_MODULE_DEBUG_POSTFIX)
get_filename_component(PYTHON_MODULE_DEBUG_POSTFIX "${_PYTHON_MODULE_EXT_SUFFIX}" NAME_WE)
endif()
if(PYBIND11_PYTHONLIBS_OVERWRITE OR NOT DEFINED PYTHON_MODULE_EXTENSION)
get_filename_component(PYTHON_MODULE_EXTENSION "${_PYTHON_MODULE_EXT_SUFFIX}" EXT)
endif()
# Make sure the Python has the same pointer-size as the chosen compiler # Make sure the Python has the same pointer-size as the chosen compiler
# Skip if CMAKE_SIZEOF_VOID_P is not defined # Skip if CMAKE_SIZEOF_VOID_P is not defined
# This should be skipped for (non-Apple) cross-compiles (like EMSCRIPTEN) # This should be skipped for (non-Apple) cross-compiles (like EMSCRIPTEN)

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import re import re
@ -29,6 +30,18 @@ issues_pages = ghapi.page.paged(
) )
issues = (issue for page in issues_pages for issue in page) issues = (issue for page in issues_pages for issue in page)
missing = [] missing = []
cats_descr = {
"feat": "New Features",
"fix": "Bug fixes",
"fix(types)": "",
"fix(cmake)": "",
"docs": "Documentation",
"tests": "Tests",
"ci": "CI",
"chore": "Other",
"unknown": "Uncategorised",
}
cats: dict[str, list[str]] = {c: [] for c in cats_descr}
for issue in issues: for issue in issues:
changelog = ENTRY.findall(issue.body or "") changelog = ENTRY.findall(issue.body or "")
@ -36,14 +49,29 @@ for issue in issues:
missing.append(issue) missing.append(issue)
else: else:
(msg,) = changelog (msg,) = changelog
if msg.startswith("- "):
msg = msg[2:]
if not msg.startswith("* "): if not msg.startswith("* "):
msg = "* " + msg msg = "* " + msg
if not msg.endswith("."): if not msg.endswith("."):
msg += "." msg += "."
msg += f"\n `#{issue.number} <{issue.html_url}>`_" msg += f"\n `#{issue.number} <{issue.html_url}>`_"
for cat in cats:
if issue.title.lower().startswith(f"{cat}:"):
cats[cat].append(msg)
break
else:
cats["unknown"].append(msg)
print(Syntax(msg, "rst", theme="ansi_light", word_wrap=True)) for cat, msgs in cats.items():
if msgs:
desc = cats_descr[cat]
print(f"[bold]{desc}:" if desc else f".. {cat}")
print()
for msg in msgs:
print(Syntax(msg, "rst", theme="ansi_light", word_wrap=True))
print()
print() print()
if missing: if missing:

View File

@ -173,12 +173,16 @@ endif()
# Check to see which Python mode we are in, new, old, or no python # Check to see which Python mode we are in, new, old, or no python
if(PYBIND11_NOPYTHON) if(PYBIND11_NOPYTHON)
set(_pybind11_nopython ON) set(_pybind11_nopython ON)
# We won't use new FindPython if PYBIND11_FINDPYTHON is defined and falselike
# Otherwise, we use if FindPythonLibs is missing or if FindPython was already used
elseif( elseif(
_pybind11_missing_old_python STREQUAL "NEW" (NOT DEFINED PYBIND11_FINDPYTHON OR PYBIND11_FINDPYTHON)
OR PYBIND11_FINDPYTHON AND (_pybind11_missing_old_python STREQUAL "NEW"
OR Python_FOUND OR PYBIND11_FINDPYTHON
OR Python2_FOUND OR Python_FOUND
OR Python3_FOUND) OR Python3_FOUND
))
# New mode # New mode
include("${CMAKE_CURRENT_LIST_DIR}/pybind11NewTools.cmake") include("${CMAKE_CURRENT_LIST_DIR}/pybind11NewTools.cmake")
@ -218,8 +222,15 @@ if(NOT _pybind11_nopython)
execute_process( execute_process(
COMMAND COMMAND
${${_Python}_EXECUTABLE} -c ${${_Python}_EXECUTABLE} -c "
"from pkg_resources import get_distribution; print(get_distribution('${PYPI_NAME}').version)" try:
from importlib.metadata import version
except ImportError:
from pkg_resources import get_distribution
def version(s):
return get_distribution(s).version
print(version('${PYPI_NAME}'))
"
RESULT_VARIABLE RESULT_PRESENT RESULT_VARIABLE RESULT_PRESENT
OUTPUT_VARIABLE PKG_VERSION OUTPUT_VARIABLE PKG_VERSION
ERROR_QUIET) ERROR_QUIET)
@ -300,21 +311,24 @@ function(_pybind11_generate_lto target prefer_thin_lto)
set(cxx_append ";-fno-fat-lto-objects") set(cxx_append ";-fno-fat-lto-objects")
endif() endif()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le" OR CMAKE_SYSTEM_PROCESSOR MATCHES "mips64") if(prefer_thin_lto)
set(NO_FLTO_ARCH TRUE) set(thin "=thin")
else() else()
set(NO_FLTO_ARCH FALSE) set(thin "")
endif() endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" if(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le" OR CMAKE_SYSTEM_PROCESSOR MATCHES "mips64")
AND prefer_thin_lto # Do nothing
AND NOT NO_FLTO_ARCH) elseif(CMAKE_SYSTEM_PROCESSOR MATCHES emscripten)
# This compile is very costly when cross-compiling, so set this without checking
set(PYBIND11_LTO_CXX_FLAGS "-flto${thin}${cxx_append}")
set(PYBIND11_LTO_LINKER_FLAGS "-flto${thin}${linker_append}")
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
_pybind11_return_if_cxx_and_linker_flags_work( _pybind11_return_if_cxx_and_linker_flags_work(
HAS_FLTO_THIN "-flto=thin${cxx_append}" "-flto=thin${linker_append}" HAS_FLTO_THIN "-flto${thin}${cxx_append}" "-flto=${thin}${linker_append}"
PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS)
endif() endif()
if(NOT HAS_FLTO_THIN)
if(NOT HAS_FLTO_THIN AND NOT NO_FLTO_ARCH)
_pybind11_return_if_cxx_and_linker_flags_work( _pybind11_return_if_cxx_and_linker_flags_work(
HAS_FLTO "-flto${cxx_append}" "-flto${linker_append}" PYBIND11_LTO_CXX_FLAGS HAS_FLTO "-flto${cxx_append}" "-flto${linker_append}" PYBIND11_LTO_CXX_FLAGS
PYBIND11_LTO_LINKER_FLAGS) PYBIND11_LTO_LINKER_FLAGS)

View File

@ -149,7 +149,7 @@ default is ``MODULE``. There are several options:
``OPT_SIZE`` ``OPT_SIZE``
Optimize for size, even if the ``CMAKE_BUILD_TYPE`` is not ``MinSizeRel``. Optimize for size, even if the ``CMAKE_BUILD_TYPE`` is not ``MinSizeRel``.
``THIN_LTO`` ``THIN_LTO``
Use thin TLO instead of regular if there's a choice (pybind11's selection Use thin LTO instead of regular if there's a choice (pybind11's selection
is disabled if ``CMAKE_INTERPROCEDURAL_OPTIMIZATIONS`` is set). is disabled if ``CMAKE_INTERPROCEDURAL_OPTIMIZATIONS`` is set).
``WITHOUT_SOABI`` ``WITHOUT_SOABI``
Disable the SOABI component (``PYBIND11_NEWPYTHON`` mode only). Disable the SOABI component (``PYBIND11_NEWPYTHON`` mode only).

View File

@ -32,17 +32,54 @@ if(NOT Python_FOUND AND NOT Python3_FOUND)
set(Python_ROOT_DIR "$ENV{pythonLocation}") set(Python_ROOT_DIR "$ENV{pythonLocation}")
endif() endif()
find_package(Python 3.6 REQUIRED COMPONENTS Interpreter Development ${_pybind11_quiet}) # Development.Module support (required for manylinux) started in 3.18
if(CMAKE_VERSION VERSION_LESS 3.18)
set(_pybind11_dev_component Development)
else()
set(_pybind11_dev_component Development.Module OPTIONAL_COMPONENTS Development.Embed)
endif()
# Callers need to be able to access Python_EXECUTABLE
set(_pybind11_global_keyword "")
if(NOT is_config AND NOT DEFINED Python_ARTIFACTS_INTERACTIVE)
set(Python_ARTIFACTS_INTERACTIVE TRUE)
if(NOT CMAKE_VERSION VERSION_LESS 3.24)
set(_pybind11_global_keyword "GLOBAL")
endif()
endif()
find_package(Python 3.6 REQUIRED COMPONENTS Interpreter ${_pybind11_dev_component}
${_pybind11_quiet} ${_pybind11_global_keyword})
# If we are in submodule mode, export the Python targets to global targets. # If we are in submodule mode, export the Python targets to global targets.
# If this behavior is not desired, FindPython _before_ pybind11. # If this behavior is not desired, FindPython _before_ pybind11.
if(NOT is_config) if(NOT is_config
set_property(TARGET Python::Python PROPERTY IMPORTED_GLOBAL TRUE) AND Python_ARTIFACTS_INTERACTIVE
AND _pybind11_global_keyword STREQUAL "")
if(TARGET Python::Python)
set_property(TARGET Python::Python PROPERTY IMPORTED_GLOBAL TRUE)
endif()
set_property(TARGET Python::Interpreter PROPERTY IMPORTED_GLOBAL TRUE) set_property(TARGET Python::Interpreter PROPERTY IMPORTED_GLOBAL TRUE)
if(TARGET Python::Module) if(TARGET Python::Module)
set_property(TARGET Python::Module PROPERTY IMPORTED_GLOBAL TRUE) set_property(TARGET Python::Module PROPERTY IMPORTED_GLOBAL TRUE)
endif() endif()
endif() endif()
# Explicitly export version for callers (including our own functions)
if(NOT is_config AND Python_ARTIFACTS_INTERACTIVE)
set(Python_VERSION
"${Python_VERSION}"
CACHE INTERNAL "")
set(Python_VERSION_MAJOR
"${Python_VERSION_MAJOR}"
CACHE INTERNAL "")
set(Python_VERSION_MINOR
"${Python_VERSION_MINOR}"
CACHE INTERNAL "")
set(Python_VERSION_PATCH
"${Python_VERSION_PATCH}"
CACHE INTERNAL "")
endif()
endif() endif()
if(Python_FOUND) if(Python_FOUND)
@ -73,15 +110,17 @@ if(NOT DEFINED ${_Python}_EXECUTABLE)
endif() endif()
if(NOT ${_Python}_EXECUTABLE STREQUAL PYBIND11_PYTHON_EXECUTABLE_LAST) if(DEFINED PYBIND11_PYTHON_EXECUTABLE_LAST AND NOT ${_Python}_EXECUTABLE STREQUAL
PYBIND11_PYTHON_EXECUTABLE_LAST)
# Detect changes to the Python version/binary in subsequent CMake runs, and refresh config if needed # Detect changes to the Python version/binary in subsequent CMake runs, and refresh config if needed
unset(PYTHON_IS_DEBUG CACHE) unset(PYTHON_IS_DEBUG CACHE)
unset(PYTHON_MODULE_EXTENSION CACHE) unset(PYTHON_MODULE_EXTENSION CACHE)
set(PYBIND11_PYTHON_EXECUTABLE_LAST
"${${_Python}_EXECUTABLE}"
CACHE INTERNAL "Python executable during the last CMake run")
endif() endif()
set(PYBIND11_PYTHON_EXECUTABLE_LAST
"${${_Python}_EXECUTABLE}"
CACHE INTERNAL "Python executable during the last CMake run")
if(NOT DEFINED PYTHON_IS_DEBUG) if(NOT DEFINED PYTHON_IS_DEBUG)
# Debug check - see https://stackoverflow.com/questions/646518/python-how-to-detect-debug-Interpreter # Debug check - see https://stackoverflow.com/questions/646518/python-how-to-detect-debug-Interpreter
execute_process( execute_process(
@ -95,25 +134,36 @@ endif()
# Get the suffix - SO is deprecated, should use EXT_SUFFIX, but this is # Get the suffix - SO is deprecated, should use EXT_SUFFIX, but this is
# required for PyPy3 (as of 7.3.1) # required for PyPy3 (as of 7.3.1)
if(NOT DEFINED PYTHON_MODULE_EXTENSION) if(NOT DEFINED PYTHON_MODULE_EXTENSION OR NOT DEFINED PYTHON_MODULE_DEBUG_POSTFIX)
execute_process( execute_process(
COMMAND COMMAND
"${${_Python}_EXECUTABLE}" "-c" "${${_Python}_EXECUTABLE}" "-c"
"import sys, importlib; s = importlib.import_module('distutils.sysconfig' if sys.version_info < (3, 10) else 'sysconfig'); print(s.get_config_var('EXT_SUFFIX') or s.get_config_var('SO'))" "import sys, importlib; s = importlib.import_module('distutils.sysconfig' if sys.version_info < (3, 10) else 'sysconfig'); print(s.get_config_var('EXT_SUFFIX') or s.get_config_var('SO'))"
OUTPUT_VARIABLE _PYTHON_MODULE_EXTENSION OUTPUT_VARIABLE _PYTHON_MODULE_EXT_SUFFIX
ERROR_VARIABLE _PYTHON_MODULE_EXTENSION_ERR ERROR_VARIABLE _PYTHON_MODULE_EXT_SUFFIX_ERR
OUTPUT_STRIP_TRAILING_WHITESPACE) OUTPUT_STRIP_TRAILING_WHITESPACE)
if(_PYTHON_MODULE_EXTENSION STREQUAL "") if(_PYTHON_MODULE_EXT_SUFFIX STREQUAL "")
message( message(
FATAL_ERROR "pybind11 could not query the module file extension, likely the 'distutils'" FATAL_ERROR "pybind11 could not query the module file extension, likely the 'distutils'"
"package is not installed. Full error message:\n${_PYTHON_MODULE_EXTENSION_ERR}") "package is not installed. Full error message:\n${_PYTHON_MODULE_EXT_SUFFIX_ERR}"
)
endif() endif()
# This needs to be available for the pybind11_extension function # This needs to be available for the pybind11_extension function
set(PYTHON_MODULE_EXTENSION if(NOT DEFINED PYTHON_MODULE_DEBUG_POSTFIX)
"${_PYTHON_MODULE_EXTENSION}" get_filename_component(_PYTHON_MODULE_DEBUG_POSTFIX "${_PYTHON_MODULE_EXT_SUFFIX}" NAME_WE)
CACHE INTERNAL "") set(PYTHON_MODULE_DEBUG_POSTFIX
"${_PYTHON_MODULE_DEBUG_POSTFIX}"
CACHE INTERNAL "")
endif()
if(NOT DEFINED PYTHON_MODULE_EXTENSION)
get_filename_component(_PYTHON_MODULE_EXTENSION "${_PYTHON_MODULE_EXT_SUFFIX}" EXT)
set(PYTHON_MODULE_EXTENSION
"${_PYTHON_MODULE_EXTENSION}"
CACHE INTERNAL "")
endif()
endif() endif()
# Python debug libraries expose slightly different objects before 3.8 # Python debug libraries expose slightly different objects before 3.8
@ -233,11 +283,13 @@ function(pybind11_add_module target_name)
endif() endif()
endif() endif()
# Use case-insensitive comparison to match the result of $<CONFIG:cfgs> if(DEFINED CMAKE_BUILD_TYPE) # see https://github.com/pybind/pybind11/issues/4454
string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) # Use case-insensitive comparison to match the result of $<CONFIG:cfgs>
if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO) string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE)
# Strip unnecessary sections of the binary on Linux/macOS if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO)
pybind11_strip(${target_name}) # Strip unnecessary sections of the binary on Linux/macOS
pybind11_strip(${target_name})
endif()
endif() endif()
if(MSVC) if(MSVC)
@ -251,6 +303,9 @@ endfunction()
function(pybind11_extension name) function(pybind11_extension name)
# The extension is precomputed # The extension is precomputed
set_target_properties(${name} PROPERTIES PREFIX "" SUFFIX "${PYTHON_MODULE_EXTENSION}") set_target_properties(
${name}
PROPERTIES PREFIX ""
DEBUG_POSTFIX "${PYTHON_MODULE_DEBUG_POSTFIX}"
SUFFIX "${PYTHON_MODULE_EXTENSION}")
endfunction() endfunction()

View File

@ -43,7 +43,7 @@ endif()
# A user can set versions manually too # A user can set versions manually too
set(Python_ADDITIONAL_VERSIONS set(Python_ADDITIONAL_VERSIONS
"3.11;3.10;3.9;3.8;3.7;3.6" "3.12;3.11;3.10;3.9;3.8;3.7;3.6"
CACHE INTERNAL "") CACHE INTERNAL "")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
@ -65,6 +65,7 @@ _pybind11_promote_to_cache(PYTHON_INCLUDE_DIRS)
_pybind11_promote_to_cache(PYTHON_LIBRARIES) _pybind11_promote_to_cache(PYTHON_LIBRARIES)
_pybind11_promote_to_cache(PYTHON_MODULE_PREFIX) _pybind11_promote_to_cache(PYTHON_MODULE_PREFIX)
_pybind11_promote_to_cache(PYTHON_MODULE_EXTENSION) _pybind11_promote_to_cache(PYTHON_MODULE_EXTENSION)
_pybind11_promote_to_cache(PYTHON_MODULE_DEBUG_POSTFIX)
_pybind11_promote_to_cache(PYTHON_VERSION_MAJOR) _pybind11_promote_to_cache(PYTHON_VERSION_MAJOR)
_pybind11_promote_to_cache(PYTHON_VERSION_MINOR) _pybind11_promote_to_cache(PYTHON_VERSION_MINOR)
_pybind11_promote_to_cache(PYTHON_VERSION) _pybind11_promote_to_cache(PYTHON_VERSION)
@ -148,8 +149,11 @@ endif()
function(pybind11_extension name) function(pybind11_extension name)
# The prefix and extension are provided by FindPythonLibsNew.cmake # The prefix and extension are provided by FindPythonLibsNew.cmake
set_target_properties(${name} PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" set_target_properties(
SUFFIX "${PYTHON_MODULE_EXTENSION}") ${name}
PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}"
DEBUG_POSTFIX "${PYTHON_MODULE_DEBUG_POSTFIX}"
SUFFIX "${PYTHON_MODULE_EXTENSION}")
endfunction() endfunction()
# Build a Python extension module: # Build a Python extension module:
@ -212,10 +216,12 @@ function(pybind11_add_module target_name)
endif() endif()
endif() endif()
# Use case-insensitive comparison to match the result of $<CONFIG:cfgs> if(DEFINED CMAKE_BUILD_TYPE) # see https://github.com/pybind/pybind11/issues/4454
string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) # Use case-insensitive comparison to match the result of $<CONFIG:cfgs>
if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO) string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE)
pybind11_strip(${target_name}) if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO)
pybind11_strip(${target_name})
endif()
endif() endif()
if(MSVC) if(MSVC)