Merge branch 'v2.11' into stable

This commit is contained in:
Ralf W. Grosse-Kunstleve 2023-07-14 09:32:48 -07:00
commit 0dcf6f289d
106 changed files with 2366 additions and 651 deletions

View File

@ -235,8 +235,8 @@ directory inside your pybind11 git clone. Files will be modified in place,
so you can use git to monitor the changes. so you can use git to monitor the changes.
```bash ```bash
docker run --rm -v $PWD:/mounted_pybind11 -it silkeh/clang:13 docker run --rm -v $PWD:/mounted_pybind11 -it silkeh/clang:15-bullseye
apt-get update && apt-get install -y python3-dev python3-pytest apt-get update && apt-get install -y git python3-dev python3-pytest
cmake -S /mounted_pybind11/ -B build -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);--use-color" -DDOWNLOAD_EIGEN=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=17 cmake -S /mounted_pybind11/ -B build -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);--use-color" -DDOWNLOAD_EIGEN=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=17
cmake --build build -j 2 cmake --build build -j 2
``` ```

View File

@ -9,11 +9,14 @@ on:
- stable - stable
- v* - v*
permissions: read-all
concurrency: concurrency:
group: test-${{ github.ref }} group: test-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
env: env:
PIP_BREAK_SYSTEM_PACKAGES: 1
PIP_ONLY_BINARY: numpy PIP_ONLY_BINARY: numpy
FORCE_COLOR: 3 FORCE_COLOR: 3
PYTEST_TIMEOUT: 300 PYTEST_TIMEOUT: 300
@ -33,9 +36,10 @@ jobs:
- '3.9' - '3.9'
- '3.10' - '3.10'
- '3.11' - '3.11'
- 'pypy-3.7' - '3.12'
- 'pypy-3.8' - 'pypy-3.8'
- 'pypy-3.9' - 'pypy-3.9'
- 'pypy-3.10'
# Items in here will either be added to the build matrix (if not # Items in here will either be added to the build matrix (if not
# present), or add new keys to an existing matrix element if all the # present), or add new keys to an existing matrix element if all the
@ -71,6 +75,7 @@ jobs:
uses: actions/setup-python@v4 uses: actions/setup-python@v4
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 _
@ -82,7 +87,7 @@ jobs:
run: brew install boost run: brew install boost
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.13 uses: jwlawson/actions-setup-cmake@v1.14
- name: Cache wheels - name: Cache wheels
if: runner.os == 'macOS' if: runner.os == 'macOS'
@ -164,7 +169,6 @@ jobs:
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD=17
-DPYBIND11_INTERNALS_VERSION=10000000 -DPYBIND11_INTERNALS_VERSION=10000000
"-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp"
${{ matrix.args }} ${{ matrix.args }}
- name: Build (unstable ABI) - name: Build (unstable ABI)
@ -179,7 +183,9 @@ jobs:
# This makes sure the setup_helpers module can build packages using # This makes sure the setup_helpers module can build packages using
# setuptools # setuptools
- name: Setuptools helpers test - name: Setuptools helpers test
run: pytest tests/extra_setuptools run: |
pip install setuptools
pytest tests/extra_setuptools
if: "!(matrix.runs-on == 'windows-2022')" if: "!(matrix.runs-on == 'windows-2022')"
@ -202,13 +208,13 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Setup Python ${{ matrix.python-version }} (deadsnakes) - name: Setup Python ${{ matrix.python-version }} (deadsnakes)
uses: deadsnakes/action@v3.0.0 uses: deadsnakes/action@v3.0.1
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.13 uses: jwlawson/actions-setup-cmake@v1.14
- name: Valgrind cache - name: Valgrind cache
if: matrix.valgrind if: matrix.valgrind
@ -241,8 +247,6 @@ jobs:
python -m pip install -r tests/requirements.txt python -m pip install -r tests/requirements.txt
- name: Configure - name: Configure
env:
SETUPTOOLS_USE_DISTUTILS: stdlib
run: > run: >
cmake -S . -B build cmake -S . -B build
-DCMAKE_BUILD_TYPE=Debug -DCMAKE_BUILD_TYPE=Debug
@ -333,8 +337,8 @@ jobs:
# Testing NVCC; forces sources to behave like .cu files # Testing NVCC; forces sources to behave like .cu files
cuda: cuda:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "🐍 3.10 • CUDA 11.7 • Ubuntu 22.04" name: "🐍 3.10 • CUDA 12.2 • Ubuntu 22.04"
container: nvidia/cuda:11.7.0-devel-ubuntu22.04 container: nvidia/cuda:12.2.0-devel-ubuntu22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -399,6 +403,7 @@ jobs:
# Testing on CentOS 7 + PGI compilers, which seems to require more workarounds # Testing on CentOS 7 + PGI compilers, which seems to require more workarounds
centos-nvhpc7: centos-nvhpc7:
if: ${{ false }} # JOB DISABLED (NEEDS WORK): https://github.com/pybind/pybind11/issues/4690
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "🐍 3 • CentOS7 / PGI 22.9 • x64" name: "🐍 3 • CentOS7 / PGI 22.9 • x64"
container: centos:7 container: centos:7
@ -474,7 +479,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.13 uses: jwlawson/actions-setup-cmake@v1.14
- name: Configure - name: Configure
shell: bash shell: bash
@ -497,6 +502,24 @@ jobs:
- name: Interface test - name: Interface test
run: cmake --build build --target test_cmake_build run: cmake --build build --target test_cmake_build
- name: Configure - Exercise cmake -DPYBIND11_TEST_OVERRIDE
if: matrix.gcc == '12'
shell: bash
run: >
cmake -S . -B build_partial
-DPYBIND11_WERROR=ON
-DDOWNLOAD_CATCH=ON
-DCMAKE_CXX_STANDARD=${{ matrix.std }}
-DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)")
"-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp"
- name: Build - Exercise cmake -DPYBIND11_TEST_OVERRIDE
if: matrix.gcc == '12'
run: cmake --build build_partial -j 2
- name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE
if: matrix.gcc == '12'
run: cmake --build build_partial --target pytest
# Testing on ICC using the oneAPI apt repo # Testing on ICC using the oneAPI apt repo
icc: icc:
@ -763,7 +786,7 @@ jobs:
architecture: x86 architecture: x86
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.13 uses: jwlawson/actions-setup-cmake@v1.14
- name: Prepare MSVC - name: Prepare MSVC
uses: ilammy/msvc-dev-cmd@v1.12.1 uses: ilammy/msvc-dev-cmd@v1.12.1
@ -816,7 +839,7 @@ jobs:
architecture: x86 architecture: x86
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.13 uses: jwlawson/actions-setup-cmake@v1.14
- name: Prepare MSVC - name: Prepare MSVC
uses: ilammy/msvc-dev-cmd@v1.12.1 uses: ilammy/msvc-dev-cmd@v1.12.1
@ -867,7 +890,7 @@ jobs:
python3 -m pip install -r tests/requirements.txt python3 -m pip install -r tests/requirements.txt
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.13 uses: jwlawson/actions-setup-cmake@v1.14
- name: Configure C++20 - name: Configure C++20
run: > run: >
@ -889,6 +912,21 @@ jobs:
- name: Interface test C++20 - name: Interface test C++20
run: cmake --build build --target test_cmake_build run: cmake --build build --target test_cmake_build
- name: Configure C++20 - Exercise cmake -DPYBIND11_TEST_OVERRIDE
run: >
cmake -S . -B build_partial
-DPYBIND11_WERROR=ON
-DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=20
"-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp"
- name: Build C++20 - Exercise cmake -DPYBIND11_TEST_OVERRIDE
run: cmake --build build_partial -j 2
- name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE
run: cmake --build build_partial --target pytest
mingw: mingw:
name: "🐍 3 • windows-latest • ${{ matrix.sys }}" name: "🐍 3 • windows-latest • ${{ matrix.sys }}"
runs-on: windows-latest runs-on: windows-latest
@ -1000,7 +1038,7 @@ jobs:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.13 uses: jwlawson/actions-setup-cmake@v1.14
- name: Install ninja-build tool - name: Install ninja-build tool
uses: seanmiddleditch/gha-setup-ninja@v3 uses: seanmiddleditch/gha-setup-ninja@v3
@ -1070,7 +1108,7 @@ jobs:
run: clang++ --version run: clang++ --version
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.13 uses: jwlawson/actions-setup-cmake@v1.14
- name: Run pip installs - name: Run pip installs
run: | run: |
@ -1105,5 +1143,23 @@ jobs:
- name: Interface test - name: Interface test
run: cmake --build . --target test_cmake_build -j 2 run: cmake --build . --target test_cmake_build -j 2
- name: CMake Configure - Exercise cmake -DPYBIND11_TEST_OVERRIDE
run: >
cmake -S . -B build_partial
-DPYBIND11_WERROR=ON
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
-DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_CXX_STANDARD=17
-DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)")
"-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp"
- name: Build - Exercise cmake -DPYBIND11_TEST_OVERRIDE
run: cmake --build build_partial -j 2
- name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE
run: cmake --build build_partial --target pytest -j 2
- name: Clean directory - name: Clean directory
run: git clean -fdx run: git clean -fdx

View File

@ -9,7 +9,11 @@ on:
- stable - stable
- v* - v*
permissions:
contents: read
env: env:
PIP_BREAK_SYSTEM_PACKAGES: 1
# For cmake: # For cmake:
VERBOSE: 1 VERBOSE: 1
@ -22,20 +26,24 @@ jobs:
matrix: matrix:
runs-on: [ubuntu-20.04, macos-latest, windows-latest] runs-on: [ubuntu-20.04, macos-latest, windows-latest]
arch: [x64] arch: [x64]
cmake: ["3.23"] cmake: ["3.26"]
include: include:
- runs-on: ubuntu-20.04 - runs-on: ubuntu-20.04
arch: x64 arch: x64
cmake: 3.4 cmake: "3.5"
- runs-on: ubuntu-20.04
arch: x64
cmake: "3.27"
- runs-on: macos-latest - runs-on: macos-latest
arch: x64 arch: x64
cmake: 3.7 cmake: "3.7"
- runs-on: windows-2019 - runs-on: windows-2019
arch: x64 # x86 compilers seem to be missing on 2019 image arch: x64 # x86 compilers seem to be missing on 2019 image
cmake: 3.18 cmake: "3.18"
name: 🐍 3.7 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }} name: 🐍 3.7 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }}
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
@ -55,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.13 uses: jwlawson/actions-setup-cmake@v1.14
with: with:
cmake-version: ${{ matrix.cmake }} cmake-version: ${{ matrix.cmake }}

View File

@ -12,6 +12,9 @@ on:
- stable - stable
- "v*" - "v*"
permissions:
contents: read
env: env:
FORCE_COLOR: 3 FORCE_COLOR: 3
# For cmake: # For cmake:
@ -38,12 +41,12 @@ jobs:
# in .github/CONTRIBUTING.md and update as needed. # in .github/CONTRIBUTING.md and update as needed.
name: Clang-Tidy name: Clang-Tidy
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: silkeh/clang:13 container: silkeh/clang:15-bullseye
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install requirements - name: Install requirements
run: apt-get update && apt-get install -y python3-dev python3-pytest run: apt-get update && apt-get install -y git python3-dev python3-pytest
- name: Configure - name: Configure
run: > run: >

View File

@ -3,10 +3,15 @@ on:
pull_request_target: pull_request_target:
types: [closed] types: [closed]
permissions: {}
jobs: jobs:
label: label:
name: Labeler name: Labeler
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps: steps:
- uses: actions/labeler@main - uses: actions/labeler@main

View File

@ -12,7 +12,11 @@ on:
types: types:
- published - published
permissions:
contents: read
env: env:
PIP_BREAK_SYSTEM_PACKAGES: 1
PIP_ONLY_BINARY: numpy PIP_ONLY_BINARY: numpy
jobs: jobs:
@ -98,13 +102,13 @@ jobs:
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
- name: Publish standard package - name: Publish standard package
uses: pypa/gh-action-pypi-publish@v1.8.1 uses: pypa/gh-action-pypi-publish@release/v1
with: with:
password: ${{ secrets.pypi_password }} password: ${{ secrets.pypi_password }}
packages-dir: standard/ packages-dir: standard/
- name: Publish global package - name: Publish global package
uses: pypa/gh-action-pypi-publish@v1.8.1 uses: pypa/gh-action-pypi-publish@release/v1
with: with:
password: ${{ secrets.pypi_password_global }} password: ${{ secrets.pypi_password_global }}
packages-dir: global/ packages-dir: global/

View File

@ -1,114 +1,116 @@
name: Upstream name: Upstream
on: on:
workflow_dispatch: workflow_dispatch:
pull_request: pull_request:
permissions:
contents: read
concurrency: concurrency:
group: upstream-${{ github.ref }} group: upstream-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
env: env:
PIP_ONLY_BINARY: numpy PIP_BREAK_SYSTEM_PACKAGES: 1
PIP_ONLY_BINARY: ":all:"
# For cmake: # For cmake:
VERBOSE: 1 VERBOSE: 1
jobs: jobs:
standard: standard:
name: "🐍 3.11 latest internals • ubuntu-latest • x64" name: "🐍 3.12 latest • ubuntu-latest • x64"
runs-on: ubuntu-latest runs-on: ubuntu-latest
# 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@v3
- name: Setup Python 3.11 - name: Setup Python 3.12
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.11-dev" python-version: "3.12-dev"
- name: Setup Boost (Linux) - name: Setup Boost
if: runner.os == 'Linux'
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.13 uses: jwlawson/actions-setup-cmake@v1.14
- name: Prepare env - name: Run pip installs
run: | run: |
python -m pip install --upgrade pip
python -m pip install -r tests/requirements.txt python -m pip install -r tests/requirements.txt
- name: Setup annotations on Linux - name: Show platform info
if: runner.os == 'Linux' run: |
run: python -m pip install pytest-github-actions-annotate-failures python -m platform
cmake --version
pip list
# First build - C++11 mode and inplace # First build - C++11 mode and inplace
- name: Configure C++11 - name: Configure C++11
run: > run: >
cmake -S . -B . cmake -S . -B build11
-DPYBIND11_WERROR=ON -DPYBIND11_WERROR=ON
-DDOWNLOAD_CATCH=ON -DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=11 -DCMAKE_CXX_STANDARD=11
-DCMAKE_BUILD_TYPE=Debug
- name: Build C++11 - name: Build C++11
run: cmake --build . -j 2 run: cmake --build build11 -j 2
- name: Python tests C++11 - name: Python tests C++11
run: cmake --build . --target pytest -j 2 run: cmake --build build11 --target pytest -j 2
- name: C++11 tests - name: C++11 tests
run: cmake --build . --target cpptest -j 2 run: cmake --build build11 --target cpptest -j 2
- name: Interface test C++11 - name: Interface test C++11
run: cmake --build . --target test_cmake_build run: cmake --build build11 --target test_cmake_build
- name: Clean directory
run: git clean -fdx
# Second build - C++17 mode and in a build directory # Second build - C++17 mode and in a build directory
- name: Configure C++17 - name: Configure C++17
run: > run: >
cmake -S . -B build2 cmake -S . -B build17
-DPYBIND11_WERROR=ON -DPYBIND11_WERROR=ON
-DDOWNLOAD_CATCH=ON -DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD=17
${{ matrix.args }}
${{ matrix.args2 }}
- name: Build - name: Build C++17
run: cmake --build build2 -j 2 run: cmake --build build17 -j 2
- name: Python tests - name: Python tests C++17
run: cmake --build build2 --target pytest run: cmake --build build17 --target pytest
- name: C++ tests - name: C++17 tests
run: cmake --build build2 --target cpptest run: cmake --build build17 --target cpptest
# Third build - C++17 mode with unstable ABI # Third build - C++17 mode with unstable ABI
- name: Configure (unstable ABI) - name: Configure (unstable ABI)
run: > run: >
cmake -S . -B build3 cmake -S . -B build17max
-DPYBIND11_WERROR=ON -DPYBIND11_WERROR=ON
-DDOWNLOAD_CATCH=ON -DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD=17
-DPYBIND11_INTERNALS_VERSION=10000000 -DPYBIND11_INTERNALS_VERSION=10000000
"-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp"
${{ matrix.args }}
- name: Build (unstable ABI) - name: Build (unstable ABI)
run: cmake --build build3 -j 2 run: cmake --build build17max -j 2
- name: Python tests (unstable ABI) - name: Python tests (unstable ABI)
run: cmake --build build3 --target pytest run: cmake --build build17max --target pytest
- name: Interface test - name: Interface test (unstable ABI)
run: cmake --build build3 --target test_cmake_build run: cmake --build build17max --target test_cmake_build
# This makes sure the setup_helpers module can build packages using # This makes sure the setup_helpers module can build packages using
# setuptools # setuptools
- name: Setuptools helpers test - name: Setuptools helpers test
run: pytest tests/extra_setuptools run: |
pip install setuptools
pytest tests/extra_setuptools

View File

@ -22,6 +22,49 @@ ci:
exclude: ^tools/JoinPaths.cmake$ exclude: ^tools/JoinPaths.cmake$
repos: repos:
# Clang format the codebase automatically
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: "v16.0.6"
hooks:
- id: clang-format
types_or: [c++, c, cuda]
# Black, the code formatter, natively supports pre-commit
- 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
rev: v0.0.276
hooks:
- id: ruff
args: ["--fix", "--show-fixes"]
# Check static types with mypy
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.4.1"
hooks:
- id: mypy
args: []
exclude: ^(tests|docs)/
additional_dependencies:
- markdown-it-py<3 # Drop this together with dropping Python 3.7 support.
- nox
- rich
- types-setuptools
# CMake formatting
- repo: https://github.com/cheshirekow/cmake-format-precommit
rev: "v0.6.13"
hooks:
- id: cmake-format
additional_dependencies: [pyyaml]
types: [file]
files: (\.cmake|CMakeLists.txt)(.in)?$
# 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.4.0"
@ -39,106 +82,35 @@ repos:
- id: requirements-txt-fixer - id: requirements-txt-fixer
- id: trailing-whitespace - id: trailing-whitespace
# Upgrade old Python syntax
- repo: https://github.com/asottile/pyupgrade
rev: "v3.3.1"
hooks:
- id: pyupgrade
args: [--py36-plus]
# Nicely sort includes
- repo: https://github.com/PyCQA/isort
rev: "5.12.0"
hooks:
- id: isort
# Black, the code formatter, natively supports pre-commit
- repo: https://github.com/psf/black
rev: "23.1.0" # Keep in sync with blacken-docs
hooks:
- id: black
# 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.13.0" rev: "1.14.0"
hooks: hooks:
- id: blacken-docs - id: blacken-docs
additional_dependencies: additional_dependencies:
- black==23.1.0 # keep in sync with black hook - black==23.3.0 # keep in sync with black hook
# 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.4.2" rev: "v1.5.1"
hooks: hooks:
- id: remove-tabs - id: remove-tabs
# Avoid directional quotes
- repo: https://github.com/sirosen/texthooks - repo: https://github.com/sirosen/texthooks
rev: "0.5.0" rev: "0.5.0"
hooks: hooks:
- id: fix-ligatures - id: fix-ligatures
- id: fix-smartquotes - id: fix-smartquotes
# Autoremoves unused imports
- repo: https://github.com/hadialqattan/pycln
rev: "v2.1.3"
hooks:
- id: pycln
stages: [manual]
# Checking for common mistakes # Checking for common mistakes
- repo: https://github.com/pre-commit/pygrep-hooks - repo: https://github.com/pre-commit/pygrep-hooks
rev: "v1.10.0" rev: "v1.10.0"
hooks: hooks:
- id: python-check-blanket-noqa
- id: python-check-blanket-type-ignore
- id: python-no-log-warn
- id: python-use-type-annotations
- id: rst-backticks - id: rst-backticks
- id: rst-directive-colons - id: rst-directive-colons
- id: rst-inline-touching-normal - id: rst-inline-touching-normal
# Automatically remove noqa that are not used
- repo: https://github.com/asottile/yesqa
rev: "v1.4.0"
hooks:
- id: yesqa
additional_dependencies: &flake8_dependencies
- flake8-bugbear
- pep8-naming
# Flake8 also supports pre-commit natively (same author)
- repo: https://github.com/PyCQA/flake8
rev: "6.0.0"
hooks:
- id: flake8
exclude: ^(docs/.*|tools/.*)$
additional_dependencies: *flake8_dependencies
# PyLint has native support - not always usable, but works for us
- repo: https://github.com/PyCQA/pylint
rev: "v2.16.1"
hooks:
- id: pylint
files: ^pybind11
# CMake formatting
- repo: https://github.com/cheshirekow/cmake-format-precommit
rev: "v0.6.13"
hooks:
- id: cmake-format
additional_dependencies: [pyyaml]
types: [file]
files: (\.cmake|CMakeLists.txt)(.in)?$
# Check static types with mypy
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v0.991"
hooks:
- id: mypy
args: []
exclude: ^(tests|docs)/
additional_dependencies: [nox, rich]
# Checks the manifest for missing files (native support) # Checks the manifest for missing files (native support)
- repo: https://github.com/mgedmin/check-manifest - repo: https://github.com/mgedmin/check-manifest
rev: "0.49" rev: "0.49"
@ -152,15 +124,15 @@ 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.2" rev: "v2.2.5"
hooks: hooks:
- id: codespell - id: codespell
exclude: ".supp$" exclude: ".supp$"
args: ["-x", ".codespell-ignore-lines"] args: ["-x.codespell-ignore-lines", "-Lccompiler"]
# 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.2" rev: "v0.9.0.5"
hooks: hooks:
- id: shellcheck - id: shellcheck
@ -173,9 +145,9 @@ repos:
entry: PyBind|Numpy|Cmake|CCache|PyTest entry: PyBind|Numpy|Cmake|CCache|PyTest
exclude: ^\.pre-commit-config.yaml$ exclude: ^\.pre-commit-config.yaml$
# Clang format the codebase automatically # PyLint has native support - not always usable, but works for us
- repo: https://github.com/pre-commit/mirrors-clang-format - repo: https://github.com/PyCQA/pylint
rev: "v15.0.7" rev: "v3.0.0a6"
hooks: hooks:
- id: clang-format - id: pylint
types_or: [c++, c, cuda] files: ^pybind11

View File

@ -5,15 +5,15 @@
# 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.
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.4...3.22)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` 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.22) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.22) cmake_policy(VERSION 3.26)
endif() endif()
# Avoid infinite recursion if tests include this as a subdirectory # Avoid infinite recursion if tests include this as a subdirectory
@ -126,6 +126,7 @@ set(PYBIND11_HEADERS
include/pybind11/complex.h include/pybind11/complex.h
include/pybind11/options.h include/pybind11/options.h
include/pybind11/eigen.h include/pybind11/eigen.h
include/pybind11/eigen/common.h
include/pybind11/eigen/matrix.h include/pybind11/eigen/matrix.h
include/pybind11/eigen/tensor.h include/pybind11/eigen/tensor.h
include/pybind11/embed.h include/pybind11/embed.h
@ -139,7 +140,8 @@ set(PYBIND11_HEADERS
include/pybind11/pytypes.h include/pybind11/pytypes.h
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)
# 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)

View File

@ -3,4 +3,4 @@ recursive-include pybind11/include/pybind11 *.h
recursive-include pybind11 *.py recursive-include pybind11 *.py
recursive-include pybind11 py.typed recursive-include pybind11 py.typed
include pybind11/share/cmake/pybind11/*.cmake include pybind11/share/cmake/pybind11/*.cmake
include LICENSE README.rst pyproject.toml setup.py setup.cfg include LICENSE README.rst SECURITY.md pyproject.toml setup.py setup.cfg

13
SECURITY.md Normal file
View File

@ -0,0 +1,13 @@
# Security Policy
## Supported Versions
Security updates are applied only to the latest release.
## Reporting a Vulnerability
If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released.
Please disclose it at [security advisory](https://github.com/pybind/pybind11/security/advisories/new).
This project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure.

View File

@ -101,8 +101,11 @@ conversion has the same overhead as implicit conversion.
m.def("str_output", m.def("str_output",
[]() { []() {
std::string s = "Send your r\xe9sum\xe9 to Alice in HR"; // Latin-1 std::string s = "Send your r\xe9sum\xe9 to Alice in HR"; // Latin-1
py::str py_s = PyUnicode_DecodeLatin1(s.data(), s.length()); py::handle py_s = PyUnicode_DecodeLatin1(s.data(), s.length(), nullptr);
return py_s; if (!py_s) {
throw py::error_already_set();
}
return py::reinterpret_steal<py::str>(py_s);
} }
); );
@ -113,7 +116,8 @@ conversion has the same overhead as implicit conversion.
The `Python C API The `Python C API
<https://docs.python.org/3/c-api/unicode.html#built-in-codecs>`_ provides <https://docs.python.org/3/c-api/unicode.html#built-in-codecs>`_ provides
several built-in codecs. several built-in codecs. Note that these all return *new* references, so
use :cpp:func:`reinterpret_steal` when converting them to a :cpp:class:`str`.
One could also use a third party encoding library such as libiconv to transcode One could also use a third party encoding library such as libiconv to transcode

View File

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

View File

@ -10,10 +10,38 @@ Changes will be added here periodically from the "Suggested changelog entry"
block in pull request descriptions. block in pull request descriptions.
IN DEVELOPMENT Version 2.11.0 (July 14, 2023)
-------------- -----------------------------
Changes will be summarized here periodically. New features:
* The newly added ``pybind11::detail::is_move_constructible`` trait can be
specialized for cases in which ``std::is_move_constructible`` does not work
as needed. This is very similar to the long-established
``pybind11::detail::is_copy_constructible``.
`#4631 <https://github.com/pybind/pybind11/pull/4631>`_
* Introduce ``recursive_container_traits``.
`#4623 <https://github.com/pybind/pybind11/pull/4623>`_
* ``pybind11/type_caster_pyobject_ptr.h`` was added to support automatic
wrapping of APIs that make use of ``PyObject *``. This header needs to
included explicitly (i.e. it is not included implicitly
with ``pybind/pybind11.h``).
`#4601 <https://github.com/pybind/pybind11/pull/4601>`_
* ``format_descriptor<>`` & ``npy_format_descriptor<>`` ``PyObject *``
specializations were added. The latter enables ``py::array_t<PyObject *>``
to/from-python conversions.
`#4674 <https://github.com/pybind/pybind11/pull/4674>`_
* ``buffer_info`` gained an ``item_type_is_equivalent_to<T>()`` member
function.
`#4674 <https://github.com/pybind/pybind11/pull/4674>`_
* The ``capsule`` API gained a user-friendly constructor
(``py::capsule(ptr, "name", dtor)``).
`#4720 <https://github.com/pybind/pybind11/pull/4720>`_
Changes: Changes:
@ -31,17 +59,68 @@ Changes:
sizes slightly (~1.5%) but the error messages are much more informative. sizes slightly (~1.5%) but the error messages are much more informative.
`#4463 <https://github.com/pybind/pybind11/pull/4463>`_ `#4463 <https://github.com/pybind/pybind11/pull/4463>`_
* The docstring generation for the ``std::array``-list caster was fixed.
Previously, signatures included the size of the list in a non-standard,
non-spec compliant way. The new format conforms to PEP 593.
**Tooling for processing the docstrings may need to be updated accordingly.**
`#4679 <https://github.com/pybind/pybind11/pull/4679>`_
* Setter return values (which are inaccessible for all practical purposes) are
no longer converted to Python (only to be discarded).
`#4621 <https://github.com/pybind/pybind11/pull/4621>`_
* Allow lambda specified to function definition to be ``noexcept(true)``
in C++17.
`#4593 <https://github.com/pybind/pybind11/pull/4593>`_
* Get rid of recursive template instantiations for concatenating type
signatures on C++17 and higher.
`#4587 <https://github.com/pybind/pybind11/pull/4587>`_
* Compatibility with Python 3.12 (beta). Note that the minimum pybind11
ABI version for Python 3.12 is version 5. (The default ABI version
for Python versions up to and including 3.11 is still version 4.).
`#4570 <https://github.com/pybind/pybind11/pull/4570>`_
* With ``PYBIND11_INTERNALS_VERSION 5`` (default for Python 3.12+), MSVC builds
use ``std::hash<std::type_index>`` and ``std::equal_to<std::type_index>``
instead of string-based type comparisons. This resolves issues when binding
types defined in the unnamed namespace.
`#4319 <https://github.com/pybind/pybind11/pull/4319>`_
* Python exception ``__notes__`` (introduced with Python 3.11) are now added to
the ``error_already_set::what()`` output.
`#4678 <https://github.com/pybind/pybind11/pull/4678>`_
Build system improvements: Build system improvements:
* CMake 3.27 support was added, CMake 3.4 support was dropped.
FindPython will be used if ``FindPythonInterp`` is not present.
`#4719 <https://github.com/pybind/pybind11/pull/4719>`_
* Update clang-tidy to 15 in CI. * Update clang-tidy to 15 in CI.
`#4387 <https://github.com/pybind/pybind11/pull/4387>`_ `#4387 <https://github.com/pybind/pybind11/pull/4387>`_
* Moved the linting framework over to Ruff. * Moved the linting framework over to Ruff.
`#4483 <https://github.com/pybind/pybind11/pull/4483>`_ `#4483 <https://github.com/pybind/pybind11/pull/4483>`_
* Skip ``lto`` checks and target generation when
``CMAKE_INTERPROCEDURAL_OPTIMIZATION`` is defined.
`#4643 <https://github.com/pybind/pybind11/pull/4643>`_
* No longer inject ``-stdlib=libc++``, not needed for modern Pythons
(macOS 10.9+).
`#4639 <https://github.com/pybind/pybind11/pull/4639>`_
* PyPy 3.10 support was added, PyPy 3.7 support was dropped.
`#4728 <https://github.com/pybind/pybind11/pull/4728>`_
* Testing with Python 3.12 beta releases was added.
`#4713 <https://github.com/pybind/pybind11/pull/4713>`_
Version 2.10.4 (Mar 16, 2023) Version 2.10.4 (Mar 16, 2023)
---------------------------- -----------------------------
Changes: Changes:

