From f239e136100cd23fe38e9c34aeb4fa46127f3f66 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Tue, 20 May 2025 19:17:04 +0200 Subject: [PATCH 01/45] Added initial testing module for stubgen tests. --- tests/CMakeLists.txt | 1 + tests/requirements.txt | 1 + tests/test_stubgen.cpp | 5 +++++ tests/test_stubgen.py | 7 +++++++ 4 files changed, 14 insertions(+) create mode 100644 tests/test_stubgen.cpp create mode 100644 tests/test_stubgen.py diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 374a138865..e68f12c84e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -169,6 +169,7 @@ set(PYBIND11_TEST_FILES test_smart_ptr test_stl test_stl_binders + test_stubgen test_tagbased_polymorphic test_thread test_type_caster_pyobject_ptr diff --git a/tests/requirements.txt b/tests/requirements.txt index 6e3a260b19..cb0fce9c72 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -9,6 +9,7 @@ numpy~=1.21.5; platform_python_implementation=="CPython" and python_version>="3. numpy~=1.22.2; platform_python_implementation=="CPython" and python_version=="3.10" numpy~=1.26.0; platform_python_implementation=="CPython" and python_version>="3.11" and python_version<"3.13" numpy~=2.2.0; platform_python_implementation=="CPython" and python_version=="3.13" +pybind11-stubgen pytest>=6 pytest-timeout scipy~=1.5.4; platform_python_implementation=="CPython" and python_version<"3.10" diff --git a/tests/test_stubgen.cpp b/tests/test_stubgen.cpp new file mode 100644 index 0000000000..a591b8de2b --- /dev/null +++ b/tests/test_stubgen.cpp @@ -0,0 +1,5 @@ +#include "pybind11_tests.h" + +TEST_SUBMODULE(stubgen, m) { + m.def("add_int", [](int a, int b) { return a + b; }, "a"_a, "b"_a); +} diff --git a/tests/test_stubgen.py b/tests/test_stubgen.py new file mode 100644 index 0000000000..fe4f29dbc1 --- /dev/null +++ b/tests/test_stubgen.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +from pybind11_tests import stubgen as m + + +def test_stubgen() -> None: + assert m.add_int(1, 2) == 3 From d713d3c184273420587de3ac63b28946cc62d658 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Tue, 20 May 2025 23:32:09 +0200 Subject: [PATCH 02/45] Added stubgen+mypy test --- tests/requirements.txt | 1 + tests/test_stubgen.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index cb0fce9c72..b26a546990 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,6 +1,7 @@ --extra-index-url=https://www.graalvm.org/python/wheels --only-binary=:all: build>=1 +mypy numpy~=1.23.0; python_version=="3.8" and platform_python_implementation=="PyPy" numpy~=1.25.0; python_version=="3.9" and platform_python_implementation=="PyPy" numpy~=2.2.0; python_version=="3.10" and platform_python_implementation=="PyPy" diff --git a/tests/test_stubgen.py b/tests/test_stubgen.py index fe4f29dbc1..78ffdd1cc3 100644 --- a/tests/test_stubgen.py +++ b/tests/test_stubgen.py @@ -1,7 +1,35 @@ from __future__ import annotations +from pathlib import Path + +import pybind11_stubgen as stubgen +from mypy import api + from pybind11_tests import stubgen as m -def test_stubgen() -> None: +def test_stubgen(tmp_path: Path) -> None: assert m.add_int(1, 2) == 3 + + stubgen.main( + [ + "pybind11_tests.stubgen", + "-o", + tmp_path.as_posix(), + ] + ) + print(f"Stubgen output: {tmp_path.as_posix()}") + stub_file = tmp_path / "pybind11_tests" / "stubgen.pyi" + assert stub_file.exists() + stub_content = stub_file.read_text() + assert ( + "def add_int(a: typing.SupportsInt, b: typing.SupportsInt) -> int:" + in stub_content + ) + normal_report, error_report, exit_status = api.run([stub_file.as_posix()]) + print("Normal report:") + print(normal_report) + print("Error report:") + print(error_report) + assert exit_status == 0 + assert "Success: no issues found in 1 source file" in normal_report From 3bfb20e2f4e8275041bfe90ef4cc1f1a61303710 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Wed, 21 May 2025 00:31:06 +0200 Subject: [PATCH 03/45] Add example test for stubgen/mypy error --- tests/CMakeLists.txt | 1 + tests/test_stubgen.py | 9 +++---- tests/test_stubgen_error.cpp | 5 ++++ tests/test_stubgen_error.py | 46 ++++++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 tests/test_stubgen_error.cpp create mode 100644 tests/test_stubgen_error.py diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e68f12c84e..9e5ee859f5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -170,6 +170,7 @@ set(PYBIND11_TEST_FILES test_stl test_stl_binders test_stubgen + test_stubgen_error test_tagbased_polymorphic test_thread test_type_caster_pyobject_ptr diff --git a/tests/test_stubgen.py b/tests/test_stubgen.py index 78ffdd1cc3..d8ea08a950 100644 --- a/tests/test_stubgen.py +++ b/tests/test_stubgen.py @@ -2,7 +2,7 @@ from pathlib import Path -import pybind11_stubgen as stubgen +import pybind11_stubgen from mypy import api from pybind11_tests import stubgen as m @@ -10,15 +10,15 @@ def test_stubgen(tmp_path: Path) -> None: assert m.add_int(1, 2) == 3 - - stubgen.main( + # Generate stub into temporary directory + pybind11_stubgen.main( [ "pybind11_tests.stubgen", "-o", tmp_path.as_posix(), ] ) - print(f"Stubgen output: {tmp_path.as_posix()}") + # Check stub file is generated and contains expected content stub_file = tmp_path / "pybind11_tests" / "stubgen.pyi" assert stub_file.exists() stub_content = stub_file.read_text() @@ -26,6 +26,7 @@ def test_stubgen(tmp_path: Path) -> None: "def add_int(a: typing.SupportsInt, b: typing.SupportsInt) -> int:" in stub_content ) + # Run mypy on the generated stub file normal_report, error_report, exit_status = api.run([stub_file.as_posix()]) print("Normal report:") print(normal_report) diff --git a/tests/test_stubgen_error.cpp b/tests/test_stubgen_error.cpp new file mode 100644 index 0000000000..3d677c3d54 --- /dev/null +++ b/tests/test_stubgen_error.cpp @@ -0,0 +1,5 @@ +#include "pybind11_tests.h" + +TEST_SUBMODULE(stubgen_error, m) { + m.def("identity_capsule", [](py::capsule c) { return c; }, "c"_a); +} diff --git a/tests/test_stubgen_error.py b/tests/test_stubgen_error.py new file mode 100644 index 0000000000..5ee93cf8cb --- /dev/null +++ b/tests/test_stubgen_error.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +import sys +from pathlib import Path + +import pybind11_stubgen +import pytest +from mypy import api + +from pybind11_tests import stubgen_error as m + + +@pytest.mark.skipif( + sys.version_info >= (3, 13), reason="CapsuleType available in 3.13+" +) +def test_stubgen(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: + """Show stubgen/mypy errors for CapsuleType (not available in Python < 3.13).""" + assert m.identity_capsule(None) is None + # Generate stub into temporary directory + pybind11_stubgen.main( + [ + "pybind11_tests.stubgen_error", + "-o", + tmp_path.as_posix(), + ] + ) + # Errors are reported using logging + assert "Can't find/import 'types.CapsuleType'" in caplog.text + # Stub file is still generated if error is not fatal + stub_file = tmp_path / "pybind11_tests" / "stubgen_error.pyi" + assert stub_file.exists() + stub_content = stub_file.read_text() + assert ( + "def identity_capsule(c: types.CapsuleType) -> types.CapsuleType:" + in stub_content + ) + # Run mypy on the generated stub file + # normal_report -> stdout, error_report -> stderr + # Type errors seem to go into normal_report + normal_report, error_report, exit_status = api.run([stub_file.as_posix()]) + print("Normal report:") + print(normal_report) + print("Error report:") + print(error_report) + assert exit_status == 1 + assert 'error: Name "types" is not defined [name-defined]' in normal_report From 9b6f056d65fe035a8078bde7871c71a8b7b0cfc9 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Wed, 21 May 2025 00:52:42 +0200 Subject: [PATCH 04/45] Add pybind11-stubgen and mypy dependency to pyproject.toml --- tests/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pyproject.toml b/tests/pyproject.toml index 469c145dfd..96733fde76 100644 --- a/tests/pyproject.toml +++ b/tests/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "scikit_build_core.build" [project] name = "pybind11_tests" version = "0.0.1" -dependencies = ["pytest", "pytest-timeout", "numpy", "scipy"] +dependencies = ["pytest", "pytest-timeout", "numpy", "scipy", "pybind11-stubgen", "mypy"] [tool.scikit-build.cmake.define] PYBIND11_FINDPYTHON = true From b5c12db3cd2025c6ead35f7440bca00cb7316faf Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Wed, 21 May 2025 00:55:24 +0200 Subject: [PATCH 05/45] Remove capsule function call with None --- tests/test_stubgen_error.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_stubgen_error.py b/tests/test_stubgen_error.py index 5ee93cf8cb..33f0268c28 100644 --- a/tests/test_stubgen_error.py +++ b/tests/test_stubgen_error.py @@ -15,7 +15,7 @@ ) def test_stubgen(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: """Show stubgen/mypy errors for CapsuleType (not available in Python < 3.13).""" - assert m.identity_capsule(None) is None + assert hasattr(m, "identity_capsule") # Generate stub into temporary directory pybind11_stubgen.main( [ From 1ab70995c60bc7fbd2357c33d8d4f80d9e08eae8 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Thu, 22 May 2025 09:43:29 +0200 Subject: [PATCH 06/45] Added skipif for Python 3.14 since mypy failes here --- tests/test_stubgen.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_stubgen.py b/tests/test_stubgen.py index d8ea08a950..39837fb60a 100644 --- a/tests/test_stubgen.py +++ b/tests/test_stubgen.py @@ -1,13 +1,18 @@ from __future__ import annotations +import sys from pathlib import Path import pybind11_stubgen +import pytest from mypy import api from pybind11_tests import stubgen as m +@pytest.mark.xfail( + sys.version_info >= (3, 14), reason="mypy does not support Python 3.14+ yet" +) def test_stubgen(tmp_path: Path) -> None: assert m.add_int(1, 2) == 3 # Generate stub into temporary directory From f3d51f63a1db44ba1e0d8587e44fe9f16f3a30de Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Thu, 22 May 2025 09:46:16 +0200 Subject: [PATCH 07/45] Added --no-color-output to mypy call for easier checking of report --- tests/test_stubgen_error.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_stubgen_error.py b/tests/test_stubgen_error.py index 33f0268c28..2dbe2aa31c 100644 --- a/tests/test_stubgen_error.py +++ b/tests/test_stubgen_error.py @@ -37,7 +37,9 @@ def test_stubgen(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: # Run mypy on the generated stub file # normal_report -> stdout, error_report -> stderr # Type errors seem to go into normal_report - normal_report, error_report, exit_status = api.run([stub_file.as_posix()]) + normal_report, error_report, exit_status = api.run( + [stub_file.as_posix(), "--no-color-output"] + ) print("Normal report:") print(normal_report) print("Error report:") From 2c5d721f06a118ef579fa049776bb79be451001f Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Thu, 22 May 2025 10:51:28 +0200 Subject: [PATCH 08/45] Install test dependencies from requirements.txt in CI --- .appveyor.yml | 2 +- .github/workflows/ci.yml | 51 ++++++++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 391cf1071c..21ec074c5e 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -17,7 +17,7 @@ install: if ($env:PLATFORM -eq "x64") { $env:PYTHON = "$env:PYTHON-x64" } $env:PATH = "C:\Python$env:PYTHON\;C:\Python$env:PYTHON\Scripts\;$env:PATH" python -W ignore -m pip install --upgrade pip wheel - python -W ignore -m pip install pytest numpy --no-warn-script-location pytest-timeout + python -W ignore -m pip install --no-warn-script-location -r tests/requirements.txt - ps: | Start-FileDownload 'https://gitlab.com/libeigen/eigen/-/archive/3.3.7/eigen-3.3.7.zip' 7z x eigen-3.3.7.zip -y > $null diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef554d4c2e..6ee929f688 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -343,8 +343,16 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Add wget and python3 - run: apt-get update && apt-get install -y python3-dev python3-numpy python3-pytest libeigen3-dev + - name: Add python3 + run: apt-get update && apt-get install -y python3-dev libeigen3-dev + + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + + - name: Prepare env + run: uv pip install --python=python3 --system -r tests/requirements.txt - name: Configure shell: bash @@ -380,7 +388,15 @@ jobs: # tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND - name: Install 🐍 3 - run: apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y cmake git python3-dev python3-pytest python3-numpy + run: apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y cmake git python3-dev + + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + + - name: Prepare env + run: uv pip install --python=python3 --system -r tests/requirements.txt - name: Configure run: cmake -S . -B build -DPYBIND11_CUDA_TESTS=ON -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON @@ -455,11 +471,11 @@ jobs: - name: Install 🐍 3 & NVHPC run: | sudo apt-get update -y && \ - sudo apt-get install -y cmake environment-modules git python3-dev python3-pip python3-numpy && \ + sudo apt-get install -y cmake environment-modules git python3-dev python3-pip && \ sudo apt-get install -y --no-install-recommends nvhpc-23-5 && \ sudo rm -rf /var/lib/apt/lists/* python3 -m pip install --upgrade pip - python3 -m pip install --upgrade pytest + python3 -m pip install --system -r tests/requirements.txt # On some systems, you many need further workarounds: # https://github.com/pybind/pybind11/pull/2475 @@ -506,10 +522,15 @@ jobs: - uses: actions/checkout@v4 - name: Add Python 3 - run: apt-get update; apt-get install -y python3-dev python3-numpy python3-pytest python3-pip libeigen3-dev + run: apt-get update; apt-get install -y python3-dev libeigen3-dev - - name: Update pip - run: python3 -m pip install --upgrade pip + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + + - name: Prepare env + run: uv pip install --python=python3 --system -r tests/requirements.txt - name: Update CMake uses: jwlawson/actions-setup-cmake@v2.0 @@ -724,7 +745,7 @@ jobs: run: | apt-get update apt-get install -y git make cmake g++ libeigen3-dev python3-dev python3-pip - pip3 install "pytest==6.*" + pip3 install -r tests/requirements.txt - name: Configure for install run: > @@ -977,7 +998,6 @@ jobs: mingw-w64-${{matrix.env}}-python-pip mingw-w64-${{matrix.env}}-cmake mingw-w64-${{matrix.env}}-make - mingw-w64-${{matrix.env}}-python-pytest mingw-w64-${{matrix.env}}-boost mingw-w64-${{matrix.env}}-catch @@ -986,12 +1006,19 @@ jobs: with: msystem: ${{matrix.sys}} install: >- - mingw-w64-${{matrix.env}}-python-numpy - mingw-w64-${{matrix.env}}-python-scipy mingw-w64-${{matrix.env}}-eigen3 - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + + - name: Prepare env + run: uv pip install --python=python --system -r tests/requirements.txt + + - name: Configure C++11 # LTO leads to many undefined reference like # `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&) From a83b6c2cc8013a3e22e1834d54d7ce53fc3c00fd Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Thu, 22 May 2025 11:04:38 +0200 Subject: [PATCH 09/45] Removed incompatible --system from requirements.txt installs in CI --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ee929f688..da31c81980 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,7 +143,7 @@ jobs: enable-cache: true - name: Prepare env - run: uv pip install --python=python --system -r tests/requirements.txt + run: uv pip install --python=python -r tests/requirements.txt - name: Setup annotations on Linux if: runner.os == 'Linux' @@ -352,7 +352,7 @@ jobs: enable-cache: true - name: Prepare env - run: uv pip install --python=python3 --system -r tests/requirements.txt + run: uv pip install --python=python3 -r tests/requirements.txt - name: Configure shell: bash @@ -396,7 +396,7 @@ jobs: enable-cache: true - name: Prepare env - run: uv pip install --python=python3 --system -r tests/requirements.txt + run: uv pip install --python=python3 -r tests/requirements.txt - name: Configure run: cmake -S . -B build -DPYBIND11_CUDA_TESTS=ON -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON @@ -475,7 +475,7 @@ jobs: sudo apt-get install -y --no-install-recommends nvhpc-23-5 && \ sudo rm -rf /var/lib/apt/lists/* python3 -m pip install --upgrade pip - python3 -m pip install --system -r tests/requirements.txt + python3 -m pip install -r tests/requirements.txt # On some systems, you many need further workarounds: # https://github.com/pybind/pybind11/pull/2475 @@ -530,7 +530,7 @@ jobs: enable-cache: true - name: Prepare env - run: uv pip install --python=python3 --system -r tests/requirements.txt + run: uv pip install --python=python3 -r tests/requirements.txt - name: Update CMake uses: jwlawson/actions-setup-cmake@v2.0 @@ -1016,7 +1016,7 @@ jobs: enable-cache: true - name: Prepare env - run: uv pip install --python=python --system -r tests/requirements.txt + run: uv pip install --python=python -r tests/requirements.txt - name: Configure C++11 From 36c4e2d3654f33b2513e91bf3e7d6e8a1d776920 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Thu, 22 May 2025 11:26:55 +0200 Subject: [PATCH 10/45] Readded --system only to uv pip calls --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da31c81980..2c71529ad5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,7 +143,7 @@ jobs: enable-cache: true - name: Prepare env - run: uv pip install --python=python -r tests/requirements.txt + run: uv pip install --python=python --system -r tests/requirements.txt - name: Setup annotations on Linux if: runner.os == 'Linux' @@ -352,7 +352,7 @@ jobs: enable-cache: true - name: Prepare env - run: uv pip install --python=python3 -r tests/requirements.txt + run: uv pip install --python=python3 --system -r tests/requirements.txt - name: Configure shell: bash @@ -396,7 +396,7 @@ jobs: enable-cache: true - name: Prepare env - run: uv pip install --python=python3 -r tests/requirements.txt + run: uv pip install --python=python3 --system -r tests/requirements.txt - name: Configure run: cmake -S . -B build -DPYBIND11_CUDA_TESTS=ON -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON @@ -530,7 +530,7 @@ jobs: enable-cache: true - name: Prepare env - run: uv pip install --python=python3 -r tests/requirements.txt + run: uv pip install --python=python3 --system -r tests/requirements.txt - name: Update CMake uses: jwlawson/actions-setup-cmake@v2.0 @@ -1016,7 +1016,7 @@ jobs: enable-cache: true - name: Prepare env - run: uv pip install --python=python -r tests/requirements.txt + run: uv pip install --python=python --system -r tests/requirements.txt - name: Configure C++11 From b351f839b65ba3cbcaebc61eed8e59b6cfa7edc1 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Thu, 22 May 2025 12:07:09 +0200 Subject: [PATCH 11/45] Using uv activate-environment to fix issue with system wide install --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c71529ad5..a51a3ae101 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -349,10 +349,11 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v6 with: + activate-environment: true enable-cache: true - name: Prepare env - run: uv pip install --python=python3 --system -r tests/requirements.txt + run: uv pip install -r tests/requirements.txt - name: Configure shell: bash @@ -527,10 +528,11 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v6 with: + activate-environment: true enable-cache: true - name: Prepare env - run: uv pip install --python=python3 --system -r tests/requirements.txt + run: uv pip install -r tests/requirements.txt - name: Update CMake uses: jwlawson/actions-setup-cmake@v2.0 From 3c881caeaf36e42da80c51d48c746b2210a6b49e Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Thu, 22 May 2025 12:08:08 +0200 Subject: [PATCH 12/45] Using mingw-uv install to fix uv not being found --- .github/workflows/ci.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a51a3ae101..b94ea0a10a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1002,6 +1002,7 @@ jobs: mingw-w64-${{matrix.env}}-make mingw-w64-${{matrix.env}}-boost mingw-w64-${{matrix.env}}-catch + mingw-w64-${{matrix.env}}-uv - uses: msys2/setup-msys2@v2 if: matrix.sys == 'mingw64' @@ -1012,11 +1013,6 @@ jobs: - uses: actions/checkout@v4 - - name: Install uv - uses: astral-sh/setup-uv@v6 - with: - enable-cache: true - - name: Prepare env run: uv pip install --python=python --system -r tests/requirements.txt From f04d790c2624387b014c2dff78213309659b9a9c Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Thu, 22 May 2025 12:37:07 +0200 Subject: [PATCH 13/45] Using pip instead of uv for mingw --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b94ea0a10a..723b9165a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1002,7 +1002,6 @@ jobs: mingw-w64-${{matrix.env}}-make mingw-w64-${{matrix.env}}-boost mingw-w64-${{matrix.env}}-catch - mingw-w64-${{matrix.env}}-uv - uses: msys2/setup-msys2@v2 if: matrix.sys == 'mingw64' @@ -1014,7 +1013,7 @@ jobs: - uses: actions/checkout@v4 - name: Prepare env - run: uv pip install --python=python --system -r tests/requirements.txt + run: python -m pip install -r tests/requirements.txt - name: Configure C++11 From 76f3b89dbfc4223cb7e98d9350d210630f651980 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Thu, 22 May 2025 13:02:52 +0200 Subject: [PATCH 14/45] Revert mingw CI to install some test requirements using setup-msys2 --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 723b9165a5..8aeef900b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1000,6 +1000,7 @@ jobs: mingw-w64-${{matrix.env}}-python-pip mingw-w64-${{matrix.env}}-cmake mingw-w64-${{matrix.env}}-make + mingw-w64-${{matrix.env}}-python-pytest mingw-w64-${{matrix.env}}-boost mingw-w64-${{matrix.env}}-catch @@ -1008,6 +1009,8 @@ jobs: with: msystem: ${{matrix.sys}} install: >- + mingw-w64-${{matrix.env}}-python-numpy + mingw-w64-${{matrix.env}}-python-scipy mingw-w64-${{matrix.env}}-eigen3 - uses: actions/checkout@v4 From d9d524018c9b0ea4286cda5e487de66aba76ccaa Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Fri, 23 May 2025 19:04:11 +0200 Subject: [PATCH 15/45] Added test to check stubgen and mypy on all tests --- tests/test_stubgen.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_stubgen.py b/tests/test_stubgen.py index 39837fb60a..bfded2b449 100644 --- a/tests/test_stubgen.py +++ b/tests/test_stubgen.py @@ -39,3 +39,26 @@ def test_stubgen(tmp_path: Path) -> None: print(error_report) assert exit_status == 0 assert "Success: no issues found in 1 source file" in normal_report + + +def test_stubgen_all(tmp_path: Path) -> None: + # Generate stub into temporary directory + pybind11_stubgen.main( + [ + "pybind11_tests", + "-o", + tmp_path.as_posix(), + ] + ) + # Check stub file is generated and contains expected content + stub_file = tmp_path / "pybind11_tests" + assert stub_file.exists() + # stub_content = stub_file.read_text() + # Run mypy on the generated stub file + normal_report, error_report, exit_status = api.run([stub_file.as_posix()]) + print("Normal report:") + print(normal_report) + print("Error report:") + print(error_report) + assert exit_status == 0 + assert "Success: no issues found in 1 source file" in normal_report From 18385c46c613d622104bd8791898a2d900f8ddd2 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Sun, 25 May 2025 13:37:02 +0200 Subject: [PATCH 16/45] Module of CapsuleType now depends on Python version --- include/pybind11/cast.h | 4 +-- include/pybind11/detail/common.h | 5 ++++ tests/CMakeLists.txt | 1 - tests/test_opaque_types.py | 11 ++++++-- tests/test_stubgen_error.cpp | 5 ---- tests/test_stubgen_error.py | 48 -------------------------------- 6 files changed, 16 insertions(+), 58 deletions(-) delete mode 100644 tests/test_stubgen_error.cpp delete mode 100644 tests/test_stubgen_error.py diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 0cc63b0b4a..ab393e0d16 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -404,7 +404,7 @@ class type_caster : public type_caster { template using cast_op_type = void *&; explicit operator void *&() { return value; } - static constexpr auto name = const_name("types.CapsuleType"); + static constexpr auto name = const_name(PYBIND11_CAPSULE_TYPE_HINT); private: void *value = nullptr; @@ -1439,7 +1439,7 @@ struct handle_type_name { }; template <> struct handle_type_name { - static constexpr auto name = const_name("types.CapsuleType"); + static constexpr auto name = const_name(PYBIND11_CAPSULE_TYPE_HINT); }; template <> struct handle_type_name { diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 2e81150f16..f8d679e79e 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -245,6 +245,11 @@ #else # define PYBIND11_BUFFER_TYPE_HINT "typing_extensions.Buffer" #endif +#if 0x030D0000 <= PY_VERSION_HEX +# define PYBIND11_CAPSULE_TYPE_HINT "types.CapsuleType" +#else +# define PYBIND11_CAPSULE_TYPE_HINT "typing_extensions.CapsuleType" +#endif // #define PYBIND11_STR_LEGACY_PERMISSIVE // If DEFINED, pybind11::str can hold PyUnicodeObject or PyBytesObject diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9e5ee859f5..e68f12c84e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -170,7 +170,6 @@ set(PYBIND11_TEST_FILES test_stl test_stl_binders test_stubgen - test_stubgen_error test_tagbased_polymorphic test_thread test_type_caster_pyobject_ptr diff --git a/tests/test_opaque_types.py b/tests/test_opaque_types.py index 498b6e02b2..f21b4dfee4 100644 --- a/tests/test_opaque_types.py +++ b/tests/test_opaque_types.py @@ -1,5 +1,7 @@ from __future__ import annotations +import sys + import pytest import env @@ -37,11 +39,16 @@ def test_pointers(msg): with pytest.raises(TypeError) as excinfo: m.get_void_ptr_value([1, 2, 3]) # This should not work + capsule_type = ( + "types.CapsuleType" + if sys.version_info >= (3, 13) + else "typing_extensions.CapsuleType" + ) assert ( msg(excinfo.value) - == """ + == f""" get_void_ptr_value(): incompatible function arguments. The following argument types are supported: - 1. (arg0: types.CapsuleType) -> int + 1. (arg0: {capsule_type}) -> int Invoked with: [1, 2, 3] """ diff --git a/tests/test_stubgen_error.cpp b/tests/test_stubgen_error.cpp deleted file mode 100644 index 3d677c3d54..0000000000 --- a/tests/test_stubgen_error.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "pybind11_tests.h" - -TEST_SUBMODULE(stubgen_error, m) { - m.def("identity_capsule", [](py::capsule c) { return c; }, "c"_a); -} diff --git a/tests/test_stubgen_error.py b/tests/test_stubgen_error.py deleted file mode 100644 index 2dbe2aa31c..0000000000 --- a/tests/test_stubgen_error.py +++ /dev/null @@ -1,48 +0,0 @@ -from __future__ import annotations - -import sys -from pathlib import Path - -import pybind11_stubgen -import pytest -from mypy import api - -from pybind11_tests import stubgen_error as m - - -@pytest.mark.skipif( - sys.version_info >= (3, 13), reason="CapsuleType available in 3.13+" -) -def test_stubgen(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: - """Show stubgen/mypy errors for CapsuleType (not available in Python < 3.13).""" - assert hasattr(m, "identity_capsule") - # Generate stub into temporary directory - pybind11_stubgen.main( - [ - "pybind11_tests.stubgen_error", - "-o", - tmp_path.as_posix(), - ] - ) - # Errors are reported using logging - assert "Can't find/import 'types.CapsuleType'" in caplog.text - # Stub file is still generated if error is not fatal - stub_file = tmp_path / "pybind11_tests" / "stubgen_error.pyi" - assert stub_file.exists() - stub_content = stub_file.read_text() - assert ( - "def identity_capsule(c: types.CapsuleType) -> types.CapsuleType:" - in stub_content - ) - # Run mypy on the generated stub file - # normal_report -> stdout, error_report -> stderr - # Type errors seem to go into normal_report - normal_report, error_report, exit_status = api.run( - [stub_file.as_posix(), "--no-color-output"] - ) - print("Normal report:") - print(normal_report) - print("Error report:") - print(error_report) - assert exit_status == 1 - assert 'error: Name "types" is not defined [name-defined]' in normal_report From c37e65eb2e21a1a317fd53b88b6d8aa771c3ee36 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Sun, 25 May 2025 13:45:15 +0200 Subject: [PATCH 17/45] Moved mypy call to separate function to assure `--no-color-output` --- tests/test_stubgen.py | 51 ++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/tests/test_stubgen.py b/tests/test_stubgen.py index bfded2b449..cbcdcb4fa4 100644 --- a/tests/test_stubgen.py +++ b/tests/test_stubgen.py @@ -2,6 +2,7 @@ import sys from pathlib import Path +from typing import NamedTuple import pybind11_stubgen import pytest @@ -10,6 +11,24 @@ from pybind11_tests import stubgen as m +class MypyResult(NamedTuple): + normal_report: str + error_report: str + exit_status: int + + +def run_mypy(stubs: Path) -> MypyResult: + """Run mypy on the given stubs directory.""" + normal_report, error_report, exit_status = api.run( + [stubs.as_posix(), "--no-color-output"] + ) + print("Normal report:") + print(normal_report) + print("Error report:") + print(error_report) + return MypyResult(normal_report, error_report, exit_status) + + @pytest.mark.xfail( sys.version_info >= (3, 14), reason="mypy does not support Python 3.14+ yet" ) @@ -32,16 +51,12 @@ def test_stubgen(tmp_path: Path) -> None: in stub_content ) # Run mypy on the generated stub file - normal_report, error_report, exit_status = api.run([stub_file.as_posix()]) - print("Normal report:") - print(normal_report) - print("Error report:") - print(error_report) - assert exit_status == 0 - assert "Success: no issues found in 1 source file" in normal_report + result = run_mypy(stub_file) + assert result.exit_status == 0 + assert "Success: no issues found in 1 source file" in result.normal_report -def test_stubgen_all(tmp_path: Path) -> None: +def test_stubgen_all(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: # Generate stub into temporary directory pybind11_stubgen.main( [ @@ -50,15 +65,15 @@ def test_stubgen_all(tmp_path: Path) -> None: tmp_path.as_posix(), ] ) + # Errors are reported using logging + assert ( + "Raw C++ types/values were found in signatures extracted from docstrings" + in caplog.text + ) # Check stub file is generated and contains expected content - stub_file = tmp_path / "pybind11_tests" - assert stub_file.exists() - # stub_content = stub_file.read_text() + stubs = tmp_path / "pybind11_tests" + assert stubs.exists() # Run mypy on the generated stub file - normal_report, error_report, exit_status = api.run([stub_file.as_posix()]) - print("Normal report:") - print(normal_report) - print("Error report:") - print(error_report) - assert exit_status == 0 - assert "Success: no issues found in 1 source file" in normal_report + result = run_mypy(stubs) + assert result.exit_status == 0 + assert "Success: no issues found in 1 source file" in result.normal_report From f4308038425387f05fc1c93e2d9c197d8bc2298a Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 25 May 2025 11:28:08 -0400 Subject: [PATCH 18/45] fix future type hints Signed-off-by: Michael Carlstrom --- include/pybind11/cast.h | 8 +- include/pybind11/detail/common.h | 25 ++ include/pybind11/detail/descr.h | 18 ++ include/pybind11/stl/filesystem.h | 6 +- include/pybind11/typing.h | 33 ++- tests/test_opaque_types.py | 31 ++- tests/test_pytypes.cpp | 7 +- tests/test_pytypes.py | 435 +++++++++++++++++++++--------- tests/test_stl.py | 89 ++++-- 9 files changed, 475 insertions(+), 177 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 0cc63b0b4a..3f0f743b3e 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -404,7 +404,7 @@ class type_caster : public type_caster { template using cast_op_type = void *&; explicit operator void *&() { return value; } - static constexpr auto name = const_name("types.CapsuleType"); + static constexpr auto name = const_name(PYBIND11_CAPSULE_TYPE_TYPE_HINT); private: void *value = nullptr; @@ -1359,7 +1359,11 @@ struct handle_type_name { }; template <> struct handle_type_name { +#if PYBIND11_USE_NEW_UNIONS + static constexpr auto name = const_name("set | frozenset"); +#else static constexpr auto name = const_name("typing.Union[set, frozenset]"); +#endif }; template <> struct handle_type_name { @@ -1439,7 +1443,7 @@ struct handle_type_name { }; template <> struct handle_type_name { - static constexpr auto name = const_name("types.CapsuleType"); + static constexpr auto name = const_name(PYBIND11_CAPSULE_TYPE_TYPE_HINT); }; template <> struct handle_type_name { diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 6a9440b60d..5c668eb1dd 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -261,6 +261,15 @@ # define PYBIND11_HAS_SUBINTERPRETER_SUPPORT #endif +// 3.13 Compatibility +#if 0x030D0000 <= PY_VERSION_HEX +# define PYBIND11_TYPE_IS_TYPE_HINT "typing.TypeIs" +# define PYBIND11_CAPSULE_TYPE_TYPE_HINT "types.CapsuleType" +#else +# define PYBIND11_TYPE_IS_TYPE_HINT "typing_extensions.TypeIs" +# define PYBIND11_CAPSULE_TYPE_TYPE_HINT "typing_extensions.CapsuleType" +#endif + // 3.12 Compatibility #if 0x030C0000 <= PY_VERSION_HEX # define PYBIND11_BUFFER_TYPE_HINT "collections.abc.Buffer" @@ -268,6 +277,22 @@ # define PYBIND11_BUFFER_TYPE_HINT "typing_extensions.Buffer" #endif +// 3.11 Compatibility +#if 0x030B0000 <= PY_VERSION_HEX +# define PYBIND11_NEVER_TYPE_HINT "typing.Never" +#else +# define PYBIND11_NEVER_TYPE_HINT "typing_extensions.Never" +#endif + +// 3.10 Compatibility +#if 0x030A0000 <= PY_VERSION_HEX +# define PYBIND11_USE_NEW_UNIONS true +# define PYBIND11_TYPE_GUARD_TYPE_HINT "typing.TypeGuard" +#else +# define PYBIND11_USE_NEW_UNIONS false +# define PYBIND11_TYPE_GUARD_TYPE_HINT "typing_extensions.TypeGuard" +#endif + // #define PYBIND11_STR_LEGACY_PERMISSIVE // If DEFINED, pybind11::str can hold PyUnicodeObject or PyBytesObject // (probably surprising and never documented, but this was the diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index e5f829d2eb..90fa6da456 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -174,12 +174,30 @@ template constexpr auto concat(const descr &d, const Args &...args) { return (d, ..., args); } + +template +constexpr descr operator|(const descr &a, + const descr &b) { + return a + const_name(" | ") + b; +} + +template +constexpr auto union_concat(const descr &d, const Args &...args) { + return (d | ... | args); +} + #else template constexpr auto concat(const descr &d, const Args &...args) -> decltype(std::declval>() + concat(args...)) { return d + const_name(", ") + concat(args...); } + +template +constexpr auto union_concat(const descr &d, const Args &...args) + -> decltype(std::declval>() + union_concat(args...)) { + return d + const_name(" | ") + union_concat(args...); +} #endif template diff --git a/include/pybind11/stl/filesystem.h b/include/pybind11/stl/filesystem.h index de64193ac4..a9ce8c1e7c 100644 --- a/include/pybind11/stl/filesystem.h +++ b/include/pybind11/stl/filesystem.h @@ -96,7 +96,11 @@ struct path_caster { return true; } - PYBIND11_TYPE_CASTER(T, io_name("typing.Union[os.PathLike, str, bytes]", "pathlib.Path")); + #if PYBIND11_USE_NEW_UNIONS + PYBIND11_TYPE_CASTER(T, io_name("os.PathLike | str | bytes", "pathlib.Path")); + #else + PYBIND11_TYPE_CASTER(T, io_name("typing.Union[os.PathLike, str, bytes]", "pathlib.Path")); + #endif }; #endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 57b2c087d1..48c93064f4 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -216,18 +216,39 @@ struct handle_type_name> { static constexpr auto name = const_name("type[") + make_caster::name + const_name("]"); }; +#if PYBIND11_USE_NEW_UNIONS +template +constexpr auto union_helper() { + return ::pybind11::detail::union_concat(make_caster::name...); +} +#else +template +constexpr auto union_helper() { + return const_name("typing.Union[") + + ::pybind11::detail::concat(make_caster::name...) + + const_name("]"); +} +#endif + template struct handle_type_name> { - static constexpr auto name = const_name("typing.Union[") - + ::pybind11::detail::concat(make_caster::name...) - + const_name("]"); + static constexpr auto name = union_helper(); }; + +#if PYBIND11_USE_NEW_UNIONS +template +struct handle_type_name> { + static constexpr auto name + = union_helper(); +}; +#else template struct handle_type_name> { static constexpr auto name = const_name("typing.Optional[") + make_caster::name + const_name("]"); }; +#endif template struct handle_type_name> { @@ -245,13 +266,13 @@ struct handle_type_name> { template struct handle_type_name> { static constexpr auto name - = const_name("typing.TypeGuard[") + make_caster::name + const_name("]"); + = const_name(PYBIND11_TYPE_GUARD_TYPE_HINT) + const_name("[") + make_caster::name + const_name("]"); }; template struct handle_type_name> { static constexpr auto name - = const_name("typing.TypeIs[") + make_caster::name + const_name("]"); + = const_name(PYBIND11_TYPE_IS_TYPE_HINT) + const_name("[") + make_caster::name + const_name("]"); }; template <> @@ -261,7 +282,7 @@ struct handle_type_name { template <> struct handle_type_name { - static constexpr auto name = const_name("typing.Never"); + static constexpr auto name = const_name(PYBIND11_NEVER_TYPE_HINT); }; #if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL) diff --git a/tests/test_opaque_types.py b/tests/test_opaque_types.py index 498b6e02b2..6790b89d25 100644 --- a/tests/test_opaque_types.py +++ b/tests/test_opaque_types.py @@ -1,5 +1,6 @@ from __future__ import annotations +import sys import pytest import env @@ -37,15 +38,27 @@ def test_pointers(msg): with pytest.raises(TypeError) as excinfo: m.get_void_ptr_value([1, 2, 3]) # This should not work - assert ( - msg(excinfo.value) - == """ - get_void_ptr_value(): incompatible function arguments. The following argument types are supported: - 1. (arg0: types.CapsuleType) -> int - - Invoked with: [1, 2, 3] - """ - ) + + if sys.version_info >= (3, 13): + assert ( + msg(excinfo.value) + == """ + get_void_ptr_value(): incompatible function arguments. The following argument types are supported: + 1. (arg0: types.CapsuleType) -> int + + Invoked with: [1, 2, 3] + """ + ) + else: + assert ( + msg(excinfo.value) + == """ + get_void_ptr_value(): incompatible function arguments. The following argument types are supported: + 1. (arg0: typing_extensions.CapsuleType) -> int + + Invoked with: [1, 2, 3] + """ + ) assert m.return_null_str() is None assert m.get_null_str_value(m.return_null_str()) is not None diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 657480a005..99b38c5a1d 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -155,7 +155,11 @@ namespace detail { template <> struct type_caster { - PYBIND11_TYPE_CASTER(RealNumber, io_name("typing.Union[float, int]", "float")); + #if PYBIND11_USE_NEW_UNIONS + PYBIND11_TYPE_CASTER(RealNumber, io_name("float | int", "float")); + #else + PYBIND11_TYPE_CASTER(RealNumber, io_name("typing.Union[float, int]", "float")); + #endif static handle cast(const RealNumber &number, return_value_policy, handle) { return py::float_(number.value).release(); @@ -172,6 +176,7 @@ struct type_caster { value.value = src.cast(); return true; } + // static void f/ }; } // namespace detail diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 545ad5b97f..8db016cd4b 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -130,10 +130,16 @@ def test_set(capture, doc): assert m.anyset_contains({"foo"}, "foo") assert doc(m.get_set) == "get_set() -> set" - assert ( - doc(m.print_anyset) - == "print_anyset(arg0: typing.Union[set, frozenset]) -> None" - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.print_anyset) + == "print_anyset(arg0: set | frozenset) -> None" + ) + else: + assert ( + doc(m.print_anyset) + == "print_anyset(arg0: typing.Union[set, frozenset]) -> None" + ) def test_frozenset(capture, doc): @@ -990,45 +996,81 @@ def test_type_annotation(doc): def test_union_annotations(doc): - assert ( - doc(m.annotate_union) - == "annotate_union(arg0: list[typing.Union[str, typing.SupportsInt, object]], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[typing.Union[str, int, object]]" - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.annotate_union) + == "annotate_union(arg0: list[str | typing.SupportsInt | object], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[str | int | object]" + ) + else: + assert ( + doc(m.annotate_union) + == "annotate_union(arg0: list[typing.Union[str, typing.SupportsInt, object]], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[typing.Union[str, int, object]]" + ) def test_union_typing_only(doc): - assert ( - doc(m.union_typing_only) - == "union_typing_only(arg0: list[typing.Union[str]]) -> list[typing.Union[int]]" - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.union_typing_only) + == "union_typing_only(arg0: list[str]) -> list[int]" + ) + else: + assert ( + doc(m.union_typing_only) + == "union_typing_only(arg0: list[typing.Union[str]]) -> list[typing.Union[int]]" + ) def test_union_object_annotations(doc): - assert ( - doc(m.annotate_union_to_object) - == "annotate_union_to_object(arg0: typing.Union[typing.SupportsInt, str]) -> object" - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.annotate_union_to_object) + == "annotate_union_to_object(arg0: typing.SupportsInt | str) -> object" + ) + else: + assert ( + doc(m.annotate_union_to_object) + == "annotate_union_to_object(arg0: typing.Union[typing.SupportsInt, str]) -> object" + ) def test_optional_annotations(doc): - assert ( - doc(m.annotate_optional) - == "annotate_optional(arg0: list) -> list[typing.Optional[str]]" - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.annotate_optional) + == "annotate_optional(arg0: list) -> list[str | None]" + ) + else: + assert ( + doc(m.annotate_optional) + == "annotate_optional(arg0: list) -> list[typing.Optional[str]]" + ) def test_type_guard_annotations(doc): - assert ( - doc(m.annotate_type_guard) - == "annotate_type_guard(arg0: object) -> typing.TypeGuard[str]" - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.annotate_type_guard) + == "annotate_type_guard(arg0: object) -> typing.TypeGuard[str]" + ) + else: + assert ( + doc(m.annotate_type_guard) + == "annotate_type_guard(arg0: object) -> typing_extensions.TypeGuard[str]" + ) def test_type_is_annotations(doc): - assert ( - doc(m.annotate_type_is) - == "annotate_type_is(arg0: object) -> typing.TypeIs[str]" - ) + if sys.version_info >= (3, 13): + assert ( + doc(m.annotate_type_is) + == "annotate_type_is(arg0: object) -> typing.TypeIs[str]" + ) + else: + assert ( + doc(m.annotate_type_is) + == "annotate_type_is(arg0: object) -> typing_extensions.TypeIs[str]" + ) def test_no_return_annotation(doc): @@ -1036,14 +1078,23 @@ def test_no_return_annotation(doc): def test_never_annotation(doc): - assert doc(m.annotate_never) == "annotate_never() -> typing.Never" + if sys.version_info >= (3, 11): + assert doc(m.annotate_never) == "annotate_never() -> typing.Never" + else: + assert doc(m.annotate_never) == "annotate_never() -> typing_extensions.Never" def test_optional_object_annotations(doc): - assert ( + if sys.version_info >= (3, 10): + assert ( doc(m.annotate_optional_to_object) - == "annotate_optional_to_object(arg0: typing.Optional[typing.SupportsInt]) -> object" + == "annotate_optional_to_object(arg0: typing.SupportsInt | None) -> object" ) + else: + assert ( + doc(m.annotate_optional_to_object) + == "annotate_optional_to_object(arg0: typing.Optional[typing.SupportsInt]) -> object" + ) @pytest.mark.skipif( @@ -1076,10 +1127,16 @@ def test_literal(doc): doc(m.identity_literal_curly_close) == 'identity_literal_curly_close(arg0: typing.Literal["}"]) -> typing.Literal["}"]' ) - assert ( - doc(m.identity_literal_arrow_with_io_name) - == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: typing.Union[float, int]) -> typing.Literal["->"]' - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.identity_literal_arrow_with_io_name) + == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: float | int) -> typing.Literal["->"]' + ) + else: + assert ( + doc(m.identity_literal_arrow_with_io_name) + == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: typing.Union[float, int]) -> typing.Literal["->"]' + ) assert ( doc(m.identity_literal_arrow_with_callable) == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]) -> collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]' @@ -1168,10 +1225,17 @@ def test_module_attribute_types() -> None: assert module_annotations["list_int"] == "list[typing.SupportsInt]" assert module_annotations["set_str"] == "set[str]" assert module_annotations["foo"] == "pybind11_tests.pytypes.foo" - assert ( - module_annotations["foo_union"] - == "typing.Union[pybind11_tests.pytypes.foo, pybind11_tests.pytypes.foo2, pybind11_tests.pytypes.foo3]" - ) + + if sys.version_info >= (3,10): + assert ( + module_annotations["foo_union"] + == "pybind11_tests.pytypes.foo | pybind11_tests.pytypes.foo2 | pybind11_tests.pytypes.foo3" + ) + else: + assert ( + module_annotations["foo_union"] + == "typing.Union[pybind11_tests.pytypes.foo, pybind11_tests.pytypes.foo2, pybind11_tests.pytypes.foo3]" + ) @pytest.mark.skipif( @@ -1250,14 +1314,24 @@ def test_final_annotation() -> None: def test_arg_return_type_hints(doc): - assert ( - doc(m.half_of_number) - == "half_of_number(arg0: typing.Union[float, int]) -> float" - ) - assert ( - doc(m.half_of_number_convert) - == "half_of_number_convert(x: typing.Union[float, int]) -> float" - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.half_of_number) + == "half_of_number(arg0: float | int) -> float" + ) + assert ( + doc(m.half_of_number_convert) + == "half_of_number_convert(x: float | int) -> float" + ) + else: + assert ( + doc(m.half_of_number) + == "half_of_number(arg0: typing.Union[float, int]) -> float" + ) + assert ( + doc(m.half_of_number_convert) + == "half_of_number_convert(x: typing.Union[float, int]) -> float" + ) assert ( doc(m.half_of_number_noconvert) == "half_of_number_noconvert(x: float) -> float" ) @@ -1266,90 +1340,183 @@ def test_arg_return_type_hints(doc): assert m.half_of_number(0) == 0 assert isinstance(m.half_of_number(0), float) assert not isinstance(m.half_of_number(0), int) - # std::vector - assert ( - doc(m.half_of_number_vector) - == "half_of_number_vector(arg0: collections.abc.Sequence[typing.Union[float, int]]) -> list[float]" - ) - # Tuple - assert ( - doc(m.half_of_number_tuple) - == "half_of_number_tuple(arg0: tuple[typing.Union[float, int], typing.Union[float, int]]) -> tuple[float, float]" - ) - # Tuple - assert ( - doc(m.half_of_number_tuple_ellipsis) - == "half_of_number_tuple_ellipsis(arg0: tuple[typing.Union[float, int], ...]) -> tuple[float, ...]" - ) - # Dict - assert ( - doc(m.half_of_number_dict) - == "half_of_number_dict(arg0: dict[str, typing.Union[float, int]]) -> dict[str, float]" - ) - # List - assert ( - doc(m.half_of_number_list) - == "half_of_number_list(arg0: list[typing.Union[float, int]]) -> list[float]" - ) - # List> - assert ( - doc(m.half_of_number_nested_list) - == "half_of_number_nested_list(arg0: list[list[typing.Union[float, int]]]) -> list[list[float]]" - ) - # Set - assert ( - doc(m.identity_set) - == "identity_set(arg0: set[typing.Union[float, int]]) -> set[float]" - ) - # Iterable - assert ( - doc(m.identity_iterable) - == "identity_iterable(arg0: collections.abc.Iterable[typing.Union[float, int]]) -> collections.abc.Iterable[float]" - ) - # Iterator - assert ( - doc(m.identity_iterator) - == "identity_iterator(arg0: collections.abc.Iterator[typing.Union[float, int]]) -> collections.abc.Iterator[float]" - ) - # Callable identity - assert ( - doc(m.identity_callable) - == "identity_callable(arg0: collections.abc.Callable[[typing.Union[float, int]], float]) -> collections.abc.Callable[[typing.Union[float, int]], float]" - ) - # Callable identity - assert ( - doc(m.identity_callable_ellipsis) - == "identity_callable_ellipsis(arg0: collections.abc.Callable[..., float]) -> collections.abc.Callable[..., float]" - ) - # Nested Callable identity - assert ( - doc(m.identity_nested_callable) - == "identity_nested_callable(arg0: collections.abc.Callable[[collections.abc.Callable[[typing.Union[float, int]], float]], collections.abc.Callable[[typing.Union[float, int]], float]]) -> collections.abc.Callable[[collections.abc.Callable[[typing.Union[float, int]], float]], collections.abc.Callable[[typing.Union[float, int]], float]]" - ) - # Callable - assert ( - doc(m.apply_callable) - == "apply_callable(arg0: typing.Union[float, int], arg1: collections.abc.Callable[[typing.Union[float, int]], float]) -> float" - ) - # Callable - assert ( - doc(m.apply_callable_ellipsis) - == "apply_callable_ellipsis(arg0: typing.Union[float, int], arg1: collections.abc.Callable[..., float]) -> float" - ) - # Union - assert ( - doc(m.identity_union) - == "identity_union(arg0: typing.Union[typing.Union[float, int], str]) -> typing.Union[float, str]" - ) - # Optional - assert ( - doc(m.identity_optional) - == "identity_optional(arg0: typing.Optional[typing.Union[float, int]]) -> typing.Optional[float]" - ) - # TypeGuard - assert ( - doc(m.check_type_guard) - == "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]" - ) - # TypeIs - assert doc(m.check_type_is) == "check_type_is(arg0: object) -> typing.TypeIs[float]" + + if sys.version_info >= (3, 10): + # std::vector + assert ( + doc(m.half_of_number_vector) + == "half_of_number_vector(arg0: collections.abc.Sequence[float | int]) -> list[float]" + ) + # Tuple + assert ( + doc(m.half_of_number_tuple) + == "half_of_number_tuple(arg0: tuple[float | int, float | int]) -> tuple[float, float]" + ) + # Tuple + assert ( + doc(m.half_of_number_tuple_ellipsis) + == "half_of_number_tuple_ellipsis(arg0: tuple[float | int, ...]) -> tuple[float, ...]" + ) + # Dict + assert ( + doc(m.half_of_number_dict) + == "half_of_number_dict(arg0: dict[str, float | int]) -> dict[str, float]" + ) + # List + assert ( + doc(m.half_of_number_list) + == "half_of_number_list(arg0: list[float | int]) -> list[float]" + ) + # List> + assert ( + doc(m.half_of_number_nested_list) + == "half_of_number_nested_list(arg0: list[list[float | int]]) -> list[list[float]]" + ) + # Set + assert ( + doc(m.identity_set) + == "identity_set(arg0: set[float | int]) -> set[float]" + ) + # Iterable + assert ( + doc(m.identity_iterable) + == "identity_iterable(arg0: collections.abc.Iterable[float | int]) -> collections.abc.Iterable[float]" + ) + # Iterator + assert ( + doc(m.identity_iterator) + == "identity_iterator(arg0: collections.abc.Iterator[float | int]) -> collections.abc.Iterator[float]" + ) + # Callable identity + assert ( + doc(m.identity_callable) + == "identity_callable(arg0: collections.abc.Callable[[float | int], float]) -> collections.abc.Callable[[float | int], float]" + ) + # Callable identity + assert ( + doc(m.identity_callable_ellipsis) + == "identity_callable_ellipsis(arg0: collections.abc.Callable[..., float]) -> collections.abc.Callable[..., float]" + ) + # Nested Callable identity + assert ( + doc(m.identity_nested_callable) + == "identity_nested_callable(arg0: collections.abc.Callable[[collections.abc.Callable[[float | int], float]], collections.abc.Callable[[float | int], float]]) -> collections.abc.Callable[[collections.abc.Callable[[float | int], float]], collections.abc.Callable[[float | int], float]]" + ) + # Callable + assert ( + doc(m.apply_callable) + == "apply_callable(arg0: float | int, arg1: collections.abc.Callable[[float | int], float]) -> float" + ) + # Callable + assert ( + doc(m.apply_callable_ellipsis) + == "apply_callable_ellipsis(arg0: float | int, arg1: collections.abc.Callable[..., float]) -> float" + ) + # Union + assert ( + doc(m.identity_union) + == "identity_union(arg0: float | int | str) -> float | str" + ) + # Optional + assert ( + doc(m.identity_optional) + == "identity_optional(arg0: float | int | None) -> float | None" + ) + # TypeGuard + assert ( + doc(m.check_type_guard) + == "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]" + ) + # TypeIs + if sys.version_info >= (3, 13): + assert doc(m.check_type_is) == "check_type_is(arg0: object) -> typing.TypeIs[float]" + else: + assert doc(m.check_type_is) == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" + else: + # std::vector + assert ( + doc(m.half_of_number_vector) + == "half_of_number_vector(arg0: collections.abc.Sequence[typing.Union[float, int]]) -> list[float]" + ) + # Tuple + assert ( + doc(m.half_of_number_tuple) + == "half_of_number_tuple(arg0: tuple[typing.Union[float, int], typing.Union[float, int]]) -> tuple[float, float]" + ) + # Tuple + assert ( + doc(m.half_of_number_tuple_ellipsis) + == "half_of_number_tuple_ellipsis(arg0: tuple[typing.Union[float, int], ...]) -> tuple[float, ...]" + ) + # Dict + assert ( + doc(m.half_of_number_dict) + == "half_of_number_dict(arg0: dict[str, typing.Union[float, int]]) -> dict[str, float]" + ) + # List + assert ( + doc(m.half_of_number_list) + == "half_of_number_list(arg0: list[typing.Union[float, int]]) -> list[float]" + ) + # List> + assert ( + doc(m.half_of_number_nested_list) + == "half_of_number_nested_list(arg0: list[list[typing.Union[float, int]]]) -> list[list[float]]" + ) + # Set + assert ( + doc(m.identity_set) + == "identity_set(arg0: set[typing.Union[float, int]]) -> set[float]" + ) + # Iterable + assert ( + doc(m.identity_iterable) + == "identity_iterable(arg0: collections.abc.Iterable[typing.Union[float, int]]) -> collections.abc.Iterable[float]" + ) + # Iterator + assert ( + doc(m.identity_iterator) + == "identity_iterator(arg0: collections.abc.Iterator[typing.Union[float, int]]) -> collections.abc.Iterator[float]" + ) + # Callable identity + assert ( + doc(m.identity_callable) + == "identity_callable(arg0: collections.abc.Callable[[typing.Union[float, int]], float]) -> collections.abc.Callable[[typing.Union[float, int]], float]" + ) + # Callable identity + assert ( + doc(m.identity_callable_ellipsis) + == "identity_callable_ellipsis(arg0: collections.abc.Callable[..., float]) -> collections.abc.Callable[..., float]" + ) + # Nested Callable identity + assert ( + doc(m.identity_nested_callable) + == "identity_nested_callable(arg0: collections.abc.Callable[[collections.abc.Callable[[typing.Union[float, int]], float]], collections.abc.Callable[[typing.Union[float, int]], float]]) -> collections.abc.Callable[[collections.abc.Callable[[typing.Union[float, int]], float]], collections.abc.Callable[[typing.Union[float, int]], float]]" + ) + # Callable + assert ( + doc(m.apply_callable) + == "apply_callable(arg0: typing.Union[float, int], arg1: collections.abc.Callable[[typing.Union[float, int]], float]) -> float" + ) + # Callable + assert ( + doc(m.apply_callable_ellipsis) + == "apply_callable_ellipsis(arg0: typing.Union[float, int], arg1: collections.abc.Callable[..., float]) -> float" + ) + # Union + assert ( + doc(m.identity_union) + == "identity_union(arg0: typing.Union[typing.Union[float, int], str]) -> typing.Union[float, str]" + ) + # Optional + assert ( + doc(m.identity_optional) + == "identity_optional(arg0: typing.Optional[typing.Union[float, int]]) -> typing.Optional[float]" + ) + # TypeGuard + assert ( + doc(m.check_type_guard) + == "check_type_guard(arg0: list[object]) -> typing_extensions.TypeGuard[list[float]]" + ) + # TypeIs + assert doc(m.check_type_is) == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" diff --git a/tests/test_stl.py b/tests/test_stl.py index c952d034fe..839e6d6207 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -1,5 +1,6 @@ from __future__ import annotations +import sys import pytest import env # noqa: F401 @@ -274,37 +275,17 @@ def __fspath__(self): assert m.parent_path(b"foo/bar") == Path("foo") assert m.parent_path(PseudoStrPath()) == Path("foo") assert m.parent_path(PseudoBytesPath()) == Path("foo") - assert ( - doc(m.parent_path) - == "parent_path(arg0: typing.Union[os.PathLike, str, bytes]) -> pathlib.Path" - ) # std::vector assert m.parent_paths(["foo/bar", "foo/baz"]) == [Path("foo"), Path("foo")] - assert ( - doc(m.parent_paths) - == "parent_paths(arg0: collections.abc.Sequence[typing.Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" - ) # py::typing::List assert m.parent_paths_list(["foo/bar", "foo/baz"]) == [Path("foo"), Path("foo")] - assert ( - doc(m.parent_paths_list) - == "parent_paths_list(arg0: list[typing.Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" - ) # Nested py::typing::List assert m.parent_paths_nested_list([["foo/bar"], ["foo/baz", "foo/buzz"]]) == [ [Path("foo")], [Path("foo"), Path("foo")], ] - assert ( - doc(m.parent_paths_nested_list) - == "parent_paths_nested_list(arg0: list[list[typing.Union[os.PathLike, str, bytes]]]) -> list[list[pathlib.Path]]" - ) # py::typing::Tuple assert m.parent_paths_tuple(("foo/bar", "foo/baz")) == (Path("foo"), Path("foo")) - assert ( - doc(m.parent_paths_tuple) - == "parent_paths_tuple(arg0: tuple[typing.Union[os.PathLike, str, bytes], typing.Union[os.PathLike, str, bytes]]) -> tuple[pathlib.Path, pathlib.Path]" - ) # py::typing::Dict assert m.parent_paths_dict( { @@ -317,11 +298,71 @@ def __fspath__(self): "key2": Path("foo"), "key3": Path("foo"), } - assert ( - doc(m.parent_paths_dict) - == "parent_paths_dict(arg0: dict[str, typing.Union[os.PathLike, str, bytes]]) -> dict[str, pathlib.Path]" - ) +@pytest.mark.skipif(not hasattr(m, "has_filesystem"), reason="no ") +def test_path_typing(doc): + if sys.version_info >= (3, 10): + # Single argument + assert ( + doc(m.parent_path) + == "parent_path(arg0: os.PathLike | str | bytes) -> pathlib.Path" + ) + # std::vector + assert ( + doc(m.parent_paths) + == "parent_paths(arg0: collections.abc.Sequence[os.PathLike | str | bytes]) -> list[pathlib.Path]" + ) + # py::typing::List + assert ( + doc(m.parent_paths_list) + == "parent_paths_list(arg0: list[os.PathLike | str | bytes]) -> list[pathlib.Path]" + ) + # Nested py::typing::List + assert ( + doc(m.parent_paths_nested_list) + == "parent_paths_nested_list(arg0: list[list[os.PathLike | str | bytes]]) -> list[list[pathlib.Path]]" + ) + # py::typing::Tuple + assert ( + doc(m.parent_paths_tuple) + == "parent_paths_tuple(arg0: tuple[os.PathLike | str | bytes, os.PathLike | str | bytes]) -> tuple[pathlib.Path, pathlib.Path]" + ) + # py::typing::Dict + assert ( + doc(m.parent_paths_dict) + == "parent_paths_dict(arg0: dict[str, os.PathLike | str | bytes]) -> dict[str, pathlib.Path]" + ) + else: + # Single argument + assert ( + doc(m.parent_path) + == "parent_path(arg0: typing.Union[os.PathLike, str, bytes]) -> pathlib.Path" + ) + # std::vector + assert ( + doc(m.parent_paths) + == "parent_paths(arg0: collections.abc.Sequence[typing.Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" + ) + # py::typing::List + assert ( + doc(m.parent_paths_list) + == "parent_paths_list(arg0: list[typing.Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" + ) + # Nested py::typing::List + assert ( + doc(m.parent_paths_nested_list) + == "parent_paths_nested_list(arg0: list[list[typing.Union[os.PathLike, str, bytes]]]) -> list[list[pathlib.Path]]" + ) + # py::typing::Tuple + assert ( + doc(m.parent_paths_tuple) + == "parent_paths_tuple(arg0: tuple[typing.Union[os.PathLike, str, bytes], typing.Union[os.PathLike, str, bytes]]) -> tuple[pathlib.Path, pathlib.Path]" + ) + # py::typing::Dict + assert ( + doc(m.parent_paths_dict) + == "parent_paths_dict(arg0: dict[str, typing.Union[os.PathLike, str, bytes]]) -> dict[str, pathlib.Path]" + ) @pytest.mark.skipif(not hasattr(m, "load_variant"), reason="no ") def test_variant(doc): From e374a2f9d816f3dad1cb035e1f17e25699911d78 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 25 May 2025 15:32:17 +0000 Subject: [PATCH 19/45] style: pre-commit fixes --- include/pybind11/stl/filesystem.h | 10 ++++---- include/pybind11/typing.h | 17 ++++++-------- tests/test_opaque_types.py | 3 ++- tests/test_pytypes.cpp | 10 ++++---- tests/test_pytypes.py | 38 ++++++++++++++++--------------- tests/test_stl.py | 3 +++ 6 files changed, 42 insertions(+), 39 deletions(-) diff --git a/include/pybind11/stl/filesystem.h b/include/pybind11/stl/filesystem.h index a9ce8c1e7c..aed17a1232 100644 --- a/include/pybind11/stl/filesystem.h +++ b/include/pybind11/stl/filesystem.h @@ -96,11 +96,11 @@ struct path_caster { return true; } - #if PYBIND11_USE_NEW_UNIONS - PYBIND11_TYPE_CASTER(T, io_name("os.PathLike | str | bytes", "pathlib.Path")); - #else - PYBIND11_TYPE_CASTER(T, io_name("typing.Union[os.PathLike, str, bytes]", "pathlib.Path")); - #endif +# if PYBIND11_USE_NEW_UNIONS + PYBIND11_TYPE_CASTER(T, io_name("os.PathLike | str | bytes", "pathlib.Path")); +# else + PYBIND11_TYPE_CASTER(T, io_name("typing.Union[os.PathLike, str, bytes]", "pathlib.Path")); +# endif }; #endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 48c93064f4..35ca762a73 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -224,9 +224,8 @@ constexpr auto union_helper() { #else template constexpr auto union_helper() { - return const_name("typing.Union[") - + ::pybind11::detail::concat(make_caster::name...) - + const_name("]"); + return const_name("typing.Union[") + ::pybind11::detail::concat(make_caster::name...) + + const_name("]"); } #endif @@ -235,12 +234,10 @@ struct handle_type_name> { static constexpr auto name = union_helper(); }; - #if PYBIND11_USE_NEW_UNIONS template struct handle_type_name> { - static constexpr auto name - = union_helper(); + static constexpr auto name = union_helper(); }; #else template @@ -265,14 +262,14 @@ struct handle_type_name> { template struct handle_type_name> { - static constexpr auto name - = const_name(PYBIND11_TYPE_GUARD_TYPE_HINT) + const_name("[") + make_caster::name + const_name("]"); + static constexpr auto name = const_name(PYBIND11_TYPE_GUARD_TYPE_HINT) + const_name("[") + + make_caster::name + const_name("]"); }; template struct handle_type_name> { - static constexpr auto name - = const_name(PYBIND11_TYPE_IS_TYPE_HINT) + const_name("[") + make_caster::name + const_name("]"); + static constexpr auto name = const_name(PYBIND11_TYPE_IS_TYPE_HINT) + const_name("[") + + make_caster::name + const_name("]"); }; template <> diff --git a/tests/test_opaque_types.py b/tests/test_opaque_types.py index 6790b89d25..9854924901 100644 --- a/tests/test_opaque_types.py +++ b/tests/test_opaque_types.py @@ -1,6 +1,7 @@ from __future__ import annotations import sys + import pytest import env @@ -38,7 +39,7 @@ def test_pointers(msg): with pytest.raises(TypeError) as excinfo: m.get_void_ptr_value([1, 2, 3]) # This should not work - + if sys.version_info >= (3, 13): assert ( msg(excinfo.value) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 99b38c5a1d..cbb85f0e1e 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -155,11 +155,11 @@ namespace detail { template <> struct type_caster { - #if PYBIND11_USE_NEW_UNIONS - PYBIND11_TYPE_CASTER(RealNumber, io_name("float | int", "float")); - #else - PYBIND11_TYPE_CASTER(RealNumber, io_name("typing.Union[float, int]", "float")); - #endif +#if PYBIND11_USE_NEW_UNIONS + PYBIND11_TYPE_CASTER(RealNumber, io_name("float | int", "float")); +#else + PYBIND11_TYPE_CASTER(RealNumber, io_name("typing.Union[float, int]", "float")); +#endif static handle cast(const RealNumber &number, return_value_policy, handle) { return py::float_(number.value).release(); diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 8db016cd4b..928b24c7fe 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -131,10 +131,7 @@ def test_set(capture, doc): assert doc(m.get_set) == "get_set() -> set" if sys.version_info >= (3, 10): - assert ( - doc(m.print_anyset) - == "print_anyset(arg0: set | frozenset) -> None" - ) + assert doc(m.print_anyset) == "print_anyset(arg0: set | frozenset) -> None" else: assert ( doc(m.print_anyset) @@ -1087,9 +1084,9 @@ def test_never_annotation(doc): def test_optional_object_annotations(doc): if sys.version_info >= (3, 10): assert ( - doc(m.annotate_optional_to_object) - == "annotate_optional_to_object(arg0: typing.SupportsInt | None) -> object" - ) + doc(m.annotate_optional_to_object) + == "annotate_optional_to_object(arg0: typing.SupportsInt | None) -> object" + ) else: assert ( doc(m.annotate_optional_to_object) @@ -1226,7 +1223,7 @@ def test_module_attribute_types() -> None: assert module_annotations["set_str"] == "set[str]" assert module_annotations["foo"] == "pybind11_tests.pytypes.foo" - if sys.version_info >= (3,10): + if sys.version_info >= (3, 10): assert ( module_annotations["foo_union"] == "pybind11_tests.pytypes.foo | pybind11_tests.pytypes.foo2 | pybind11_tests.pytypes.foo3" @@ -1315,10 +1312,7 @@ def test_final_annotation() -> None: def test_arg_return_type_hints(doc): if sys.version_info >= (3, 10): - assert ( - doc(m.half_of_number) - == "half_of_number(arg0: float | int) -> float" - ) + assert doc(m.half_of_number) == "half_of_number(arg0: float | int) -> float" assert ( doc(m.half_of_number_convert) == "half_of_number_convert(x: float | int) -> float" @@ -1374,8 +1368,7 @@ def test_arg_return_type_hints(doc): ) # Set assert ( - doc(m.identity_set) - == "identity_set(arg0: set[float | int]) -> set[float]" + doc(m.identity_set) == "identity_set(arg0: set[float | int]) -> set[float]" ) # Iterable assert ( @@ -1429,10 +1422,16 @@ def test_arg_return_type_hints(doc): ) # TypeIs if sys.version_info >= (3, 13): - assert doc(m.check_type_is) == "check_type_is(arg0: object) -> typing.TypeIs[float]" + assert ( + doc(m.check_type_is) + == "check_type_is(arg0: object) -> typing.TypeIs[float]" + ) else: - assert doc(m.check_type_is) == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" - else: + assert ( + doc(m.check_type_is) + == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" + ) + else: # std::vector assert ( doc(m.half_of_number_vector) @@ -1519,4 +1518,7 @@ def test_arg_return_type_hints(doc): == "check_type_guard(arg0: list[object]) -> typing_extensions.TypeGuard[list[float]]" ) # TypeIs - assert doc(m.check_type_is) == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" + assert ( + doc(m.check_type_is) + == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" + ) diff --git a/tests/test_stl.py b/tests/test_stl.py index 839e6d6207..ec814c1fd5 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -1,6 +1,7 @@ from __future__ import annotations import sys + import pytest import env # noqa: F401 @@ -299,6 +300,7 @@ def __fspath__(self): "key3": Path("foo"), } + @pytest.mark.skipif(not hasattr(m, "has_filesystem"), reason="no ") def test_path_typing(doc): if sys.version_info >= (3, 10): @@ -364,6 +366,7 @@ def test_path_typing(doc): == "parent_paths_dict(arg0: dict[str, typing.Union[os.PathLike, str, bytes]]) -> dict[str, pathlib.Path]" ) + @pytest.mark.skipif(not hasattr(m, "load_variant"), reason="no ") def test_variant(doc): assert m.load_variant(1) == "int" From 325f080cb0e63edc969f6c7643275c819f87bcfd Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 25 May 2025 11:34:22 -0400 Subject: [PATCH 20/45] remove unused var Signed-off-by: Michael Carlstrom --- tests/test_stl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_stl.py b/tests/test_stl.py index ec814c1fd5..1b4eb8c9a1 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -259,7 +259,7 @@ def test_reference_sensitive_optional(): @pytest.mark.skipif(not hasattr(m, "has_filesystem"), reason="no ") -def test_fs_path(doc): +def test_fs_path(): from pathlib import Path class PseudoStrPath: From 6dcc1d92be02e20b9f63baabfb84fcd62f1e7dd4 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 25 May 2025 11:47:36 -0400 Subject: [PATCH 21/45] remove union_helper Signed-off-by: Michael Carlstrom --- include/pybind11/typing.h | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 35ca762a73..f5c11a1ead 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -216,36 +216,27 @@ struct handle_type_name> { static constexpr auto name = const_name("type[") + make_caster::name + const_name("]"); }; +template +struct handle_type_name> { #if PYBIND11_USE_NEW_UNIONS -template -constexpr auto union_helper() { - return ::pybind11::detail::union_concat(make_caster::name...); -} + static constexpr auto name = ::pybind11::detail::union_concat(make_caster::name...); #else -template -constexpr auto union_helper() { - return const_name("typing.Union[") + ::pybind11::detail::concat(make_caster::name...) - + const_name("]"); -} + static constexpr auto name = const_name("typing.Union[") + + ::pybind11::detail::concat(make_caster::name...) + + const_name("]"); #endif - -template -struct handle_type_name> { - static constexpr auto name = union_helper(); }; -#if PYBIND11_USE_NEW_UNIONS template struct handle_type_name> { - static constexpr auto name = union_helper(); -}; +#if PYBIND11_USE_NEW_UNIONS + static constexpr auto name + = ::pybind11::detail::union_concat(make_caster::name, make_caster::name); #else -template -struct handle_type_name> { static constexpr auto name = const_name("typing.Optional[") + make_caster::name + const_name("]"); -}; #endif +}; template struct handle_type_name> { From 8570b25325fb1f20104041fd28c85b43df82ef19 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 25 May 2025 11:49:07 -0400 Subject: [PATCH 22/45] fix speelling error Signed-off-by: Michael Carlstrom --- include/pybind11/typing.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index f5c11a1ead..50c92fd7f8 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -222,7 +222,7 @@ struct handle_type_name> { static constexpr auto name = ::pybind11::detail::union_concat(make_caster::name...); #else static constexpr auto name = const_name("typing.Union[") - + ::pybind11::detail::concat(make_caster::name...) + + ::pybind11::detail::concat(make_caster::name...) + const_name("]"); #endif }; From 5ea93d46d27a1bcd2f1e6cbcb8e7608e72b59105 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 25 May 2025 12:00:27 -0400 Subject: [PATCH 23/45] base case for union_concat Signed-off-by: Michael Carlstrom --- include/pybind11/detail/descr.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 90fa6da456..e198b1b00f 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -157,6 +157,8 @@ constexpr descr<1, Type> _() { #endif // #ifndef _ constexpr descr<0> concat() { return {}; } +constexpr descr<0> union_concat() { return {}; } + template constexpr descr concat(const descr &descr) { From f51488611483a54c8ae8777a338306f3f8616764 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 25 May 2025 16:00:50 +0000 Subject: [PATCH 24/45] style: pre-commit fixes --- include/pybind11/detail/descr.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index e198b1b00f..62ce1509d4 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -159,7 +159,6 @@ constexpr descr<1, Type> _() { constexpr descr<0> concat() { return {}; } constexpr descr<0> union_concat() { return {}; } - template constexpr descr concat(const descr &descr) { return descr; From 0d2de7a2b624693a9e0a7eed19b176df34ef89c2 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 25 May 2025 12:13:35 -0400 Subject: [PATCH 25/45] add case for one descr Signed-off-by: Michael Carlstrom --- include/pybind11/detail/descr.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 62ce1509d4..51bfcfa9c9 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -164,6 +164,11 @@ constexpr descr concat(const descr &descr) { return descr; } +template +constexpr descr union_concat(const descr &descr) { + return descr; +} + #ifdef __cpp_fold_expressions template constexpr descr operator,(const descr &a, From c489b95f1189080a01ee52d6e19e3f1339512bbf Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 25 May 2025 17:50:36 -0400 Subject: [PATCH 26/45] weakref and final test Signed-off-by: Michael Carlstrom --- include/pybind11/cast.h | 2 +- tests/test_pytypes.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index cec9117a94..eb4d344c8e 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1453,7 +1453,7 @@ struct handle_type_name { }; template <> struct handle_type_name { - static constexpr auto name = const_name("weakref"); + static constexpr auto name = const_name("weakref.ReferenceType"); }; template <> struct handle_type_name { diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 928b24c7fe..31f4b05855 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1129,15 +1129,19 @@ def test_literal(doc): doc(m.identity_literal_arrow_with_io_name) == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: float | int) -> typing.Literal["->"]' ) + assert ( + doc(m.identity_literal_arrow_with_callable) + == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], float | int], float]) -> collections.abc.Callable[[typing.Literal["->"], float | int], float]' + ) else: assert ( doc(m.identity_literal_arrow_with_io_name) == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: typing.Union[float, int]) -> typing.Literal["->"]' ) - assert ( - doc(m.identity_literal_arrow_with_callable) - == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]) -> collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]' - ) + assert ( + doc(m.identity_literal_arrow_with_callable) + == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]) -> collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]' + ) assert ( doc(m.identity_literal_all_special_chars) == 'identity_literal_all_special_chars(arg0: typing.Literal["!@!!->{%}"]) -> typing.Literal["!@!!->{%}"]' From b66ec644c67402dfbcbd2b4d33b4eefc878302ec Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Mon, 26 May 2025 00:59:38 +0200 Subject: [PATCH 27/45] Fixed several incorrect type hints in tests --- tests/pybind11_tests.h | 2 +- tests/test_builtin_casters.cpp | 2 +- tests/test_copy_move.cpp | 6 +++--- tests/test_custom_type_casters.cpp | 15 +++++++++------ 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/pybind11_tests.h b/tests/pybind11_tests.h index 0eb0398df0..be6a8277cb 100644 --- a/tests/pybind11_tests.h +++ b/tests/pybind11_tests.h @@ -73,7 +73,7 @@ PYBIND11_NAMESPACE_BEGIN(detail) template <> class type_caster { public: - PYBIND11_TYPE_CASTER(RValueCaster, const_name("RValueCaster")); + PYBIND11_TYPE_CASTER(RValueCaster, const_name("typing.Literal[\"rvalue\", \"lvalue\"]")); static handle cast(RValueCaster &&, return_value_policy, handle) { return py::str("rvalue").release(); } diff --git a/tests/test_builtin_casters.cpp b/tests/test_builtin_casters.cpp index c516f8de7e..272d9e7d1f 100644 --- a/tests/test_builtin_casters.cpp +++ b/tests/test_builtin_casters.cpp @@ -20,7 +20,7 @@ PYBIND11_NAMESPACE_BEGIN(detail) template <> class type_caster { public: - static constexpr auto name = const_name(); + static constexpr auto name = const_name("typing.Annotated[typing.Any, \"ConstRefCasted\"]"); // Input is unimportant, a new value will always be constructed based on the // cast operator. diff --git a/tests/test_copy_move.cpp b/tests/test_copy_move.cpp index ddffbe3a94..e2bad85181 100644 --- a/tests/test_copy_move.cpp +++ b/tests/test_copy_move.cpp @@ -107,7 +107,7 @@ PYBIND11_NAMESPACE_BEGIN(pybind11) PYBIND11_NAMESPACE_BEGIN(detail) template <> struct type_caster { - PYBIND11_TYPE_CASTER(MoveOnlyInt, const_name("MoveOnlyInt")); + PYBIND11_TYPE_CASTER(MoveOnlyInt, const_name("typing.Annotated[int, \"MoveOnlyInt\"]")); bool load(handle src, bool) { value = MoveOnlyInt(src.cast()); return true; @@ -119,7 +119,7 @@ struct type_caster { template <> struct type_caster { - PYBIND11_TYPE_CASTER(MoveOrCopyInt, const_name("MoveOrCopyInt")); + PYBIND11_TYPE_CASTER(MoveOrCopyInt, const_name("typing.Annotated[int, \"MoveOrCopyInt\"]")); bool load(handle src, bool) { value = MoveOrCopyInt(src.cast()); return true; @@ -135,7 +135,7 @@ struct type_caster { CopyOnlyInt value; public: - static constexpr auto name = const_name("CopyOnlyInt"); + static constexpr auto name = const_name("typing.Annotated[int, \"CopyOnlyInt\"]"); bool load(handle src, bool) { value = CopyOnlyInt(src.cast()); return true; diff --git a/tests/test_custom_type_casters.cpp b/tests/test_custom_type_casters.cpp index 0ca2d25414..01ebf8f41c 100644 --- a/tests/test_custom_type_casters.cpp +++ b/tests/test_custom_type_casters.cpp @@ -28,9 +28,9 @@ struct type_caster { public: // Classic #ifdef PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY - PYBIND11_TYPE_CASTER(ArgInspector1, _("ArgInspector1")); + PYBIND11_TYPE_CASTER(ArgInspector1, _("typing.Annotated[str, \"ArgInspector1\"]")); #else - PYBIND11_TYPE_CASTER(ArgInspector1, const_name("ArgInspector1")); + PYBIND11_TYPE_CASTER(ArgInspector1, const_name("typing.Annotated[str, \"ArgInspector1\"]")); #endif bool load(handle src, bool convert) { @@ -48,7 +48,7 @@ struct type_caster { template <> struct type_caster { public: - PYBIND11_TYPE_CASTER(ArgInspector2, const_name("ArgInspector2")); + PYBIND11_TYPE_CASTER(ArgInspector2, const_name("typing.Annotated[str, \"ArgInspector2\"]")); bool load(handle src, bool convert) { value.arg = "loading ArgInspector2 argument " + std::string(convert ? "WITH" : "WITHOUT") @@ -65,7 +65,8 @@ struct type_caster { template <> struct type_caster { public: - PYBIND11_TYPE_CASTER(ArgAlwaysConverts, const_name("ArgAlwaysConverts")); + PYBIND11_TYPE_CASTER(ArgAlwaysConverts, + const_name("typing.Annotated[typing.Any, \"ArgAlwaysConverts\"]")); bool load(handle, bool convert) { return convert; } @@ -96,7 +97,8 @@ namespace PYBIND11_NAMESPACE { namespace detail { template <> struct type_caster { - PYBIND11_TYPE_CASTER(DestructionTester, const_name("DestructionTester")); + PYBIND11_TYPE_CASTER(DestructionTester, + const_name("typing.Annotated[typing.Any, \"DestructionTester\"]")); bool load(handle, bool) { return true; } static handle cast(const DestructionTester &, return_value_policy, handle) { @@ -118,7 +120,8 @@ namespace py_ = ::pybind11; // Define caster. This is effectively no-op, we only ensure it compiles and we // don't have any symbol collision when using macro mixin. struct my_caster { - PYBIND11_TYPE_CASTER(MyType, py_::detail::const_name("MyType")); + PYBIND11_TYPE_CASTER(MyType, + py_::detail::const_name("typing.Annotated[typing.Any, \"MyType\"]")); bool load(py_::handle, bool) { return true; } static py_::handle cast(const MyType &, py_::return_value_policy, py_::handle) { From 7c9f453d0f7d0b4548331ff97a8732b563fc6377 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Mon, 26 May 2025 01:27:38 +0200 Subject: [PATCH 28/45] Added `-m stubgen` marker to enable testing of stubgen on all test modules --- tests/conftest.py | 13 +++++++++++++ tests/pytest.ini | 2 ++ tests/test_stubgen.py | 1 + 3 files changed, 16 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 8f5352a1ff..ef7e96257b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -242,3 +242,16 @@ def pytest_report_header(): lines.append("free-threaded Python build") return lines + + +def pytest_collection_modifyitems( + session: pytest.Session, # noqa: ARG001 + config: pytest.Config, + items: list[pytest.Item], +) -> None: + if not config.getoption("-m"): + for item in items: + if "stubgen" in item.keywords: + item.add_marker( + pytest.mark.skip(reason="Use `-m stubgen` to enable stubgen tests.") + ) diff --git a/tests/pytest.ini b/tests/pytest.ini index 6ca7a91362..4636221289 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -20,3 +20,5 @@ filterwarnings = # bogus numpy ABI warning (see numpy/#432) ignore:.*numpy.dtype size changed.*:RuntimeWarning ignore:.*numpy.ufunc size changed.*:RuntimeWarning +markers = + stubgen: enables typing stub generation on all test modules diff --git a/tests/test_stubgen.py b/tests/test_stubgen.py index cbcdcb4fa4..6890d42bd2 100644 --- a/tests/test_stubgen.py +++ b/tests/test_stubgen.py @@ -56,6 +56,7 @@ def test_stubgen(tmp_path: Path) -> None: assert "Success: no issues found in 1 source file" in result.normal_report +@pytest.mark.stubgen def test_stubgen_all(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: # Generate stub into temporary directory pybind11_stubgen.main( From 41e2bbe4167773bc2b869bc671e3df28a292b853 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Mon, 26 May 2025 16:48:37 -0400 Subject: [PATCH 29/45] Add acrpss_version_type_hint_checker Signed-off-by: Michael Carlstrom --- tests/conftest.py | 26 ++++++++++++++ tests/test_opaque_types.py | 24 ++++--------- tests/test_pytypes.cpp | 1 - tests/test_pytypes.py | 70 +++++++++++--------------------------- 4 files changed, 52 insertions(+), 69 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 8f5352a1ff..577dda30f1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -242,3 +242,29 @@ def pytest_report_header(): lines.append("free-threaded Python build") return lines + + +UNION_HINT = "typing.Union" + + +def replace_unions(expected: str) -> str: + return expected + + +def across_version_type_hint_checker(doc: str, expected: str) -> None: + if sys.version_info < (3, 13): + expected = expected.replace("typing.TypeIs", "typing_extensions.TypeIs") + expected = expected.replace( + "types.CapsuleType", "typing_extensions.CapsuleType" + ) + if sys.version_info < (3, 12): + expected = expected.replace( + "collections.abc.Buffer", "typing_extensions.Buffer" + ) + if sys.version_info < (3, 11): + expected = expected.replace("typing.Never", "typing_extensions.Never") + if sys.version_info < (3, 10): + expected = expected.replace("typing.TypeGuard", "typing_extensions.TypeGuard") + expected = replace_unions(expected) + + assert doc == expected diff --git a/tests/test_opaque_types.py b/tests/test_opaque_types.py index 9854924901..e386fd3c78 100644 --- a/tests/test_opaque_types.py +++ b/tests/test_opaque_types.py @@ -1,8 +1,7 @@ from __future__ import annotations -import sys - import pytest +from conftest import across_version_type_hint_checker import env from pybind11_tests import ConstructorStats, UserType @@ -40,26 +39,15 @@ def test_pointers(msg): with pytest.raises(TypeError) as excinfo: m.get_void_ptr_value([1, 2, 3]) # This should not work - if sys.version_info >= (3, 13): - assert ( - msg(excinfo.value) - == """ - get_void_ptr_value(): incompatible function arguments. The following argument types are supported: - 1. (arg0: types.CapsuleType) -> int - - Invoked with: [1, 2, 3] + across_version_type_hint_checker( + msg(excinfo.value), """ - ) - else: - assert ( - msg(excinfo.value) - == """ get_void_ptr_value(): incompatible function arguments. The following argument types are supported: - 1. (arg0: typing_extensions.CapsuleType) -> int + 1. (arg0: types.CapsuleType) -> int Invoked with: [1, 2, 3] - """ - ) + """, + ) assert m.return_null_str() is None assert m.get_null_str_value(m.return_null_str()) is not None diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index cbb85f0e1e..e74fe6ab6a 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -176,7 +176,6 @@ struct type_caster { value.value = src.cast(); return true; } - // static void f/ }; } // namespace detail diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 31f4b05855..1edfeb8101 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -5,6 +5,7 @@ import types import pytest +from conftest import across_version_type_hint_checker import env from pybind11_tests import detailed_error_messages_enabled @@ -1045,29 +1046,16 @@ def test_optional_annotations(doc): def test_type_guard_annotations(doc): - if sys.version_info >= (3, 10): - assert ( - doc(m.annotate_type_guard) - == "annotate_type_guard(arg0: object) -> typing.TypeGuard[str]" - ) - else: - assert ( - doc(m.annotate_type_guard) - == "annotate_type_guard(arg0: object) -> typing_extensions.TypeGuard[str]" - ) + across_version_type_hint_checker( + doc(m.annotate_type_guard), + "annotate_type_guard(arg0: object) -> typing.TypeGuard[str]", + ) def test_type_is_annotations(doc): - if sys.version_info >= (3, 13): - assert ( - doc(m.annotate_type_is) - == "annotate_type_is(arg0: object) -> typing.TypeIs[str]" - ) - else: - assert ( - doc(m.annotate_type_is) - == "annotate_type_is(arg0: object) -> typing_extensions.TypeIs[str]" - ) + across_version_type_hint_checker( + doc(m.annotate_type_is), "annotate_type_is(arg0: object) -> typing.TypeIs[str]" + ) def test_no_return_annotation(doc): @@ -1075,10 +1063,9 @@ def test_no_return_annotation(doc): def test_never_annotation(doc): - if sys.version_info >= (3, 11): - assert doc(m.annotate_never) == "annotate_never() -> typing.Never" - else: - assert doc(m.annotate_never) == "annotate_never() -> typing_extensions.Never" + across_version_type_hint_checker( + doc(m.annotate_never), "annotate_never() -> typing.Never" + ) def test_optional_object_annotations(doc): @@ -1419,22 +1406,6 @@ def test_arg_return_type_hints(doc): doc(m.identity_optional) == "identity_optional(arg0: float | int | None) -> float | None" ) - # TypeGuard - assert ( - doc(m.check_type_guard) - == "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]" - ) - # TypeIs - if sys.version_info >= (3, 13): - assert ( - doc(m.check_type_is) - == "check_type_is(arg0: object) -> typing.TypeIs[float]" - ) - else: - assert ( - doc(m.check_type_is) - == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" - ) else: # std::vector assert ( @@ -1516,13 +1487,12 @@ def test_arg_return_type_hints(doc): doc(m.identity_optional) == "identity_optional(arg0: typing.Optional[typing.Union[float, int]]) -> typing.Optional[float]" ) - # TypeGuard - assert ( - doc(m.check_type_guard) - == "check_type_guard(arg0: list[object]) -> typing_extensions.TypeGuard[list[float]]" - ) - # TypeIs - assert ( - doc(m.check_type_is) - == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" - ) + + # TypeIs + across_version_type_hint_checker( + doc(m.check_type_is), "check_type_is(arg0: object) -> typing.TypeIs[float]" + ) + across_version_type_hint_checker( + doc(m.check_type_guard), + "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]", + ) From bd41651c62f91377d6d5369753acd19cba4268aa Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Mon, 26 May 2025 17:04:00 -0400 Subject: [PATCH 30/45] cleanup Signed-off-by: Michael Carlstrom --- test.pyi | 2 ++ tests/conftest.py | 8 -------- 2 files changed, 2 insertions(+), 8 deletions(-) create mode 100644 test.pyi diff --git a/test.pyi b/test.pyi new file mode 100644 index 0000000000..ef1cd2f3f4 --- /dev/null +++ b/test.pyi @@ -0,0 +1,2 @@ +def foo() -> int | str: ... + diff --git a/tests/conftest.py b/tests/conftest.py index 577dda30f1..081303e649 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -244,13 +244,6 @@ def pytest_report_header(): return lines -UNION_HINT = "typing.Union" - - -def replace_unions(expected: str) -> str: - return expected - - def across_version_type_hint_checker(doc: str, expected: str) -> None: if sys.version_info < (3, 13): expected = expected.replace("typing.TypeIs", "typing_extensions.TypeIs") @@ -265,6 +258,5 @@ def across_version_type_hint_checker(doc: str, expected: str) -> None: expected = expected.replace("typing.Never", "typing_extensions.Never") if sys.version_info < (3, 10): expected = expected.replace("typing.TypeGuard", "typing_extensions.TypeGuard") - expected = replace_unions(expected) assert doc == expected From 3d4185ac25773a703d4a631666196ce67ec58f73 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 21:04:23 +0000 Subject: [PATCH 31/45] style: pre-commit fixes --- test.pyi | 1 - 1 file changed, 1 deletion(-) diff --git a/test.pyi b/test.pyi index ef1cd2f3f4..5a40fac3ee 100644 --- a/test.pyi +++ b/test.pyi @@ -1,2 +1 @@ def foo() -> int | str: ... - From df38f9979ad5f05823e687b691cef5d69c04b4f8 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Mon, 26 May 2025 17:04:49 -0400 Subject: [PATCH 32/45] remove test.pyi Signed-off-by: Michael Carlstrom --- test.pyi | 1 - 1 file changed, 1 deletion(-) delete mode 100644 test.pyi diff --git a/test.pyi b/test.pyi deleted file mode 100644 index 5a40fac3ee..0000000000 --- a/test.pyi +++ /dev/null @@ -1 +0,0 @@ -def foo() -> int | str: ... From 8746f749fc49d415e7ce89fbaade8f8aefd61705 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Tue, 27 May 2025 02:58:51 -0400 Subject: [PATCH 33/45] use new unions and add fixture Signed-off-by: Michael Carlstrom --- include/pybind11/cast.h | 4 - include/pybind11/detail/common.h | 2 - include/pybind11/stl.h | 8 +- include/pybind11/stl/filesystem.h | 4 - include/pybind11/typing.h | 11 - tests/conftest.py | 29 ++- tests/test_buffers.py | 12 +- tests/test_opaque_types.py | 11 +- tests/test_pytypes.cpp | 4 - tests/test_pytypes.py | 409 ++++++++++-------------------- tests/test_stl.py | 98 +++---- 11 files changed, 193 insertions(+), 399 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index eb4d344c8e..7a6edf25b7 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1361,11 +1361,7 @@ struct handle_type_name { }; template <> struct handle_type_name { -#if PYBIND11_USE_NEW_UNIONS static constexpr auto name = const_name("set | frozenset"); -#else - static constexpr auto name = const_name("typing.Union[set, frozenset]"); -#endif }; template <> struct handle_type_name { diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 8b14c9311b..7f9748853e 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -286,10 +286,8 @@ // 3.10 Compatibility #if 0x030A0000 <= PY_VERSION_HEX -# define PYBIND11_USE_NEW_UNIONS true # define PYBIND11_TYPE_GUARD_TYPE_HINT "typing.TypeGuard" #else -# define PYBIND11_USE_NEW_UNIONS false # define PYBIND11_TYPE_GUARD_TYPE_HINT "typing_extensions.TypeGuard" #endif diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 31cf9b20cb..0f84264105 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -558,7 +558,8 @@ struct optional_caster { } PYBIND11_TYPE_CASTER(Type, - const_name("typing.Optional[") + value_conv::name + const_name("]")); + ::pybind11::detail::union_concat(make_caster::name, + make_caster::name)); }; #if defined(PYBIND11_HAS_OPTIONAL) @@ -642,10 +643,7 @@ struct variant_caster> { } using Type = V; - PYBIND11_TYPE_CASTER(Type, - const_name("typing.Union[") - + ::pybind11::detail::concat(make_caster::name...) - + const_name("]")); + PYBIND11_TYPE_CASTER(Type, ::pybind11::detail::union_concat(make_caster::name...)); }; #if defined(PYBIND11_HAS_VARIANT) diff --git a/include/pybind11/stl/filesystem.h b/include/pybind11/stl/filesystem.h index aed17a1232..1c0b18c1ef 100644 --- a/include/pybind11/stl/filesystem.h +++ b/include/pybind11/stl/filesystem.h @@ -96,11 +96,7 @@ struct path_caster { return true; } -# if PYBIND11_USE_NEW_UNIONS PYBIND11_TYPE_CASTER(T, io_name("os.PathLike | str | bytes", "pathlib.Path")); -# else - PYBIND11_TYPE_CASTER(T, io_name("typing.Union[os.PathLike, str, bytes]", "pathlib.Path")); -# endif }; #endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 50c92fd7f8..333f40101e 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -218,24 +218,13 @@ struct handle_type_name> { template struct handle_type_name> { -#if PYBIND11_USE_NEW_UNIONS static constexpr auto name = ::pybind11::detail::union_concat(make_caster::name...); -#else - static constexpr auto name = const_name("typing.Union[") - + ::pybind11::detail::concat(make_caster::name...) - + const_name("]"); -#endif }; template struct handle_type_name> { -#if PYBIND11_USE_NEW_UNIONS static constexpr auto name = ::pybind11::detail::union_concat(make_caster::name, make_caster::name); -#else - static constexpr auto name - = const_name("typing.Optional[") + make_caster::name + const_name("]"); -#endif }; template diff --git a/tests/conftest.py b/tests/conftest.py index 081303e649..22d41f8881 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,6 +16,7 @@ import sysconfig import textwrap import traceback +from typing import Callable import pytest @@ -244,19 +245,25 @@ def pytest_report_header(): return lines -def across_version_type_hint_checker(doc: str, expected: str) -> None: +@pytest.fixture +def backport_typehints() -> Callable[[SanitizedString], SanitizedString]: + d = {} if sys.version_info < (3, 13): - expected = expected.replace("typing.TypeIs", "typing_extensions.TypeIs") - expected = expected.replace( - "types.CapsuleType", "typing_extensions.CapsuleType" - ) + d["typing_extensions.TypeIs"] = "typing.TypeIs" + d["typing_extensions.CapsuleType"] = "types.CapsuleType" if sys.version_info < (3, 12): - expected = expected.replace( - "collections.abc.Buffer", "typing_extensions.Buffer" - ) + d["typing_extensions.Buffer"] = "collections.abc.Buffer" if sys.version_info < (3, 11): - expected = expected.replace("typing.Never", "typing_extensions.Never") + d["typing_extensions.Never"] = "typing.Never" if sys.version_info < (3, 10): - expected = expected.replace("typing.TypeGuard", "typing_extensions.TypeGuard") + d["typing_extensions.TypeGuard"] = "typing.TypeGuard" + + def backport(sanatized_string: SanitizedString) -> SanitizedString: + text = sanatized_string.string + for old, new in d.items(): + text = text.replace(old, new) + + sanatized_string.string = text + return sanatized_string - assert doc == expected + return backport diff --git a/tests/test_buffers.py b/tests/test_buffers.py index a712f2bda4..f666df5bad 100644 --- a/tests/test_buffers.py +++ b/tests/test_buffers.py @@ -3,7 +3,6 @@ import ctypes import io import struct -import sys import pytest @@ -228,12 +227,11 @@ def test_ctypes_from_buffer(): assert not cinfo.readonly -def test_buffer_docstring(): - if sys.version_info >= (3, 12): - docstring = "get_buffer_info(arg0: collections.abc.Buffer) -> pybind11_tests.buffers.buffer_info" - else: - docstring = "get_buffer_info(arg0: typing_extensions.Buffer) -> pybind11_tests.buffers.buffer_info" - assert m.get_buffer_info.__doc__.strip() == docstring +def test_buffer_docstring(doc, backport_typehints): + assert ( + backport_typehints(doc(m.get_buffer_info)) + == "get_buffer_info(arg0: collections.abc.Buffer) -> m.buffers.buffer_info" + ) def test_buffer_exception(): diff --git a/tests/test_opaque_types.py b/tests/test_opaque_types.py index e386fd3c78..7a4d7a43da 100644 --- a/tests/test_opaque_types.py +++ b/tests/test_opaque_types.py @@ -1,7 +1,6 @@ from __future__ import annotations import pytest -from conftest import across_version_type_hint_checker import env from pybind11_tests import ConstructorStats, UserType @@ -28,7 +27,7 @@ def test_string_list(): assert m.print_opaque_list(cvp.stringList) == "Opaque list: [Element 1, Element 3]" -def test_pointers(msg): +def test_pointers(msg, backport_typehints): living_before = ConstructorStats.get(UserType).alive() assert m.get_void_ptr_value(m.return_void_ptr()) == 0x1234 assert m.get_void_ptr_value(UserType()) # Should also work for other C++ types @@ -39,14 +38,14 @@ def test_pointers(msg): with pytest.raises(TypeError) as excinfo: m.get_void_ptr_value([1, 2, 3]) # This should not work - across_version_type_hint_checker( - msg(excinfo.value), - """ + assert ( + backport_typehints(msg(excinfo.value)) + == """ get_void_ptr_value(): incompatible function arguments. The following argument types are supported: 1. (arg0: types.CapsuleType) -> int Invoked with: [1, 2, 3] - """, + """ ) assert m.return_null_str() is None diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index e74fe6ab6a..32d635f403 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -155,11 +155,7 @@ namespace detail { template <> struct type_caster { -#if PYBIND11_USE_NEW_UNIONS PYBIND11_TYPE_CASTER(RealNumber, io_name("float | int", "float")); -#else - PYBIND11_TYPE_CASTER(RealNumber, io_name("typing.Union[float, int]", "float")); -#endif static handle cast(const RealNumber &number, return_value_policy, handle) { return py::float_(number.value).release(); diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 1edfeb8101..a199d72f0a 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -5,7 +5,6 @@ import types import pytest -from conftest import across_version_type_hint_checker import env from pybind11_tests import detailed_error_messages_enabled @@ -131,13 +130,7 @@ def test_set(capture, doc): assert m.anyset_contains({"foo"}, "foo") assert doc(m.get_set) == "get_set() -> set" - if sys.version_info >= (3, 10): - assert doc(m.print_anyset) == "print_anyset(arg0: set | frozenset) -> None" - else: - assert ( - doc(m.print_anyset) - == "print_anyset(arg0: typing.Union[set, frozenset]) -> None" - ) + assert doc(m.print_anyset) == "print_anyset(arg0: set | frozenset) -> None" def test_frozenset(capture, doc): @@ -994,67 +987,40 @@ def test_type_annotation(doc): def test_union_annotations(doc): - if sys.version_info >= (3, 10): - assert ( - doc(m.annotate_union) - == "annotate_union(arg0: list[str | typing.SupportsInt | object], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[str | int | object]" - ) - else: - assert ( - doc(m.annotate_union) - == "annotate_union(arg0: list[typing.Union[str, typing.SupportsInt, object]], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[typing.Union[str, int, object]]" - ) + assert ( + doc(m.annotate_union) + == "annotate_union(arg0: list[str | typing.SupportsInt | object], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[str | int | object]" + ) def test_union_typing_only(doc): - if sys.version_info >= (3, 10): - assert ( - doc(m.union_typing_only) - == "union_typing_only(arg0: list[str]) -> list[int]" - ) - else: - assert ( - doc(m.union_typing_only) - == "union_typing_only(arg0: list[typing.Union[str]]) -> list[typing.Union[int]]" - ) + assert doc(m.union_typing_only) == "union_typing_only(arg0: list[str]) -> list[int]" def test_union_object_annotations(doc): - if sys.version_info >= (3, 10): - assert ( - doc(m.annotate_union_to_object) - == "annotate_union_to_object(arg0: typing.SupportsInt | str) -> object" - ) - else: - assert ( - doc(m.annotate_union_to_object) - == "annotate_union_to_object(arg0: typing.Union[typing.SupportsInt, str]) -> object" - ) + assert ( + doc(m.annotate_union_to_object) + == "annotate_union_to_object(arg0: typing.SupportsInt | str) -> object" + ) def test_optional_annotations(doc): - if sys.version_info >= (3, 10): - assert ( - doc(m.annotate_optional) - == "annotate_optional(arg0: list) -> list[str | None]" - ) - else: - assert ( - doc(m.annotate_optional) - == "annotate_optional(arg0: list) -> list[typing.Optional[str]]" - ) + assert ( + doc(m.annotate_optional) == "annotate_optional(arg0: list) -> list[str | None]" + ) -def test_type_guard_annotations(doc): - across_version_type_hint_checker( - doc(m.annotate_type_guard), - "annotate_type_guard(arg0: object) -> typing.TypeGuard[str]", +def test_type_guard_annotations(doc, backport_typehints): + assert ( + backport_typehints(doc(m.annotate_type_guard)) + == "annotate_type_guard(arg0: object) -> typing.TypeGuard[str]" ) -def test_type_is_annotations(doc): - across_version_type_hint_checker( - doc(m.annotate_type_is), "annotate_type_is(arg0: object) -> typing.TypeIs[str]" +def test_type_is_annotations(doc, backport_typehints): + assert ( + backport_typehints(doc(m.annotate_type_is)) + == "annotate_type_is(arg0: object) -> typing.TypeIs[str]" ) @@ -1062,23 +1028,17 @@ def test_no_return_annotation(doc): assert doc(m.annotate_no_return) == "annotate_no_return() -> typing.NoReturn" -def test_never_annotation(doc): - across_version_type_hint_checker( - doc(m.annotate_never), "annotate_never() -> typing.Never" +def test_never_annotation(doc, backport_typehints): + assert ( + backport_typehints(doc(m.annotate_never)) == "annotate_never() -> typing.Never" ) def test_optional_object_annotations(doc): - if sys.version_info >= (3, 10): - assert ( - doc(m.annotate_optional_to_object) - == "annotate_optional_to_object(arg0: typing.SupportsInt | None) -> object" - ) - else: - assert ( - doc(m.annotate_optional_to_object) - == "annotate_optional_to_object(arg0: typing.Optional[typing.SupportsInt]) -> object" - ) + assert ( + doc(m.annotate_optional_to_object) + == "annotate_optional_to_object(arg0: typing.SupportsInt | None) -> object" + ) @pytest.mark.skipif( @@ -1111,24 +1071,14 @@ def test_literal(doc): doc(m.identity_literal_curly_close) == 'identity_literal_curly_close(arg0: typing.Literal["}"]) -> typing.Literal["}"]' ) - if sys.version_info >= (3, 10): - assert ( - doc(m.identity_literal_arrow_with_io_name) - == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: float | int) -> typing.Literal["->"]' - ) - assert ( - doc(m.identity_literal_arrow_with_callable) - == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], float | int], float]) -> collections.abc.Callable[[typing.Literal["->"], float | int], float]' - ) - else: - assert ( - doc(m.identity_literal_arrow_with_io_name) - == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: typing.Union[float, int]) -> typing.Literal["->"]' - ) - assert ( - doc(m.identity_literal_arrow_with_callable) - == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]) -> collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]' - ) + assert ( + doc(m.identity_literal_arrow_with_io_name) + == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: float | int) -> typing.Literal["->"]' + ) + assert ( + doc(m.identity_literal_arrow_with_callable) + == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], float | int], float]) -> collections.abc.Callable[[typing.Literal["->"], float | int], float]' + ) assert ( doc(m.identity_literal_all_special_chars) == 'identity_literal_all_special_chars(arg0: typing.Literal["!@!!->{%}"]) -> typing.Literal["!@!!->{%}"]' @@ -1214,16 +1164,10 @@ def test_module_attribute_types() -> None: assert module_annotations["set_str"] == "set[str]" assert module_annotations["foo"] == "pybind11_tests.pytypes.foo" - if sys.version_info >= (3, 10): - assert ( - module_annotations["foo_union"] - == "pybind11_tests.pytypes.foo | pybind11_tests.pytypes.foo2 | pybind11_tests.pytypes.foo3" - ) - else: - assert ( - module_annotations["foo_union"] - == "typing.Union[pybind11_tests.pytypes.foo, pybind11_tests.pytypes.foo2, pybind11_tests.pytypes.foo3]" - ) + assert ( + module_annotations["foo_union"] + == "pybind11_tests.pytypes.foo | pybind11_tests.pytypes.foo2 | pybind11_tests.pytypes.foo3" + ) @pytest.mark.skipif( @@ -1301,22 +1245,12 @@ def test_final_annotation() -> None: assert module_annotations["CONST_INT"] == "typing.Final[int]" -def test_arg_return_type_hints(doc): - if sys.version_info >= (3, 10): - assert doc(m.half_of_number) == "half_of_number(arg0: float | int) -> float" - assert ( - doc(m.half_of_number_convert) - == "half_of_number_convert(x: float | int) -> float" - ) - else: - assert ( - doc(m.half_of_number) - == "half_of_number(arg0: typing.Union[float, int]) -> float" - ) - assert ( - doc(m.half_of_number_convert) - == "half_of_number_convert(x: typing.Union[float, int]) -> float" - ) +def test_arg_return_type_hints(doc, backport_typehints): + assert doc(m.half_of_number) == "half_of_number(arg0: float | int) -> float" + assert ( + doc(m.half_of_number_convert) + == "half_of_number_convert(x: float | int) -> float" + ) assert ( doc(m.half_of_number_noconvert) == "half_of_number_noconvert(x: float) -> float" ) @@ -1326,173 +1260,90 @@ def test_arg_return_type_hints(doc): assert isinstance(m.half_of_number(0), float) assert not isinstance(m.half_of_number(0), int) - if sys.version_info >= (3, 10): - # std::vector - assert ( - doc(m.half_of_number_vector) - == "half_of_number_vector(arg0: collections.abc.Sequence[float | int]) -> list[float]" - ) - # Tuple - assert ( - doc(m.half_of_number_tuple) - == "half_of_number_tuple(arg0: tuple[float | int, float | int]) -> tuple[float, float]" - ) - # Tuple - assert ( - doc(m.half_of_number_tuple_ellipsis) - == "half_of_number_tuple_ellipsis(arg0: tuple[float | int, ...]) -> tuple[float, ...]" - ) - # Dict - assert ( - doc(m.half_of_number_dict) - == "half_of_number_dict(arg0: dict[str, float | int]) -> dict[str, float]" - ) - # List - assert ( - doc(m.half_of_number_list) - == "half_of_number_list(arg0: list[float | int]) -> list[float]" - ) - # List> - assert ( - doc(m.half_of_number_nested_list) - == "half_of_number_nested_list(arg0: list[list[float | int]]) -> list[list[float]]" - ) - # Set - assert ( - doc(m.identity_set) == "identity_set(arg0: set[float | int]) -> set[float]" - ) - # Iterable - assert ( - doc(m.identity_iterable) - == "identity_iterable(arg0: collections.abc.Iterable[float | int]) -> collections.abc.Iterable[float]" - ) - # Iterator - assert ( - doc(m.identity_iterator) - == "identity_iterator(arg0: collections.abc.Iterator[float | int]) -> collections.abc.Iterator[float]" - ) - # Callable identity - assert ( - doc(m.identity_callable) - == "identity_callable(arg0: collections.abc.Callable[[float | int], float]) -> collections.abc.Callable[[float | int], float]" - ) - # Callable identity - assert ( - doc(m.identity_callable_ellipsis) - == "identity_callable_ellipsis(arg0: collections.abc.Callable[..., float]) -> collections.abc.Callable[..., float]" - ) - # Nested Callable identity - assert ( - doc(m.identity_nested_callable) - == "identity_nested_callable(arg0: collections.abc.Callable[[collections.abc.Callable[[float | int], float]], collections.abc.Callable[[float | int], float]]) -> collections.abc.Callable[[collections.abc.Callable[[float | int], float]], collections.abc.Callable[[float | int], float]]" - ) - # Callable - assert ( - doc(m.apply_callable) - == "apply_callable(arg0: float | int, arg1: collections.abc.Callable[[float | int], float]) -> float" - ) - # Callable - assert ( - doc(m.apply_callable_ellipsis) - == "apply_callable_ellipsis(arg0: float | int, arg1: collections.abc.Callable[..., float]) -> float" - ) - # Union - assert ( - doc(m.identity_union) - == "identity_union(arg0: float | int | str) -> float | str" - ) - # Optional - assert ( - doc(m.identity_optional) - == "identity_optional(arg0: float | int | None) -> float | None" - ) - else: - # std::vector - assert ( - doc(m.half_of_number_vector) - == "half_of_number_vector(arg0: collections.abc.Sequence[typing.Union[float, int]]) -> list[float]" - ) - # Tuple - assert ( - doc(m.half_of_number_tuple) - == "half_of_number_tuple(arg0: tuple[typing.Union[float, int], typing.Union[float, int]]) -> tuple[float, float]" - ) - # Tuple - assert ( - doc(m.half_of_number_tuple_ellipsis) - == "half_of_number_tuple_ellipsis(arg0: tuple[typing.Union[float, int], ...]) -> tuple[float, ...]" - ) - # Dict - assert ( - doc(m.half_of_number_dict) - == "half_of_number_dict(arg0: dict[str, typing.Union[float, int]]) -> dict[str, float]" - ) - # List - assert ( - doc(m.half_of_number_list) - == "half_of_number_list(arg0: list[typing.Union[float, int]]) -> list[float]" - ) - # List> - assert ( - doc(m.half_of_number_nested_list) - == "half_of_number_nested_list(arg0: list[list[typing.Union[float, int]]]) -> list[list[float]]" - ) - # Set - assert ( - doc(m.identity_set) - == "identity_set(arg0: set[typing.Union[float, int]]) -> set[float]" - ) - # Iterable - assert ( - doc(m.identity_iterable) - == "identity_iterable(arg0: collections.abc.Iterable[typing.Union[float, int]]) -> collections.abc.Iterable[float]" - ) - # Iterator - assert ( - doc(m.identity_iterator) - == "identity_iterator(arg0: collections.abc.Iterator[typing.Union[float, int]]) -> collections.abc.Iterator[float]" - ) - # Callable identity - assert ( - doc(m.identity_callable) - == "identity_callable(arg0: collections.abc.Callable[[typing.Union[float, int]], float]) -> collections.abc.Callable[[typing.Union[float, int]], float]" - ) - # Callable identity - assert ( - doc(m.identity_callable_ellipsis) - == "identity_callable_ellipsis(arg0: collections.abc.Callable[..., float]) -> collections.abc.Callable[..., float]" - ) - # Nested Callable identity - assert ( - doc(m.identity_nested_callable) - == "identity_nested_callable(arg0: collections.abc.Callable[[collections.abc.Callable[[typing.Union[float, int]], float]], collections.abc.Callable[[typing.Union[float, int]], float]]) -> collections.abc.Callable[[collections.abc.Callable[[typing.Union[float, int]], float]], collections.abc.Callable[[typing.Union[float, int]], float]]" - ) - # Callable - assert ( - doc(m.apply_callable) - == "apply_callable(arg0: typing.Union[float, int], arg1: collections.abc.Callable[[typing.Union[float, int]], float]) -> float" - ) - # Callable - assert ( - doc(m.apply_callable_ellipsis) - == "apply_callable_ellipsis(arg0: typing.Union[float, int], arg1: collections.abc.Callable[..., float]) -> float" - ) - # Union - assert ( - doc(m.identity_union) - == "identity_union(arg0: typing.Union[typing.Union[float, int], str]) -> typing.Union[float, str]" - ) - # Optional - assert ( - doc(m.identity_optional) - == "identity_optional(arg0: typing.Optional[typing.Union[float, int]]) -> typing.Optional[float]" - ) - + # std::vector + assert ( + doc(m.half_of_number_vector) + == "half_of_number_vector(arg0: collections.abc.Sequence[float | int]) -> list[float]" + ) + # Tuple + assert ( + doc(m.half_of_number_tuple) + == "half_of_number_tuple(arg0: tuple[float | int, float | int]) -> tuple[float, float]" + ) + # Tuple + assert ( + doc(m.half_of_number_tuple_ellipsis) + == "half_of_number_tuple_ellipsis(arg0: tuple[float | int, ...]) -> tuple[float, ...]" + ) + # Dict + assert ( + doc(m.half_of_number_dict) + == "half_of_number_dict(arg0: dict[str, float | int]) -> dict[str, float]" + ) + # List + assert ( + doc(m.half_of_number_list) + == "half_of_number_list(arg0: list[float | int]) -> list[float]" + ) + # List> + assert ( + doc(m.half_of_number_nested_list) + == "half_of_number_nested_list(arg0: list[list[float | int]]) -> list[list[float]]" + ) + # Set + assert doc(m.identity_set) == "identity_set(arg0: set[float | int]) -> set[float]" + # Iterable + assert ( + doc(m.identity_iterable) + == "identity_iterable(arg0: collections.abc.Iterable[float | int]) -> collections.abc.Iterable[float]" + ) + # Iterator + assert ( + doc(m.identity_iterator) + == "identity_iterator(arg0: collections.abc.Iterator[float | int]) -> collections.abc.Iterator[float]" + ) + # Callable identity + assert ( + doc(m.identity_callable) + == "identity_callable(arg0: collections.abc.Callable[[float | int], float]) -> collections.abc.Callable[[float | int], float]" + ) + # Callable identity + assert ( + doc(m.identity_callable_ellipsis) + == "identity_callable_ellipsis(arg0: collections.abc.Callable[..., float]) -> collections.abc.Callable[..., float]" + ) + # Nested Callable identity + assert ( + doc(m.identity_nested_callable) + == "identity_nested_callable(arg0: collections.abc.Callable[[collections.abc.Callable[[float | int], float]], collections.abc.Callable[[float | int], float]]) -> collections.abc.Callable[[collections.abc.Callable[[float | int], float]], collections.abc.Callable[[float | int], float]]" + ) + # Callable + assert ( + doc(m.apply_callable) + == "apply_callable(arg0: float | int, arg1: collections.abc.Callable[[float | int], float]) -> float" + ) + # Callable + assert ( + doc(m.apply_callable_ellipsis) + == "apply_callable_ellipsis(arg0: float | int, arg1: collections.abc.Callable[..., float]) -> float" + ) + # Union + assert ( + doc(m.identity_union) + == "identity_union(arg0: float | int | str) -> float | str" + ) + # Optional + assert ( + doc(m.identity_optional) + == "identity_optional(arg0: float | int | None) -> float | None" + ) # TypeIs - across_version_type_hint_checker( - doc(m.check_type_is), "check_type_is(arg0: object) -> typing.TypeIs[float]" + assert ( + backport_typehints(doc(m.check_type_is)) + == "check_type_is(arg0: object) -> typing.TypeIs[float]" ) - across_version_type_hint_checker( - doc(m.check_type_guard), - "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]", + # TypeGuard + assert ( + backport_typehints(doc(m.check_type_guard)) + == "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]" ) diff --git a/tests/test_stl.py b/tests/test_stl.py index 1b4eb8c9a1..66523780a7 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -1,7 +1,5 @@ from __future__ import annotations -import sys - import pytest import env # noqa: F401 @@ -303,68 +301,36 @@ def __fspath__(self): @pytest.mark.skipif(not hasattr(m, "has_filesystem"), reason="no ") def test_path_typing(doc): - if sys.version_info >= (3, 10): - # Single argument - assert ( - doc(m.parent_path) - == "parent_path(arg0: os.PathLike | str | bytes) -> pathlib.Path" - ) - # std::vector - assert ( - doc(m.parent_paths) - == "parent_paths(arg0: collections.abc.Sequence[os.PathLike | str | bytes]) -> list[pathlib.Path]" - ) - # py::typing::List - assert ( - doc(m.parent_paths_list) - == "parent_paths_list(arg0: list[os.PathLike | str | bytes]) -> list[pathlib.Path]" - ) - # Nested py::typing::List - assert ( - doc(m.parent_paths_nested_list) - == "parent_paths_nested_list(arg0: list[list[os.PathLike | str | bytes]]) -> list[list[pathlib.Path]]" - ) - # py::typing::Tuple - assert ( - doc(m.parent_paths_tuple) - == "parent_paths_tuple(arg0: tuple[os.PathLike | str | bytes, os.PathLike | str | bytes]) -> tuple[pathlib.Path, pathlib.Path]" - ) - # py::typing::Dict - assert ( - doc(m.parent_paths_dict) - == "parent_paths_dict(arg0: dict[str, os.PathLike | str | bytes]) -> dict[str, pathlib.Path]" - ) - else: - # Single argument - assert ( - doc(m.parent_path) - == "parent_path(arg0: typing.Union[os.PathLike, str, bytes]) -> pathlib.Path" - ) - # std::vector - assert ( - doc(m.parent_paths) - == "parent_paths(arg0: collections.abc.Sequence[typing.Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" - ) - # py::typing::List - assert ( - doc(m.parent_paths_list) - == "parent_paths_list(arg0: list[typing.Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" - ) - # Nested py::typing::List - assert ( - doc(m.parent_paths_nested_list) - == "parent_paths_nested_list(arg0: list[list[typing.Union[os.PathLike, str, bytes]]]) -> list[list[pathlib.Path]]" - ) - # py::typing::Tuple - assert ( - doc(m.parent_paths_tuple) - == "parent_paths_tuple(arg0: tuple[typing.Union[os.PathLike, str, bytes], typing.Union[os.PathLike, str, bytes]]) -> tuple[pathlib.Path, pathlib.Path]" - ) - # py::typing::Dict - assert ( - doc(m.parent_paths_dict) - == "parent_paths_dict(arg0: dict[str, typing.Union[os.PathLike, str, bytes]]) -> dict[str, pathlib.Path]" - ) + # Single argument + assert ( + doc(m.parent_path) + == "parent_path(arg0: os.PathLike | str | bytes) -> pathlib.Path" + ) + # std::vector + assert ( + doc(m.parent_paths) + == "parent_paths(arg0: collections.abc.Sequence[os.PathLike | str | bytes]) -> list[pathlib.Path]" + ) + # py::typing::List + assert ( + doc(m.parent_paths_list) + == "parent_paths_list(arg0: list[os.PathLike | str | bytes]) -> list[pathlib.Path]" + ) + # Nested py::typing::List + assert ( + doc(m.parent_paths_nested_list) + == "parent_paths_nested_list(arg0: list[list[os.PathLike | str | bytes]]) -> list[list[pathlib.Path]]" + ) + # py::typing::Tuple + assert ( + doc(m.parent_paths_tuple) + == "parent_paths_tuple(arg0: tuple[os.PathLike | str | bytes, os.PathLike | str | bytes]) -> tuple[pathlib.Path, pathlib.Path]" + ) + # py::typing::Dict + assert ( + doc(m.parent_paths_dict) + == "parent_paths_dict(arg0: dict[str, os.PathLike | str | bytes]) -> dict[str, pathlib.Path]" + ) @pytest.mark.skipif(not hasattr(m, "load_variant"), reason="no ") @@ -381,7 +347,7 @@ def test_variant(doc): assert ( doc(m.load_variant) - == "load_variant(arg0: typing.Union[typing.SupportsInt, str, typing.SupportsFloat, None]) -> str" + == "load_variant(arg0: typing.SupportsInt | str | typing.SupportsFloat | None) -> str" ) @@ -397,7 +363,7 @@ def test_variant_monostate(doc): assert ( doc(m.load_monostate_variant) - == "load_monostate_variant(arg0: typing.Union[None, typing.SupportsInt, str]) -> str" + == "load_monostate_variant(arg0: None | typing.SupportsInt | str) -> str" ) From f733d21a20a8a7baf3e1493d4ac3b4ee5c831af5 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Tue, 27 May 2025 15:59:40 -0700 Subject: [PATCH 34/45] timohl suggested cleanup Signed-off-by: Michael Carlstrom --- include/pybind11/detail/descr.h | 6 ++++++ include/pybind11/stl.h | 3 +-- include/pybind11/typing.h | 2 +- tests/conftest.py | 4 +--- tests/test_stl.py | 4 +++- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 51bfcfa9c9..be3793c1b6 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -204,6 +204,12 @@ constexpr auto union_concat(const descr &d, const Args &...args) -> decltype(std::declval>() + union_concat(args...)) { return d + const_name(" | ") + union_concat(args...); } + +template +constexpr operator|(const Args &...args) + -> decltype(std::declval>() + union_concat(args...)) { + return union_concat(args...); +} #endif template diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 0f84264105..bd0f152b6e 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -558,8 +558,7 @@ struct optional_caster { } PYBIND11_TYPE_CASTER(Type, - ::pybind11::detail::union_concat(make_caster::name, - make_caster::name)); + value_conv::name | make_caster::name); }; #if defined(PYBIND11_HAS_OPTIONAL) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 333f40101e..ea6a080046 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -224,7 +224,7 @@ struct handle_type_name> { template struct handle_type_name> { static constexpr auto name - = ::pybind11::detail::union_concat(make_caster::name, make_caster::name); + = make_caster::name | make_caster::name; }; template diff --git a/tests/conftest.py b/tests/conftest.py index 22d41f8881..b8338d46f1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -259,11 +259,9 @@ def backport_typehints() -> Callable[[SanitizedString], SanitizedString]: d["typing_extensions.TypeGuard"] = "typing.TypeGuard" def backport(sanatized_string: SanitizedString) -> SanitizedString: - text = sanatized_string.string for old, new in d.items(): - text = text.replace(old, new) + sanatized_string.string = sanatized_string.string.replace(old, new) - sanatized_string.string = text return sanatized_string return backport diff --git a/tests/test_stl.py b/tests/test_stl.py index 66523780a7..f01b7840e4 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -227,11 +227,13 @@ def test_boost_optional(): assert int(props.access_by_copy) == 42 -def test_reference_sensitive_optional(): +def test_reference_sensitive_optional(doc): assert m.double_or_zero_refsensitive(None) == 0 assert m.double_or_zero_refsensitive(42) == 84 pytest.raises(TypeError, m.double_or_zero_refsensitive, "foo") + assert doc(m.double_or_zero_refsensitive) == "double_or_zero_refsensitive(arg0: typing.SupportsInt | None) -> int" + assert m.half_or_none_refsensitive(0) is None assert m.half_or_none_refsensitive(42) == 21 pytest.raises(TypeError, m.half_or_none_refsensitive, "foo") From 62a2ee638359c2f48bdd5df8bfbad70d34ad04a7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 23:00:04 +0000 Subject: [PATCH 35/45] style: pre-commit fixes --- include/pybind11/detail/descr.h | 2 +- include/pybind11/stl.h | 3 +-- include/pybind11/typing.h | 3 +-- tests/test_stl.py | 5 ++++- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index be3793c1b6..df59189252 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -207,7 +207,7 @@ constexpr auto union_concat(const descr &d, const Args &...args) template constexpr operator|(const Args &...args) - -> decltype(std::declval>() + union_concat(args...)) { + ->decltype(std::declval>() + union_concat(args...)) { return union_concat(args...); } #endif diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index bd0f152b6e..01be0b47c6 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -557,8 +557,7 @@ struct optional_caster { return true; } - PYBIND11_TYPE_CASTER(Type, - value_conv::name | make_caster::name); + PYBIND11_TYPE_CASTER(Type, value_conv::name | make_caster::name); }; #if defined(PYBIND11_HAS_OPTIONAL) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index ea6a080046..1715026efa 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -223,8 +223,7 @@ struct handle_type_name> { template struct handle_type_name> { - static constexpr auto name - = make_caster::name | make_caster::name; + static constexpr auto name = make_caster::name | make_caster::name; }; template diff --git a/tests/test_stl.py b/tests/test_stl.py index f01b7840e4..4a57635e27 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -232,7 +232,10 @@ def test_reference_sensitive_optional(doc): assert m.double_or_zero_refsensitive(42) == 84 pytest.raises(TypeError, m.double_or_zero_refsensitive, "foo") - assert doc(m.double_or_zero_refsensitive) == "double_or_zero_refsensitive(arg0: typing.SupportsInt | None) -> int" + assert ( + doc(m.double_or_zero_refsensitive) + == "double_or_zero_refsensitive(arg0: typing.SupportsInt | None) -> int" + ) assert m.half_or_none_refsensitive(0) is None assert m.half_or_none_refsensitive(42) == 21 From 06a418831db7e2767c930f4888c1b178fcfc402c Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Tue, 27 May 2025 16:02:58 -0700 Subject: [PATCH 36/45] add missing auto Signed-off-by: Michael Carlstrom --- include/pybind11/detail/descr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index df59189252..0aade83b9f 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -206,7 +206,7 @@ constexpr auto union_concat(const descr &d, const Args &...args) } template -constexpr operator|(const Args &...args) +constexpr auto operator|(const Args &...args) ->decltype(std::declval>() + union_concat(args...)) { return union_concat(args...); } From c287018dd2dbfb444f4af34a25cac12b2202cf18 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 23:03:39 +0000 Subject: [PATCH 37/45] style: pre-commit fixes --- include/pybind11/detail/descr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 0aade83b9f..86e7cd1841 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -207,7 +207,7 @@ constexpr auto union_concat(const descr &d, const Args &...args) template constexpr auto operator|(const Args &...args) - ->decltype(std::declval>() + union_concat(args...)) { + -> decltype(std::declval>() + union_concat(args...)) { return union_concat(args...); } #endif From 08ff0d3f9ba8403c3593a56a55e3ed3575fa9bbb Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Tue, 27 May 2025 16:25:46 -0700 Subject: [PATCH 38/45] move operator| def --- include/pybind11/detail/descr.h | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 86e7cd1841..701662c4cf 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -169,6 +169,12 @@ constexpr descr union_concat(const descr &descr) { return descr; } +template +constexpr descr operator|(const descr &a, + const descr &b) { + return a + const_name(" | ") + b; +} + #ifdef __cpp_fold_expressions template constexpr descr operator,(const descr &a, @@ -181,12 +187,6 @@ constexpr auto concat(const descr &d, const Args &...args) { return (d, ..., args); } -template -constexpr descr operator|(const descr &a, - const descr &b) { - return a + const_name(" | ") + b; -} - template constexpr auto union_concat(const descr &d, const Args &...args) { return (d | ... | args); @@ -205,11 +205,6 @@ constexpr auto union_concat(const descr &d, const Args &...args) return d + const_name(" | ") + union_concat(args...); } -template -constexpr auto operator|(const Args &...args) - -> decltype(std::declval>() + union_concat(args...)) { - return union_concat(args...); -} #endif template From d9688077ff6e5f92d37c62e629cd8e0ea742aa8b Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Wed, 28 May 2025 09:22:06 +0000 Subject: [PATCH 39/45] Fixed test requirements install for mingw --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8aeef900b4..5893f9412f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1016,7 +1016,7 @@ jobs: - uses: actions/checkout@v4 - name: Prepare env - run: python -m pip install -r tests/requirements.txt + run: python -m pip install -r pybind11-stubgen mypy - name: Configure C++11 From 2cada63c7f35c157c9ba31c956e5ab29eacb54f6 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Wed, 28 May 2025 09:49:48 +0000 Subject: [PATCH 40/45] Fix for mingw ci --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5893f9412f..0b971ad649 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1001,6 +1001,7 @@ jobs: mingw-w64-${{matrix.env}}-cmake mingw-w64-${{matrix.env}}-make mingw-w64-${{matrix.env}}-python-pytest + mingw-w64-${{matrix.env}}-mypy mingw-w64-${{matrix.env}}-boost mingw-w64-${{matrix.env}}-catch @@ -1016,7 +1017,10 @@ jobs: - uses: actions/checkout@v4 - name: Prepare env - run: python -m pip install -r pybind11-stubgen mypy + run: | + python -m venv .venv + . .venv/bin/activate + python -m pip install pybind11-stubgen - name: Configure C++11 From dfdd41884ad326c32f6e1fdb3cf9458de47552a5 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Wed, 28 May 2025 09:52:30 +0000 Subject: [PATCH 41/45] Moved mypy to pip install in mingw ci --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b971ad649..ae142730d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1001,7 +1001,6 @@ jobs: mingw-w64-${{matrix.env}}-cmake mingw-w64-${{matrix.env}}-make mingw-w64-${{matrix.env}}-python-pytest - mingw-w64-${{matrix.env}}-mypy mingw-w64-${{matrix.env}}-boost mingw-w64-${{matrix.env}}-catch @@ -1020,7 +1019,7 @@ jobs: run: | python -m venv .venv . .venv/bin/activate - python -m pip install pybind11-stubgen + python -m pip install pybind11-stubgen mypy - name: Configure C++11 From 2f9709c11cda7de0e0b911bb9264b1e88ad2e238 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Wed, 28 May 2025 15:21:09 +0200 Subject: [PATCH 42/45] Removed Python 3.14 xfail for stubgen --- tests/test_stubgen.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/test_stubgen.py b/tests/test_stubgen.py index 6890d42bd2..0907b1aad9 100644 --- a/tests/test_stubgen.py +++ b/tests/test_stubgen.py @@ -1,6 +1,5 @@ from __future__ import annotations -import sys from pathlib import Path from typing import NamedTuple @@ -29,9 +28,6 @@ def run_mypy(stubs: Path) -> MypyResult: return MypyResult(normal_report, error_report, exit_status) -@pytest.mark.xfail( - sys.version_info >= (3, 14), reason="mypy does not support Python 3.14+ yet" -) def test_stubgen(tmp_path: Path) -> None: assert m.add_int(1, 2) == 3 # Generate stub into temporary directory From 27430131d9cbda2a6071175c68b4d3ea67c4bcb9 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Wed, 28 May 2025 15:29:32 +0200 Subject: [PATCH 43/45] Added --break-system-packages to mingw ci --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae142730d1..3cfebec41b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1017,9 +1017,7 @@ jobs: - name: Prepare env run: | - python -m venv .venv - . .venv/bin/activate - python -m pip install pybind11-stubgen mypy + python -m pip install --break-system-packages pybind11-stubgen mypy - name: Configure C++11 From 268dc3087073ac672838b73cd0b95039256ca84b Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Wed, 28 May 2025 16:58:41 +0200 Subject: [PATCH 44/45] Renabled stubgen test for one CI job --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cfebec41b..c9f0e26762 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,7 +162,7 @@ jobs: -DPYBIND11_WERROR=ON -DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON - -DPYBIND11_PYTEST_ARGS=-v + -DPYBIND11_PYTEST_ARGS="-v -m stubgen" -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=11 @@ -190,7 +190,7 @@ jobs: cmake -S. -Bbuild2 -Werror=dev -DPYBIND11_WERROR=ON -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF - -DPYBIND11_PYTEST_ARGS=-v + -DPYBIND11_PYTEST_ARGS="-v -m stubgen" -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=17 From f9e4686952dea9feb5532cb88f5e089ce706c049 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Wed, 28 May 2025 17:53:49 +0200 Subject: [PATCH 45/45] Several added or fixed bindings in tests to generate correct type hints --- tests/test_custom_type_casters.cpp | 1 + tests/test_kwargs_and_defaults.cpp | 2 +- tests/test_numpy_dtypes.cpp | 8 ++++++++ tests/test_stl.cpp | 1 + tests/test_virtual_functions.cpp | 3 +-- 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/test_custom_type_casters.cpp b/tests/test_custom_type_casters.cpp index 01ebf8f41c..3529f37631 100644 --- a/tests/test_custom_type_casters.cpp +++ b/tests/test_custom_type_casters.cpp @@ -216,5 +216,6 @@ TEST_SUBMODULE(custom_type_casters, m) { m.def("other_lib_type", [](other_lib::MyType x) { return x; }); + py::class_(m, "ADL_issue_test"); m.def("_adl_issue", [](const ADL_issue::test &) {}); } diff --git a/tests/test_kwargs_and_defaults.cpp b/tests/test_kwargs_and_defaults.cpp index 831947f160..fa409433f6 100644 --- a/tests/test_kwargs_and_defaults.cpp +++ b/tests/test_kwargs_and_defaults.cpp @@ -297,7 +297,7 @@ TEST_SUBMODULE(kwargs_and_defaults, m) { m.def( "class_default_argument", [](py::object a) { return py::repr(std::move(a)); }, - "a"_a = py::module_::import("decimal").attr("Decimal")); + py::arg_v("a", py::module_::import("decimal").attr("Decimal"), "decimal.Decimal")); // Initial implementation of kw_only was broken when used on a method/constructor before any // other arguments diff --git a/tests/test_numpy_dtypes.cpp b/tests/test_numpy_dtypes.cpp index f206da7323..8f3ca51d69 100644 --- a/tests/test_numpy_dtypes.cpp +++ b/tests/test_numpy_dtypes.cpp @@ -354,6 +354,14 @@ TEST_SUBMODULE(numpy_dtypes, m) { // ... or after py::class_(m, "PackedStruct"); + py::class_(m, "SimpleStructReordered"); + py::class_(m, "NestedStruct"); + py::class_(m, "PartialStruct"); + py::class_(m, "PartialNestedStruct"); + py::class_(m, "StringStruct"); + py::class_(m, "ArrayStruct"); + py::class_(m, "EnumStruct"); + py::class_(m, "ComplexStruct"); PYBIND11_NUMPY_DTYPE_EX(StructWithUglyNames, __x__, "x", __y__, "y"); diff --git a/tests/test_stl.cpp b/tests/test_stl.cpp index 5e6d6a333f..75e633290f 100644 --- a/tests/test_stl.cpp +++ b/tests/test_stl.cpp @@ -550,6 +550,7 @@ TEST_SUBMODULE(stl, m) { // #528: templated constructor // (no python tests: the test here is that this compiles) + py::class_(m, "TplCtorClass"); m.def("tpl_ctor_vector", [](std::vector &) {}); m.def("tpl_ctor_map", [](std::unordered_map &) {}); m.def("tpl_ctor_set", [](std::unordered_set &) {}); diff --git a/tests/test_virtual_functions.cpp b/tests/test_virtual_functions.cpp index a6164eb81d..9a496b59b0 100644 --- a/tests/test_virtual_functions.cpp +++ b/tests/test_virtual_functions.cpp @@ -324,12 +324,11 @@ TEST_SUBMODULE(virtual_functions, m) { // test_recursive_dispatch_issue // #3357: Recursive dispatch fails to find python function override + pybind11::class_(m, "Data").def(pybind11::init<>()); pybind11::class_(m, "Adder") .def(pybind11::init<>()) .def("__call__", &AdderBase::operator()); - pybind11::class_(m, "Data").def(pybind11::init<>()); - m.def("add2", [](const AdderBase::Data &first, const AdderBase::Data &second,