Skip to content

Demo: Testing pybind11-stubgen + mypy #5678

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 51 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
f239e13
Added initial testing module for stubgen tests.
timohl May 20, 2025
d713d3c
Added stubgen+mypy test
timohl May 20, 2025
3bfb20e
Add example test for stubgen/mypy error
timohl May 20, 2025
9b6f056
Add pybind11-stubgen and mypy dependency to pyproject.toml
timohl May 20, 2025
b5c12db
Remove capsule function call with None
timohl May 20, 2025
1ab7099
Added skipif for Python 3.14 since mypy failes here
May 22, 2025
f3d51f6
Added --no-color-output to mypy call for easier checking of report
May 22, 2025
2c5d721
Install test dependencies from requirements.txt in CI
May 22, 2025
a83b6c2
Removed incompatible --system from requirements.txt installs in CI
May 22, 2025
36c4e2d
Readded --system only to uv pip calls
May 22, 2025
b351f83
Using uv activate-environment to fix issue with system wide install
May 22, 2025
3c881ca
Using mingw-uv install to fix uv not being found
May 22, 2025
f04d790
Using pip instead of uv for mingw
May 22, 2025
76f3b89
Revert mingw CI to install some test requirements using setup-msys2
May 22, 2025
d9d5240
Added test to check stubgen and mypy on all tests
timohl May 23, 2025
18385c4
Module of CapsuleType now depends on Python version
timohl May 25, 2025
c37e65e
Moved mypy call to separate function to assure `--no-color-output`
timohl May 25, 2025
f430803
fix future type hints
InvincibleRMC May 25, 2025
14f9c7d
Merge branch 'master' into fix-remaining-future-type-hints
InvincibleRMC May 25, 2025
e374a2f
style: pre-commit fixes
pre-commit-ci[bot] May 25, 2025
325f080
remove unused var
InvincibleRMC May 25, 2025
6dcc1d9
remove union_helper
InvincibleRMC May 25, 2025
8570b25
fix speelling error
InvincibleRMC May 25, 2025
5ea93d4
base case for union_concat
InvincibleRMC May 25, 2025
f514886
style: pre-commit fixes
pre-commit-ci[bot] May 25, 2025
0d2de7a
add case for one descr
InvincibleRMC May 25, 2025
c489b95
weakref and final test
InvincibleRMC May 25, 2025
b66ec64
Fixed several incorrect type hints in tests
timohl May 25, 2025
7c9f453
Added `-m stubgen` marker to enable testing of stubgen on all test mo…
timohl May 25, 2025
55c2999
Merge branch 'master' into fix-remaining-future-type-hints
InvincibleRMC May 26, 2025
41e2bbe
Add acrpss_version_type_hint_checker
InvincibleRMC May 26, 2025
bd41651
cleanup
InvincibleRMC May 26, 2025
3d4185a
style: pre-commit fixes
pre-commit-ci[bot] May 26, 2025
df38f99
remove test.pyi
InvincibleRMC May 26, 2025
eecf91d
Merge branch 'master' into fix-remaining-future-type-hints
InvincibleRMC May 27, 2025
8746f74
use new unions and add fixture
InvincibleRMC May 27, 2025
d2c2e60
Merge branch 'master' into fix-remaining-future-type-hints
InvincibleRMC May 27, 2025
f733d21
timohl suggested cleanup
InvincibleRMC May 27, 2025
62a2ee6
style: pre-commit fixes
pre-commit-ci[bot] May 27, 2025
06a4188
add missing auto
InvincibleRMC May 27, 2025
c287018
style: pre-commit fixes
pre-commit-ci[bot] May 27, 2025
08ff0d3
move operator| def
InvincibleRMC May 27, 2025
d968807
Fixed test requirements install for mingw
timohl May 28, 2025
d60dc81
Merge branch 'master' into stubgen-testing
timohl May 28, 2025
2cada63
Fix for mingw ci
timohl May 28, 2025
dfdd418
Moved mypy to pip install in mingw ci
timohl May 28, 2025
2f9709c
Removed Python 3.14 xfail for stubgen
timohl May 28, 2025
2743013
Added --break-system-packages to mingw ci
timohl May 28, 2025
268dc30
Renabled stubgen test for one CI job
timohl May 28, 2025
76b7c52
Merge branch 'fix-remaining-future-type-hints' into stubgen-testing
timohl May 28, 2025
f9e4686
Several added or fixed bindings in tests to generate correct type hints
timohl May 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 39 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -343,8 +343,17 @@ 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:
activate-environment: true
enable-cache: true