View File

@ -58,6 +58,16 @@ interactive Python session demonstrating this example is shown below:
Static member functions can be bound in the same way using Static member functions can be bound in the same way using
:func:`class_::def_static`. :func:`class_::def_static`.
.. note::
Binding C++ types in unnamed namespaces (also known as anonymous namespaces)
works reliably on many platforms, but not all. The `XFAIL_CONDITION` in
tests/test_unnamed_namespace_a.py encodes the currently known conditions.
For background see `#4319 <https://github.com/pybind/pybind11/pull/4319>`_.
If portability is a concern, it is therefore not recommended to bind C++
types in unnamed namespaces. It will be safest to manually pick unique
namespace names.
Keyword and default arguments Keyword and default arguments
============================= =============================
It is possible to specify keyword and default arguments using the syntax It is possible to specify keyword and default arguments using the syntax
@ -539,3 +549,7 @@ The ``name`` property returns the name of the enum value as a unicode string.
... ...
By default, these are omitted to conserve space. By default, these are omitted to conserve space.
.. warning::
Contrary to Python customs, enum values from the wrappers should not be compared using ``is``, but with ``==`` (see `#1177 <https://github.com/pybind/pybind11/issues/1177>`_ for background).

View File

@ -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.4...3.18) cmake_minimum_required(VERSION 3.5...3.26)
project(example LANGUAGES CXX) project(example LANGUAGES CXX)
add_subdirectory(pybind11) add_subdirectory(pybind11)
@ -261,6 +261,9 @@ PyPI integration, can be found in the [cmake_example]_ repository.
.. versionchanged:: 2.6 .. versionchanged:: 2.6
CMake 3.4+ is required. CMake 3.4+ is required.
.. versionchanged:: 2.11
CMake 3.5+ is required.
Further information can be found at :doc:`cmake/index`. Further information can be found at :doc:`cmake/index`.
pybind11_add_module pybind11_add_module
@ -495,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.4) cmake_minimum_required(VERSION 3.5...3.26)
project(example LANGUAGES CXX) project(example LANGUAGES CXX)
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)
@ -553,7 +556,7 @@ information about usage in C++, see :doc:`/advanced/embedding`.
.. code-block:: cmake .. code-block:: cmake
cmake_minimum_required(VERSION 3.4...3.18) cmake_minimum_required(VERSION 3.5...3.26)
project(example LANGUAGES CXX) project(example LANGUAGES CXX)
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)

View File

@ -353,7 +353,7 @@ def prepare(app):
f.write(contents) f.write(contents)
def clean_up(app, exception): def clean_up(app, exception): # noqa: ARG001
(DIR / "readme.rst").unlink() (DIR / "readme.rst").unlink()

View File

@ -284,7 +284,8 @@ There are three possible solutions:
COMPONENTS Interpreter Development)`` on modern CMake (3.12+, 3.15+ better, COMPONENTS Interpreter Development)`` on modern CMake (3.12+, 3.15+ better,
3.18.2+ best). Pybind11 in these cases uses the new CMake FindPython instead 3.18.2+ best). Pybind11 in these cases uses the new CMake FindPython instead
of the old, deprecated search tools, and these modules are much better at of the old, deprecated search tools, and these modules are much better at
finding the correct Python. finding the correct Python. If FindPythonLibs/Interp are not available
(CMake 3.27+), then this will be ignored and FindPython will be used.
3. Set ``PYBIND11_NOPYTHON`` to ``TRUE``. Pybind11 will not search for Python. 3. Set ``PYBIND11_NOPYTHON`` to ``TRUE``. Pybind11 will not search for Python.
However, you will have to use the target-based system, and do more setup However, you will have to use the target-based system, and do more setup
yourself, because it does not know about or include things that depend on yourself, because it does not know about or include things that depend on

View File

