diff --git a/misc/test-stubgenc.sh b/misc/test-stubgenc.sh index 175c912e6712..7da135f0bf16 100755 --- a/misc/test-stubgenc.sh +++ b/misc/test-stubgenc.sh @@ -1,14 +1,19 @@ #!/bin/bash -# This script is expected to be run from root of the mypy repo + +set -e +set -x + +cd "$(dirname $0)/.." # Install dependencies, demo project and mypy python -m pip install -r test-requirements.txt -python -m pip install pybind11-mypy-demo==0.0.1 +python -m pip install ./test-data/pybind11_mypy_demo python -m pip install . # Remove expected stubs and generate new inplace -rm -rf test-data/stubgen/pybind11_mypy_demo -stubgen -p pybind11_mypy_demo -o test-data/stubgen/ +STUBGEN_OUTPUT_FOLDER=./test-data/pybind11_mypy_demo/stubgen +rm -rf $STUBGEN_OUTPUT_FOLDER/* +stubgen -p pybind11_mypy_demo -o $STUBGEN_OUTPUT_FOLDER # Compare generated stubs to expected ones -git diff --exit-code test-data/stubgen/pybind11_mypy_demo +git diff --exit-code $STUBGEN_OUTPUT_FOLDER diff --git a/test-data/pybind11_mypy_demo/pyproject.toml b/test-data/pybind11_mypy_demo/pyproject.toml new file mode 100644 index 000000000000..878abe731b1b --- /dev/null +++ b/test-data/pybind11_mypy_demo/pyproject.toml @@ -0,0 +1,10 @@ +[build-system] +requires = [ + "setuptools>=42", + "wheel", + # Officially supported pybind11 version. This is pinned to guarantee 100% reproducible CI. + # As a result, the version needs to be bumped manually at will. + "pybind11==2.9.2", +] + +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/test-data/pybind11_mypy_demo/setup.py b/test-data/pybind11_mypy_demo/setup.py new file mode 100644 index 000000000000..0da1cfbcef19 --- /dev/null +++ b/test-data/pybind11_mypy_demo/setup.py @@ -0,0 +1,18 @@ +# pybind11 is available at setup time due to pyproject.toml +from pybind11.setup_helpers import Pybind11Extension +from setuptools import setup + +# Documentation: https://pybind11.readthedocs.io/en/stable/compiling.html +ext_modules = [ + Pybind11Extension( + "pybind11_mypy_demo", + ["src/main.cpp"], + cxx_std=17, + ), +] + +setup( + name="pybind11-mypy-demo", + version="0.0.1", + ext_modules=ext_modules, +) diff --git a/test-data/pybind11_mypy_demo/src/main.cpp b/test-data/pybind11_mypy_demo/src/main.cpp new file mode 100644 index 000000000000..5cedef391b2d --- /dev/null +++ b/test-data/pybind11_mypy_demo/src/main.cpp @@ -0,0 +1,170 @@ +/** + * This file contains the pybind11 reference implementation for the stugen tests, + * and was originally inspired by: + * + * https://github.com/sizmailov/pybind11-mypy-demo + * + * Copyright (c) 2016 The Pybind Development Team, All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You are under no obligation whatsoever to provide any bug fixes, patches, or + * upgrades to the features, functionality or performance of the source code + * ("Enhancements") to anyone; however, if you choose to make your Enhancements + * available either publicly, or directly to the author of this software, without + * imposing a separate written license agreement for such Enhancements, then you + * hereby grant the following license: a non-exclusive, royalty-free perpetual + * license to install, use, modify, prepare derivative works, incorporate into + * other computer software, distribute, and sublicense such enhancements or + * derivative works thereof, in binary and source code form. + */ + +#include +#include + +namespace py = pybind11; + +namespace basics { + +int answer() { + return 42; +} + +int sum(int a, int b) { + return a + b; +} + +double midpoint(double left, double right){ + return left + (right - left)/2; +} + +double weighted_midpoint(double left, double right, double alpha=0.5) { + return left + (right - left) * alpha; +} + +struct Point { + + enum class LengthUnit { + mm=0, + pixel, + inch + }; + + enum class AngleUnit { + radian=0, + degree + }; + + Point() : Point(0, 0) {} + Point(double x, double y) : x(x), y(y) {} + + static const Point origin; + static const Point x_axis; + static const Point y_axis; + + static LengthUnit length_unit; + static AngleUnit angle_unit; + + double length() const { + return std::sqrt(x * x + y * y); + } + + double distance_to(double other_x, double other_y) const { + double dx = x - other_x; + double dy = y - other_y; + return std::sqrt(dx*dx + dy*dy); + } + + double distance_to(const Point& other) const { + return distance_to(other.x, other.y); + } + + double x, y; +}; + +const Point Point::origin = Point(0, 0); +const Point Point::x_axis = Point(1, 0); +const Point Point::y_axis = Point(0, 1); + +Point::LengthUnit Point::length_unit = Point::LengthUnit::mm; +Point::AngleUnit Point::angle_unit = Point::AngleUnit::radian; + +} // namespace: basics + +void bind_basics(py::module& basics) { + + using namespace basics; + + // Functions + basics.def("answer", &answer); + basics.def("sum", &sum); + basics.def("midpoint", &midpoint, py::arg("left"), py::arg("right")); + basics.def("weighted_midpoint", weighted_midpoint, py::arg("left"), py::arg("right"), py::arg("alpha")=0.5); + + // Classes + py::class_ pyPoint(basics, "Point"); + py::enum_ pyLengthUnit(pyPoint, "LengthUnit"); + py::enum_ pyAngleUnit(pyPoint, "AngleUnit"); + + pyPoint + .def(py::init<>()) + .def(py::init(), py::arg("x"), py::arg("y")) + .def("distance_to", py::overload_cast(&Point::distance_to, py::const_), py::arg("x"), py::arg("y")) + .def("distance_to", py::overload_cast(&Point::distance_to, py::const_), py::arg("other")) + .def_readwrite("x", &Point::x) + .def_property("y", + [](Point& self){ return self.y; }, + [](Point& self, double value){ self.y = value; } + ) + .def_property_readonly("length", &Point::length) + .def_property_readonly_static("x_axis", [](py::object cls){return Point::x_axis;}) + .def_property_readonly_static("y_axis", [](py::object cls){return Point::y_axis;}) + .def_readwrite_static("length_unit", &Point::length_unit) + .def_property_static("angle_unit", + [](py::object& /*cls*/){ return Point::angle_unit; }, + [](py::object& /*cls*/, Point::AngleUnit value){ Point::angle_unit = value; } + ); + + pyPoint.attr("origin") = Point::origin; + + pyLengthUnit + .value("mm", Point::LengthUnit::mm) + .value("pixel", Point::LengthUnit::pixel) + .value("inch", Point::LengthUnit::inch); + + pyAngleUnit + .value("radian", Point::AngleUnit::radian) + .value("degree", Point::AngleUnit::degree); + + // Module-level attributes + basics.attr("PI") = std::acos(-1); + basics.attr("__version__") = "0.0.1"; +} + +PYBIND11_MODULE(pybind11_mypy_demo, m) { + auto basics = m.def_submodule("basics"); + bind_basics(basics); +} \ No newline at end of file diff --git a/test-data/stubgen/pybind11_mypy_demo/__init__.pyi b/test-data/pybind11_mypy_demo/stubgen/pybind11_mypy_demo/__init__.pyi similarity index 100% rename from test-data/stubgen/pybind11_mypy_demo/__init__.pyi rename to test-data/pybind11_mypy_demo/stubgen/pybind11_mypy_demo/__init__.pyi diff --git a/test-data/stubgen/pybind11_mypy_demo/basics.pyi b/test-data/pybind11_mypy_demo/stubgen/pybind11_mypy_demo/basics.pyi similarity index 95% rename from test-data/stubgen/pybind11_mypy_demo/basics.pyi rename to test-data/pybind11_mypy_demo/stubgen/pybind11_mypy_demo/basics.pyi index 99093fd6087a..226080ac9d57 100644 --- a/test-data/stubgen/pybind11_mypy_demo/basics.pyi +++ b/test-data/pybind11_mypy_demo/stubgen/pybind11_mypy_demo/basics.pyi @@ -18,6 +18,8 @@ class Point: def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... + @property + def value(self) -> int: ... class LengthUnit: __entries: ClassVar[dict] = ... @@ -34,6 +36,8 @@ class Point: def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... + @property + def value(self) -> int: ... angle_unit: ClassVar[Point.AngleUnit] = ... length_unit: ClassVar[Point.LengthUnit] = ... x_axis: ClassVar[Point] = ... # read-only