- name: Prepare env
run: uv pip install -r tests/requirements.txt

- name: Configure
shell: bash
Expand Down Expand Up @@ -380,7 +389,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
Expand Down Expand Up @@ -455,11 +472,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 -r tests/requirements.txt

# On some systems, you many need further workarounds:
# https://github.com/pybind/pybind11/pull/2475
Expand Down Expand Up @@ -506,10 +523,16 @@ 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:
activate-environment: true
enable-cache: true

- name: Prepare env
run: uv pip install -r tests/requirements.txt

- name: Update CMake
uses: jwlawson/[email protected]
Expand Down Expand Up @@ -724,7 +747,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: >
Expand Down Expand Up @@ -992,6 +1015,11 @@ jobs:

- uses: actions/checkout@v4

- name: Prepare env
run: |
python -m pip install --break-system-packages pybind11-stubgen mypy


- name: Configure C++11
# LTO leads to many undefined reference like
# `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&)
Expand Down
8 changes: 4 additions & 4 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ class type_caster<void> : public type_caster<void_type> {
template <typename T>
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;
Expand Down Expand Up @@ -1361,7 +1361,7 @@ struct handle_type_name<dict> {
};
template <>
struct handle_type_name<anyset> {
static constexpr auto name = const_name("typing.Union[set, frozenset]");
static constexpr auto name = const_name("set | frozenset");
};
template <>
struct handle_type_name<set> {
Expand Down Expand Up @@ -1441,15 +1441,15 @@ struct handle_type_name<type> {
};
template <>
struct handle_type_name<capsule> {
static constexpr auto name = const_name("types.CapsuleType");
static constexpr auto name = const_name(PYBIND11_CAPSULE_TYPE_TYPE_HINT);
};
template <>
struct handle_type_name<ellipsis> {
static constexpr auto name = const_name("ellipsis");
};
template <>
struct handle_type_name<weakref> {
static constexpr auto name = const_name("weakref");
static constexpr auto name = const_name("weakref.ReferenceType");
};
template <>
struct handle_type_name<args> {
Expand Down
23 changes: 23 additions & 0 deletions include/pybind11/detail/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,13 +261,36 @@
# 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"
#else
# 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_TYPE_GUARD_TYPE_HINT "typing.TypeGuard"
#else
# 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
Expand Down
25 changes: 25 additions & 0 deletions include/pybind11/detail/descr.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,24 @@ constexpr descr<1, Type> _() {
#endif // #ifndef _

constexpr descr<0> concat() { return {}; }
constexpr descr<0> union_concat() { return {}; }

template <size_t N, typename... Ts>
constexpr descr<N, Ts...> concat(const descr<N, Ts...> &descr) {
return descr;
}

template <size_t N, typename... Ts>
constexpr descr<N, Ts...> union_concat(const descr<N, Ts...> &descr) {
return descr;
}

template <size_t N1, size_t N2, typename... Ts1, typename... Ts2>
constexpr descr<N1 + N2 + 3, Ts1..., Ts2...> operator|(const descr<N1, Ts1...> &a,
const descr<N2, Ts2...> &b) {
return a + const_name(" | ") + b;
}

#ifdef __cpp_fold_expressions
template <size_t N1, size_t N2, typename... Ts1, typename... Ts2>
constexpr descr<N1 + N2 + 2, Ts1..., Ts2...> operator,(const descr<N1, Ts1...> &a,
Expand All @@ -174,12 +186,25 @@ template <size_t N, typename... Ts, typename... Args>
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args) {
return (d, ..., args);
}

template <size_t N, typename... Ts, typename... Args>
constexpr auto union_concat(const descr<N, Ts...> &d, const Args &...args) {
return (d | ... | args);
}

#else
template <size_t N, typename... Ts, typename... Args>
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args)
-> decltype(std::declval<descr<N + 2, Ts...>>() + concat(args...)) {
return d + const_name(", ") + concat(args...);
}

template <size_t N, typename... Ts, typename... Args>
constexpr auto union_concat(const descr<N, Ts...> &d, const Args &...args)
-> decltype(std::declval<descr<N + 3, Ts...>>() + union_concat(args...)) {
return d + const_name(" | ") + union_concat(args...);
}

#endif

template <size_t N, typename... Ts>
Expand Down
8 changes: 2 additions & 6 deletions include/pybind11/stl.h
Original file line number Diff line number Diff line change
Expand Up @@ -557,8 +557,7 @@ struct optional_caster {
return true;
}

PYBIND11_TYPE_CASTER(Type,
const_name("typing.Optional[") + value_conv::name + const_name("]"));
PYBIND11_TYPE_CASTER(Type, value_conv::name | make_caster<none>::name);
};

#if defined(PYBIND11_HAS_OPTIONAL)
Expand Down Expand Up @@ -642,10 +641,7 @@ struct variant_caster<V<Ts...>> {
}

using Type = V<Ts...>;
PYBIND11_TYPE_CASTER(Type,
const_name("typing.Union[")
+ ::pybind11::detail::concat(make_caster<Ts>::name...)
+ const_name("]"));
PYBIND11_TYPE_CASTER(Type, ::pybind11::detail::union_concat(make_caster<Ts>::name...));
};

#if defined(PYBIND11_HAS_VARIANT)
Expand Down
2 changes: 1 addition & 1 deletion include/pybind11/stl/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ struct path_caster {
return true;
}

PYBIND11_TYPE_CASTER(T, io_name("typing.Union[os.PathLike, str, bytes]", "pathlib.Path"));
PYBIND11_TYPE_CASTER(T, io_name("os.PathLike | str | bytes", "pathlib.Path"));
};

#endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM)
Expand Down
17 changes: 7 additions & 10 deletions include/pybind11/typing.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,15 +218,12 @@ struct handle_type_name<typing::Type<T>> {

template <typename... Types>
struct handle_type_name<typing::Union<Types...>> {
static constexpr auto name = const_name("typing.Union[")
+ ::pybind11::detail::concat(make_caster<Types>::name...)
+ const_name("]");
static constexpr auto name = ::pybind11::detail::union_concat(make_caster<Types>::name...);
};

template <typename T>
struct handle_type_name<typing::Optional<T>> {
static constexpr auto name
= const_name("typing.Optional[") + make_caster<T>::name + const_name("]");
static constexpr auto name = make_caster<T>::name | make_caster<none>::name;
};

template <typename T>
Expand All @@ -244,14 +241,14 @@ struct handle_type_name<typing::ClassVar<T>> {

template <typename T>
struct handle_type_name<typing::TypeGuard<T>> {
static constexpr auto name
= const_name("typing.TypeGuard[") + make_caster<T>::name + const_name("]");
static constexpr auto name = const_name(PYBIND11_TYPE_GUARD_TYPE_HINT) + const_name("[")
+ make_caster<T>::name + const_name("]");
};

template <typename T>
struct handle_type_name<typing::TypeIs<T>> {
static constexpr auto name
= const_name("typing.TypeIs[") + make_caster<T>::name + const_name("]");
static constexpr auto name = const_name(PYBIND11_TYPE_IS_TYPE_HINT) + const_name("[")
+ make_caster<T>::name + const_name("]");
};

template <>
Expand All @@ -261,7 +258,7 @@ struct handle_type_name<typing::NoReturn> {

template <>
struct handle_type_name<typing::Never> {
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)
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 36 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import sysconfig
import textwrap
import traceback
from typing import Callable

import pytest

Expand Down Expand Up @@ -242,3 +243,38 @@ 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.")
)


@pytest.fixture
def backport_typehints() -> Callable[[SanitizedString], SanitizedString]:
d = {}
if sys.version_info < (3, 13):
d["typing_extensions.TypeIs"] = "typing.TypeIs"
d["typing_extensions.CapsuleType"] = "types.CapsuleType"
if sys.version_info < (3, 12):
d["typing_extensions.Buffer"] = "collections.abc.Buffer"
if sys.version_info < (3, 11):
d["typing_extensions.Never"] = "typing.Never"
if sys.version_info < (3, 10):
d["typing_extensions.TypeGuard"] = "typing.TypeGuard"

def backport(sanatized_string: SanitizedString) -> SanitizedString:
for old, new in d.items():
sanatized_string.string = sanatized_string.string.replace(old, new)

return sanatized_string

return backport
2 changes: 1 addition & 1 deletion tests/pybind11_tests.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ PYBIND11_NAMESPACE_BEGIN(detail)
template <>
class type_caster<RValueCaster> {
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();
}
Expand Down
2 changes: 1 addition & 1 deletion tests/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions tests/pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading
Loading