@ -33,10 +33,12 @@ If you don't have nox, you should either use ``pipx run nox`` instead, or use
- Run ``nox -s tests_packaging`` to ensure this was done correctly. - Run ``nox -s tests_packaging`` to ensure this was done correctly.
- Ensure that all the information in ``setup.cfg`` is up-to-date, like - Ensure that all the information in ``setup.cfg`` is up-to-date, like
supported Python versions. supported Python versions.
- Add release date in ``docs/changelog.rst``. - Add release date in ``docs/changelog.rst`` and integrate the output of
- Check to make sure ``nox -s make_changelog``.
`needs-changelog <https://github.com/pybind/pybind11/pulls?q=is%3Apr+is%3Aclosed+label%3A%22needs+changelog%22>`_ - Note that the ``make_changelog`` command inspects
issues are entered in the changelog (clear the label when done). `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 - ``git add`` and ``git commit``, ``git push``. **Ensure CI passes**. (If it
fails due to a known flake issue, either ignore or restart CI.) 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 - Add a release branch if this is a new minor version, or update the existing release branch if it is a patch version

View File

@ -8,6 +8,20 @@ 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.11:
v2.11
=====
* The minimum version of CMake is now 3.5. A future version will likely move to
requiring something like CMake 3.15. Note that CMake 3.27 is removing the
long-deprecated support for ``FindPythonInterp`` if you set 3.27 as the
minimum or maximum supported version. To prepare for that future, CMake 3.15+
using ``FindPython`` or setting ``PYBIND11_FINDPYTHON`` is highly recommended,
otherwise pybind11 will automatically switch to using ``FindPython`` if
``FindPythonInterp`` is not available.
.. _upgrade-guide-2.9: .. _upgrade-guide-2.9:
v2.9 v2.9

View File

@ -26,6 +26,9 @@ struct is_method {
explicit is_method(const handle &c) : class_(c) {} explicit is_method(const handle &c) : class_(c) {}
}; };
/// Annotation for setters
struct is_setter {};
/// Annotation for operators /// Annotation for operators
struct is_operator {}; struct is_operator {};
@ -188,8 +191,8 @@ struct argument_record {
struct function_record { struct function_record {
function_record() function_record()
: is_constructor(false), is_new_style_constructor(false), is_stateless(false), : is_constructor(false), is_new_style_constructor(false), is_stateless(false),
is_operator(false), is_method(false), has_args(false), has_kwargs(false), is_operator(false), is_method(false), is_setter(false), has_args(false),
prepend(false) {} has_kwargs(false), prepend(false) {}
/// Function name /// Function name
char *name = nullptr; /* why no C++ strings? They generate heavier code.. */ char *name = nullptr; /* why no C++ strings? They generate heavier code.. */
@ -230,6 +233,9 @@ struct function_record {
/// True if this is a method /// True if this is a method
bool is_method : 1; bool is_method : 1;
/// True if this is a setter
bool is_setter : 1;
/// True if the function has a '*args' argument /// True if the function has a '*args' argument
bool has_args : 1; bool has_args : 1;
@ -426,6 +432,12 @@ struct process_attribute<is_method> : process_attribute_default<is_method> {
} }
}; };
/// Process an attribute which indicates that this function is a setter
template <>
struct process_attribute<is_setter> : process_attribute_default<is_setter> {
static void init(const is_setter &, function_record *r) { r->is_setter = true; }
};
/// Process an attribute which indicates the parent scope of a method /// Process an attribute which indicates the parent scope of a method
template <> template <>
struct process_attribute<scope> : process_attribute_default<scope> { struct process_attribute<scope> : process_attribute_default<scope> {

View File

@ -37,6 +37,9 @@ inline std::vector<ssize_t> f_strides(const std::vector<ssize_t> &shape, ssize_t
return strides; return strides;
} }
template <typename T, typename SFINAE = void>
struct compare_buffer_info;
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
/// Information record describing a Python buffer object /// Information record describing a Python buffer object
@ -150,6 +153,17 @@ struct buffer_info {
Py_buffer *view() const { return m_view; } Py_buffer *view() const { return m_view; }
Py_buffer *&view() { return m_view; } Py_buffer *&view() { return m_view; }
/* True if the buffer item type is equivalent to `T`. */
// To define "equivalent" by example:
// `buffer_info::item_type_is_equivalent_to<int>(b)` and
// `buffer_info::item_type_is_equivalent_to<long>(b)` may both be true
// on some platforms, but `int` and `unsigned` will never be equivalent.
// For the ground truth, please inspect `detail::compare_buffer_info<>`.
template <typename T>
bool item_type_is_equivalent_to() const {
return detail::compare_buffer_info<T>::compare(*this);
}
private: private:
struct private_ctr_tag {}; struct private_ctr_tag {};
@ -170,9 +184,10 @@ private:
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
template <typename T, typename SFINAE = void> template <typename T, typename SFINAE>
struct compare_buffer_info { struct compare_buffer_info {
static bool compare(const buffer_info &b) { static bool compare(const buffer_info &b) {
// NOLINTNEXTLINE(bugprone-sizeof-expression) Needed for `PyObject *`
return b.format == format_descriptor<T>::format() && b.itemsize == (ssize_t) sizeof(T); return b.format == format_descriptor<T>::format() && b.itemsize == (ssize_t) sizeof(T);
} }
}; };

View File

@ -964,7 +964,7 @@ struct move_always<
enable_if_t< enable_if_t<
all_of<move_is_plain_type<T>, all_of<move_is_plain_type<T>,
negation<is_copy_constructible<T>>, negation<is_copy_constructible<T>>,
std::is_move_constructible<T>, is_move_constructible<T>,
std::is_same<decltype(std::declval<make_caster<T>>().operator T &()), T &>>::value>> std::is_same<decltype(std::declval<make_caster<T>>().operator T &()), T &>>::value>>
: std::true_type {}; : std::true_type {};
template <typename T, typename SFINAE = void> template <typename T, typename SFINAE = void>
@ -975,7 +975,7 @@ struct move_if_unreferenced<
enable_if_t< enable_if_t<
all_of<move_is_plain_type<T>, all_of<move_is_plain_type<T>,
negation<move_always<T>>, negation<move_always<T>>,
std::is_move_constructible<T>, is_move_constructible<T>,
std::is_same<decltype(std::declval<make_caster<T>>().operator T &()), T &>>::value>> std::is_same<decltype(std::declval<make_caster<T>>().operator T &()), T &>>::value>>
: std::true_type {}; : std::true_type {};
template <typename T> template <typename T>
@ -1017,11 +1017,14 @@ type_caster<T, SFINAE> &load_type(type_caster<T, SFINAE> &conv, const handle &ha
"Internal error: type_caster should only be used for C++ types"); "Internal error: type_caster should only be used for C++ types");
if (!conv.load(handle, true)) { if (!conv.load(handle, true)) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error("Unable to cast Python instance to C++ type (#define " throw cast_error(
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); "Unable to cast Python instance of type "
+ str(type::handle_of(handle)).cast<std::string>()
+ " to C++ type '?' (#define "
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
#else #else
throw cast_error("Unable to cast Python instance of type " throw cast_error("Unable to cast Python instance of type "
+ (std::string) str(type::handle_of(handle)) + " to C++ type '" + str(type::handle_of(handle)).cast<std::string>() + " to C++ type '"
+ type_id<T>() + "'"); + type_id<T>() + "'");
#endif #endif
} }
@ -1038,7 +1041,11 @@ make_caster<T> load_type(const handle &handle) {
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
// pytype -> C++ type // pytype -> C++ type
template <typename T, detail::enable_if_t<!detail::is_pyobject<T>::value, int> = 0> template <typename T,
detail::enable_if_t<!detail::is_pyobject<T>::value
&& !detail::is_same_ignoring_cvref<T, PyObject *>::value,
int>
= 0>
T cast(const handle &handle) { T cast(const handle &handle) {
using namespace detail; using namespace detail;
static_assert(!cast_is_temporary_value_reference<T>::value, static_assert(!cast_is_temporary_value_reference<T>::value,
@ -1052,6 +1059,34 @@ T cast(const handle &handle) {
return T(reinterpret_borrow<object>(handle)); return T(reinterpret_borrow<object>(handle));
} }
// Note that `cast<PyObject *>(obj)` increments the reference count of `obj`.
// This is necessary for the case that `obj` is a temporary, and could
// not possibly be different, given
// 1. the established convention that the passed `handle` is borrowed, and
// 2. we don't want to force all generic code using `cast<T>()` to special-case
// handling of `T` = `PyObject *` (to increment the reference count there).
// It is the responsibility of the caller to ensure that the reference count
// is decremented.
template <typename T,
typename Handle,
detail::enable_if_t<detail::is_same_ignoring_cvref<T, PyObject *>::value
&& detail::is_same_ignoring_cvref<Handle, handle>::value,
int>
= 0>
T cast(Handle &&handle) {
return handle.inc_ref().ptr();
}
// To optimize way an inc_ref/dec_ref cycle:
template <typename T,
typename Object,
detail::enable_if_t<detail::is_same_ignoring_cvref<T, PyObject *>::value
&& detail::is_same_ignoring_cvref<Object, object>::value,
int>
= 0>
T cast(Object &&obj) {
return obj.release().ptr();
}
// C++ type -> py::object // C++ type -> py::object
template <typename T, detail::enable_if_t<!detail::is_pyobject<T>::value, int> = 0> template <typename T, detail::enable_if_t<!detail::is_pyobject<T>::value, int> = 0>
object cast(T &&value, object cast(T &&value,
@ -1085,12 +1120,13 @@ detail::enable_if_t<!detail::move_never<T>::value, T> move(object &&obj) {
if (obj.ref_count() > 1) { if (obj.ref_count() > 1) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error( throw cast_error(
"Unable to cast Python instance to C++ rvalue: instance has multiple references" "Unable to cast Python " + str(type::handle_of(obj)).cast<std::string>()
" (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); + " instance to C++ rvalue: instance has multiple references"
" (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
#else #else
throw cast_error("Unable to move from Python " + (std::string) str(type::handle_of(obj)) throw cast_error("Unable to move from Python "
+ " instance to C++ " + type_id<T>() + str(type::handle_of(obj)).cast<std::string>() + " instance to C++ "
+ " instance: instance has multiple references"); + type_id<T>() + " instance: instance has multiple references");
#endif #endif
} }
@ -1195,9 +1231,10 @@ PYBIND11_NAMESPACE_END(detail)
// The overloads could coexist, i.e. the #if is not strictly speaking needed, // The overloads could coexist, i.e. the #if is not strictly speaking needed,
// but it is an easy minor optimization. // but it is an easy minor optimization.
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
inline cast_error cast_error_unable_to_convert_call_arg() { inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name) {
return cast_error("Unable to convert call argument to Python object (#define " return cast_error("Unable to convert call argument '" + name
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"); + "' to Python object (#define "
"PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)");
} }
#else #else
inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name, inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name,
@ -1220,7 +1257,7 @@ tuple make_tuple(Args &&...args_) {
for (size_t i = 0; i < args.size(); i++) { for (size_t i = 0; i < args.size(); i++) {
if (!args[i]) { if (!args[i]) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error_unable_to_convert_call_arg(); throw cast_error_unable_to_convert_call_arg(std::to_string(i));
#else #else
std::array<std::string, size> argtypes{{type_id<Args>()...}}; std::array<std::string, size> argtypes{{type_id<Args>()...}};
throw cast_error_unable_to_convert_call_arg(std::to_string(i), argtypes[i]); throw cast_error_unable_to_convert_call_arg(std::to_string(i), argtypes[i]);
@ -1510,7 +1547,7 @@ private:
detail::make_caster<T>::cast(std::forward<T>(x), policy, {})); detail::make_caster<T>::cast(std::forward<T>(x), policy, {}));
if (!o) { if (!o) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error_unable_to_convert_call_arg(); throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()));
#else #else
throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()), throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()),
type_id<T>()); type_id<T>());
@ -1542,7 +1579,7 @@ private:
} }
if (!a.value) { if (!a.value) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error_unable_to_convert_call_arg(); throw cast_error_unable_to_convert_call_arg(a.name);
#else #else
throw cast_error_unable_to_convert_call_arg(a.name, a.type); throw cast_error_unable_to_convert_call_arg(a.name, a.type);
#endif #endif

View File

@ -10,12 +10,12 @@
#pragma once #pragma once
#define PYBIND11_VERSION_MAJOR 2 #define PYBIND11_VERSION_MAJOR 2
#define PYBIND11_VERSION_MINOR 10 #define PYBIND11_VERSION_MINOR 11
#define PYBIND11_VERSION_PATCH 4 #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 0x020A0400 #define PYBIND11_VERSION_HEX 0x020B0000
// Define some generic pybind11 helper macros for warning management. // Define some generic pybind11 helper macros for warning management.
// //
@ -329,8 +329,7 @@ PYBIND11_WARNING_POP
&& defined(_MSC_VER)) /* PyPy Windows: pytest hangs indefinitely at the end of the \ && defined(_MSC_VER)) /* PyPy Windows: pytest hangs indefinitely at the end of the \
process (see PR #4268) */ \ process (see PR #4268) */ \
&& !defined(PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF) && !defined(PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF)
// The following define will be enabled by default in the 2.11 release # define PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF
// define PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF
#endif #endif
// #define PYBIND11_STR_LEGACY_PERMISSIVE // #define PYBIND11_STR_LEGACY_PERMISSIVE
@ -662,6 +661,10 @@ template <class T>
using remove_cvref_t = typename remove_cvref<T>::type; using remove_cvref_t = typename remove_cvref<T>::type;
#endif #endif
/// Example usage: is_same_ignoring_cvref<T, PyObject *>::value
template <typename T, typename U>
using is_same_ignoring_cvref = std::is_same<detail::remove_cvref_t<T>, U>;
/// Index sequences /// Index sequences
#if defined(PYBIND11_CPP14) #if defined(PYBIND11_CPP14)
using std::index_sequence; using std::index_sequence;
@ -755,7 +758,16 @@ template <typename C, typename R, typename... A>
struct remove_class<R (C::*)(A...) const> { struct remove_class<R (C::*)(A...) const> {
using type = R(A...); using type = R(A...);
}; };
#ifdef __cpp_noexcept_function_type
template <typename C, typename R, typename... A>
struct remove_class<R (C::*)(A...) noexcept> {
using type = R(A...);
};
template <typename C, typename R, typename... A>
struct remove_class<R (C::*)(A...) const noexcept> {
using type = R(A...);
};
#endif
/// Helper template to strip away type modifiers /// Helper template to strip away type modifiers
template <typename T> template <typename T>
struct intrinsic_type { struct intrinsic_type {
@ -1013,6 +1025,15 @@ PYBIND11_RUNTIME_EXCEPTION(reference_cast_error, PyExc_RuntimeError) /// Used in
template <typename T, typename SFINAE = void> template <typename T, typename SFINAE = void>
struct format_descriptor {}; struct format_descriptor {};
template <typename T>
struct format_descriptor<
T,
detail::enable_if_t<detail::is_same_ignoring_cvref<T, PyObject *>::value>> {
static constexpr const char c = 'O';
static constexpr const char value[2] = {c, '\0'};
static std::string format() { return std::string(1, c); }
};
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
// Returns the index of the given type in the type char array below, and in the list in numpy.h // Returns the index of the given type in the type char array below, and in the list in numpy.h
// The order here is: bool; 8 ints ((signed,unsigned)x(8,16,32,64)bits); float,double,long double; // The order here is: bool; 8 ints ((signed,unsigned)x(8,16,32,64)bits); float,double,long double;
@ -1226,8 +1247,9 @@ constexpr
#endif #endif
// Pybind offers detailed error messages by default for all builts that are debug (through the // Pybind offers detailed error messages by default for all builts that are debug (through the
// negation of ndebug). This can also be manually enabled by users, for any builds, through // negation of NDEBUG). This can also be manually enabled by users, for any builds, through
// defining PYBIND11_DETAILED_ERROR_MESSAGES. // defining PYBIND11_DETAILED_ERROR_MESSAGES. This information is primarily useful for those
// who are writing (as opposed to merely using) libraries that use pybind11.
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) && !defined(NDEBUG) #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) && !defined(NDEBUG)
# define PYBIND11_DETAILED_ERROR_MESSAGES # define PYBIND11_DETAILED_ERROR_MESSAGES
#endif #endif

View File

@ -143,11 +143,24 @@ constexpr descr<N, Ts...> concat(const descr<N, Ts...> &descr) {
return descr; return descr;
} }
#ifdef __cpp_fold_expressions
template <size_t N1, size_t N2, typename... Ts1, typename... Ts2>
constexpr descr<N1 + N2 + 2, Ts1..., Ts2...> operator,(const descr<N1, Ts1...> &a,
const descr<N2, Ts2...> &b) {
return a + const_name(", ") + b;
}
template <size_t N, typename... Ts, typename... Args>
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args) {
return (d, ..., args);
}
#else
template <size_t N, typename... Ts, typename... Args> template <size_t N, typename... Ts, typename... Args>
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args) constexpr auto concat(const descr<N, Ts...> &d, const Args &...args)
-> decltype(std::declval<descr<N + 2, Ts...>>() + concat(args...)) { -> decltype(std::declval<descr<N + 2, Ts...>>() + concat(args...)) {
return d + const_name(", ") + concat(args...); return d + const_name(", ") + concat(args...);
} }
#endif
template <size_t N, typename... Ts> template <size_t N, typename... Ts>
constexpr descr<N + 2, Ts...> type_descr(const descr<N, Ts...> &descr) { constexpr descr<N + 2, Ts...> type_descr(const descr<N, Ts...> &descr) {

View File

@ -175,7 +175,7 @@ void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
template <typename Class> template <typename Class>
void construct(value_and_holder &v_h, Cpp<Class> &&result, bool need_alias) { void construct(value_and_holder &v_h, Cpp<Class> &&result, bool need_alias) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
static_assert(std::is_move_constructible<Cpp<Class>>::value, static_assert(is_move_constructible<Cpp<Class>>::value,
"pybind11::init() return-by-value factory function requires a movable class"); "pybind11::init() return-by-value factory function requires a movable class");
if (Class::has_alias && need_alias) { if (Class::has_alias && need_alias) {
construct_alias_from_cpp<Class>(is_alias_constructible<Class>{}, v_h, std::move(result)); construct_alias_from_cpp<Class>(is_alias_constructible<Class>{}, v_h, std::move(result));
@ -190,7 +190,7 @@ void construct(value_and_holder &v_h, Cpp<Class> &&result, bool need_alias) {
template <typename Class> template <typename Class>
void construct(value_and_holder &v_h, Alias<Class> &&result, bool) { void construct(value_and_holder &v_h, Alias<Class> &&result, bool) {
static_assert( static_assert(
std::is_move_constructible<Alias<Class>>::value, is_move_constructible<Alias<Class>>::value,
"pybind11::init() return-by-alias-value factory function requires a movable alias class"); "pybind11::init() return-by-alias-value factory function requires a movable alias class");
v_h.value_ptr() = new Alias<Class>(std::move(result)); v_h.value_ptr() = new Alias<Class>(std::move(result));
} }

View File

@ -34,9 +34,18 @@
/// 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
# define PYBIND11_INTERNALS_VERSION 4 # if PY_VERSION_HEX >= 0x030C0000
// Version bump for Python 3.12+, before first 3.12 beta release.
# define PYBIND11_INTERNALS_VERSION 5
# else
# define PYBIND11_INTERNALS_VERSION 4
# endif
#endif #endif
// This requirement is mainly to reduce the support burden (see PR #4570).
static_assert(PY_VERSION_HEX < 0x030C0000 || PYBIND11_INTERNALS_VERSION >= 5,
"pybind11 ABI version 5 is the minimum for Python 3.12+");
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
using ExceptionTranslator = void (*)(std::exception_ptr); using ExceptionTranslator = void (*)(std::exception_ptr);
@ -114,7 +123,8 @@ inline void tls_replace_value(PYBIND11_TLS_KEY_REF key, void *value) {
// libstdc++, this doesn't happen: equality and the type_index hash are based on the type name, // libstdc++, this doesn't happen: equality and the type_index hash are based on the type name,
// which works. If not under a known-good stl, provide our own name-based hash and equality // which works. If not under a known-good stl, provide our own name-based hash and equality
// functions that use the type name. // functions that use the type name.
#if defined(__GLIBCXX__) #if (PYBIND11_INTERNALS_VERSION <= 4 && defined(__GLIBCXX__)) \
|| (PYBIND11_INTERNALS_VERSION >= 5 && !defined(_LIBCPP_VERSION))
inline bool same_type(const std::type_info &lhs, const std::type_info &rhs) { return lhs == rhs; } inline bool same_type(const std::type_info &lhs, const std::type_info &rhs) { return lhs == rhs; }
using type_hash = std::hash<std::type_index>; using type_hash = std::hash<std::type_index>;
using type_equal_to = std::equal_to<std::type_index>; using type_equal_to = std::equal_to<std::type_index>;
@ -421,6 +431,38 @@ inline void translate_local_exception(std::exception_ptr p) {
} }
#endif #endif
inline object get_python_state_dict() {
object state_dict;
#if PYBIND11_INTERNALS_VERSION <= 4 || PY_VERSION_HEX < 0x03080000 || defined(PYPY_VERSION)
state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins());
#else
# if PY_VERSION_HEX < 0x03090000
PyInterpreterState *istate = _PyInterpreterState_Get();
# else
PyInterpreterState *istate = PyInterpreterState_Get();
# endif
if (istate) {
state_dict = reinterpret_borrow<object>(PyInterpreterState_GetDict(istate));
}
#endif
if (!state_dict) {
raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED");
}
return state_dict;
}
inline object get_internals_obj_from_state_dict(handle state_dict) {
return reinterpret_borrow<object>(dict_getitemstring(state_dict.ptr(), PYBIND11_INTERNALS_ID));
}
inline internals **get_internals_pp_from_capsule(handle obj) {
void *raw_ptr = PyCapsule_GetPointer(obj.ptr(), /*name=*/nullptr);
if (raw_ptr == nullptr) {
raise_from(PyExc_SystemError, "pybind11::detail::get_internals_pp_from_capsule() FAILED");
}
return static_cast<internals **>(raw_ptr);
}
/// Return a reference to the current `internals` data /// Return a reference to the current `internals` data
PYBIND11_NOINLINE internals &get_internals() { PYBIND11_NOINLINE internals &get_internals() {
auto **&internals_pp = get_internals_pp(); auto **&internals_pp = get_internals_pp();
@ -445,12 +487,12 @@ PYBIND11_NOINLINE internals &get_internals() {
#endif #endif
error_scope err_scope; error_scope err_scope;
PYBIND11_STR_TYPE id(PYBIND11_INTERNALS_ID); dict state_dict = get_python_state_dict();
auto builtins = handle(PyEval_GetBuiltins()); if (object internals_obj = get_internals_obj_from_state_dict(state_dict)) {
if (builtins.contains(id) && isinstance<capsule>(builtins[id])) { internals_pp = get_internals_pp_from_capsule(internals_obj);
internals_pp = static_cast<internals **>(capsule(builtins[id])); }
if (internals_pp && *internals_pp) {
// We loaded builtins through python's builtins, which means that our `error_already_set` // We loaded the internals through `state_dict`, which means that our `error_already_set`
// and `builtin_exception` may be different local classes than the ones set up in the // and `builtin_exception` may be different local classes than the ones set up in the
// initial exception translator, below, so add another for our local exception classes. // initial exception translator, below, so add another for our local exception classes.
// //
@ -469,12 +511,14 @@ PYBIND11_NOINLINE internals &get_internals() {
#if defined(WITH_THREAD) #if defined(WITH_THREAD)
PyThreadState *tstate = PyThreadState_Get(); PyThreadState *tstate = PyThreadState_Get();
// NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->tstate)) { if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->tstate)) {
pybind11_fail("get_internals: could not successfully initialize the tstate TSS key!"); pybind11_fail("get_internals: could not successfully initialize the tstate TSS key!");
} }
PYBIND11_TLS_REPLACE_VALUE(internals_ptr->tstate, tstate); PYBIND11_TLS_REPLACE_VALUE(internals_ptr->tstate, tstate);
# if PYBIND11_INTERNALS_VERSION > 4 # if PYBIND11_INTERNALS_VERSION > 4
// NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->loader_life_support_tls_key)) { if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->loader_life_support_tls_key)) {
pybind11_fail("get_internals: could not successfully initialize the " pybind11_fail("get_internals: could not successfully initialize the "
"loader_life_support TSS key!"); "loader_life_support TSS key!");
@ -482,7 +526,7 @@ PYBIND11_NOINLINE internals &get_internals() {
# endif # endif
internals_ptr->istate = tstate->interp; internals_ptr->istate = tstate->interp;
#endif #endif
builtins[id] = capsule(internals_pp); state_dict[PYBIND11_INTERNALS_ID] = capsule(internals_pp);
internals_ptr->registered_exception_translators.push_front(&translate_exception); internals_ptr->registered_exception_translators.push_front(&translate_exception);
internals_ptr->static_property_type = make_static_property_type(); internals_ptr->static_property_type = make_static_property_type();
internals_ptr->default_metaclass = make_default_metaclass(); internals_ptr->default_metaclass = make_default_metaclass();
@ -514,6 +558,7 @@ struct local_internals {
struct shared_loader_life_support_data { struct shared_loader_life_support_data {
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key) PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)
shared_loader_life_support_data() { shared_loader_life_support_data() {
// NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
if (!PYBIND11_TLS_KEY_CREATE(loader_life_support_tls_key)) { if (!PYBIND11_TLS_KEY_CREATE(loader_life_support_tls_key)) {
pybind11_fail("local_internals: could not successfully initialize the " pybind11_fail("local_internals: could not successfully initialize the "
"loader_life_support TLS key!"); "loader_life_support TLS key!");

View File

@ -258,9 +258,9 @@ struct value_and_holder {
// Main constructor for a found value/holder: // Main constructor for a found value/holder:
value_and_holder(instance *i, const detail::type_info *type, size_t vpos, size_t index) value_and_holder(instance *i, const detail::type_info *type, size_t vpos, size_t index)
: inst{i}, index{index}, type{type}, vh{inst->simple_layout : inst{i}, index{index}, type{type},
? inst->simple_value_holder vh{inst->simple_layout ? inst->simple_value_holder
: &inst->nonsimple.values_and_holders[vpos]} {} : &inst->nonsimple.values_and_holders[vpos]} {}
// Default constructor (used to signal a value-and-holder not found by get_value_and_holder()) // Default constructor (used to signal a value-and-holder not found by get_value_and_holder())
value_and_holder() = default; value_and_holder() = default;
@ -822,23 +822,179 @@ using movable_cast_op_type
typename std::add_rvalue_reference<intrinsic_t<T>>::type, typename std::add_rvalue_reference<intrinsic_t<T>>::type,
typename std::add_lvalue_reference<intrinsic_t<T>>::type>>; typename std::add_lvalue_reference<intrinsic_t<T>>::type>>;
// Does the container have a mapped type and is it recursive?
// Implemented by specializations below.
template <typename Container, typename SFINAE = void>
struct container_mapped_type_traits {
static constexpr bool has_mapped_type = false;
static constexpr bool has_recursive_mapped_type = false;
};
template <typename Container>
struct container_mapped_type_traits<
Container,
typename std::enable_if<
std::is_same<typename Container::mapped_type, Container>::value>::type> {
static constexpr bool has_mapped_type = true;
static constexpr bool has_recursive_mapped_type = true;
};
template <typename Container>
struct container_mapped_type_traits<
Container,
typename std::enable_if<
negation<std::is_same<typename Container::mapped_type, Container>>::value>::type> {
static constexpr bool has_mapped_type = true;
static constexpr bool has_recursive_mapped_type = false;
};
// Does the container have a value type and is it recursive?
// Implemented by specializations below.
template <typename Container, typename SFINAE = void>
struct container_value_type_traits : std::false_type {
static constexpr bool has_value_type = false;
static constexpr bool has_recursive_value_type = false;
};
template <typename Container>
struct container_value_type_traits<
Container,
typename std::enable_if<
std::is_same<typename Container::value_type, Container>::value>::type> {
static constexpr bool has_value_type = true;
static constexpr bool has_recursive_value_type = true;
};
template <typename Container>
struct container_value_type_traits<
Container,
typename std::enable_if<
negation<std::is_same<typename Container::value_type, Container>>::value>::type> {
static constexpr bool has_value_type = true;
static constexpr bool has_recursive_value_type = false;
};
/*
* Tag to be used for representing the bottom of recursively defined types.
* Define this tag so we don't have to use void.
*/
struct recursive_bottom {};
/*
* Implementation detail of `recursive_container_traits` below.
* `T` is the `value_type` of the container, which might need to be modified to
* avoid recursive types and const types.
*/
template <typename T, bool is_this_a_map>
struct impl_type_to_check_recursively {
/*
* If the container is recursive, then no further recursion should be done.
*/
using if_recursive = recursive_bottom;
/*
* Otherwise yield `T` unchanged.
*/
using if_not_recursive = T;
};
/*
* For pairs - only as value type of a map -, the first type should remove the `const`.
* Also, if the map is recursive, then the recursive checking should consider
* the first type only.
*/
template <typename A, typename B>
struct impl_type_to_check_recursively<std::pair<A, B>, /* is_this_a_map = */ true> {
using if_recursive = typename std::remove_const<A>::type;
using if_not_recursive = std::pair<typename std::remove_const<A>::type, B>;
};
/*
* Implementation of `recursive_container_traits` below.
*/
template <typename Container, typename SFINAE = void>
struct impl_recursive_container_traits {
using type_to_check_recursively = recursive_bottom;
};
template <typename Container>
struct impl_recursive_container_traits<
Container,
typename std::enable_if<container_value_type_traits<Container>::has_value_type>::type> {
static constexpr bool is_recursive
= container_mapped_type_traits<Container>::has_recursive_mapped_type
|| container_value_type_traits<Container>::has_recursive_value_type;
/*
* This member dictates which type Pybind11 should check recursively in traits
* such as `is_move_constructible`, `is_copy_constructible`, `is_move_assignable`, ...
* Direct access to `value_type` should be avoided:
* 1. `value_type` might recursively contain the type again
* 2. `value_type` of STL map types is `std::pair<A const, B>`, the `const`
* should be removed.
*
*/
using type_to_check_recursively = typename std::conditional<
is_recursive,
typename impl_type_to_check_recursively<
typename Container::value_type,
container_mapped_type_traits<Container>::has_mapped_type>::if_recursive,
typename impl_type_to_check_recursively<
typename Container::value_type,
container_mapped_type_traits<Container>::has_mapped_type>::if_not_recursive>::type;
};
/*
* This trait defines the `type_to_check_recursively` which is needed to properly
* handle recursively defined traits such as `is_move_constructible` without going
* into an infinite recursion.
* Should be used instead of directly accessing the `value_type`.
* It cancels the recursion by returning the `recursive_bottom` tag.
*
* The default definition of `type_to_check_recursively` is as follows:
*
* 1. By default, it is `recursive_bottom`, so that the recursion is canceled.
* 2. If the type is non-recursive and defines a `value_type`, then the `value_type` is used.
* If the `value_type` is a pair and a `mapped_type` is defined,
* then the `const` is removed from the first type.
* 3. If the type is recursive and `value_type` is not a pair, then `recursive_bottom` is returned.
* 4. If the type is recursive and `value_type` is a pair and a `mapped_type` is defined,
* then `const` is removed from the first type and the first type is returned.
*
* This behavior can be extended by the user as seen in test_stl_binders.cpp.
*
* This struct is exactly the same as impl_recursive_container_traits.
* The duplication achieves that user-defined specializations don't compete
* with internal specializations, but take precedence.
*/
template <typename Container, typename SFINAE = void>
struct recursive_container_traits : impl_recursive_container_traits<Container> {};
template <typename T>
struct is_move_constructible
: all_of<std::is_move_constructible<T>,
is_move_constructible<
typename recursive_container_traits<T>::type_to_check_recursively>> {};
template <>
struct is_move_constructible<recursive_bottom> : std::true_type {};
// Likewise for std::pair
// (after C++17 it is mandatory that the move constructor not exist when the two types aren't
// themselves move constructible, but this can not be relied upon when T1 or T2 are themselves
// containers).
template <typename T1, typename T2>
struct is_move_constructible<std::pair<T1, T2>>
: all_of<is_move_constructible<T1>, is_move_constructible<T2>> {};
// std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when // std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when
// T is non-copyable, but code containing such a copy constructor fails to actually compile. // T is non-copyable, but code containing such a copy constructor fails to actually compile.
template <typename T, typename SFINAE = void> template <typename T>
struct is_copy_constructible : std::is_copy_constructible<T> {}; struct is_copy_constructible
: all_of<std::is_copy_constructible<T>,
is_copy_constructible<
typename recursive_container_traits<T>::type_to_check_recursively>> {};
// Specialization for types that appear to be copy constructible but also look like stl containers template <>
// (we specifically check for: has `value_type` and `reference` with `reference = value_type&`): if struct is_copy_constructible<recursive_bottom> : std::true_type {};
// so, copy constructability depends on whether the value_type is copy constructible.
template <typename Container>
struct is_copy_constructible<
Container,
enable_if_t<
all_of<std::is_copy_constructible<Container>,
std::is_same<typename Container::value_type &, typename Container::reference>,
// Avoid infinite recursion
negation<std::is_same<Container, typename Container::value_type>>>::value>>
: is_copy_constructible<typename Container::value_type> {};
// Likewise for std::pair // Likewise for std::pair
// (after C++17 it is mandatory that the copy constructor not exist when the two types aren't // (after C++17 it is mandatory that the copy constructor not exist when the two types aren't
@ -849,14 +1005,16 @@ struct is_copy_constructible<std::pair<T1, T2>>
: all_of<is_copy_constructible<T1>, is_copy_constructible<T2>> {}; : all_of<is_copy_constructible<T1>, is_copy_constructible<T2>> {};
// The same problems arise with std::is_copy_assignable, so we use the same workaround. // The same problems arise with std::is_copy_assignable, so we use the same workaround.
template <typename T, typename SFINAE = void> template <typename T>
struct is_copy_assignable : std::is_copy_assignable<T> {}; struct is_copy_assignable
template <typename Container> : all_of<
struct is_copy_assignable<Container, std::is_copy_assignable<T>,
enable_if_t<all_of<std::is_copy_assignable<Container>, is_copy_assignable<typename recursive_container_traits<T>::type_to_check_recursively>> {
std::is_same<typename Container::value_type &, };
typename Container::reference>>::value>>
: is_copy_assignable<typename Container::value_type> {}; template <>
struct is_copy_assignable<recursive_bottom> : std::true_type {};
template <typename T1, typename T2> template <typename T1, typename T2>
struct is_copy_assignable<std::pair<T1, T2>> struct is_copy_assignable<std::pair<T1, T2>>
: all_of<is_copy_assignable<T1>, is_copy_assignable<T2>> {}; : all_of<is_copy_assignable<T1>, is_copy_assignable<T2>> {};
@ -994,7 +1152,7 @@ protected:
return [](const void *arg) -> void * { return new T(*reinterpret_cast<const T *>(arg)); }; return [](const void *arg) -> void * { return new T(*reinterpret_cast<const T *>(arg)); };
} }
template <typename T, typename = enable_if_t<std::is_move_constructible<T>::value>> template <typename T, typename = enable_if_t<is_move_constructible<T>::value>>
static auto make_move_constructor(const T *) static auto make_move_constructor(const T *)
-> decltype(new T(std::declval<T &&>()), Constructor{}) { -> decltype(new T(std::declval<T &&>()), Constructor{}) {
return [](const void *arg) -> void * { return [](const void *arg) -> void * {

View File

@ -0,0 +1,9 @@
// Copyright (c) 2023 The pybind Community.
#pragma once
// Common message for `static_assert()`s, which are useful to easily
// preempt much less obvious errors.
#define PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED \
"Pointer types (in particular `PyObject *`) are not supported as scalar types for Eigen " \
"types."

View File

@ -10,6 +10,7 @@
#pragma once #pragma once
#include "../numpy.h" #include "../numpy.h"
#include "common.h"
/* HINT: To suppress warnings originating from the Eigen headers, use -isystem. /* HINT: To suppress warnings originating from the Eigen headers, use -isystem.
See also: See also:
@ -287,6 +288,8 @@ handle eigen_encapsulate(Type *src) {
template <typename Type> template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> { struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
using Scalar = typename Type::Scalar; using Scalar = typename Type::Scalar;
static_assert(!std::is_pointer<Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using props = EigenProps<Type>; using props = EigenProps<Type>;
bool load(handle src, bool convert) { bool load(handle src, bool convert) {
@ -405,6 +408,9 @@ private:
// Base class for casting reference/map/block/etc. objects back to python. // Base class for casting reference/map/block/etc. objects back to python.
template <typename MapType> template <typename MapType>
struct eigen_map_caster { struct eigen_map_caster {
static_assert(!std::is_pointer<typename MapType::Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
private: private:
using props = EigenProps<MapType>; using props = EigenProps<MapType>;
@ -457,6 +463,8 @@ private:
using Type = Eigen::Ref<PlainObjectType, 0, StrideType>; using Type = Eigen::Ref<PlainObjectType, 0, StrideType>;
using props = EigenProps<Type>; using props = EigenProps<Type>;
using Scalar = typename props::Scalar; using Scalar = typename props::Scalar;
static_assert(!std::is_pointer<Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using MapType = Eigen::Map<PlainObjectType, 0, StrideType>; using MapType = Eigen::Map<PlainObjectType, 0, StrideType>;
using Array using Array
= array_t<Scalar, = array_t<Scalar,
@ -604,6 +612,9 @@ private:
// regular Eigen::Matrix, then casting that. // regular Eigen::Matrix, then casting that.
template <typename Type> template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_other<Type>::value>> { struct type_caster<Type, enable_if_t<is_eigen_other<Type>::value>> {
static_assert(!std::is_pointer<typename Type::Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
protected: protected:
using Matrix using Matrix
= Eigen::Matrix<typename Type::Scalar, Type::RowsAtCompileTime, Type::ColsAtCompileTime>; = Eigen::Matrix<typename Type::Scalar, Type::RowsAtCompileTime, Type::ColsAtCompileTime>;
@ -632,6 +643,8 @@ public:
template <typename Type> template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> { struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> {
using Scalar = typename Type::Scalar; using Scalar = typename Type::Scalar;
static_assert(!std::is_pointer<Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using StorageIndex = remove_reference_t<decltype(*std::declval<Type>().outerIndexPtr())>; using StorageIndex = remove_reference_t<decltype(*std::declval<Type>().outerIndexPtr())>;
using Index = typename Type::Index; using Index = typename Type::Index;
static constexpr bool rowMajor = Type::IsRowMajor; static constexpr bool rowMajor = Type::IsRowMajor;

View File

@ -8,6 +8,7 @@
#pragma once #pragma once
#include "../numpy.h" #include "../numpy.h"
#include "common.h"
#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)
static_assert(__GNUC__ > 5, "Eigen Tensor support in pybind11 requires GCC > 5.0"); static_assert(__GNUC__ > 5, "Eigen Tensor support in pybind11 requires GCC > 5.0");
@ -164,6 +165,8 @@ PYBIND11_WARNING_POP
template <typename Type> template <typename Type>
struct type_caster<Type, typename eigen_tensor_helper<Type>::ValidType> { struct type_caster<Type, typename eigen_tensor_helper<Type>::ValidType> {
static_assert(!std::is_pointer<typename Type::Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using Helper = eigen_tensor_helper<Type>; using Helper = eigen_tensor_helper<Type>;
static constexpr auto temp_name = get_tensor_descriptor<Type, false>::value; static constexpr auto temp_name = get_tensor_descriptor<Type, false>::value;
PYBIND11_TYPE_CASTER(Type, temp_name); PYBIND11_TYPE_CASTER(Type, temp_name);
@ -359,6 +362,8 @@ struct get_storage_pointer_type<MapType, void_t<typename MapType::PointerArgType
template <typename Type, int Options> template <typename Type, int Options>
struct type_caster<Eigen::TensorMap<Type, Options>, struct type_caster<Eigen::TensorMap<Type, Options>,
typename eigen_tensor_helper<remove_cv_t<Type>>::ValidType> { typename eigen_tensor_helper<remove_cv_t<Type>>::ValidType> {
static_assert(!std::is_pointer<typename Type::Scalar>::value,
PYBIND11_EIGEN_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using MapType = Eigen::TensorMap<Type, Options>; using MapType = Eigen::TensorMap<Type, Options>;
using Helper = eigen_tensor_helper<remove_cv_t<Type>>; using Helper = eigen_tensor_helper<remove_cv_t<Type>>;

View File

@ -198,9 +198,10 @@ inline void initialize_interpreter(bool init_signal_handlers = true,
init_signal_handlers, argc, argv, add_program_dir_to_path); init_signal_handlers, argc, argv, add_program_dir_to_path);
#else #else
PyConfig config; PyConfig config;
PyConfig_InitIsolatedConfig(&config); PyConfig_InitPythonConfig(&config);
config.isolated = 0; // See PR #4473 for background
config.use_environment = 1; config.parse_argv = 0;
config.install_signal_handlers = init_signal_handlers ? 1 : 0; config.install_signal_handlers = init_signal_handlers ? 1 : 0;
initialize_interpreter(&config, argc, argv, add_program_dir_to_path); initialize_interpreter(&config, argc, argv, add_program_dir_to_path);
#endif #endif
@ -242,16 +243,14 @@ inline void initialize_interpreter(bool init_signal_handlers = true,
\endrst */ \endrst */
inline void finalize_interpreter() { inline void finalize_interpreter() {
handle builtins(PyEval_GetBuiltins());
const char *id = PYBIND11_INTERNALS_ID;
// Get the internals pointer (without creating it if it doesn't exist). It's possible for the // Get the internals pointer (without creating it if it doesn't exist). It's possible for the
// internals to be created during Py_Finalize() (e.g. if a py::capsule calls `get_internals()` // internals to be created during Py_Finalize() (e.g. if a py::capsule calls `get_internals()`
// during destruction), so we get the pointer-pointer here and check it after Py_Finalize(). // during destruction), so we get the pointer-pointer here and check it after Py_Finalize().
detail::internals **internals_ptr_ptr = detail::get_internals_pp(); detail::internals **internals_ptr_ptr = detail::get_internals_pp();
// It could also be stashed in builtins, so look there too: // It could also be stashed in state_dict, so look there too:
if (builtins.contains(id) && isinstance<capsule>(builtins[id])) { if (object internals_obj
internals_ptr_ptr = capsule(builtins[id]); = get_internals_obj_from_state_dict(detail::get_python_state_dict())) {
internals_ptr_ptr = detail::get_internals_pp_from_capsule(internals_obj);
} }
// Local internals contains data managed by the current interpreter, so we must clear them to // Local internals contains data managed by the current interpreter, so we must clear them to
// avoid undefined behaviors when initializing another interpreter // avoid undefined behaviors when initializing another interpreter

View File

@ -564,6 +564,8 @@ public:
m_ptr = from_args(args).release().ptr(); m_ptr = from_args(args).release().ptr();
} }
/// Return dtype for the given typenum (one of the NPY_TYPES).
/// https://numpy.org/devdocs/reference/c-api/array.html#c.PyArray_DescrFromType
explicit dtype(int typenum) explicit dtype(int typenum)
: object(detail::npy_api::get().PyArray_DescrFromType_(typenum), stolen_t{}) { : object(detail::npy_api::get().PyArray_DescrFromType_(typenum), stolen_t{}) {
if (m_ptr == nullptr) { if (m_ptr == nullptr) {
@ -1283,12 +1285,16 @@ private:
public: public:
static constexpr int value = values[detail::is_fmt_numeric<T>::index]; static constexpr int value = values[detail::is_fmt_numeric<T>::index];
static pybind11::dtype dtype() { static pybind11::dtype dtype() { return pybind11::dtype(/*typenum*/ value); }
if (auto *ptr = npy_api::get().PyArray_DescrFromType_(value)) { };
return reinterpret_steal<pybind11::dtype>(ptr);
} template <typename T>
pybind11_fail("Unsupported buffer format!"); struct npy_format_descriptor<T, enable_if_t<is_same_ignoring_cvref<T, PyObject *>::value>> {
} static constexpr auto name = const_name("object");
static constexpr int value = npy_api::NPY_OBJECT_;
static pybind11::dtype dtype() { return pybind11::dtype(/*typenum*/ value); }
}; };
#define PYBIND11_DECL_CHAR_FMT \ #define PYBIND11_DECL_CHAR_FMT \

View File

@ -84,6 +84,7 @@ public:
cpp_function() = default; cpp_function() = default;
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
cpp_function(std::nullptr_t) {} cpp_function(std::nullptr_t) {}
cpp_function(std::nullptr_t, const is_setter &) {}
/// Construct a cpp_function from a vanilla function pointer /// Construct a cpp_function from a vanilla function pointer
template <typename Return, typename... Args, typename... Extra> template <typename Return, typename... Args, typename... Extra>
@ -244,10 +245,16 @@ protected:
using Guard = extract_guard_t<Extra...>; using Guard = extract_guard_t<Extra...>;
/* Perform the function call */ /* Perform the function call */
handle result handle result;
= cast_out::cast(std::move(args_converter).template call<Return, Guard>(cap->f), if (call.func.is_setter) {
policy, (void) std::move(args_converter).template call<Return, Guard>(cap->f);
call.parent); result = none().release();
} else {
result = cast_out::cast(
std::move(args_converter).template call<Return, Guard>(cap->f),
policy,
call.parent);
}
/* Invoke call policy post-call hook */ /* Invoke call policy post-call hook */
process_attributes<Extra...>::postcall(call, result); process_attributes<Extra...>::postcall(call, result);
@ -501,8 +508,8 @@ protected:
rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS; rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS;
capsule rec_capsule(unique_rec.release(), capsule rec_capsule(unique_rec.release(),
detail::get_function_record_capsule_name(),
[](void *ptr) { destruct((detail::function_record *) ptr); }); [](void *ptr) { destruct((detail::function_record *) ptr); });
rec_capsule.set_name(detail::get_function_record_capsule_name());
guarded_strdup.release(); guarded_strdup.release();
object scope_module; object scope_module;
@ -1729,7 +1736,8 @@ public:
template <typename Getter, typename Setter, typename... Extra> template <typename Getter, typename Setter, typename... Extra>
class_ & class_ &
def_property(const char *name, const Getter &fget, const Setter &fset, const Extra &...extra) { def_property(const char *name, const Getter &fget, const Setter &fset, const Extra &...extra) {
return def_property(name, fget, cpp_function(method_adaptor<type>(fset)), extra...); return def_property(
name, fget, cpp_function(method_adaptor<type>(fset), is_setter()), extra...);
} }
template <typename Getter, typename... Extra> template <typename Getter, typename... Extra>
class_ &def_property(const char *name, class_ &def_property(const char *name,

View File

@ -471,13 +471,24 @@ inline const char *obj_class_name(PyObject *obj) {
std::string error_string(); std::string error_string();
// The code in this struct is very unusual, to minimize the chances of
// masking bugs (elsewhere) by errors during the error handling (here).
// This is meant to be a lifeline for troubleshooting long-running processes
// that crash under conditions that are virtually impossible to reproduce.
// Low-level implementation alternatives are preferred to higher-level ones
// that might raise cascading exceptions. Last-ditch-kind-of attempts are made
// to report as much of the original error as possible, even if there are
// secondary issues obtaining some of the details.
struct error_fetch_and_normalize { struct error_fetch_and_normalize {
// Immediate normalization is long-established behavior (starting with // This comment only applies to Python <= 3.11:
// https://github.com/pybind/pybind11/commit/135ba8deafb8bf64a15b24d1513899eb600e2011 // Immediate normalization is long-established behavior (starting with
// from Sep 2016) and safest. Normalization could be deferred, but this could mask // https://github.com/pybind/pybind11/commit/135ba8deafb8bf64a15b24d1513899eb600e2011
// errors elsewhere, the performance gain is very minor in typical situations // from Sep 2016) and safest. Normalization could be deferred, but this could mask
// (usually the dominant bottleneck is EH unwinding), and the implementation here // errors elsewhere, the performance gain is very minor in typical situations
// would be more complex. // (usually the dominant bottleneck is EH unwinding), and the implementation here
// would be more complex.
// Starting with Python 3.12, PyErr_Fetch() normalizes exceptions immediately.
// Any errors during normalization are tracked under __notes__.
explicit error_fetch_and_normalize(const char *called) { explicit error_fetch_and_normalize(const char *called) {
PyErr_Fetch(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr()); PyErr_Fetch(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr());
if (!m_type) { if (!m_type) {
@ -492,6 +503,14 @@ struct error_fetch_and_normalize {
"of the original active exception type."); "of the original active exception type.");
} }
m_lazy_error_string = exc_type_name_orig; m_lazy_error_string = exc_type_name_orig;
#if PY_VERSION_HEX >= 0x030C0000
// The presence of __notes__ is likely due to exception normalization
// errors, although that is not necessarily true, therefore insert a
// hint only:
if (PyObject_HasAttrString(m_value.ptr(), "__notes__")) {
m_lazy_error_string += "[WITH __notes__]";
}
#else
// PyErr_NormalizeException() may change the exception type if there are cascading // PyErr_NormalizeException() may change the exception type if there are cascading
// failures. This can potentially be extremely confusing. // failures. This can potentially be extremely confusing.
PyErr_NormalizeException(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr()); PyErr_NormalizeException(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr());
@ -506,12 +525,12 @@ struct error_fetch_and_normalize {
+ " failed to obtain the name " + " failed to obtain the name "
"of the normalized active exception type."); "of the normalized active exception type.");
} }
#if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x07030a00 # if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x07030a00
// This behavior runs the risk of masking errors in the error handling, but avoids a // This behavior runs the risk of masking errors in the error handling, but avoids a
// conflict with PyPy, which relies on the normalization here to change OSError to // conflict with PyPy, which relies on the normalization here to change OSError to
// FileNotFoundError (https://github.com/pybind/pybind11/issues/4075). // FileNotFoundError (https://github.com/pybind/pybind11/issues/4075).
m_lazy_error_string = exc_type_name_norm; m_lazy_error_string = exc_type_name_norm;
#else # else
if (exc_type_name_norm != m_lazy_error_string) { if (exc_type_name_norm != m_lazy_error_string) {
std::string msg = std::string(called) std::string msg = std::string(called)
+ ": MISMATCH of original and normalized " + ": MISMATCH of original and normalized "
@ -523,6 +542,7 @@ struct error_fetch_and_normalize {
msg += ": " + format_value_and_trace(); msg += ": " + format_value_and_trace();
pybind11_fail(msg); pybind11_fail(msg);
} }
# endif
#endif #endif
} }
@ -558,6 +578,40 @@ struct error_fetch_and_normalize {
} }
} }
} }
#if PY_VERSION_HEX >= 0x030B0000
auto notes
= reinterpret_steal<object>(PyObject_GetAttrString(m_value.ptr(), "__notes__"));
if (!notes) {
PyErr_Clear(); // No notes is good news.
} else {
auto len_notes = PyList_Size(notes.ptr());
if (len_notes < 0) {
result += "\nFAILURE obtaining len(__notes__): " + detail::error_string();
} else {
result += "\n__notes__ (len=" + std::to_string(len_notes) + "):";
for (ssize_t i = 0; i < len_notes; i++) {
PyObject *note = PyList_GET_ITEM(notes.ptr(), i);
auto note_bytes = reinterpret_steal<object>(
PyUnicode_AsEncodedString(note, "utf-8", "backslashreplace"));
if (!note_bytes) {
result += "\nFAILURE obtaining __notes__[" + std::to_string(i)
+ "]: " + detail::error_string();
} else {
char *buffer = nullptr;
Py_ssize_t length = 0;
if (PyBytes_AsStringAndSize(note_bytes.ptr(), &buffer, &length)
== -1) {
result += "\nFAILURE formatting __notes__[" + std::to_string(i)
+ "]: " + detail::error_string();
} else {
result += '\n';
result += std::string(buffer, static_cast<std::size_t>(length));
}
}
}
}
}
#endif
} else { } else {
result = "<MESSAGE UNAVAILABLE>"; result = "<MESSAGE UNAVAILABLE>";
} }
@ -1871,28 +1925,13 @@ public:
} }
} }
/// Capsule name is nullptr.
capsule(const void *value, void (*destructor)(void *)) { capsule(const void *value, void (*destructor)(void *)) {
m_ptr = PyCapsule_New(const_cast<void *>(value), nullptr, [](PyObject *o) { initialize_with_void_ptr_destructor(value, nullptr, destructor);
// guard if destructor called while err indicator is set }
error_scope error_guard;
auto destructor = reinterpret_cast<void (*)(void *)>(PyCapsule_GetContext(o));
if (destructor == nullptr && PyErr_Occurred()) {
throw error_already_set();
}
const char *name = get_name_in_error_scope(o);
void *ptr = PyCapsule_GetPointer(o, name);
if (ptr == nullptr) {
throw error_already_set();
}
if (destructor != nullptr) { capsule(const void *value, const char *name, void (*destructor)(void *)) {
destructor(ptr); initialize_with_void_ptr_destructor(value, name, destructor);
}
});
if (!m_ptr || PyCapsule_SetContext(m_ptr, reinterpret_cast<void *>(destructor)) != 0) {
throw error_already_set();
}
} }
explicit capsule(void (*destructor)()) { explicit capsule(void (*destructor)()) {
@ -1960,6 +1999,32 @@ private:
return name; return name;
} }
void initialize_with_void_ptr_destructor(const void *value,
const char *name,
void (*destructor)(void *)) {
m_ptr = PyCapsule_New(const_cast<void *>(value), name, [](PyObject *o) {
// guard if destructor called while err indicator is set
error_scope error_guard;
auto destructor = reinterpret_cast<void (*)(void *)>(PyCapsule_GetContext(o));
if (destructor == nullptr && PyErr_Occurred()) {
throw error_already_set();
}
const char *name = get_name_in_error_scope(o);
void *ptr = PyCapsule_GetPointer(o, name);
if (ptr == nullptr) {
throw error_already_set();
}
if (destructor != nullptr) {
destructor(ptr);
}
});
if (!m_ptr || PyCapsule_SetContext(m_ptr, reinterpret_cast<void *>(destructor)) != 0) {
throw error_already_set();
}
}
}; };
class tuple : public object { class tuple : public object {

View File

@ -273,11 +273,11 @@ public:
} }
PYBIND11_TYPE_CASTER(ArrayType, PYBIND11_TYPE_CASTER(ArrayType,
const_name("List[") + value_conv::name const_name<Resizable>(const_name(""), const_name("Annotated["))
+ const_name("List[") + value_conv::name + const_name("]")
+ const_name<Resizable>(const_name(""), + const_name<Resizable>(const_name(""),
const_name("[") + const_name<Size>() const_name(", FixedSize(")
+ const_name("]")) + const_name<Size>() + const_name(")]")));
+ const_name("]"));
}; };
template <typename Type, size_t Size> template <typename Type, size_t Size>
@ -316,6 +316,7 @@ struct optional_caster {
if (!std::is_lvalue_reference<T>::value) { if (!std::is_lvalue_reference<T>::value) {
policy = return_value_policy_override<Value>::policy(policy); policy = return_value_policy_override<Value>::policy(policy);
} }
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
return value_conv::cast(*std::forward<T>(src), policy, parent); return value_conv::cast(*std::forward<T>(src), policy, parent);
} }

View File

@ -61,9 +61,11 @@ struct is_comparable<
/* For a vector/map data structure, recursively check the value type /* For a vector/map data structure, recursively check the value type
(which is std::pair for maps) */ (which is std::pair for maps) */
template <typename T> template <typename T>
struct is_comparable<T, enable_if_t<container_traits<T>::is_vector>> { struct is_comparable<T, enable_if_t<container_traits<T>::is_vector>>
static constexpr const bool value = is_comparable<typename T::value_type>::value; : is_comparable<typename recursive_container_traits<T>::type_to_check_recursively> {};
};
template <>
struct is_comparable<recursive_bottom> : std::true_type {};
/* For pairs, recursively check the two data types */ /* For pairs, recursively check the two data types */
template <typename T> template <typename T>
@ -355,13 +357,17 @@ void vector_accessor(enable_if_t<vector_needs_copy<Vector>::value, Class_> &cl)
using DiffType = typename Vector::difference_type; using DiffType = typename Vector::difference_type;
using ItType = typename Vector::iterator; using ItType = typename Vector::iterator;
cl.def("__getitem__", [](const Vector &v, DiffType i) -> T { cl.def("__getitem__", [](const Vector &v, DiffType i) -> T {
if (i < 0 && (i += v.size()) < 0) { if (i < 0) {
i += v.size();
if (i < 0) {
throw index_error();
}
}
auto i_st = static_cast<SizeType>(i);
if (i_st >= v.size()) {
throw index_error(); throw index_error();
} }
if ((SizeType) i >= v.size()) { return v[i_st];
throw index_error();
}
return v[(SizeType) i];
}); });
cl.def( cl.def(

View File

@ -0,0 +1,61 @@
// Copyright (c) 2023 The pybind Community.
#pragma once
#include "detail/common.h"
#include "detail/descr.h"
#include "cast.h"
#include "pytypes.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
template <>
class type_caster<PyObject> {
public:
static constexpr auto name = const_name("object"); // See discussion under PR #4601.
// This overload is purely to guard against accidents.
template <typename T,
detail::enable_if_t<!is_same_ignoring_cvref<T, PyObject *>::value, int> = 0>
static handle cast(T &&, return_value_policy, handle /*parent*/) {
static_assert(is_same_ignoring_cvref<T, PyObject *>::value,
"Invalid C++ type T for to-Python conversion (type_caster<PyObject>).");
return nullptr; // Unreachable.
}
static handle cast(PyObject *src, return_value_policy policy, handle /*parent*/) {
if (src == nullptr) {
throw error_already_set();
}
if (PyErr_Occurred()) {
raise_from(PyExc_SystemError, "src != nullptr but PyErr_Occurred()");
throw error_already_set();
}
if (policy == return_value_policy::take_ownership) {
return src;
}
if (policy == return_value_policy::reference
|| policy == return_value_policy::automatic_reference) {
return handle(src).inc_ref();
}
pybind11_fail("type_caster<PyObject>::cast(): unsupported return_value_policy: "
+ std::to_string(static_cast<int>(policy)));
}
bool load(handle src, bool) {
value = reinterpret_borrow<object>(src);
return true;
}
template <typename T>
using cast_op_type = PyObject *;
explicit operator PyObject *() { return value.ptr(); }
private:
object value;
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -1,6 +1,6 @@
import sys import sys
if sys.version_info < (3, 6): if sys.version_info < (3, 6): # noqa: UP036
msg = "pybind11 does not support Python < 3.6. 2.9 was the last release supporting Python 2.7 and 3.5." msg = "pybind11 does not support Python < 3.6. 2.9 was the last release supporting Python 2.7 and 3.5."
raise ImportError(msg) raise ImportError(msg)

View File

@ -8,5 +8,5 @@ def _to_int(s: str) -> Union[int, str]:
return s return s
__version__ = "2.10.4" __version__ = "2.11.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

@ -3,7 +3,7 @@ import os
DIR = os.path.abspath(os.path.dirname(__file__)) DIR = os.path.abspath(os.path.dirname(__file__))
def get_include(user: bool = False) -> str: # pylint: disable=unused-argument def get_include(user: bool = False) -> str: # noqa: ARG001
""" """
Return the path to the pybind11 include directory. The historical "user" Return the path to the pybind11 include directory. The historical "user"
argument is unused, and may be removed. argument is unused, and may be removed.

View File

@ -66,8 +66,8 @@ 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 from distutils.command.build_ext import build_ext as _build_ext # type: ignore[assignment]
from distutils.extension import Extension as _Extension from distutils.extension import Extension as _Extension # type: ignore[assignment]
import distutils.ccompiler import distutils.ccompiler
import distutils.errors import distutils.errors
@ -84,7 +84,7 @@ STD_TMPL = "/std:c++{}" if WIN else "-std=c++{}"
# directory into your path if it sits beside your setup.py. # directory into your path if it sits beside your setup.py.
class Pybind11Extension(_Extension): # type: ignore[misc] class Pybind11Extension(_Extension):
""" """
Build a C++11+ Extension module with pybind11. This automatically adds the Build a C++11+ Extension module with pybind11. This automatically adds the
recommended flags when you init the extension and assumes C++ sources - you recommended flags when you init the extension and assumes C++ sources - you
@ -144,7 +144,6 @@ class Pybind11Extension(_Extension): # type: ignore[misc]
self.cxx_std = cxx_std self.cxx_std = cxx_std
cflags = [] cflags = []
ldflags = []
if WIN: if WIN:
cflags += ["/EHsc", "/bigobj"] cflags += ["/EHsc", "/bigobj"]
else: else:
@ -154,11 +153,7 @@ class Pybind11Extension(_Extension): # type: ignore[misc]
c_cpp_flags = shlex.split(env_cflags) + shlex.split(env_cppflags) c_cpp_flags = shlex.split(env_cflags) + shlex.split(env_cppflags)
if not any(opt.startswith("-g") for opt in c_cpp_flags): if not any(opt.startswith("-g") for opt in c_cpp_flags):
cflags += ["-g0"] cflags += ["-g0"]
if MACOS:
cflags += ["-stdlib=libc++"]
ldflags += ["-stdlib=libc++"]
self._add_cflags(cflags) self._add_cflags(cflags)
self._add_ldflags(ldflags)
@property @property
def cxx_std(self) -> int: def cxx_std(self) -> int:
@ -271,7 +266,7 @@ def auto_cpp_level(compiler: Any) -> Union[str, int]:
raise RuntimeError(msg) raise RuntimeError(msg)
class build_ext(_build_ext): # type: ignore[misc] # noqa: N801 class build_ext(_build_ext): # noqa: N801
""" """
Customized build_ext that allows an auto-search for the highest supported Customized build_ext that allows an auto-search for the highest supported
C++ level for Pybind11Extension. This is only needed for the auto-search C++ level for Pybind11Extension. This is only needed for the auto-search
@ -341,7 +336,7 @@ def naive_recompile(obj: str, src: str) -> bool:
return os.stat(obj).st_mtime < os.stat(src).st_mtime return os.stat(obj).st_mtime < os.stat(src).st_mtime
def no_recompile(obg: str, src: str) -> bool: # pylint: disable=unused-argument def no_recompile(obg: str, src: str) -> bool: # noqa: ARG001
""" """
This is the safest but slowest choice (and is the default) - will always This is the safest but slowest choice (and is the default) - will always
recompile sources. recompile sources.

View File

@ -2,6 +2,7 @@
requires = ["setuptools>=42", "cmake>=3.18", "ninja"] requires = ["setuptools>=42", "cmake>=3.18", "ninja"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[tool.check-manifest] [tool.check-manifest]
ignore = [ ignore = [
"tests/**", "tests/**",
@ -15,11 +16,6 @@ ignore = [
"noxfile.py", "noxfile.py",
] ]
[tool.isort]
# Needs the compiled .so modules and env.py from tests
known_first_party = "env,pybind11_cross_module_tests,pybind11_tests,"
# For black compatibility
profile = "black"
[tool.mypy] [tool.mypy]
files = ["pybind11"] files = ["pybind11"]
@ -30,7 +26,7 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
warn_unreachable = true warn_unreachable = true
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
module = ["ghapi.*", "setuptools.*"] module = ["ghapi.*"]
ignore_missing_imports = true ignore_missing_imports = true
@ -58,4 +54,45 @@ messages_control.disable = [
"invalid-name", "invalid-name",
"protected-access", "protected-access",
"missing-module-docstring", "missing-module-docstring",
"unused-argument", # covered by Ruff ARG
] ]
[tool.ruff]
select = [
"E", "F", "W", # flake8
"B", # flake8-bugbear
"I", # isort
"N", # pep8-naming
"ARG", # flake8-unused-arguments
"C4", # flake8-comprehensions
"EM", # flake8-errmsg
"ICN", # flake8-import-conventions
"ISC", # flake8-implicit-str-concat
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PL", # pylint
"PT", # flake8-pytest-style
"RET", # flake8-return
"RUF100", # Ruff-specific
"SIM", # flake8-simplify
"UP", # pyupgrade
"YTT", # flake8-2020
]
ignore = [
"PLR", # Design related pylint
"E501", # Line too long (Black is enough)
"PT011", # Too broad with raises in pytest
"PT004", # Fixture that doesn't return needs underscore (no, it is fine)
"SIM118", # iter(x) is not always the same as iter(x.keys())
]
target-version = "py37"
src = ["src"]
unfixable = ["T20"]
exclude = []
line-length = 120
isort.known-first-party = ["env", "pybind11_cross_module_tests", "pybind11_tests"]
[tool.ruff.per-file-ignores]
"tests/**" = ["EM", "N"]
"tests/test_call_policies.py" = ["PLC1901"]

View File

@ -20,6 +20,7 @@ classifiers =
Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
License :: OSI Approved :: BSD License License :: OSI Approved :: BSD License
Programming Language :: Python :: Implementation :: PyPy Programming Language :: Python :: Implementation :: PyPy
Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: CPython
@ -40,11 +41,3 @@ project_urls =
[options] [options]
python_requires = >=3.6 python_requires = >=3.6
zip_safe = False zip_safe = False
[flake8]
max-line-length = 120
show_source = True
exclude = .git, __pycache__, build, dist, docs, tools, venv
extend-ignore = E203, E722
extend-select = B902, B904

View File

@ -96,7 +96,7 @@ def get_and_replace(
# Use our input files instead when making the SDist (and anything that depends # Use our input files instead when making the SDist (and anything that depends
# on it, like a wheel) # on it, like a wheel)
class SDist(setuptools.command.sdist.sdist): # type: ignore[misc] class SDist(setuptools.command.sdist.sdist):
def make_release_tree(self, base_dir: str, files: List[str]) -> None: def make_release_tree(self, base_dir: str, files: List[str]) -> None:
super().make_release_tree(base_dir, files) super().make_release_tree(base_dir, files)

View File

@ -5,20 +5,17 @@
# 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.
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` 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.21) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.21) cmake_policy(VERSION 3.26)
endif() endif()
# Only needed for CMake < 3.5 support
include(CMakeParseArguments)
# 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.
# #
# Usage: # Usage:
@ -154,7 +151,11 @@ set(PYBIND11_TEST_FILES
test_stl_binders test_stl_binders
test_tagbased_polymorphic test_tagbased_polymorphic
test_thread test_thread
test_type_caster_pyobject_ptr
test_union test_union
test_unnamed_namespace_a
test_unnamed_namespace_b
test_vector_unique_ptr_member
test_virtual_functions) test_virtual_functions)
# Invoking cmake with something like: # Invoking cmake with something like:

View File

@ -8,8 +8,8 @@ import contextlib
import difflib import difflib
import gc import gc
import multiprocessing import multiprocessing
import os
import re import re
import sys
import textwrap import textwrap
import traceback import traceback
@ -25,8 +25,9 @@ except Exception:
@pytest.fixture(scope="session", autouse=True) @pytest.fixture(scope="session", autouse=True)
def always_forkserver_on_unix(): def use_multiprocessing_forkserver_on_linux():
if os.name == "nt": if sys.platform != "linux":
# The default on Windows and macOS is "spawn": If it's not broken, don't fix it.
return return
# Full background: https://github.com/pybind/pybind11/issues/4105#issuecomment-1301004592 # Full background: https://github.com/pybind/pybind11/issues/4105#issuecomment-1301004592
@ -34,8 +35,6 @@ def always_forkserver_on_unix():
# It is actually a well-known pitfall, unfortunately without guard rails. # It is actually a well-known pitfall, unfortunately without guard rails.
# "forkserver" is more performant than "spawn" (~9s vs ~13s for tests/test_gil_scoped.py, # "forkserver" is more performant than "spawn" (~9s vs ~13s for tests/test_gil_scoped.py,
# visit the issuecomment link above for details). # visit the issuecomment link above for details).
# Windows does not have fork() and the associated pitfall, therefore it is best left
# running with defaults.
multiprocessing.set_start_method("forkserver") multiprocessing.set_start_method("forkserver")
@ -83,9 +82,8 @@ class Output:
b = _strip_and_dedent(other).splitlines() b = _strip_and_dedent(other).splitlines()
if a == b: if a == b:
return True return True
else: self.explanation = _make_explanation(a, b)
self.explanation = _make_explanation(a, b) return False
return False
class Unordered(Output): class Unordered(Output):
@ -96,9 +94,8 @@ class Unordered(Output):
b = _split_and_sort(other) b = _split_and_sort(other)
if a == b: if a == b:
return True return True
else: self.explanation = _make_explanation(a, b)
self.explanation = _make_explanation(a, b) return False
return False
class Capture: class Capture:
@ -119,9 +116,8 @@ class Capture:
b = other b = other
if a == b: if a == b:
return True return True
else: self.explanation = a.explanation
self.explanation = a.explanation return False
return False
def __str__(self): def __str__(self):
return self.out return self.out
@ -138,7 +134,7 @@ class Capture:
return Output(self.err) return Output(self.err)
@pytest.fixture @pytest.fixture()
def capture(capsys): def capture(capsys):
"""Extended `capsys` with context manager and custom equality operators""" """Extended `capsys` with context manager and custom equality operators"""
return Capture(capsys) return Capture(capsys)
@ -159,25 +155,22 @@ class SanitizedString:
b = _strip_and_dedent(other) b = _strip_and_dedent(other)
if a == b: if a == b:
return True return True
else: self.explanation = _make_explanation(a.splitlines(), b.splitlines())
self.explanation = _make_explanation(a.splitlines(), b.splitlines()) return False
return False
def _sanitize_general(s): def _sanitize_general(s):
s = s.strip() s = s.strip()
s = s.replace("pybind11_tests.", "m.") s = s.replace("pybind11_tests.", "m.")
s = _long_marker.sub(r"\1", s) return _long_marker.sub(r"\1", s)
return s
def _sanitize_docstring(thing): def _sanitize_docstring(thing):
s = thing.__doc__ s = thing.__doc__
s = _sanitize_general(s) return _sanitize_general(s)
return s
@pytest.fixture @pytest.fixture()
def doc(): def doc():
"""Sanitize docstrings and add custom failure explanation""" """Sanitize docstrings and add custom failure explanation"""
return SanitizedString(_sanitize_docstring) return SanitizedString(_sanitize_docstring)
@ -186,30 +179,20 @@ def doc():
def _sanitize_message(thing): def _sanitize_message(thing):
s = str(thing) s = str(thing)
s = _sanitize_general(s) s = _sanitize_general(s)
s = _hexadecimal.sub("0", s) return _hexadecimal.sub("0", s)
return s
@pytest.fixture @pytest.fixture()
def msg(): def msg():
"""Sanitize messages and add custom failure explanation""" """Sanitize messages and add custom failure explanation"""
return SanitizedString(_sanitize_message) return SanitizedString(_sanitize_message)
# noinspection PyUnusedLocal def pytest_assertrepr_compare(op, left, right): # noqa: ARG001
def pytest_assertrepr_compare(op, left, right):
"""Hook to insert custom failure explanation""" """Hook to insert custom failure explanation"""
if hasattr(left, "explanation"): if hasattr(left, "explanation"):
return left.explanation return left.explanation
return None
@contextlib.contextmanager
def suppress(exception):
"""Suppress the desired exception"""
try:
yield
except exception:
pass
def gc_collect(): def gc_collect():
@ -220,7 +203,7 @@ def gc_collect():
def pytest_configure(): def pytest_configure():
pytest.suppress = suppress pytest.suppress = contextlib.suppress
pytest.gc_collect = gc_collect pytest.gc_collect = gc_collect

View File

@ -24,5 +24,4 @@ def deprecated_call():
pytest_major_minor = (int(pieces[0]), int(pieces[1])) pytest_major_minor = (int(pieces[0]), int(pieces[1]))
if pytest_major_minor < (3, 9): if pytest_major_minor < (3, 9):
return pytest.warns((DeprecationWarning, PendingDeprecationWarning)) return pytest.warns((DeprecationWarning, PendingDeprecationWarning))
else: return pytest.deprecated_call()
return pytest.deprecated_call()

View File

@ -43,6 +43,7 @@ main_headers = {
"include/pybind11/pytypes.h", "include/pybind11/pytypes.h",
"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",
} }
detail_headers = { detail_headers = {
@ -56,6 +57,7 @@ detail_headers = {
} }
eigen_headers = { eigen_headers = {
"include/pybind11/eigen/common.h",
"include/pybind11/eigen/matrix.h", "include/pybind11/eigen/matrix.h",
"include/pybind11/eigen/tensor.h", "include/pybind11/eigen/tensor.h",
} }
@ -110,6 +112,7 @@ sdist_files = {
"MANIFEST.in", "MANIFEST.in",
"README.rst", "README.rst",
"PKG-INFO", "PKG-INFO",
"SECURITY.md",
} }
local_sdist_files = { local_sdist_files = {

View File

@ -6,4 +6,4 @@ numpy==1.22.2; platform_python_implementation!="PyPy" and python_version>="3.10"
pytest==7.0.0 pytest==7.0.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.8.0; platform_python_implementation!="PyPy" and python_version=="3.10" scipy==1.10.0; platform_python_implementation!="PyPy" and python_version=="3.10"

View File

@ -4,7 +4,7 @@ asyncio = pytest.importorskip("asyncio")
m = pytest.importorskip("pybind11_tests.async_module") m = pytest.importorskip("pybind11_tests.async_module")
@pytest.fixture @pytest.fixture()
def event_loop(): def event_loop():
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
yield loop yield loop
@ -16,7 +16,7 @@ async def get_await_result(x):
def test_await(event_loop): def test_await(event_loop):
assert 5 == event_loop.run_until_complete(get_await_result(m.SupportsAsync())) assert event_loop.run_until_complete(get_await_result(m.SupportsAsync())) == 5
def test_await_missing(event_loop): def test_await_missing(event_loop):

View File

@ -7,12 +7,47 @@
BSD-style license that can be found in the LICENSE file. BSD-style license that can be found in the LICENSE file.
*/ */
#include <pybind11/complex.h>
#include <pybind11/stl.h> #include <pybind11/stl.h>
#include "constructor_stats.h" #include "constructor_stats.h"
#include "pybind11_tests.h" #include "pybind11_tests.h"
TEST_SUBMODULE(buffers, m) { TEST_SUBMODULE(buffers, m) {
m.attr("long_double_and_double_have_same_size") = (sizeof(long double) == sizeof(double));
m.def("format_descriptor_format_buffer_info_equiv",
[](const std::string &cpp_name, const py::buffer &buffer) {
// https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables
static auto *format_table = new std::map<std::string, std::string>;
static auto *equiv_table
= new std::map<std::string, bool (py::buffer_info::*)() const>;
if (format_table->empty()) {
#define PYBIND11_ASSIGN_HELPER(...) \
(*format_table)[#__VA_ARGS__] = py::format_descriptor<__VA_ARGS__>::format(); \
(*equiv_table)[#__VA_ARGS__] = &py::buffer_info::item_type_is_equivalent_to<__VA_ARGS__>;
PYBIND11_ASSIGN_HELPER(PyObject *)
PYBIND11_ASSIGN_HELPER(bool)
PYBIND11_ASSIGN_HELPER(std::int8_t)
PYBIND11_ASSIGN_HELPER(std::uint8_t)
PYBIND11_ASSIGN_HELPER(std::int16_t)
PYBIND11_ASSIGN_HELPER(std::uint16_t)
PYBIND11_ASSIGN_HELPER(std::int32_t)
PYBIND11_ASSIGN_HELPER(std::uint32_t)
PYBIND11_ASSIGN_HELPER(std::int64_t)
PYBIND11_ASSIGN_HELPER(std::uint64_t)
PYBIND11_ASSIGN_HELPER(float)
PYBIND11_ASSIGN_HELPER(double)
PYBIND11_ASSIGN_HELPER(long double)
PYBIND11_ASSIGN_HELPER(std::complex<float>)
PYBIND11_ASSIGN_HELPER(std::complex<double>)
PYBIND11_ASSIGN_HELPER(std::complex<long double>)
#undef PYBIND11_ASSIGN_HELPER
}
return std::pair<std::string, bool>(
(*format_table)[cpp_name], (buffer.request().*((*equiv_table)[cpp_name]))());
});
// test_from_python / test_to_python: // test_from_python / test_to_python:
class Matrix { class Matrix {
public: public:

View File

@ -10,6 +10,63 @@ from pybind11_tests import buffers as m
np = pytest.importorskip("numpy") np = pytest.importorskip("numpy")
if m.long_double_and_double_have_same_size:
# Determined by the compiler used to build the pybind11 tests
# (e.g. MSVC gets here, but MinGW might not).
np_float128 = None
np_complex256 = None
else:
# Determined by the compiler used to build numpy (e.g. MinGW).
np_float128 = getattr(np, *["float128"] * 2)
np_complex256 = getattr(np, *["complex256"] * 2)
CPP_NAME_FORMAT_NP_DTYPE_TABLE = [
("PyObject *", "O", object),
("bool", "?", np.bool_),
("std::int8_t", "b", np.int8),
("std::uint8_t", "B", np.uint8),
("std::int16_t", "h", np.int16),
("std::uint16_t", "H", np.uint16),
("std::int32_t", "i", np.int32),
("std::uint32_t", "I", np.uint32),
("std::int64_t", "q", np.int64),
("std::uint64_t", "Q", np.uint64),
("float", "f", np.float32),
("double", "d", np.float64),
("long double", "g", np_float128),
("std::complex<float>", "Zf", np.complex64),
("std::complex<double>", "Zd", np.complex128),
("std::complex<long double>", "Zg", np_complex256),
]
CPP_NAME_FORMAT_TABLE = [
(cpp_name, format)
for cpp_name, format, np_dtype in CPP_NAME_FORMAT_NP_DTYPE_TABLE
if np_dtype is not None
]
CPP_NAME_NP_DTYPE_TABLE = [
(cpp_name, np_dtype) for cpp_name, _, np_dtype in CPP_NAME_FORMAT_NP_DTYPE_TABLE
]
@pytest.mark.parametrize(("cpp_name", "np_dtype"), CPP_NAME_NP_DTYPE_TABLE)
def test_format_descriptor_format_buffer_info_equiv(cpp_name, np_dtype):
if np_dtype is None:
pytest.skip(
f"cpp_name=`{cpp_name}`: `long double` and `double` have same size."
)
if isinstance(np_dtype, str):
pytest.skip(f"np.{np_dtype} does not exist.")
np_array = np.array([], dtype=np_dtype)
for other_cpp_name, expected_format in CPP_NAME_FORMAT_TABLE:
format, np_array_is_matching = m.format_descriptor_format_buffer_info_equiv(
other_cpp_name, np_array
)
assert format == expected_format
if other_cpp_name == cpp_name:
assert np_array_is_matching
else:
assert not np_array_is_matching
def test_from_python(): def test_from_python():
with pytest.raises(RuntimeError) as excinfo: with pytest.raises(RuntimeError) as excinfo:
@ -54,7 +111,8 @@ def test_to_python():
mat2 = np.array(mat, copy=False) mat2 = np.array(mat, copy=False)
assert mat2.shape == (5, 4) assert mat2.shape == (5, 4)
assert abs(mat2).sum() == 11 assert abs(mat2).sum() == 11
assert mat2[2, 3] == 4 and mat2[3, 2] == 7 assert mat2[2, 3] == 4
assert mat2[3, 2] == 7
mat2[2, 3] = 5 mat2[2, 3] = 5
assert mat2[2, 3] == 5 assert mat2[2, 3] == 5

View File

@ -126,8 +126,8 @@ def test_bytes_to_string():
assert m.strlen(b"hi") == 2 assert m.strlen(b"hi") == 2
assert m.string_length(b"world") == 5 assert m.string_length(b"world") == 5
assert m.string_length("a\x00b".encode()) == 3 assert m.string_length(b"a\x00b") == 3
assert m.strlen("a\x00b".encode()) == 1 # C-string limitation assert m.strlen(b"a\x00b") == 1 # C-string limitation
# passing in a utf8 encoded string should work # passing in a utf8 encoded string should work
assert m.string_length("💩".encode()) == 4 assert m.string_length("💩".encode()) == 4
@ -421,13 +421,15 @@ def test_reference_wrapper():
a2 = m.refwrap_list(copy=True) a2 = m.refwrap_list(copy=True)
assert [x.value for x in a1] == [2, 3] assert [x.value for x in a1] == [2, 3]
assert [x.value for x in a2] == [2, 3] assert [x.value for x in a2] == [2, 3]
assert not a1[0] is a2[0] and not a1[1] is a2[1] assert a1[0] is not a2[0]
assert a1[1] is not a2[1]
b1 = m.refwrap_list(copy=False) b1 = m.refwrap_list(copy=False)
b2 = m.refwrap_list(copy=False) b2 = m.refwrap_list(copy=False)
assert [x.value for x in b1] == [1, 2] assert [x.value for x in b1] == [1, 2]
assert [x.value for x in b2] == [1, 2] assert [x.value for x in b2] == [1, 2]
assert b1[0] is b2[0] and b1[1] is b2[1] assert b1[0] is b2[0]
assert b1[1] is b2[1]
assert m.refwrap_iiw(IncType(5)) == 5 assert m.refwrap_iiw(IncType(5)) == 5
assert m.refwrap_call_iiw(IncType(10), m.refwrap_iiw) == [10, 10, 10, 10] assert m.refwrap_call_iiw(IncType(10), m.refwrap_iiw) == [10, 10, 10, 10]

View File

@ -5,6 +5,7 @@ import pytest
import env # noqa: F401 import env # noqa: F401
from pybind11_tests import callbacks as m from pybind11_tests import callbacks as m
from pybind11_tests import detailed_error_messages_enabled
def test_callbacks(): def test_callbacks():
@ -70,11 +71,20 @@ def test_keyword_args_and_generalized_unpacking():
with pytest.raises(RuntimeError) as excinfo: with pytest.raises(RuntimeError) as excinfo:
m.test_arg_conversion_error1(f) m.test_arg_conversion_error1(f)
assert "Unable to convert call argument" in str(excinfo.value) assert str(excinfo.value) == "Unable to convert call argument " + (
"'1' of type 'UnregisteredType' to Python object"
if detailed_error_messages_enabled
else "'1' to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"
)
with pytest.raises(RuntimeError) as excinfo: with pytest.raises(RuntimeError) as excinfo:
m.test_arg_conversion_error2(f) m.test_arg_conversion_error2(f)
assert "Unable to convert call argument" in str(excinfo.value) assert str(excinfo.value) == "Unable to convert call argument " + (
"'expected_name' of type 'UnregisteredType' to Python object"
if detailed_error_messages_enabled
else "'expected_name' to Python object "
"(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"
)
def test_lambda_closure_cleanup(): def test_lambda_closure_cleanup():

View File

@ -85,7 +85,7 @@ TEST_SUBMODULE(class_, m) {
.def_static("new_instance", &NoConstructor::new_instance, "Return an instance"); .def_static("new_instance", &NoConstructor::new_instance, "Return an instance");
py::class_<NoConstructorNew>(m, "NoConstructorNew") py::class_<NoConstructorNew>(m, "NoConstructorNew")
.def(py::init([](const NoConstructorNew &self) { return self; })) // Need a NOOP __init__ .def(py::init([]() { return nullptr; })) // Need a NOOP __init__
.def_static("__new__", .def_static("__new__",
[](const py::object &) { return NoConstructorNew::new_instance(); }); [](const py::object &) { return NoConstructorNew::new_instance(); });

View File

@ -6,10 +6,7 @@ from pybind11_tests import class_ as m
def test_obj_class_name(): def test_obj_class_name():
if env.PYPY: expected_name = "UserType" if env.PYPY else "pybind11_tests.UserType"
expected_name = "UserType"
else:
expected_name = "pybind11_tests.UserType"
assert m.obj_class_name(UserType(1)) == expected_name assert m.obj_class_name(UserType(1)) == expected_name
assert m.obj_class_name(UserType) == expected_name assert m.obj_class_name(UserType) == expected_name
@ -32,7 +29,7 @@ def test_instance(msg):
assert cstats.alive() == 0 assert cstats.alive() == 0
def test_instance_new(msg): def test_instance_new():
instance = m.NoConstructorNew() # .__new__(m.NoConstructor.__class__) instance = m.NoConstructorNew() # .__new__(m.NoConstructor.__class__)
cstats = ConstructorStats.get(m.NoConstructorNew) cstats = ConstructorStats.get(m.NoConstructorNew)
assert cstats.alive() == 1 assert cstats.alive() == 1
@ -221,7 +218,7 @@ def test_automatic_upcasting():
def test_isinstance(): def test_isinstance():
objects = [tuple(), dict(), m.Pet("Polly", "parrot")] + [m.Dog("Molly")] * 4 objects = [(), {}, m.Pet("Polly", "parrot")] + [m.Dog("Molly")] * 4
expected = (True, True, True, True, True, False, False) expected = (True, True, True, True, True, False, False)
assert m.check_instances(objects) == expected assert m.check_instances(objects) == expected
@ -427,7 +424,7 @@ def test_exception_rvalue_abort():
# https://github.com/pybind/pybind11/issues/1568 # https://github.com/pybind/pybind11/issues/1568
def test_multiple_instances_with_same_pointer(capture): def test_multiple_instances_with_same_pointer():
n = 100 n = 100
instances = [m.SamePointer() for _ in range(n)] instances = [m.SamePointer() for _ in range(n)]
for i in range(n): for i in range(n):

View File

@ -1,6 +1,3 @@
# Built-in in CMake 3.5+
include(CMakeParseArguments)
add_custom_target(test_cmake_build) add_custom_target(test_cmake_build)
function(pybind11_add_build_test name) function(pybind11_add_build_test name)

View File

@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` 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.18) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.18) cmake_policy(VERSION 3.26)
endif() endif()
project(test_installed_embed CXX) project(test_installed_embed CXX)

View File

@ -1,13 +1,13 @@
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
project(test_installed_module CXX) project(test_installed_module CXX)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` 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.18) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.18) cmake_policy(VERSION 3.26)
endif() endif()
project(test_installed_function CXX) project(test_installed_function CXX)

View File

@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` 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.18) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.18) cmake_policy(VERSION 3.26)
endif() endif()
project(test_installed_target CXX) project(test_installed_target CXX)

View File

@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` 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.18) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.18) cmake_policy(VERSION 3.26)
endif() endif()
project(test_subdirectory_embed CXX) project(test_subdirectory_embed CXX)

View File

@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` 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.18) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.18) cmake_policy(VERSION 3.26)
endif() endif()
project(test_subdirectory_function CXX) project(test_subdirectory_function CXX)

View File

@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.4) cmake_minimum_required(VERSION 3.5)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with # The `cmake_minimum_required(VERSION 3.5...3.26)` 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.18) if(${CMAKE_VERSION} VERSION_LESS 3.26)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else() else()
cmake_policy(VERSION 3.18) cmake_policy(VERSION 3.26)
endif() endif()
project(test_subdirectory_target CXX) project(test_subdirectory_target CXX)

View File

@ -3,9 +3,9 @@ import pytest
from pybind11_tests import const_name as m from pybind11_tests import const_name as m
@pytest.mark.parametrize("func", (m.const_name_tests, m.underscore_tests)) @pytest.mark.parametrize("func", [m.const_name_tests, m.underscore_tests])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"selector, expected", ("selector", "expected"),
enumerate( enumerate(
( (
"", "",

View File

@ -148,4 +148,7 @@ TEST_SUBMODULE(constants_and_functions, m) {
py::arg_v("y", 42, "<the answer>"), py::arg_v("y", 42, "<the answer>"),
py::arg_v("z", default_value)); py::arg_v("z", default_value));
}); });
// test noexcept(true) lambda (#4565)
m.def("l1", []() noexcept(true) { return 0; });
} }

View File

@ -50,3 +50,7 @@ def test_function_record_leaks():
m.register_large_capture_with_invalid_arguments(m) m.register_large_capture_with_invalid_arguments(m)
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
m.register_with_raising_repr(m, RaisingRepr()) m.register_with_raising_repr(m, RaisingRepr())
def test_noexcept_lambda():
assert m.l1() == 0

View File

@ -13,6 +13,8 @@
#include "constructor_stats.h" #include "constructor_stats.h"
#include "pybind11_tests.h" #include "pybind11_tests.h"
#include <type_traits>
template <typename derived> template <typename derived>
struct empty { struct empty {
static const derived &get_one() { return instance_; } static const derived &get_one() { return instance_; }
@ -293,3 +295,239 @@ TEST_SUBMODULE(copy_move_policies, m) {
// Make sure that cast from pytype rvalue to other pytype works // Make sure that cast from pytype rvalue to other pytype works
m.def("get_pytype_rvalue_castissue", [](double i) { return py::float_(i).cast<py::int_>(); }); m.def("get_pytype_rvalue_castissue", [](double i) { return py::float_(i).cast<py::int_>(); });
} }
/*
* Rest of the file:
* static_assert based tests for pybind11 adaptations of
* std::is_move_constructible, std::is_copy_constructible and
* std::is_copy_assignable (no adaptation of std::is_move_assignable).
* Difference between pybind11 and std traits: pybind11 traits will also check
* the contained value_types.
*/
struct NotMovable {
NotMovable() = default;
NotMovable(NotMovable const &) = default;
NotMovable(NotMovable &&) = delete;
NotMovable &operator=(NotMovable const &) = default;
NotMovable &operator=(NotMovable &&) = delete;
};
static_assert(!std::is_move_constructible<NotMovable>::value,
"!std::is_move_constructible<NotMovable>::value");
static_assert(std::is_copy_constructible<NotMovable>::value,
"std::is_copy_constructible<NotMovable>::value");
static_assert(!pybind11::detail::is_move_constructible<NotMovable>::value,
"!pybind11::detail::is_move_constructible<NotMovable>::value");
static_assert(pybind11::detail::is_copy_constructible<NotMovable>::value,
"pybind11::detail::is_copy_constructible<NotMovable>::value");
static_assert(!std::is_move_assignable<NotMovable>::value,
"!std::is_move_assignable<NotMovable>::value");
static_assert(std::is_copy_assignable<NotMovable>::value,
"std::is_copy_assignable<NotMovable>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotMovable>::value,
// "!pybind11::detail::is_move_assignable<NotMovable>::value");
static_assert(pybind11::detail::is_copy_assignable<NotMovable>::value,
"pybind11::detail::is_copy_assignable<NotMovable>::value");
struct NotCopyable {
NotCopyable() = default;
NotCopyable(NotCopyable const &) = delete;
NotCopyable(NotCopyable &&) = default;
NotCopyable &operator=(NotCopyable const &) = delete;
NotCopyable &operator=(NotCopyable &&) = default;
};
static_assert(std::is_move_constructible<NotCopyable>::value,
"std::is_move_constructible<NotCopyable>::value");
static_assert(!std::is_copy_constructible<NotCopyable>::value,
"!std::is_copy_constructible<NotCopyable>::value");
static_assert(pybind11::detail::is_move_constructible<NotCopyable>::value,
"pybind11::detail::is_move_constructible<NotCopyable>::value");
static_assert(!pybind11::detail::is_copy_constructible<NotCopyable>::value,
"!pybind11::detail::is_copy_constructible<NotCopyable>::value");
static_assert(std::is_move_assignable<NotCopyable>::value,
"std::is_move_assignable<NotCopyable>::value");
static_assert(!std::is_copy_assignable<NotCopyable>::value,
"!std::is_copy_assignable<NotCopyable>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotCopyable>::value,
// "!pybind11::detail::is_move_assignable<NotCopyable>::value");
static_assert(!pybind11::detail::is_copy_assignable<NotCopyable>::value,
"!pybind11::detail::is_copy_assignable<NotCopyable>::value");
struct NotCopyableNotMovable {
NotCopyableNotMovable() = default;
NotCopyableNotMovable(NotCopyableNotMovable const &) = delete;
NotCopyableNotMovable(NotCopyableNotMovable &&) = delete;
NotCopyableNotMovable &operator=(NotCopyableNotMovable const &) = delete;
NotCopyableNotMovable &operator=(NotCopyableNotMovable &&) = delete;
};
static_assert(!std::is_move_constructible<NotCopyableNotMovable>::value,
"!std::is_move_constructible<NotCopyableNotMovable>::value");
static_assert(!std::is_copy_constructible<NotCopyableNotMovable>::value,
"!std::is_copy_constructible<NotCopyableNotMovable>::value");
static_assert(!pybind11::detail::is_move_constructible<NotCopyableNotMovable>::value,
"!pybind11::detail::is_move_constructible<NotCopyableNotMovable>::value");
static_assert(!pybind11::detail::is_copy_constructible<NotCopyableNotMovable>::value,
"!pybind11::detail::is_copy_constructible<NotCopyableNotMovable>::value");
static_assert(!std::is_move_assignable<NotCopyableNotMovable>::value,
"!std::is_move_assignable<NotCopyableNotMovable>::value");
static_assert(!std::is_copy_assignable<NotCopyableNotMovable>::value,
"!std::is_copy_assignable<NotCopyableNotMovable>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotCopyableNotMovable>::value,
// "!pybind11::detail::is_move_assignable<NotCopyableNotMovable>::value");
static_assert(!pybind11::detail::is_copy_assignable<NotCopyableNotMovable>::value,
"!pybind11::detail::is_copy_assignable<NotCopyableNotMovable>::value");
struct NotMovableVector : std::vector<NotMovable> {};
static_assert(std::is_move_constructible<NotMovableVector>::value,
"std::is_move_constructible<NotMovableVector>::value");
static_assert(std::is_copy_constructible<NotMovableVector>::value,
"std::is_copy_constructible<NotMovableVector>::value");
static_assert(!pybind11::detail::is_move_constructible<NotMovableVector>::value,
"!pybind11::detail::is_move_constructible<NotMovableVector>::value");
static_assert(pybind11::detail::is_copy_constructible<NotMovableVector>::value,
"pybind11::detail::is_copy_constructible<NotMovableVector>::value");
static_assert(std::is_move_assignable<NotMovableVector>::value,
"std::is_move_assignable<NotMovableVector>::value");
static_assert(std::is_copy_assignable<NotMovableVector>::value,
"std::is_copy_assignable<NotMovableVector>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotMovableVector>::value,
// "!pybind11::detail::is_move_assignable<NotMovableVector>::value");
static_assert(pybind11::detail::is_copy_assignable<NotMovableVector>::value,
"pybind11::detail::is_copy_assignable<NotMovableVector>::value");
struct NotCopyableVector : std::vector<NotCopyable> {};
static_assert(std::is_move_constructible<NotCopyableVector>::value,
"std::is_move_constructible<NotCopyableVector>::value");
static_assert(std::is_copy_constructible<NotCopyableVector>::value,
"std::is_copy_constructible<NotCopyableVector>::value");
static_assert(pybind11::detail::is_move_constructible<NotCopyableVector>::value,
"pybind11::detail::is_move_constructible<NotCopyableVector>::value");
static_assert(!pybind11::detail::is_copy_constructible<NotCopyableVector>::value,
"!pybind11::detail::is_copy_constructible<NotCopyableVector>::value");
static_assert(std::is_move_assignable<NotCopyableVector>::value,
"std::is_move_assignable<NotCopyableVector>::value");
static_assert(std::is_copy_assignable<NotCopyableVector>::value,
"std::is_copy_assignable<NotCopyableVector>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotCopyableVector>::value,
// "!pybind11::detail::is_move_assignable<NotCopyableVector>::value");
static_assert(!pybind11::detail::is_copy_assignable<NotCopyableVector>::value,
"!pybind11::detail::is_copy_assignable<NotCopyableVector>::value");
struct NotCopyableNotMovableVector : std::vector<NotCopyableNotMovable> {};
static_assert(std::is_move_constructible<NotCopyableNotMovableVector>::value,
"std::is_move_constructible<NotCopyableNotMovableVector>::value");
static_assert(std::is_copy_constructible<NotCopyableNotMovableVector>::value,
"std::is_copy_constructible<NotCopyableNotMovableVector>::value");
static_assert(!pybind11::detail::is_move_constructible<NotCopyableNotMovableVector>::value,
"!pybind11::detail::is_move_constructible<NotCopyableNotMovableVector>::value");
static_assert(!pybind11::detail::is_copy_constructible<NotCopyableNotMovableVector>::value,
"!pybind11::detail::is_copy_constructible<NotCopyableNotMovableVector>::value");
static_assert(std::is_move_assignable<NotCopyableNotMovableVector>::value,
"std::is_move_assignable<NotCopyableNotMovableVector>::value");
static_assert(std::is_copy_assignable<NotCopyableNotMovableVector>::value,
"std::is_copy_assignable<NotCopyableNotMovableVector>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotCopyableNotMovableVector>::value,
// "!pybind11::detail::is_move_assignable<NotCopyableNotMovableVector>::value");
static_assert(!pybind11::detail::is_copy_assignable<NotCopyableNotMovableVector>::value,
"!pybind11::detail::is_copy_assignable<NotCopyableNotMovableVector>::value");
struct NotMovableMap : std::map<int, NotMovable> {};
static_assert(std::is_move_constructible<NotMovableMap>::value,
"std::is_move_constructible<NotMovableMap>::value");
static_assert(std::is_copy_constructible<NotMovableMap>::value,
"std::is_copy_constructible<NotMovableMap>::value");
static_assert(!pybind11::detail::is_move_constructible<NotMovableMap>::value,
"!pybind11::detail::is_move_constructible<NotMovableMap>::value");
static_assert(pybind11::detail::is_copy_constructible<NotMovableMap>::value,
"pybind11::detail::is_copy_constructible<NotMovableMap>::value");
static_assert(std::is_move_assignable<NotMovableMap>::value,
"std::is_move_assignable<NotMovableMap>::value");
static_assert(std::is_copy_assignable<NotMovableMap>::value,
"std::is_copy_assignable<NotMovableMap>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotMovableMap>::value,
// "!pybind11::detail::is_move_assignable<NotMovableMap>::value");
static_assert(pybind11::detail::is_copy_assignable<NotMovableMap>::value,
"pybind11::detail::is_copy_assignable<NotMovableMap>::value");
struct NotCopyableMap : std::map<int, NotCopyable> {};
static_assert(std::is_move_constructible<NotCopyableMap>::value,
"std::is_move_constructible<NotCopyableMap>::value");
static_assert(std::is_copy_constructible<NotCopyableMap>::value,
"std::is_copy_constructible<NotCopyableMap>::value");
static_assert(pybind11::detail::is_move_constructible<NotCopyableMap>::value,
"pybind11::detail::is_move_constructible<NotCopyableMap>::value");
static_assert(!pybind11::detail::is_copy_constructible<NotCopyableMap>::value,
"!pybind11::detail::is_copy_constructible<NotCopyableMap>::value");
static_assert(std::is_move_assignable<NotCopyableMap>::value,
"std::is_move_assignable<NotCopyableMap>::value");
static_assert(std::is_copy_assignable<NotCopyableMap>::value,
"std::is_copy_assignable<NotCopyableMap>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotCopyableMap>::value,
// "!pybind11::detail::is_move_assignable<NotCopyableMap>::value");
static_assert(!pybind11::detail::is_copy_assignable<NotCopyableMap>::value,
"!pybind11::detail::is_copy_assignable<NotCopyableMap>::value");
struct NotCopyableNotMovableMap : std::map<int, NotCopyableNotMovable> {};
static_assert(std::is_move_constructible<NotCopyableNotMovableMap>::value,
"std::is_move_constructible<NotCopyableNotMovableMap>::value");
static_assert(std::is_copy_constructible<NotCopyableNotMovableMap>::value,
"std::is_copy_constructible<NotCopyableNotMovableMap>::value");
static_assert(!pybind11::detail::is_move_constructible<NotCopyableNotMovableMap>::value,
"!pybind11::detail::is_move_constructible<NotCopyableNotMovableMap>::value");
static_assert(!pybind11::detail::is_copy_constructible<NotCopyableNotMovableMap>::value,
"!pybind11::detail::is_copy_constructible<NotCopyableNotMovableMap>::value");
static_assert(std::is_move_assignable<NotCopyableNotMovableMap>::value,
"std::is_move_assignable<NotCopyableNotMovableMap>::value");
static_assert(std::is_copy_assignable<NotCopyableNotMovableMap>::value,
"std::is_copy_assignable<NotCopyableNotMovableMap>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<NotCopyableNotMovableMap>::value,
// "!pybind11::detail::is_move_assignable<NotCopyableNotMovableMap>::value");
static_assert(!pybind11::detail::is_copy_assignable<NotCopyableNotMovableMap>::value,
"!pybind11::detail::is_copy_assignable<NotCopyableNotMovableMap>::value");
struct RecursiveVector : std::vector<RecursiveVector> {};
static_assert(std::is_move_constructible<RecursiveVector>::value,
"std::is_move_constructible<RecursiveVector>::value");
static_assert(std::is_copy_constructible<RecursiveVector>::value,
"std::is_copy_constructible<RecursiveVector>::value");
static_assert(pybind11::detail::is_move_constructible<RecursiveVector>::value,
"pybind11::detail::is_move_constructible<RecursiveVector>::value");
static_assert(pybind11::detail::is_copy_constructible<RecursiveVector>::value,
"pybind11::detail::is_copy_constructible<RecursiveVector>::value");
static_assert(std::is_move_assignable<RecursiveVector>::value,
"std::is_move_assignable<RecursiveVector>::value");
static_assert(std::is_copy_assignable<RecursiveVector>::value,
"std::is_copy_assignable<RecursiveVector>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<RecursiveVector>::value,
// "!pybind11::detail::is_move_assignable<RecursiveVector>::value");
static_assert(pybind11::detail::is_copy_assignable<RecursiveVector>::value,
"pybind11::detail::is_copy_assignable<RecursiveVector>::value");
struct RecursiveMap : std::map<int, RecursiveMap> {};
static_assert(std::is_move_constructible<RecursiveMap>::value,
"std::is_move_constructible<RecursiveMap>::value");
static_assert(std::is_copy_constructible<RecursiveMap>::value,
"std::is_copy_constructible<RecursiveMap>::value");
static_assert(pybind11::detail::is_move_constructible<RecursiveMap>::value,
"pybind11::detail::is_move_constructible<RecursiveMap>::value");
static_assert(pybind11::detail::is_copy_constructible<RecursiveMap>::value,
"pybind11::detail::is_copy_constructible<RecursiveMap>::value");
static_assert(std::is_move_assignable<RecursiveMap>::value,
"std::is_move_assignable<RecursiveMap>::value");
static_assert(std::is_copy_assignable<RecursiveMap>::value,
"std::is_copy_assignable<RecursiveMap>::value");
// pybind11 does not have this
// static_assert(!pybind11::detail::is_move_assignable<RecursiveMap>::value,
// "!pybind11::detail::is_move_assignable<RecursiveMap>::value");
static_assert(pybind11::detail::is_copy_assignable<RecursiveMap>::value,
"pybind11::detail::is_copy_assignable<RecursiveMap>::value");

View File

@ -100,7 +100,8 @@ def test_custom_caster_destruction():
cstats = m.destruction_tester_cstats() cstats = m.destruction_tester_cstats()
# This one *doesn't* have take_ownership: the pointer should be used but not destroyed: # This one *doesn't* have take_ownership: the pointer should be used but not destroyed:
z = m.custom_caster_no_destroy() z = m.custom_caster_no_destroy()
assert cstats.alive() == 1 and cstats.default_constructions == 1 assert cstats.alive() == 1
assert cstats.default_constructions == 1
assert z assert z
# take_ownership applied: this constructs a new object, casts it, then destroys it: # take_ownership applied: this constructs a new object, casts it, then destroys it:

View File

@ -7,7 +7,7 @@ import env # noqa: F401
from pybind11_tests import custom_type_setup as m from pybind11_tests import custom_type_setup as m
@pytest.fixture @pytest.fixture()
def gc_tester(): def gc_tester():
"""Tests that an object is garbage collected. """Tests that an object is garbage collected.

View File

@ -263,79 +263,96 @@ def test_eigen_return_references():
primary = np.ones((10, 10)) primary = np.ones((10, 10))
a = m.ReturnTester() a = m.ReturnTester()
a_get1 = a.get() a_get1 = a.get()
assert not a_get1.flags.owndata and a_get1.flags.writeable assert not a_get1.flags.owndata
assert a_get1.flags.writeable
assign_both(a_get1, primary, 3, 3, 5) assign_both(a_get1, primary, 3, 3, 5)
a_get2 = a.get_ptr() a_get2 = a.get_ptr()
assert not a_get2.flags.owndata and a_get2.flags.writeable assert not a_get2.flags.owndata
assert a_get2.flags.writeable
assign_both(a_get1, primary, 2, 3, 6) assign_both(a_get1, primary, 2, 3, 6)
a_view1 = a.view() a_view1 = a.view()
assert not a_view1.flags.owndata and not a_view1.flags.writeable assert not a_view1.flags.owndata
assert not a_view1.flags.writeable
with pytest.raises(ValueError): with pytest.raises(ValueError):
a_view1[2, 3] = 4 a_view1[2, 3] = 4
a_view2 = a.view_ptr() a_view2 = a.view_ptr()
assert not a_view2.flags.owndata and not a_view2.flags.writeable assert not a_view2.flags.owndata
assert not a_view2.flags.writeable
with pytest.raises(ValueError): with pytest.raises(ValueError):
a_view2[2, 3] = 4 a_view2[2, 3] = 4
a_copy1 = a.copy_get() a_copy1 = a.copy_get()
assert a_copy1.flags.owndata and a_copy1.flags.writeable assert a_copy1.flags.owndata
assert a_copy1.flags.writeable
np.testing.assert_array_equal(a_copy1, primary) np.testing.assert_array_equal(a_copy1, primary)
a_copy1[7, 7] = -44 # Shouldn't affect anything else a_copy1[7, 7] = -44 # Shouldn't affect anything else
c1want = array_copy_but_one(primary, 7, 7, -44) c1want = array_copy_but_one(primary, 7, 7, -44)
a_copy2 = a.copy_view() a_copy2 = a.copy_view()
assert a_copy2.flags.owndata and a_copy2.flags.writeable assert a_copy2.flags.owndata
assert a_copy2.flags.writeable
np.testing.assert_array_equal(a_copy2, primary) np.testing.assert_array_equal(a_copy2, primary)
a_copy2[4, 4] = -22 # Shouldn't affect anything else a_copy2[4, 4] = -22 # Shouldn't affect anything else
c2want = array_copy_but_one(primary, 4, 4, -22) c2want = array_copy_but_one(primary, 4, 4, -22)
a_ref1 = a.ref() a_ref1 = a.ref()
assert not a_ref1.flags.owndata and a_ref1.flags.writeable assert not a_ref1.flags.owndata
assert a_ref1.flags.writeable
assign_both(a_ref1, primary, 1, 1, 15) assign_both(a_ref1, primary, 1, 1, 15)
a_ref2 = a.ref_const() a_ref2 = a.ref_const()
assert not a_ref2.flags.owndata and not a_ref2.flags.writeable assert not a_ref2.flags.owndata
assert not a_ref2.flags.writeable
with pytest.raises(ValueError): with pytest.raises(ValueError):
a_ref2[5, 5] = 33 a_ref2[5, 5] = 33
a_ref3 = a.ref_safe() a_ref3 = a.ref_safe()
assert not a_ref3.flags.owndata and a_ref3.flags.writeable assert not a_ref3.flags.owndata
assert a_ref3.flags.writeable
assign_both(a_ref3, primary, 0, 7, 99) assign_both(a_ref3, primary, 0, 7, 99)
a_ref4 = a.ref_const_safe() a_ref4 = a.ref_const_safe()
assert not a_ref4.flags.owndata and not a_ref4.flags.writeable assert not a_ref4.flags.owndata
assert not a_ref4.flags.writeable
with pytest.raises(ValueError): with pytest.raises(ValueError):
a_ref4[7, 0] = 987654321 a_ref4[7, 0] = 987654321
a_copy3 = a.copy_ref() a_copy3 = a.copy_ref()
assert a_copy3.flags.owndata and a_copy3.flags.writeable assert a_copy3.flags.owndata
assert a_copy3.flags.writeable
np.testing.assert_array_equal(a_copy3, primary) np.testing.assert_array_equal(a_copy3, primary)
a_copy3[8, 1] = 11 a_copy3[8, 1] = 11
c3want = array_copy_but_one(primary, 8, 1, 11) c3want = array_copy_but_one(primary, 8, 1, 11)
a_copy4 = a.copy_ref_const() a_copy4 = a.copy_ref_const()
assert a_copy4.flags.owndata and a_copy4.flags.writeable assert a_copy4.flags.owndata
assert a_copy4.flags.writeable
np.testing.assert_array_equal(a_copy4, primary) np.testing.assert_array_equal(a_copy4, primary)
a_copy4[8, 4] = 88 a_copy4[8, 4] = 88
c4want = array_copy_but_one(primary, 8, 4, 88) c4want = array_copy_but_one(primary, 8, 4, 88)
a_block1 = a.block(3, 3, 2, 2) a_block1 = a.block(3, 3, 2, 2)
assert not a_block1.flags.owndata and a_block1.flags.writeable assert not a_block1.flags.owndata
assert a_block1.flags.writeable
a_block1[0, 0] = 55 a_block1[0, 0] = 55
primary[3, 3] = 55 primary[3, 3] = 55
a_block2 = a.block_safe(2, 2, 3, 2) a_block2 = a.block_safe(2, 2, 3, 2)
assert not a_block2.flags.owndata and a_block2.flags.writeable assert not a_block2.flags.owndata
assert a_block2.flags.writeable
a_block2[2, 1] = -123 a_block2[2, 1] = -123
primary[4, 3] = -123 primary[4, 3] = -123
a_block3 = a.block_const(6, 7, 4, 3) a_block3 = a.block_const(6, 7, 4, 3)
assert not a_block3.flags.owndata and not a_block3.flags.writeable assert not a_block3.flags.owndata
assert not a_block3.flags.writeable
with pytest.raises(ValueError): with pytest.raises(ValueError):
a_block3[2, 2] = -44444 a_block3[2, 2] = -44444
a_copy5 = a.copy_block(2, 2, 2, 3) a_copy5 = a.copy_block(2, 2, 2, 3)
assert a_copy5.flags.owndata and a_copy5.flags.writeable assert a_copy5.flags.owndata
assert a_copy5.flags.writeable
np.testing.assert_array_equal(a_copy5, primary[2:4, 2:5]) np.testing.assert_array_equal(a_copy5, primary[2:4, 2:5])
a_copy5[1, 1] = 777 a_copy5[1, 1] = 777
c5want = array_copy_but_one(primary[2:4, 2:5], 1, 1, 777) c5want = array_copy_but_one(primary[2:4, 2:5], 1, 1, 777)
a_corn1 = a.corners() a_corn1 = a.corners()
assert not a_corn1.flags.owndata and a_corn1.flags.writeable assert not a_corn1.flags.owndata
assert a_corn1.flags.writeable
a_corn1 *= 50 a_corn1 *= 50
a_corn1[1, 1] = 999 a_corn1[1, 1] = 999
primary[0, 0] = 50 primary[0, 0] = 50
@ -343,7 +360,8 @@ def test_eigen_return_references():
primary[9, 0] = 50 primary[9, 0] = 50
primary[9, 9] = 999 primary[9, 9] = 999
a_corn2 = a.corners_const() a_corn2 = a.corners_const()
assert not a_corn2.flags.owndata and not a_corn2.flags.writeable assert not a_corn2.flags.owndata
assert not a_corn2.flags.writeable
with pytest.raises(ValueError): with pytest.raises(ValueError):
a_corn2[1, 0] = 51 a_corn2[1, 0] = 51
@ -503,10 +521,14 @@ def test_numpy_ref_mutators():
assert [zc[1, 2], zcro[1, 2], zr[1, 2], zrro[1, 2]] == [23] * 4 assert [zc[1, 2], zcro[1, 2], zr[1, 2], zrro[1, 2]] == [23] * 4
assert not zc.flags.owndata and zc.flags.writeable assert not zc.flags.owndata
assert not zr.flags.owndata and zr.flags.writeable assert zc.flags.writeable
assert not zcro.flags.owndata and not zcro.flags.writeable assert not zr.flags.owndata
assert not zrro.flags.owndata and not zrro.flags.writeable assert zr.flags.writeable
assert not zcro.flags.owndata
assert not zcro.flags.writeable
assert not zrro.flags.owndata
assert not zrro.flags.writeable
zc[1, 2] = 99 zc[1, 2] = 99
expect = np.array([[11.0, 12, 13], [21, 22, 99], [31, 32, 33]]) expect = np.array([[11.0, 12, 13], [21, 22, 99], [31, 32, 33]])
@ -530,7 +552,8 @@ def test_numpy_ref_mutators():
# the const should drop away) # the const should drop away)
y1 = np.array(m.get_cm_const_ref()) y1 = np.array(m.get_cm_const_ref())
assert y1.flags.owndata and y1.flags.writeable assert y1.flags.owndata
assert y1.flags.writeable
# We should get copies of the eigen data, which was modified above: # We should get copies of the eigen data, which was modified above:
assert y1[1, 2] == 99 assert y1[1, 2] == 99
y1[1, 2] += 12 y1[1, 2] += 12
@ -603,38 +626,38 @@ def test_nocopy_wrapper():
# All but the second should fail with m.get_elem_nocopy: # All but the second should fail with m.get_elem_nocopy:
with pytest.raises(TypeError) as excinfo: with pytest.raises(TypeError) as excinfo:
m.get_elem_nocopy(int_matrix_colmajor) m.get_elem_nocopy(int_matrix_colmajor)
assert "get_elem_nocopy(): incompatible function arguments." in str( assert "get_elem_nocopy(): incompatible function arguments." in str(excinfo.value)
excinfo.value assert ", flags.f_contiguous" in str(excinfo.value)
) and ", flags.f_contiguous" in str(excinfo.value)
assert m.get_elem_nocopy(dbl_matrix_colmajor) == 8 assert m.get_elem_nocopy(dbl_matrix_colmajor) == 8
with pytest.raises(TypeError) as excinfo: with pytest.raises(TypeError) as excinfo:
m.get_elem_nocopy(int_matrix_rowmajor) m.get_elem_nocopy(int_matrix_rowmajor)
assert "get_elem_nocopy(): incompatible function arguments." in str( assert "get_elem_nocopy(): incompatible function arguments." in str(excinfo.value)
excinfo.value assert ", flags.f_contiguous" in str(excinfo.value)
) and ", flags.f_contiguous" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo: with pytest.raises(TypeError) as excinfo:
m.get_elem_nocopy(dbl_matrix_rowmajor) m.get_elem_nocopy(dbl_matrix_rowmajor)
assert "get_elem_nocopy(): incompatible function arguments." in str( assert "get_elem_nocopy(): incompatible function arguments." in str(excinfo.value)
excinfo.value assert ", flags.f_contiguous" in str(excinfo.value)
) and ", flags.f_contiguous" in str(excinfo.value)
# For the row-major test, we take a long matrix in row-major, so only the third is allowed: # For the row-major test, we take a long matrix in row-major, so only the third is allowed:
with pytest.raises(TypeError) as excinfo: with pytest.raises(TypeError) as excinfo:
m.get_elem_rm_nocopy(int_matrix_colmajor) m.get_elem_rm_nocopy(int_matrix_colmajor)
assert "get_elem_rm_nocopy(): incompatible function arguments." in str( assert "get_elem_rm_nocopy(): incompatible function arguments." in str(
excinfo.value excinfo.value
) and ", flags.c_contiguous" in str(excinfo.value) )
assert ", flags.c_contiguous" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo: with pytest.raises(TypeError) as excinfo:
m.get_elem_rm_nocopy(dbl_matrix_colmajor) m.get_elem_rm_nocopy(dbl_matrix_colmajor)
assert "get_elem_rm_nocopy(): incompatible function arguments." in str( assert "get_elem_rm_nocopy(): incompatible function arguments." in str(
excinfo.value excinfo.value
) and ", flags.c_contiguous" in str(excinfo.value) )
assert ", flags.c_contiguous" in str(excinfo.value)
assert m.get_elem_rm_nocopy(int_matrix_rowmajor) == 8 assert m.get_elem_rm_nocopy(int_matrix_rowmajor) == 8
with pytest.raises(TypeError) as excinfo: with pytest.raises(TypeError) as excinfo:
m.get_elem_rm_nocopy(dbl_matrix_rowmajor) m.get_elem_rm_nocopy(dbl_matrix_rowmajor)
assert "get_elem_rm_nocopy(): incompatible function arguments." in str( assert "get_elem_rm_nocopy(): incompatible function arguments." in str(
excinfo.value excinfo.value
) and ", flags.c_contiguous" in str(excinfo.value) )
assert ", flags.c_contiguous" in str(excinfo.value)
def test_eigen_ref_life_support(): def test_eigen_ref_life_support():

View File

@ -11,14 +11,15 @@ try:
submodules += [avoid.c_style, avoid.f_style] submodules += [avoid.c_style, avoid.f_style]
except ImportError as e: except ImportError as e:
# Ensure config, build, toolchain, etc. issues are not masked here: # Ensure config, build, toolchain, etc. issues are not masked here:
raise RuntimeError( msg = (
"import eigen_tensor_avoid_stl_array FAILED, while " "import eigen_tensor_avoid_stl_array FAILED, while "
"import pybind11_tests.eigen_tensor succeeded. " "import pybind11_tests.eigen_tensor succeeded. "
"Please ensure that " "Please ensure that "
"test_eigen_tensor.cpp & " "test_eigen_tensor.cpp & "
"eigen_tensor_avoid_stl_array.cpp " "eigen_tensor_avoid_stl_array.cpp "
"are built together (or both are not built if Eigen is not available)." "are built together (or both are not built if Eigen is not available)."
) from e )
raise RuntimeError(msg) from e
tensor_ref = np.empty((3, 5, 2), dtype=np.int64) tensor_ref = np.empty((3, 5, 2), dtype=np.int64)
@ -147,10 +148,7 @@ def test_bad_python_to_cpp_casts(m):
m.round_trip_tensor_noconvert(tensor_ref.astype(np.float64)) m.round_trip_tensor_noconvert(tensor_ref.astype(np.float64))
) )
if m.needed_options == "F": bad_options = "C" if m.needed_options == "F" else "F"
bad_options = "C"
else:
bad_options = "F"
# Shape, dtype and the order need to be correct for a TensorMap cast # Shape, dtype and the order need to be correct for a TensorMap cast
with pytest.raises( with pytest.raises(
TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments" TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments"
@ -173,19 +171,19 @@ def test_bad_python_to_cpp_casts(m):
np.zeros((3, 5), dtype=np.float64, order=m.needed_options) np.zeros((3, 5), dtype=np.float64, order=m.needed_options)
) )
temp = np.zeros((3, 5, 2), dtype=np.float64, order=m.needed_options)
with pytest.raises( with pytest.raises(
TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments" TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments"
): ):
temp = np.zeros((3, 5, 2), dtype=np.float64, order=m.needed_options)
m.round_trip_view_tensor( m.round_trip_view_tensor(
temp[:, ::-1, :], temp[:, ::-1, :],
) )
temp = np.zeros((3, 5, 2), dtype=np.float64, order=m.needed_options)
temp.setflags(write=False)
with pytest.raises( with pytest.raises(
TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments" TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments"
): ):
temp = np.zeros((3, 5, 2), dtype=np.float64, order=m.needed_options)
temp.setflags(write=False)
m.round_trip_view_tensor(temp) m.round_trip_view_tensor(temp)
@ -282,9 +280,9 @@ def test_doc_string(m, doc):
order_flag = f"flags.{m.needed_options.lower()}_contiguous" order_flag = f"flags.{m.needed_options.lower()}_contiguous"
assert doc(m.round_trip_view_tensor) == ( assert doc(m.round_trip_view_tensor) == (
f"round_trip_view_tensor(arg0: numpy.ndarray[numpy.float64[?, ?, ?], flags.writeable, {order_flag}])" f"round_trip_view_tensor(arg0: numpy.ndarray[numpy.float64[?, ?, ?], flags.writeable, {order_flag}])"
+ f" -> numpy.ndarray[numpy.float64[?, ?, ?], flags.writeable, {order_flag}]" f" -> numpy.ndarray[numpy.float64[?, ?, ?], flags.writeable, {order_flag}]"
) )
assert doc(m.round_trip_const_view_tensor) == ( assert doc(m.round_trip_const_view_tensor) == (
f"round_trip_const_view_tensor(arg0: numpy.ndarray[numpy.float64[?, ?, ?], {order_flag}])" f"round_trip_const_view_tensor(arg0: numpy.ndarray[numpy.float64[?, ?, ?], {order_flag}])"
+ " -> numpy.ndarray[numpy.float64[?, ?, ?]]" " -> numpy.ndarray[numpy.float64[?, ?, ?]]"
) )

View File

@ -184,7 +184,7 @@ TEST_CASE("Custom PyConfig") {
py::initialize_interpreter(); py::initialize_interpreter();
} }
TEST_CASE("Custom PyConfig with argv") { TEST_CASE("scoped_interpreter with PyConfig_InitIsolatedConfig and argv") {
py::finalize_interpreter(); py::finalize_interpreter();
{ {
PyConfig config; PyConfig config;
@ -199,6 +199,26 @@ TEST_CASE("Custom PyConfig with argv") {
} }
py::initialize_interpreter(); py::initialize_interpreter();
} }
TEST_CASE("scoped_interpreter with PyConfig_InitPythonConfig and argv") {
py::finalize_interpreter();
{
PyConfig config;
PyConfig_InitPythonConfig(&config);
// `initialize_interpreter() overrides the default value for config.parse_argv (`1`) by
// changing it to `0`. This test exercises `scoped_interpreter` with the default config.
char *argv[] = {strdup("a.out"), strdup("arg1")};
py::scoped_interpreter argv_scope(&config, 2, argv);
std::free(argv[0]);
std::free(argv[1]);
auto module = py::module::import("test_interpreter");
auto py_widget = module.attr("DerivedWidget")("The question");
const auto &cpp_widget = py_widget.cast<const Widget &>();
REQUIRE(cpp_widget.argv0() == "arg1");
}
py::initialize_interpreter();
}
#endif #endif
TEST_CASE("Add program dir to path pre-PyConfig") { TEST_CASE("Add program dir to path pre-PyConfig") {
@ -235,10 +255,10 @@ TEST_CASE("Add program dir to path using PyConfig") {
} }
#endif #endif
bool has_pybind11_internals_builtin() { bool has_state_dict_internals_obj() {
auto builtins = py::handle(PyEval_GetBuiltins()); return bool(
return builtins.contains(PYBIND11_INTERNALS_ID); py::detail::get_internals_obj_from_state_dict(py::detail::get_python_state_dict()));
}; }
bool has_pybind11_internals_static() { bool has_pybind11_internals_static() {
auto **&ipp = py::detail::get_internals_pp(); auto **&ipp = py::detail::get_internals_pp();
@ -248,7 +268,7 @@ bool has_pybind11_internals_static() {
TEST_CASE("Restart the interpreter") { TEST_CASE("Restart the interpreter") {
// Verify pre-restart state. // Verify pre-restart state.
REQUIRE(py::module_::import("widget_module").attr("add")(1, 2).cast<int>() == 3); REQUIRE(py::module_::import("widget_module").attr("add")(1, 2).cast<int>() == 3);
REQUIRE(has_pybind11_internals_builtin()); REQUIRE(has_state_dict_internals_obj());
REQUIRE(has_pybind11_internals_static()); REQUIRE(has_pybind11_internals_static());
REQUIRE(py::module_::import("external_module").attr("A")(123).attr("value").cast<int>() REQUIRE(py::module_::import("external_module").attr("A")(123).attr("value").cast<int>()
== 123); == 123);
@ -265,10 +285,10 @@ TEST_CASE("Restart the interpreter") {
REQUIRE(Py_IsInitialized() == 1); REQUIRE(Py_IsInitialized() == 1);
// Internals are deleted after a restart. // Internals are deleted after a restart.
REQUIRE_FALSE(has_pybind11_internals_builtin()); REQUIRE_FALSE(has_state_dict_internals_obj());
REQUIRE_FALSE(has_pybind11_internals_static()); REQUIRE_FALSE(has_pybind11_internals_static());
pybind11::detail::get_internals(); pybind11::detail::get_internals();
REQUIRE(has_pybind11_internals_builtin()); REQUIRE(has_state_dict_internals_obj());
REQUIRE(has_pybind11_internals_static()); REQUIRE(has_pybind11_internals_static());
REQUIRE(reinterpret_cast<uintptr_t>(*py::detail::get_internals_pp()) REQUIRE(reinterpret_cast<uintptr_t>(*py::detail::get_internals_pp())
== py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>()); == py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>());
@ -283,13 +303,13 @@ TEST_CASE("Restart the interpreter") {
py::detail::get_internals(); py::detail::get_internals();
*static_cast<bool *>(ran) = true; *static_cast<bool *>(ran) = true;
}); });
REQUIRE_FALSE(has_pybind11_internals_builtin()); REQUIRE_FALSE(has_state_dict_internals_obj());
REQUIRE_FALSE(has_pybind11_internals_static()); REQUIRE_FALSE(has_pybind11_internals_static());
REQUIRE_FALSE(ran); REQUIRE_FALSE(ran);
py::finalize_interpreter(); py::finalize_interpreter();
REQUIRE(ran); REQUIRE(ran);
py::initialize_interpreter(); py::initialize_interpreter();
REQUIRE_FALSE(has_pybind11_internals_builtin()); REQUIRE_FALSE(has_state_dict_internals_obj());
REQUIRE_FALSE(has_pybind11_internals_static()); REQUIRE_FALSE(has_pybind11_internals_static());
// C++ modules can be reloaded. // C++ modules can be reloaded.
@ -311,7 +331,7 @@ TEST_CASE("Subinterpreter") {
REQUIRE(m.attr("add")(1, 2).cast<int>() == 3); REQUIRE(m.attr("add")(1, 2).cast<int>() == 3);
} }
REQUIRE(has_pybind11_internals_builtin()); REQUIRE(has_state_dict_internals_obj());
REQUIRE(has_pybind11_internals_static()); REQUIRE(has_pybind11_internals_static());
/// Create and switch to a subinterpreter. /// Create and switch to a subinterpreter.
@ -321,7 +341,7 @@ TEST_CASE("Subinterpreter") {
// Subinterpreters get their own copy of builtins. detail::get_internals() still // Subinterpreters get their own copy of builtins. detail::get_internals() still
// works by returning from the static variable, i.e. all interpreters share a single // works by returning from the static variable, i.e. all interpreters share a single
// global pybind11::internals; // global pybind11::internals;
REQUIRE_FALSE(has_pybind11_internals_builtin()); REQUIRE_FALSE(has_state_dict_internals_obj());
REQUIRE(has_pybind11_internals_static()); REQUIRE(has_pybind11_internals_static());
// Modules tags should be gone. // Modules tags should be gone.

View File

@ -1,3 +1,5 @@
# ruff: noqa: SIM201 SIM300 SIM202
import pytest import pytest
from pybind11_tests import enums as m from pybind11_tests import enums as m

View File

@ -339,4 +339,9 @@ TEST_SUBMODULE(exceptions, m) {
} }
return py::str("UNEXPECTED"); return py::str("UNEXPECTED");
}); });
m.def("test_fn_cast_int", [](const py::function &fn) {
// function returns None instead of int, should give a useful error message
fn().cast<int>();
});
} }

View File

@ -94,8 +94,7 @@ def ignore_pytest_unraisable_warning(f):
if hasattr(pytest, unraisable): # Python >= 3.8 and pytest >= 6 if hasattr(pytest, unraisable): # Python >= 3.8 and pytest >= 6
dec = pytest.mark.filterwarnings(f"ignore::pytest.{unraisable}") dec = pytest.mark.filterwarnings(f"ignore::pytest.{unraisable}")
return dec(f) return dec(f)
else: return f
return f
# TODO: find out why this fails on PyPy, https://foss.heptapod.net/pypy/pypy/-/issues/3583 # TODO: find out why this fails on PyPy, https://foss.heptapod.net/pypy/pypy/-/issues/3583
@ -183,7 +182,7 @@ def test_custom(msg):
m.throws5_1() m.throws5_1()
assert msg(excinfo.value) == "MyException5 subclass" assert msg(excinfo.value) == "MyException5 subclass"
with pytest.raises(m.MyException5) as excinfo: with pytest.raises(m.MyException5) as excinfo: # noqa: PT012
try: try:
m.throws5() m.throws5()
except m.MyException5_1 as err: except m.MyException5_1 as err:
@ -212,7 +211,7 @@ def test_nested_throws(capture):
m.try_catch(m.MyException5, throw_myex) m.try_catch(m.MyException5, throw_myex)
assert str(excinfo.value) == "nested error" assert str(excinfo.value) == "nested error"
def pycatch(exctype, f, *args): def pycatch(exctype, f, *args): # noqa: ARG001
try: try:
f(*args) f(*args)
except m.MyException as e: except m.MyException as e:
@ -303,12 +302,12 @@ class FlakyException(Exception):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"exc_type, exc_value, expected_what", ("exc_type", "exc_value", "expected_what"),
( [
(ValueError, "plain_str", "ValueError: plain_str"), (ValueError, "plain_str", "ValueError: plain_str"),
(ValueError, ("tuple_elem",), "ValueError: tuple_elem"), (ValueError, ("tuple_elem",), "ValueError: tuple_elem"),
(FlakyException, ("happy",), "FlakyException: FlakyException.__str__"), (FlakyException, ("happy",), "FlakyException: FlakyException.__str__"),
), ],
) )
def test_error_already_set_what_with_happy_exceptions( def test_error_already_set_what_with_happy_exceptions(
exc_type, exc_value, expected_what exc_type, exc_value, expected_what
@ -318,8 +317,7 @@ def test_error_already_set_what_with_happy_exceptions(
assert what == expected_what assert what == expected_what
@pytest.mark.skipif("env.PYPY", reason="PyErr_NormalizeException Segmentation fault") def _test_flaky_exception_failure_point_init_before_py_3_12():
def test_flaky_exception_failure_point_init():
with pytest.raises(RuntimeError) as excinfo: with pytest.raises(RuntimeError) as excinfo:
m.error_already_set_what(FlakyException, ("failure_point_init",)) m.error_already_set_what(FlakyException, ("failure_point_init",))
lines = str(excinfo.value).splitlines() lines = str(excinfo.value).splitlines()
@ -333,7 +331,33 @@ def test_flaky_exception_failure_point_init():
# Checking the first two lines of the traceback as formatted in error_string(): # Checking the first two lines of the traceback as formatted in error_string():
assert "test_exceptions.py(" in lines[3] assert "test_exceptions.py(" in lines[3]
assert lines[3].endswith("): __init__") assert lines[3].endswith("): __init__")
assert lines[4].endswith("): test_flaky_exception_failure_point_init") assert lines[4].endswith(
"): _test_flaky_exception_failure_point_init_before_py_3_12"
)
def _test_flaky_exception_failure_point_init_py_3_12():
# Behavior change in Python 3.12: https://github.com/python/cpython/issues/102594
what, py_err_set_after_what = m.error_already_set_what(
FlakyException, ("failure_point_init",)
)
assert not py_err_set_after_what
lines = what.splitlines()
assert lines[0].endswith("ValueError[WITH __notes__]: triggered_failure_point_init")
assert lines[1] == "__notes__ (len=1):"
assert "Normalization failed:" in lines[2]
assert "FlakyException" in lines[2]
@pytest.mark.skipif(
"env.PYPY and sys.version_info[:2] < (3, 12)",
reason="PyErr_NormalizeException Segmentation fault",
)
def test_flaky_exception_failure_point_init():
if sys.version_info[:2] < (3, 12):
_test_flaky_exception_failure_point_init_before_py_3_12()
else:
_test_flaky_exception_failure_point_init_py_3_12()
def test_flaky_exception_failure_point_str(): def test_flaky_exception_failure_point_str():
@ -342,10 +366,7 @@ def test_flaky_exception_failure_point_str():
) )
assert not py_err_set_after_what assert not py_err_set_after_what
lines = what.splitlines() lines = what.splitlines()
if env.PYPY and len(lines) == 3: n = 3 if env.PYPY and len(lines) == 3 else 5
n = 3 # Traceback is missing.
else:
n = 5
assert ( assert (
lines[:n] lines[:n]
== [ == [
@ -381,3 +402,12 @@ def test_pypy_oserror_normalization():
# https://github.com/pybind/pybind11/issues/4075 # https://github.com/pybind/pybind11/issues/4075
what = m.test_pypy_oserror_normalization() what = m.test_pypy_oserror_normalization()
assert "this_filename_must_not_exist" in what assert "this_filename_must_not_exist" in what
def test_fn_cast_int_exception():
with pytest.raises(RuntimeError) as excinfo:
m.test_fn_cast_int(lambda: None)
assert str(excinfo.value).startswith(
"Unable to cast Python instance of type <class 'NoneType'> to C++ type"
)

View File

@ -96,7 +96,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: handle, arg1: int, arg2: handle) -> None
""" # noqa: E501 line too long """
) )

View File

@ -148,10 +148,7 @@ ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK = ALL_BASIC_TESTS + (_intentional_dead
def _run_in_process(target, *args, **kwargs): def _run_in_process(target, *args, **kwargs):
if len(args) == 0: test_fn = target if len(args) == 0 else args[0]
test_fn = target
else:
test_fn = args[0]
# Do not need to wait much, 10s should be more than enough. # Do not need to wait much, 10s should be more than enough.
timeout = 0.1 if test_fn is _intentional_deadlock else 10 timeout = 0.1 if test_fn is _intentional_deadlock else 10
process = multiprocessing.Process(target=target, args=args, kwargs=kwargs) process = multiprocessing.Process(target=target, args=args, kwargs=kwargs)
@ -178,7 +175,8 @@ def _run_in_process(target, *args, **kwargs):
elif test_fn is _intentional_deadlock: elif test_fn is _intentional_deadlock:
assert process.exitcode is None assert process.exitcode is None
return 0 return 0
elif process.exitcode is None:
if process.exitcode is None:
assert t_delta > 0.9 * timeout assert t_delta > 0.9 * timeout
msg = "DEADLOCK, most likely, exactly what this test is meant to detect." msg = "DEADLOCK, most likely, exactly what this test is meant to detect."
if env.PYPY and env.WIN: if env.PYPY and env.WIN:

View File

@ -9,16 +9,16 @@ def test_captured(capsys):
m.captured_output(msg) m.captured_output(msg)
stdout, stderr = capsys.readouterr() stdout, stderr = capsys.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
m.captured_output_default(msg) m.captured_output_default(msg)
stdout, stderr = capsys.readouterr() stdout, stderr = capsys.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
m.captured_err(msg) m.captured_err(msg)
stdout, stderr = capsys.readouterr() stdout, stderr = capsys.readouterr()
assert stdout == "" assert not stdout
assert stderr == msg assert stderr == msg
@ -30,7 +30,7 @@ def test_captured_large_string(capsys):
m.captured_output_default(msg) m.captured_output_default(msg)
stdout, stderr = capsys.readouterr() stdout, stderr = capsys.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
def test_captured_utf8_2byte_offset0(capsys): def test_captured_utf8_2byte_offset0(capsys):
@ -40,7 +40,7 @@ def test_captured_utf8_2byte_offset0(capsys):
m.captured_output_default(msg) m.captured_output_default(msg)
stdout, stderr = capsys.readouterr() stdout, stderr = capsys.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
def test_captured_utf8_2byte_offset1(capsys): def test_captured_utf8_2byte_offset1(capsys):
@ -50,7 +50,7 @@ def test_captured_utf8_2byte_offset1(capsys):
m.captured_output_default(msg) m.captured_output_default(msg)
stdout, stderr = capsys.readouterr() stdout, stderr = capsys.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
def test_captured_utf8_3byte_offset0(capsys): def test_captured_utf8_3byte_offset0(capsys):
@ -60,7 +60,7 @@ def test_captured_utf8_3byte_offset0(capsys):
m.captured_output_default(msg) m.captured_output_default(msg)
stdout, stderr = capsys.readouterr() stdout, stderr = capsys.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
def test_captured_utf8_3byte_offset1(capsys): def test_captured_utf8_3byte_offset1(capsys):
@ -70,7 +70,7 @@ def test_captured_utf8_3byte_offset1(capsys):
m.captured_output_default(msg) m.captured_output_default(msg)
stdout, stderr = capsys.readouterr() stdout, stderr = capsys.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
def test_captured_utf8_3byte_offset2(capsys): def test_captured_utf8_3byte_offset2(capsys):
@ -80,7 +80,7 @@ def test_captured_utf8_3byte_offset2(capsys):
m.captured_output_default(msg) m.captured_output_default(msg)
stdout, stderr = capsys.readouterr() stdout, stderr = capsys.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
def test_captured_utf8_4byte_offset0(capsys): def test_captured_utf8_4byte_offset0(capsys):
@ -90,7 +90,7 @@ def test_captured_utf8_4byte_offset0(capsys):
m.captured_output_default(msg) m.captured_output_default(msg)
stdout, stderr = capsys.readouterr() stdout, stderr = capsys.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
def test_captured_utf8_4byte_offset1(capsys): def test_captured_utf8_4byte_offset1(capsys):
@ -100,7 +100,7 @@ def test_captured_utf8_4byte_offset1(capsys):
m.captured_output_default(msg) m.captured_output_default(msg)
stdout, stderr = capsys.readouterr() stdout, stderr = capsys.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
def test_captured_utf8_4byte_offset2(capsys): def test_captured_utf8_4byte_offset2(capsys):
@ -110,7 +110,7 @@ def test_captured_utf8_4byte_offset2(capsys):
m.captured_output_default(msg) m.captured_output_default(msg)
stdout, stderr = capsys.readouterr() stdout, stderr = capsys.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
def test_captured_utf8_4byte_offset3(capsys): def test_captured_utf8_4byte_offset3(capsys):
@ -120,7 +120,7 @@ def test_captured_utf8_4byte_offset3(capsys):
m.captured_output_default(msg) m.captured_output_default(msg)
stdout, stderr = capsys.readouterr() stdout, stderr = capsys.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
def test_guard_capture(capsys): def test_guard_capture(capsys):
@ -128,7 +128,7 @@ def test_guard_capture(capsys):
m.guard_output(msg) m.guard_output(msg)
stdout, stderr = capsys.readouterr() stdout, stderr = capsys.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
def test_series_captured(capture): def test_series_captured(capture):
@ -145,7 +145,7 @@ def test_flush(capfd):
with m.ostream_redirect(): with m.ostream_redirect():
m.noisy_function(msg, flush=False) m.noisy_function(msg, flush=False)
stdout, stderr = capfd.readouterr() stdout, stderr = capfd.readouterr()
assert stdout == "" assert not stdout
m.noisy_function(msg2, flush=True) m.noisy_function(msg2, flush=True)
stdout, stderr = capfd.readouterr() stdout, stderr = capfd.readouterr()
@ -164,15 +164,15 @@ def test_not_captured(capfd):
m.raw_output(msg) m.raw_output(msg)
stdout, stderr = capfd.readouterr() stdout, stderr = capfd.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
assert stream.getvalue() == "" assert not stream.getvalue()
stream = StringIO() stream = StringIO()
with redirect_stdout(stream): with redirect_stdout(stream):
m.captured_output(msg) m.captured_output(msg)
stdout, stderr = capfd.readouterr() stdout, stderr = capfd.readouterr()
assert stdout == "" assert not stdout
assert stderr == "" assert not stderr
assert stream.getvalue() == msg assert stream.getvalue() == msg
@ -182,16 +182,16 @@ def test_err(capfd):
with redirect_stderr(stream): with redirect_stderr(stream):
m.raw_err(msg) m.raw_err(msg)
stdout, stderr = capfd.readouterr() stdout, stderr = capfd.readouterr()
assert stdout == "" assert not stdout
assert stderr == msg assert stderr == msg
assert stream.getvalue() == "" assert not stream.getvalue()
stream = StringIO() stream = StringIO()
with redirect_stderr(stream): with redirect_stderr(stream):
m.captured_err(msg) m.captured_err(msg)
stdout, stderr = capfd.readouterr() stdout, stderr = capfd.readouterr()
assert stdout == "" assert not stdout
assert stderr == "" assert not stderr
assert stream.getvalue() == msg assert stream.getvalue() == msg
@ -221,14 +221,13 @@ def test_redirect(capfd):
m.raw_output(msg) m.raw_output(msg)
stdout, stderr = capfd.readouterr() stdout, stderr = capfd.readouterr()
assert stdout == msg assert stdout == msg
assert stream.getvalue() == "" assert not stream.getvalue()
stream = StringIO() stream = StringIO()
with redirect_stdout(stream): with redirect_stdout(stream), m.ostream_redirect():
with m.ostream_redirect(): m.raw_output(msg)
m.raw_output(msg)
stdout, stderr = capfd.readouterr() stdout, stderr = capfd.readouterr()
assert stdout == "" assert not stdout
assert stream.getvalue() == msg assert stream.getvalue() == msg
stream = StringIO() stream = StringIO()
@ -236,7 +235,7 @@ def test_redirect(capfd):
m.raw_output(msg) m.raw_output(msg)
stdout, stderr = capfd.readouterr() stdout, stderr = capfd.readouterr()
assert stdout == msg assert stdout == msg
assert stream.getvalue() == "" assert not stream.getvalue()
def test_redirect_err(capfd): def test_redirect_err(capfd):
@ -244,13 +243,12 @@ def test_redirect_err(capfd):
msg2 = "StdErr" msg2 = "StdErr"
stream = StringIO() stream = StringIO()
with redirect_stderr(stream): with redirect_stderr(stream), m.ostream_redirect(stdout=False):
with m.ostream_redirect(stdout=False): m.raw_output(msg)
m.raw_output(msg) m.raw_err(msg2)
m.raw_err(msg2)
stdout, stderr = capfd.readouterr() stdout, stderr = capfd.readouterr()
assert stdout == msg assert stdout == msg
assert stderr == "" assert not stderr
assert stream.getvalue() == msg2 assert stream.getvalue() == msg2
@ -260,14 +258,12 @@ def test_redirect_both(capfd):
stream = StringIO() stream = StringIO()
stream2 = StringIO() stream2 = StringIO()
with redirect_stdout(stream): with redirect_stdout(stream), redirect_stderr(stream2), m.ostream_redirect():
with redirect_stderr(stream2): m.raw_output(msg)
with m.ostream_redirect(): m.raw_err(msg2)
m.raw_output(msg)
m.raw_err(msg2)
stdout, stderr = capfd.readouterr() stdout, stderr = capfd.readouterr()
assert stdout == "" assert not stdout
assert stderr == "" assert not stderr
assert stream.getvalue() == msg assert stream.getvalue() == msg
assert stream2.getvalue() == msg2 assert stream2.getvalue() == msg2

View File

@ -25,7 +25,7 @@ def test_function_signatures(doc):
) )
def test_named_arguments(msg): def test_named_arguments():
assert m.kw_func0(5, 10) == "x=5, y=10" assert m.kw_func0(5, 10) == "x=5, y=10"
assert m.kw_func1(5, 10) == "x=5, y=10" assert m.kw_func1(5, 10) == "x=5, y=10"
@ -43,8 +43,7 @@ def test_named_arguments(msg):
# noinspection PyArgumentList # noinspection PyArgumentList
m.kw_func2(x=5, y=10, z=12) m.kw_func2(x=5, y=10, z=12)
assert excinfo.match( assert excinfo.match(
r"(?s)^kw_func2\(\): incompatible.*Invoked with: kwargs: ((x=5|y=10|z=12)(, |$))" r"(?s)^kw_func2\(\): incompatible.*Invoked with: kwargs: ((x=5|y=10|z=12)(, |$)){3}$"
+ "{3}$"
) )
assert m.kw_func4() == "{13 17}" assert m.kw_func4() == "{13 17}"
@ -59,7 +58,7 @@ def test_arg_and_kwargs():
assert m.args_function(*args) == args assert m.args_function(*args) == args
args = "a1", "a2" args = "a1", "a2"
kwargs = dict(arg3="a3", arg4=4) kwargs = {"arg3": "a3", "arg4": 4}
assert m.args_kwargs_function(*args, **kwargs) == (args, kwargs) assert m.args_kwargs_function(*args, **kwargs) == (args, kwargs)
@ -177,7 +176,7 @@ def test_mixed_args_and_kwargs(msg):
assert ( assert (
m.args_kwonly_kwargs_defaults.__doc__ m.args_kwonly_kwargs_defaults.__doc__
== "args_kwonly_kwargs_defaults(i: int = 1, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n" # noqa: E501 line too long == "args_kwonly_kwargs_defaults(i: int = 1, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n"
) )
assert m.args_kwonly_kwargs_defaults() == (1, 3.14159, (), 42, {}) assert m.args_kwonly_kwargs_defaults() == (1, 3.14159, (), 42, {})
assert m.args_kwonly_kwargs_defaults(2) == (2, 3.14159, (), 42, {}) assert m.args_kwonly_kwargs_defaults(2) == (2, 3.14159, (), 42, {})
@ -233,15 +232,15 @@ def test_keyword_only_args(msg):
x.method(i=1, j=2) x.method(i=1, j=2)
assert ( assert (
m.first_arg_kw_only.__init__.__doc__ m.first_arg_kw_only.__init__.__doc__
== "__init__(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 0) -> None\n" # noqa: E501 line too long == "__init__(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 0) -> None\n"
) )
assert ( assert (
m.first_arg_kw_only.method.__doc__ m.first_arg_kw_only.method.__doc__
== "method(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 1, j: int = 2) -> None\n" # noqa: E501 line too long == "method(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 1, j: int = 2) -> None\n"
) )
def test_positional_only_args(msg): def test_positional_only_args():
assert m.pos_only_all(1, 2) == (1, 2) assert m.pos_only_all(1, 2) == (1, 2)
assert m.pos_only_all(2, 1) == (2, 1) assert m.pos_only_all(2, 1) == (2, 1)
@ -283,7 +282,7 @@ def test_positional_only_args(msg):
# Mix it with args and kwargs: # Mix it with args and kwargs:
assert ( assert (
m.args_kwonly_full_monty.__doc__ m.args_kwonly_full_monty.__doc__
== "args_kwonly_full_monty(arg0: int = 1, arg1: int = 2, /, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n" # noqa: E501 line too long == "args_kwonly_full_monty(arg0: int = 1, arg1: int = 2, /, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n"
) )
assert m.args_kwonly_full_monty() == (1, 2, 3.14159, (), 42, {}) assert m.args_kwonly_full_monty() == (1, 2, 3.14159, (), 42, {})
assert m.args_kwonly_full_monty(8) == (8, 2, 3.14159, (), 42, {}) assert m.args_kwonly_full_monty(8) == (8, 2, 3.14159, (), 42, {})
@ -326,18 +325,18 @@ def test_positional_only_args(msg):
# https://github.com/pybind/pybind11/pull/3402#issuecomment-963341987 # https://github.com/pybind/pybind11/pull/3402#issuecomment-963341987
assert ( assert (
m.first_arg_kw_only.pos_only.__doc__ m.first_arg_kw_only.pos_only.__doc__
== "pos_only(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, /, i: int, j: int) -> None\n" # noqa: E501 line too long == "pos_only(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, /, i: int, j: int) -> None\n"
) )
def test_signatures(): def test_signatures():
assert "kw_only_all(*, i: int, j: int) -> tuple\n" == m.kw_only_all.__doc__ assert m.kw_only_all.__doc__ == "kw_only_all(*, i: int, j: int) -> tuple\n"
assert "kw_only_mixed(i: int, *, j: int) -> tuple\n" == m.kw_only_mixed.__doc__ assert m.kw_only_mixed.__doc__ == "kw_only_mixed(i: int, *, j: int) -> tuple\n"
assert "pos_only_all(i: int, j: int, /) -> tuple\n" == m.pos_only_all.__doc__ assert m.pos_only_all.__doc__ == "pos_only_all(i: int, j: int, /) -> tuple\n"
assert "pos_only_mix(i: int, /, j: int) -> tuple\n" == m.pos_only_mix.__doc__ assert m.pos_only_mix.__doc__ == "pos_only_mix(i: int, /, j: int) -> tuple\n"
assert ( assert (
"pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple\n" m.pos_kw_only_mix.__doc__
== m.pos_kw_only_mix.__doc__ == "pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple\n"
) )

View File

@ -177,6 +177,38 @@ struct RValueRefParam {
std::size_t func4(std::string &&s) const & { return s.size(); } std::size_t func4(std::string &&s) const & { return s.size(); }
}; };
namespace pybind11_tests {
namespace exercise_is_setter {
struct FieldBase {
int int_value() const { return int_value_; }
FieldBase &SetIntValue(int int_value) {
int_value_ = int_value;
return *this;
}
private:
int int_value_ = -99;
};
struct Field : FieldBase {};
void add_bindings(py::module &m) {
py::module sm = m.def_submodule("exercise_is_setter");
// NOTE: FieldBase is not wrapped, therefore ...
py::class_<Field>(sm, "Field")
.def(py::init<>())
.def_property(
"int_value",
&Field::int_value,
&Field::SetIntValue // ... the `FieldBase &` return value here cannot be converted.
);
}
} // namespace exercise_is_setter
} // namespace pybind11_tests
TEST_SUBMODULE(methods_and_attributes, m) { TEST_SUBMODULE(methods_and_attributes, m) {
// test_methods_and_attributes // test_methods_and_attributes
py::class_<ExampleMandA> emna(m, "ExampleMandA"); py::class_<ExampleMandA> emna(m, "ExampleMandA");
@ -456,4 +488,6 @@ TEST_SUBMODULE(methods_and_attributes, m) {
.def("func2", &RValueRefParam::func2) .def("func2", &RValueRefParam::func2)
.def("func3", &RValueRefParam::func3) .def("func3", &RValueRefParam::func3)
.def("func4", &RValueRefParam::func4); .def("func4", &RValueRefParam::func4);
pybind11_tests::exercise_is_setter::add_bindings(m);
} }

View File

@ -183,9 +183,9 @@ def test_static_properties():
# Only static attributes can be deleted # Only static attributes can be deleted
del m.TestPropertiesOverride.def_readonly_static del m.TestPropertiesOverride.def_readonly_static
assert hasattr(m.TestPropertiesOverride, "def_readonly_static")
assert ( assert (
hasattr(m.TestPropertiesOverride, "def_readonly_static") m.TestPropertiesOverride.def_readonly_static
and m.TestPropertiesOverride.def_readonly_static
is m.TestProperties.def_readonly_static is m.TestProperties.def_readonly_static
) )
assert "def_readonly_static" not in m.TestPropertiesOverride.__dict__ assert "def_readonly_static" not in m.TestPropertiesOverride.__dict__
@ -256,10 +256,7 @@ def test_no_mixed_overloads():
@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"]) @pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
def test_property_return_value_policies(access): def test_property_return_value_policies(access):
if not access.startswith("static"): obj = m.TestPropRVP() if not access.startswith("static") else m.TestPropRVP
obj = m.TestPropRVP()
else:
obj = m.TestPropRVP
ref = getattr(obj, access + "_ref") ref = getattr(obj, access + "_ref")
assert ref.value == 1 assert ref.value == 1
@ -525,3 +522,12 @@ def test_rvalue_ref_param():
assert r.func2("1234") == 4 assert r.func2("1234") == 4
assert r.func3("12345") == 5 assert r.func3("12345") == 5
assert r.func4("123456") == 6 assert r.func4("123456") == 6
def test_is_setter():
fld = m.exercise_is_setter.Field()
assert fld.int_value == -99
setter_return = fld.int_value = 100
assert isinstance(setter_return, int)
assert setter_return == 100
assert fld.int_value == 100

View File

@ -1,3 +1,5 @@
import builtins
import pytest import pytest
import env import env
@ -61,7 +63,6 @@ def test_importing():
from pybind11_tests.modules import OD from pybind11_tests.modules import OD
assert OD is OrderedDict assert OD is OrderedDict
assert str(OD([(1, "a"), (2, "b")])) == "OrderedDict([(1, 'a'), (2, 'b')])"
def test_pydoc(): def test_pydoc():
@ -86,12 +87,7 @@ def test_builtin_key_type():
Previous versions of pybind11 would add a unicode key in python 2. Previous versions of pybind11 would add a unicode key in python 2.
""" """
if hasattr(__builtins__, "keys"): assert all(type(k) == str for k in dir(builtins))
keys = __builtins__.keys()
else: # this is to make pypy happy since builtins is different there.
keys = __builtins__.__dict__.keys()
assert {type(k) for k in keys} == {str}
@pytest.mark.xfail("env.PYPY", reason="PyModule_GetName()") @pytest.mark.xfail("env.PYPY", reason="PyModule_GetName()")

View File

@ -523,4 +523,30 @@ TEST_SUBMODULE(numpy_array, sm) {
sm.def("test_fmt_desc_const_double", [](const py::array_t<const double> &) {}); sm.def("test_fmt_desc_const_double", [](const py::array_t<const double> &) {});
sm.def("round_trip_float", [](double d) { return d; }); sm.def("round_trip_float", [](double d) { return d; });
sm.def("pass_array_pyobject_ptr_return_sum_str_values",
[](const py::array_t<PyObject *> &objs) {
std::string sum_str_values;
for (const auto &obj : objs) {
sum_str_values += py::str(obj.attr("value"));
}
return sum_str_values;
});
sm.def("pass_array_pyobject_ptr_return_as_list",
[](const py::array_t<PyObject *> &objs) -> py::list { return objs; });
sm.def("return_array_pyobject_ptr_cpp_loop", [](const py::list &objs) {
py::size_t arr_size = py::len(objs);
py::array_t<PyObject *> arr_from_list(static_cast<py::ssize_t>(arr_size));
PyObject **data = arr_from_list.mutable_data();
for (py::size_t i = 0; i < arr_size; i++) {
assert(data[i] == nullptr);
data[i] = py::cast<PyObject *>(objs[i].attr("value"));
}
return arr_from_list;
});
sm.def("return_array_pyobject_ptr_from_list",
[](const py::list &objs) -> py::array_t<PyObject *> { return objs; });
} }

View File

@ -22,7 +22,7 @@ def test_dtypes():
) )
@pytest.fixture(scope="function") @pytest.fixture()
def arr(): def arr():
return np.array([[1, 2, 3], [4, 5, 6]], "=u2") return np.array([[1, 2, 3], [4, 5, 6]], "=u2")
@ -67,7 +67,7 @@ def test_array_attributes():
@pytest.mark.parametrize( @pytest.mark.parametrize(
"args, ret", [([], 0), ([0], 0), ([1], 3), ([0, 1], 1), ([1, 2], 5)] ("args", "ret"), [([], 0), ([0], 0), ([1], 3), ([0, 1], 1), ([1, 2], 5)]
) )
def test_index_offset(arr, args, ret): def test_index_offset(arr, args, ret):
assert m.index_at(arr, *args) == ret assert m.index_at(arr, *args) == ret
@ -93,7 +93,7 @@ def test_dim_check_fail(arr):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"args, ret", ("args", "ret"),
[ [
([], [1, 2, 3, 4, 5, 6]), ([], [1, 2, 3, 4, 5, 6]),
([1], [4, 5, 6]), ([1], [4, 5, 6]),
@ -211,12 +211,14 @@ def test_wrap():
assert b[0, 0] == 1234 assert b[0, 0] == 1234
a1 = np.array([1, 2], dtype=np.int16) a1 = np.array([1, 2], dtype=np.int16)
assert a1.flags.owndata and a1.base is None assert a1.flags.owndata
assert a1.base is None
a2 = m.wrap(a1) a2 = m.wrap(a1)
assert_references(a1, a2) assert_references(a1, a2)
a1 = np.array([[1, 2], [3, 4]], dtype=np.float32, order="F") a1 = np.array([[1, 2], [3, 4]], dtype=np.float32, order="F")
assert a1.flags.owndata and a1.base is None assert a1.flags.owndata
assert a1.base is None
a2 = m.wrap(a1) a2 = m.wrap(a1)
assert_references(a1, a2) assert_references(a1, a2)
@ -451,13 +453,15 @@ def test_array_resize():
try: try:
m.array_resize3(a, 3, True) m.array_resize3(a, 3, True)
except ValueError as e: except ValueError as e:
assert str(e).startswith("cannot resize an array") assert str(e).startswith("cannot resize an array") # noqa: PT017
# transposed array doesn't own data # transposed array doesn't own data
b = a.transpose() b = a.transpose()
try: try:
m.array_resize3(b, 3, False) m.array_resize3(b, 3, False)
except ValueError as e: except ValueError as e:
assert str(e).startswith("cannot resize this array: it does not own its data") assert str(e).startswith( # noqa: PT017
"cannot resize this array: it does not own its data"
)
# ... but reshape should be fine # ... but reshape should be fine
m.array_reshape2(b) m.array_reshape2(b)
assert b.shape == (8, 8) assert b.shape == (8, 8)
@ -591,3 +595,74 @@ def test_round_trip_float():
arr = np.zeros((), np.float64) arr = np.zeros((), np.float64)
arr[()] = 37.2 arr[()] = 37.2
assert m.round_trip_float(arr) == 37.2 assert m.round_trip_float(arr) == 37.2
# HINT: An easy and robust way (although only manual unfortunately) to check for
# ref-count leaks in the test_.*pyobject_ptr.* functions below is to
# * temporarily insert `while True:` (one-by-one),
# * run this test, and
# * run the Linux `top` command in another shell to visually monitor
# `RES` for a minute or two.
# If there is a leak, it is usually evident in seconds because the `RES`
# value increases without bounds. (Don't forget to Ctrl-C the test!)
# For use as a temporary user-defined object, to maximize sensitivity of the tests below:
# * Ref-count leaks will be immediately evident.
# * Sanitizers are much more likely to detect heap-use-after-free due to
# other ref-count bugs.
class PyValueHolder:
def __init__(self, value):
self.value = value
def WrapWithPyValueHolder(*values):
return [PyValueHolder(v) for v in values]
def UnwrapPyValueHolder(vhs):
return [vh.value for vh in vhs]
def test_pass_array_pyobject_ptr_return_sum_str_values_ndarray():
# Intentionally all temporaries, do not change.
assert (
m.pass_array_pyobject_ptr_return_sum_str_values(
np.array(WrapWithPyValueHolder(-3, "four", 5.0), dtype=object)
)
== "-3four5.0"
)
def test_pass_array_pyobject_ptr_return_sum_str_values_list():
# Intentionally all temporaries, do not change.
assert (
m.pass_array_pyobject_ptr_return_sum_str_values(
WrapWithPyValueHolder(2, "three", -4.0)
)
== "2three-4.0"
)
def test_pass_array_pyobject_ptr_return_as_list():
# Intentionally all temporaries, do not change.
assert UnwrapPyValueHolder(
m.pass_array_pyobject_ptr_return_as_list(
np.array(WrapWithPyValueHolder(-1, "two", 3.0), dtype=object)
)
) == [-1, "two", 3.0]
@pytest.mark.parametrize(
("return_array_pyobject_ptr", "unwrap"),
[
(m.return_array_pyobject_ptr_cpp_loop, list),
(m.return_array_pyobject_ptr_from_list, UnwrapPyValueHolder),
],
)
def test_return_array_pyobject_ptr_cpp_loop(return_array_pyobject_ptr, unwrap):
# Intentionally all temporaries, do not change.
arr_from_list = return_array_pyobject_ptr(WrapWithPyValueHolder(6, "seven", -8.0))
assert isinstance(arr_from_list, np.ndarray)
assert arr_from_list.dtype == np.dtype("O")
assert unwrap(arr_from_list) == [6, "seven", -8.0]

View File

@ -130,14 +130,10 @@ def test_dtype(simple_dtype):
partial_nested_fmt(), partial_nested_fmt(),
"[('a','S3'),('b','S3')]", "[('a','S3'),('b','S3')]",
( (
"{{'names':['a','b','c','d']," "{'names':['a','b','c','d'],"
+ "'formats':[('S4',(3,)),('" f"'formats':[('S4',(3,)),('{e}i4',(2,)),('u1',(3,)),('{e}f4',(4,2))],"
+ e "'offsets':[0,12,20,24],'itemsize':56}"
+ "i4',(2,)),('u1',(3,)),('" ),
+ e
+ "f4',(4,2))],"
+ "'offsets':[0,12,20,24],'itemsize':56}}"
).format(e=e),
"[('e1','" + e + "i8'),('e2','u1')]", "[('e1','" + e + "i8'),('e2','u1')]",
"[('x','i1'),('y','" + e + "u8')]", "[('x','i1'),('y','" + e + "u8')]",
"[('cflt','" + e + "c8'),('cdbl','" + e + "c16')]", "[('cflt','" + e + "c8'),('cdbl','" + e + "c16')]",
@ -291,19 +287,17 @@ def test_array_array():
arr = m.create_array_array(3) arr = m.create_array_array(3)
assert str(arr.dtype).replace(" ", "") == ( assert str(arr.dtype).replace(" ", "") == (
"{{'names':['a','b','c','d']," "{'names':['a','b','c','d'],"
+ "'formats':[('S4',(3,)),('" f"'formats':[('S4',(3,)),('{e}i4',(2,)),('u1',(3,)),('{e}f4',(4,2))],"
+ e "'offsets':[0,12,20,24],'itemsize':56}"
+ "i4',(2,)),('u1',(3,)),('{e}f4',(4,2))]," )
+ "'offsets':[0,12,20,24],'itemsize':56}}"
).format(e=e)
assert m.print_array_array(arr) == [ assert m.print_array_array(arr) == [
"a={{A,B,C,D},{K,L,M,N},{U,V,W,X}},b={0,1}," "a={{A,B,C,D},{K,L,M,N},{U,V,W,X}},b={0,1},"
+ "c={0,1,2},d={{0,1},{10,11},{20,21},{30,31}}", "c={0,1,2},d={{0,1},{10,11},{20,21},{30,31}}",
"a={{W,X,Y,Z},{G,H,I,J},{Q,R,S,T}},b={1000,1001}," "a={{W,X,Y,Z},{G,H,I,J},{Q,R,S,T}},b={1000,1001},"
+ "c={10,11,12},d={{100,101},{110,111},{120,121},{130,131}}", "c={10,11,12},d={{100,101},{110,111},{120,121},{130,131}}",
"a={{S,T,U,V},{C,D,E,F},{M,N,O,P}},b={2000,2001}," "a={{S,T,U,V},{C,D,E,F},{M,N,O,P}},b={2000,2001},"
+ "c={20,21,22},d={{200,201},{210,211},{220,221},{230,231}}", "c={20,21,22},d={{200,201},{210,211},{220,221},{230,231}}",
] ]
assert arr["a"].tolist() == [ assert arr["a"].tolist() == [
[b"ABCD", b"KLMN", b"UVWX"], [b"ABCD", b"KLMN", b"UVWX"],

View File

@ -149,7 +149,7 @@ def test_docs(doc):
doc(m.vectorized_func) doc(m.vectorized_func)
== """ == """
vectorized_func(arg0: numpy.ndarray[numpy.int32], arg1: numpy.ndarray[numpy.float32], arg2: numpy.ndarray[numpy.float64]) -> object vectorized_func(arg0: numpy.ndarray[numpy.int32], arg1: numpy.ndarray[numpy.float32], arg2: numpy.ndarray[numpy.float64]) -> object
""" # noqa: E501 line too long """
) )

View File

@ -260,6 +260,15 @@ TEST_SUBMODULE(pytypes, m) {
}); });
}); });
m.def("return_capsule_with_destructor_3", []() {
py::print("creating capsule");
auto cap = py::capsule((void *) 1233, "oname", [](void *ptr) {
py::print("destructing capsule: {}"_s.format((size_t) ptr));
});
py::print("original name: {}"_s.format(cap.name()));
return cap;
});
m.def("return_renamed_capsule_with_destructor_2", []() { m.def("return_renamed_capsule_with_destructor_2", []() {
py::print("creating capsule"); py::print("creating capsule");
auto cap = py::capsule((void *) 1234, [](void *ptr) { auto cap = py::capsule((void *) 1234, [](void *ptr) {

View File

@ -15,7 +15,7 @@ def test_obj_class_name():
assert m.obj_class_name([]) == "list" assert m.obj_class_name([]) == "list"
def test_handle_from_move_only_type_with_operator_PyObject(): # noqa: N802 def test_handle_from_move_only_type_with_operator_PyObject():
assert m.handle_from_move_only_type_with_operator_PyObject_ncnst() assert m.handle_from_move_only_type_with_operator_PyObject_ncnst()
assert m.handle_from_move_only_type_with_operator_PyObject_const() assert m.handle_from_move_only_type_with_operator_PyObject_const()
@ -33,7 +33,7 @@ def test_iterator(doc):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"pytype, from_iter_func", ("pytype", "from_iter_func"),
[ [
(frozenset, m.get_frozenset_from_iterable), (frozenset, m.get_frozenset_from_iterable),
(list, m.get_list_from_iterable), (list, m.get_list_from_iterable),
@ -87,7 +87,7 @@ def test_list(capture, doc):
assert doc(m.print_list) == "print_list(arg0: list) -> None" assert doc(m.print_list) == "print_list(arg0: list) -> None"
def test_none(capture, doc): def test_none(doc):
assert doc(m.get_none) == "get_none() -> None" assert doc(m.get_none) == "get_none() -> None"
assert doc(m.print_none) == "print_none(arg0: None) -> None" assert doc(m.print_none) == "print_none(arg0: None) -> None"
@ -182,10 +182,10 @@ class CustomContains:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"arg,func", ("arg", "func"),
[ [
(set(), m.anyset_contains), (set(), m.anyset_contains),
(dict(), m.dict_contains), ({}, m.dict_contains),
(CustomContains(), m.obj_contains), (CustomContains(), m.obj_contains),
], ],
) )
@ -273,7 +273,7 @@ def test_bytes(doc):
assert doc(m.bytes_from_str) == "bytes_from_str() -> bytes" assert doc(m.bytes_from_str) == "bytes_from_str() -> bytes"
def test_bytearray(doc): def test_bytearray():
assert m.bytearray_from_char_ssize_t().decode() == "$%" assert m.bytearray_from_char_ssize_t().decode() == "$%"
assert m.bytearray_from_char_size_t().decode() == "@$!" assert m.bytearray_from_char_size_t().decode() == "@$!"
assert m.bytearray_from_string().decode() == "foo" assert m.bytearray_from_string().decode() == "foo"
@ -319,6 +319,19 @@ def test_capsule(capture):
""" """
) )
with capture:
a = m.return_capsule_with_destructor_3()
del a
pytest.gc_collect()
assert (
capture.unordered
== """
creating capsule
destructing capsule: 1233
original name: oname
"""
)
with capture: with capture:
a = m.return_renamed_capsule_with_destructor_2() a = m.return_renamed_capsule_with_destructor_2()
del a del a
@ -385,7 +398,7 @@ def test_accessors():
assert d["implicit_list"] == [1, 2, 3] assert d["implicit_list"] == [1, 2, 3]
assert all(x in TestObject.__dict__ for x in d["implicit_dict"]) assert all(x in TestObject.__dict__ for x in d["implicit_dict"])
assert m.tuple_accessor(tuple()) == (0, 1, 2) assert m.tuple_accessor(()) == (0, 1, 2)
d = m.accessor_assignment() d = m.accessor_assignment()
assert d["get"] == 0 assert d["get"] == 0
@ -475,7 +488,7 @@ def test_pybind11_str_raw_str():
assert cvt({}) == "{}" assert cvt({}) == "{}"
assert cvt({3: 4}) == "{3: 4}" assert cvt({3: 4}) == "{3: 4}"
assert cvt(set()) == "set()" assert cvt(set()) == "set()"
assert cvt({3, 3}) == "{3}" assert cvt({3}) == "{3}"
valid_orig = "DZ" valid_orig = "DZ"
valid_utf8 = valid_orig.encode("utf-8") valid_utf8 = valid_orig.encode("utf-8")
@ -536,7 +549,7 @@ def test_print(capture):
assert str(excinfo.value) == "Unable to convert call argument " + ( assert str(excinfo.value) == "Unable to convert call argument " + (
"'1' of type 'UnregisteredType' to Python object" "'1' of type 'UnregisteredType' to Python object"
if detailed_error_messages_enabled if detailed_error_messages_enabled
else "to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)" else "'1' to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"
) )
@ -593,7 +606,7 @@ def test_issue2361():
@pytest.mark.parametrize( @pytest.mark.parametrize(
"method, args, fmt, expected_view", ("method", "args", "fmt", "expected_view"),
[ [
(m.test_memoryview_object, (b"red",), "B", b"red"), (m.test_memoryview_object, (b"red",), "B", b"red"),
(m.test_memoryview_buffer_info, (b"green",), "B", b"green"), (m.test_memoryview_buffer_info, (b"green",), "B", b"green"),
@ -651,7 +664,7 @@ def test_memoryview_from_memory():
def test_builtin_functions(): def test_builtin_functions():
assert m.get_len([i for i in range(42)]) == 42 assert m.get_len(list(range(42))) == 42
with pytest.raises(TypeError) as exc_info: with pytest.raises(TypeError) as exc_info:
m.get_len(i for i in range(42)) m.get_len(i for i in range(42))
assert str(exc_info.value) in [ assert str(exc_info.value) in [
@ -695,7 +708,7 @@ def test_pass_bytes_or_unicode_to_string_types():
@pytest.mark.parametrize( @pytest.mark.parametrize(
"create_weakref, create_weakref_with_callback", ("create_weakref", "create_weakref_with_callback"),
[ [
(m.weakref_from_handle, m.weakref_from_handle_and_function), (m.weakref_from_handle, m.weakref_from_handle_and_function),
(m.weakref_from_object, m.weakref_from_object_and_function), (m.weakref_from_object, m.weakref_from_object_and_function),
@ -710,7 +723,7 @@ def test_weakref(create_weakref, create_weakref_with_callback):
callback_called = False callback_called = False
def callback(wr): def callback(_):
nonlocal callback_called nonlocal callback_called
callback_called = True callback_called = True
@ -730,7 +743,7 @@ def test_weakref(create_weakref, create_weakref_with_callback):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"create_weakref, has_callback", ("create_weakref", "has_callback"),
[ [
(m.weakref_from_handle, False), (m.weakref_from_handle, False),
(m.weakref_from_object, False), (m.weakref_from_object, False),
@ -748,10 +761,7 @@ def test_weakref_err(create_weakref, has_callback):
ob = C() ob = C()
# Should raise TypeError on CPython # Should raise TypeError on CPython
with pytest.raises(TypeError) if not env.PYPY else contextlib.nullcontext(): with pytest.raises(TypeError) if not env.PYPY else contextlib.nullcontext():
if has_callback: _ = create_weakref(ob, callback) if has_callback else create_weakref(ob)
_ = create_weakref(ob, callback)
else:
_ = create_weakref(ob)
def test_cpp_iterators(): def test_cpp_iterators():
@ -814,33 +824,36 @@ def test_populate_obj_str_attrs():
@pytest.mark.parametrize( @pytest.mark.parametrize(
"a,b", [("foo", "bar"), (1, 2), (1.0, 2.0), (list(range(3)), list(range(3, 6)))] ("a", "b"),
[("foo", "bar"), (1, 2), (1.0, 2.0), (list(range(3)), list(range(3, 6)))],
) )
def test_inplace_append(a, b): def test_inplace_append(a, b):
expected = a + b expected = a + b
assert m.inplace_append(a, b) == expected assert m.inplace_append(a, b) == expected
@pytest.mark.parametrize("a,b", [(3, 2), (3.0, 2.0), (set(range(3)), set(range(2)))]) @pytest.mark.parametrize(
("a", "b"), [(3, 2), (3.0, 2.0), (set(range(3)), set(range(2)))]
)
def test_inplace_subtract(a, b): def test_inplace_subtract(a, b):
expected = a - b expected = a - b
assert m.inplace_subtract(a, b) == expected assert m.inplace_subtract(a, b) == expected
@pytest.mark.parametrize("a,b", [(3, 2), (3.0, 2.0), ([1], 3)]) @pytest.mark.parametrize(("a", "b"), [(3, 2), (3.0, 2.0), ([1], 3)])
def test_inplace_multiply(a, b): def test_inplace_multiply(a, b):
expected = a * b expected = a * b
assert m.inplace_multiply(a, b) == expected assert m.inplace_multiply(a, b) == expected
@pytest.mark.parametrize("a,b", [(6, 3), (6.0, 3.0)]) @pytest.mark.parametrize(("a", "b"), [(6, 3), (6.0, 3.0)])
def test_inplace_divide(a, b): def test_inplace_divide(a, b):
expected = a / b expected = a / b
assert m.inplace_divide(a, b) == expected assert m.inplace_divide(a, b) == expected
@pytest.mark.parametrize( @pytest.mark.parametrize(
"a,b", ("a", "b"),
[ [
(False, True), (False, True),
( (
@ -857,7 +870,7 @@ def test_inplace_or(a, b):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"a,b", ("a", "b"),
[ [
(True, False), (True, False),
( (
@ -873,13 +886,13 @@ def test_inplace_and(a, b):
assert m.inplace_and(a, b) == expected assert m.inplace_and(a, b) == expected
@pytest.mark.parametrize("a,b", [(8, 1), (-3, 2)]) @pytest.mark.parametrize(("a", "b"), [(8, 1), (-3, 2)])
def test_inplace_lshift(a, b): def test_inplace_lshift(a, b):
expected = a << b expected = a << b
assert m.inplace_lshift(a, b) == expected assert m.inplace_lshift(a, b) == expected
@pytest.mark.parametrize("a,b", [(8, 1), (-2, 2)]) @pytest.mark.parametrize(("a", "b"), [(8, 1), (-2, 2)])
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

View File

@ -1,5 +1,5 @@
import pytest import pytest
from pytest import approx from pytest import approx # noqa: PT013
from pybind11_tests import ConstructorStats from pybind11_tests import ConstructorStats
from pybind11_tests import sequences_and_iterators as m from pybind11_tests import sequences_and_iterators as m
@ -103,7 +103,8 @@ def test_sequence():
assert "Sequence" in repr(s) assert "Sequence" in repr(s)
assert len(s) == 5 assert len(s) == 5
assert s[0] == 0 and s[3] == 0 assert s[0] == 0
assert s[3] == 0
assert 12.34 not in s assert 12.34 not in s
s[0], s[3] = 12.34, 56.78 s[0], s[3] = 12.34, 56.78
assert 12.34 in s assert 12.34 in s
@ -245,7 +246,7 @@ def test_iterator_rvp():
def test_carray_iterator(): def test_carray_iterator():
"""#4100: Check for proper iterator overload with C-Arrays""" """#4100: Check for proper iterator overload with C-Arrays"""
args_gt = list(float(i) for i in range(3)) args_gt = [float(i) for i in range(3)]
arr_h = m.CArrayHolder(*args_gt) arr_h = m.CArrayHolder(*args_gt)
args = list(arr_h) args = list(arr_h)
assert args_gt == args assert args_gt == args

View File

@ -14,7 +14,7 @@ def test_vector(doc):
assert m.cast_bool_vector() == [True, False] assert m.cast_bool_vector() == [True, False]
assert m.load_bool_vector([True, False]) assert m.load_bool_vector([True, False])
assert m.load_bool_vector(tuple([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"
@ -23,7 +23,7 @@ def test_vector(doc):
assert m.cast_ptr_vector() == ["lvalue", "lvalue"] assert m.cast_ptr_vector() == ["lvalue", "lvalue"]
def test_deque(doc): def test_deque():
"""std::deque <-> list""" """std::deque <-> list"""
lst = m.cast_deque() lst = m.cast_deque()
assert lst == [1] assert lst == [1]
@ -39,8 +39,11 @@ 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() -> List[int[2]]" assert doc(m.cast_array) == "cast_array() -> Annotated[List[int], FixedSize(2)]"
assert doc(m.load_array) == "load_array(arg0: List[int[2]]) -> bool" assert (
doc(m.load_array)
== "load_array(arg0: Annotated[List[int], FixedSize(2)]) -> bool"
)
def test_valarray(doc): def test_valarray(doc):
@ -95,7 +98,8 @@ def test_recursive_casting():
# Issue #853 test case: # Issue #853 test case:
z = m.cast_unique_ptr_vector() z = m.cast_unique_ptr_vector()
assert z[0].value == 7 and z[1].value == 42 assert z[0].value == 7
assert z[1].value == 42
def test_move_out_container(): def test_move_out_container():
@ -366,7 +370,7 @@ def test_issue_1561():
"""check fix for issue #1561""" """check fix for issue #1561"""
bar = m.Issue1561Outer() bar = m.Issue1561Outer()
bar.list = [m.Issue1561Inner("bar")] bar.list = [m.Issue1561Inner("bar")]
bar.list assert bar.list
assert bar.list[0].data == "bar" assert bar.list[0].data == "bar"

View File

@ -70,6 +70,44 @@ NestMap *times_hundred(int n) {
return m; return m;
} }
/*
* Recursive data structures as test for issue #4623
*/
struct RecursiveVector : std::vector<RecursiveVector> {
using Parent = std::vector<RecursiveVector>;
using Parent::Parent;
};
struct RecursiveMap : std::map<int, RecursiveMap> {
using Parent = std::map<int, RecursiveMap>;
using Parent::Parent;
};
/*
* Pybind11 does not catch more complicated recursion schemes, such as mutual
* recursion.
* In that case custom recursive_container_traits specializations need to be added,
* thus manually telling pybind11 about the recursion.
*/
struct MutuallyRecursiveContainerPairMV;
struct MutuallyRecursiveContainerPairVM;
struct MutuallyRecursiveContainerPairMV : std::map<int, MutuallyRecursiveContainerPairVM> {};
struct MutuallyRecursiveContainerPairVM : std::vector<MutuallyRecursiveContainerPairMV> {};
namespace pybind11 {
namespace detail {
template <typename SFINAE>
struct recursive_container_traits<MutuallyRecursiveContainerPairMV, SFINAE> {
using type_to_check_recursively = recursive_bottom;
};
template <typename SFINAE>
struct recursive_container_traits<MutuallyRecursiveContainerPairVM, SFINAE> {
using type_to_check_recursively = recursive_bottom;
};
} // namespace detail
} // namespace pybind11
TEST_SUBMODULE(stl_binders, m) { TEST_SUBMODULE(stl_binders, m) {
// test_vector_int // test_vector_int
py::bind_vector<std::vector<unsigned int>>(m, "VectorInt", py::buffer_protocol()); py::bind_vector<std::vector<unsigned int>>(m, "VectorInt", py::buffer_protocol());
@ -129,6 +167,12 @@ TEST_SUBMODULE(stl_binders, m) {
m, "VectorUndeclStruct", py::buffer_protocol()); m, "VectorUndeclStruct", py::buffer_protocol());
}); });
// Bind recursive container types
py::bind_vector<RecursiveVector>(m, "RecursiveVector");
py::bind_map<RecursiveMap>(m, "RecursiveMap");
py::bind_map<MutuallyRecursiveContainerPairMV>(m, "MutuallyRecursiveContainerPairMV");
py::bind_vector<MutuallyRecursiveContainerPairVM>(m, "MutuallyRecursiveContainerPairVM");
// The rest depends on numpy: // The rest depends on numpy:
try { try {
py::module_::import("numpy"); py::module_::import("numpy");

View File

@ -186,9 +186,9 @@ def test_map_string_double():
um["ua"] = 1.1 um["ua"] = 1.1
um["ub"] = 2.6 um["ub"] = 2.6
assert sorted(list(um)) == ["ua", "ub"] assert sorted(um) == ["ua", "ub"]
assert list(um.keys()) == list(um) assert list(um.keys()) == list(um)
assert sorted(list(um.items())) == [("ua", 1.1), ("ub", 2.6)] assert sorted(um.items()) == [("ua", 1.1), ("ub", 2.6)]
assert list(zip(um.keys(), um.values())) == list(um.items()) assert list(zip(um.keys(), um.values())) == list(um.items())
assert "UnorderedMapStringDouble" in str(um) assert "UnorderedMapStringDouble" in str(um)
@ -304,11 +304,11 @@ def test_map_delitem():
um["ua"] = 1.1 um["ua"] = 1.1
um["ub"] = 2.6 um["ub"] = 2.6
assert sorted(list(um)) == ["ua", "ub"] assert sorted(um) == ["ua", "ub"]
assert sorted(list(um.items())) == [("ua", 1.1), ("ub", 2.6)] assert sorted(um.items()) == [("ua", 1.1), ("ub", 2.6)]
del um["ua"] del um["ua"]
assert sorted(list(um)) == ["ub"] assert sorted(um) == ["ub"]
assert sorted(list(um.items())) == [("ub", 2.6)] assert sorted(um.items()) == [("ub", 2.6)]
def test_map_view_types(): def test_map_view_types():
@ -335,3 +335,21 @@ def test_map_view_types():
assert type(unordered_map_string_double.items()) is items_type assert type(unordered_map_string_double.items()) is items_type
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
def test_recursive_vector():
recursive_vector = m.RecursiveVector()
recursive_vector.append(m.RecursiveVector())
recursive_vector[0].append(m.RecursiveVector())
recursive_vector[0].append(m.RecursiveVector())
# Can't use len() since test_stl_binders.cpp does not include stl.h,
# so the necessary conversion is missing
assert recursive_vector[0].count(m.RecursiveVector()) == 2
def test_recursive_map():
recursive_map = m.RecursiveMap()
recursive_map[100] = m.RecursiveMap()
recursive_map[100][101] = m.RecursiveMap()
recursive_map[100][102] = m.RecursiveMap()
assert list(recursive_map[100].keys()) == [101, 102]

View File

@ -0,0 +1,130 @@
#include <pybind11/functional.h>
#include <pybind11/stl.h>
#include <pybind11/type_caster_pyobject_ptr.h>
#include "pybind11_tests.h"
#include <cstddef>
#include <vector>
namespace {
std::vector<PyObject *> make_vector_pyobject_ptr(const py::object &ValueHolder) {
std::vector<PyObject *> vec_obj;
for (int i = 1; i < 3; i++) {
vec_obj.push_back(ValueHolder(i * 93).release().ptr());
}
// This vector now owns the refcounts.
return vec_obj;
}
} // namespace
TEST_SUBMODULE(type_caster_pyobject_ptr, m) {
m.def("cast_from_pyobject_ptr", []() {
PyObject *ptr = PyLong_FromLongLong(6758L);
return py::cast(ptr, py::return_value_policy::take_ownership);
});
m.def("cast_handle_to_pyobject_ptr", [](py::handle obj) {
auto rc1 = obj.ref_count();
auto *ptr = py::cast<PyObject *>(obj);
auto rc2 = obj.ref_count();
if (rc2 != rc1 + 1) {
return -1;
}
return 100 - py::reinterpret_steal<py::object>(ptr).attr("value").cast<int>();
});
m.def("cast_object_to_pyobject_ptr", [](py::object obj) {
py::handle hdl = obj;
auto rc1 = hdl.ref_count();
auto *ptr = py::cast<PyObject *>(std::move(obj));
auto rc2 = hdl.ref_count();
if (rc2 != rc1) {
return -1;
}
return 300 - py::reinterpret_steal<py::object>(ptr).attr("value").cast<int>();
});
m.def("cast_list_to_pyobject_ptr", [](py::list lst) {
// This is to cover types implicitly convertible to object.
py::handle hdl = lst;
auto rc1 = hdl.ref_count();
auto *ptr = py::cast<PyObject *>(std::move(lst));
auto rc2 = hdl.ref_count();
if (rc2 != rc1) {
return -1;
}
return 400 - static_cast<int>(py::len(py::reinterpret_steal<py::list>(ptr)));
});
m.def(
"return_pyobject_ptr",
[]() { return PyLong_FromLongLong(2314L); },
py::return_value_policy::take_ownership);
m.def("pass_pyobject_ptr", [](PyObject *ptr) {
return 200 - py::reinterpret_borrow<py::object>(ptr).attr("value").cast<int>();
});
m.def("call_callback_with_object_return",
[](const std::function<py::object(int)> &cb, int value) { return cb(value); });
m.def(
"call_callback_with_pyobject_ptr_return",
[](const std::function<PyObject *(int)> &cb, int value) { return cb(value); },
py::return_value_policy::take_ownership);
m.def(
"call_callback_with_pyobject_ptr_arg",
[](const std::function<int(PyObject *)> &cb, py::handle obj) { return cb(obj.ptr()); },
py::arg("cb"), // This triggers return_value_policy::automatic_reference
py::arg("obj"));
m.def("cast_to_pyobject_ptr_nullptr", [](bool set_error) {
if (set_error) {
PyErr_SetString(PyExc_RuntimeError, "Reflective of healthy error handling.");
}
PyObject *ptr = nullptr;
py::cast(ptr);
});
m.def("cast_to_pyobject_ptr_non_nullptr_with_error_set", []() {
PyErr_SetString(PyExc_RuntimeError, "Reflective of unhealthy error handling.");
py::cast(Py_None);
});
m.def("pass_list_pyobject_ptr", [](const std::vector<PyObject *> &vec_obj) {
int acc = 0;
for (const auto &ptr : vec_obj) {
acc = acc * 1000 + py::reinterpret_borrow<py::object>(ptr).attr("value").cast<int>();
}
return acc;
});
m.def("return_list_pyobject_ptr_take_ownership",
make_vector_pyobject_ptr,
// Ownership is transferred one-by-one when the vector is converted to a Python list.
py::return_value_policy::take_ownership);
m.def("return_list_pyobject_ptr_reference",
make_vector_pyobject_ptr,
// Ownership is not transferred.
py::return_value_policy::reference);
m.def("dec_ref_each_pyobject_ptr", [](const std::vector<PyObject *> &vec_obj) {
std::size_t i = 0;
for (; i < vec_obj.size(); i++) {
py::handle h(vec_obj[i]);
if (static_cast<std::size_t>(h.ref_count()) < 2) {
break; // Something is badly wrong.
}
h.dec_ref();
}
return i;
});
m.def("pass_pyobject_ptr_and_int", [](PyObject *, int) {});
#ifdef PYBIND11_NO_COMPILE_SECTION // Change to ifndef for manual testing.
{
PyObject *ptr = nullptr;
(void) py::cast(*ptr);
}
#endif
}

View File

@ -0,0 +1,104 @@
import pytest
from pybind11_tests import type_caster_pyobject_ptr as m
# For use as a temporary user-defined object, to maximize sensitivity of the tests below.
class ValueHolder:
def __init__(self, value):
self.value = value
def test_cast_from_pyobject_ptr():
assert m.cast_from_pyobject_ptr() == 6758
def test_cast_handle_to_pyobject_ptr():
assert m.cast_handle_to_pyobject_ptr(ValueHolder(24)) == 76
def test_cast_object_to_pyobject_ptr():
assert m.cast_object_to_pyobject_ptr(ValueHolder(43)) == 257
def test_cast_list_to_pyobject_ptr():
assert m.cast_list_to_pyobject_ptr([1, 2, 3, 4, 5]) == 395
def test_return_pyobject_ptr():
assert m.return_pyobject_ptr() == 2314
def test_pass_pyobject_ptr():
assert m.pass_pyobject_ptr(ValueHolder(82)) == 118
@pytest.mark.parametrize(
"call_callback",
[
m.call_callback_with_object_return,
m.call_callback_with_pyobject_ptr_return,
],
)
def test_call_callback_with_object_return(call_callback):
def cb(value):
if value < 0:
raise ValueError("Raised from cb")
return ValueHolder(1000 - value)
assert call_callback(cb, 287).value == 713
with pytest.raises(ValueError, match="^Raised from cb$"):
call_callback(cb, -1)
def test_call_callback_with_pyobject_ptr_arg():
def cb(obj):
return 300 - obj.value
assert m.call_callback_with_pyobject_ptr_arg(cb, ValueHolder(39)) == 261
@pytest.mark.parametrize("set_error", [True, False])
def test_cast_to_python_nullptr(set_error):
expected = {
True: r"^Reflective of healthy error handling\.$",
False: (
r"^Internal error: pybind11::error_already_set called "
r"while Python error indicator not set\.$"
),
}[set_error]
with pytest.raises(RuntimeError, match=expected):
m.cast_to_pyobject_ptr_nullptr(set_error)
def test_cast_to_python_non_nullptr_with_error_set():
with pytest.raises(SystemError) as excinfo:
m.cast_to_pyobject_ptr_non_nullptr_with_error_set()
assert str(excinfo.value) == "src != nullptr but PyErr_Occurred()"
assert str(excinfo.value.__cause__) == "Reflective of unhealthy error handling."
def test_pass_list_pyobject_ptr():
acc = m.pass_list_pyobject_ptr([ValueHolder(842), ValueHolder(452)])
assert acc == 842452
def test_return_list_pyobject_ptr_take_ownership():
vec_obj = m.return_list_pyobject_ptr_take_ownership(ValueHolder)
assert [e.value for e in vec_obj] == [93, 186]
def test_return_list_pyobject_ptr_reference():
vec_obj = m.return_list_pyobject_ptr_reference(ValueHolder)
assert [e.value for e in vec_obj] == [93, 186]
# Commenting out the next `assert` will leak the Python references.
# An easy way to see evidence of the leaks:
# Insert `while True:` as the first line of this function and monitor the
# process RES (Resident Memory Size) with the Unix top command.
assert m.dec_ref_each_pyobject_ptr(vec_obj) == 2
def test_type_caster_name_via_incompatible_function_arguments_type_error():
with pytest.raises(TypeError, match=r"1\. \(arg0: object, arg1: int\) -> None"):
m.pass_pyobject_ptr_and_int(ValueHolder(101), ValueHolder(202))

View File

@ -0,0 +1,38 @@
#include "pybind11_tests.h"
namespace {
struct any_struct {};
} // namespace
TEST_SUBMODULE(unnamed_namespace_a, m) {
if (py::detail::get_type_info(typeid(any_struct)) == nullptr) {
py::class_<any_struct>(m, "unnamed_namespace_a_any_struct");
} else {
m.attr("unnamed_namespace_a_any_struct") = py::none();
}
m.attr("PYBIND11_INTERNALS_VERSION") = PYBIND11_INTERNALS_VERSION;
m.attr("defined_WIN32_or__WIN32") =
#if defined(WIN32) || defined(_WIN32)
true;
#else
false;
#endif
m.attr("defined___clang__") =
#if defined(__clang__)
true;
#else
false;
#endif
m.attr("defined__LIBCPP_VERSION") =
#if defined(_LIBCPP_VERSION)
true;
#else
false;
#endif
m.attr("defined___GLIBCXX__") =
#if defined(__GLIBCXX__)
true;
#else
false;
#endif
}

View File

@ -0,0 +1,34 @@
import pytest
from pybind11_tests import unnamed_namespace_a as m
from pybind11_tests import unnamed_namespace_b as mb
XFAIL_CONDITION = (
"(m.PYBIND11_INTERNALS_VERSION <= 4 and (m.defined___clang__ or not m.defined___GLIBCXX__))"
" or "
"(m.PYBIND11_INTERNALS_VERSION >= 5 and not m.defined_WIN32_or__WIN32"
" and "
"(m.defined___clang__ or m.defined__LIBCPP_VERSION))"
)
XFAIL_REASON = "Known issues: https://github.com/pybind/pybind11/pull/4319"
@pytest.mark.xfail(XFAIL_CONDITION, reason=XFAIL_REASON, strict=False)
@pytest.mark.parametrize(
"any_struct", [m.unnamed_namespace_a_any_struct, mb.unnamed_namespace_b_any_struct]
)
def test_have_class_any_struct(any_struct):
assert any_struct is not None
def test_have_at_least_one_class_any_struct():
assert (
m.unnamed_namespace_a_any_struct is not None
or mb.unnamed_namespace_b_any_struct is not None
)
@pytest.mark.xfail(XFAIL_CONDITION, reason=XFAIL_REASON, strict=True)
def test_have_both_class_any_struct():
assert m.unnamed_namespace_a_any_struct is not None
assert mb.unnamed_namespace_b_any_struct is not None

View File

@ -0,0 +1,13 @@
#include "pybind11_tests.h"
namespace {
struct any_struct {};
} // namespace
TEST_SUBMODULE(unnamed_namespace_b, m) {
if (py::detail::get_type_info(typeid(any_struct)) == nullptr) {
py::class_<any_struct>(m, "unnamed_namespace_b_any_struct");
} else {
m.attr("unnamed_namespace_b_any_struct") = py::none();
}
}

View File

@ -0,0 +1,5 @@
from pybind11_tests import unnamed_namespace_b as m
def test_have_attr_any_struct():
assert hasattr(m, "unnamed_namespace_b_any_struct")

View File

@ -0,0 +1,54 @@
#include "pybind11_tests.h"
#include <cstddef>
#include <memory>
#include <vector>
namespace pybind11_tests {
namespace vector_unique_ptr_member {
struct DataType {};
// Reduced from a use case in the wild.
struct VectorOwner {
static std::unique_ptr<VectorOwner> Create(std::size_t num_elems) {
return std::unique_ptr<VectorOwner>(
new VectorOwner(std::vector<std::unique_ptr<DataType>>(num_elems)));
}
std::size_t data_size() const { return data_.size(); }
private:
explicit VectorOwner(std::vector<std::unique_ptr<DataType>> data) : data_(std::move(data)) {}
const std::vector<std::unique_ptr<DataType>> data_;
};
} // namespace vector_unique_ptr_member
} // namespace pybind11_tests
namespace pybind11 {
namespace detail {
template <>
struct is_copy_constructible<pybind11_tests::vector_unique_ptr_member::VectorOwner>
: std::false_type {};
template <>
struct is_move_constructible<pybind11_tests::vector_unique_ptr_member::VectorOwner>
: std::false_type {};
} // namespace detail
} // namespace pybind11
using namespace pybind11_tests::vector_unique_ptr_member;
py::object py_cast_VectorOwner_ptr(VectorOwner *ptr) { return py::cast(ptr); }
TEST_SUBMODULE(vector_unique_ptr_member, m) {
py::class_<VectorOwner>(m, "VectorOwner")
.def_static("Create", &VectorOwner::Create)
.def("data_size", &VectorOwner::data_size);
m.def("py_cast_VectorOwner_ptr", py_cast_VectorOwner_ptr);
}

Some files were not shown because too many files have changed in this diff Show More