From bb15d87742c0855743ae5825f95ca4a1f2b518b4 Mon Sep 17 00:00:00 2001 From: Marcel Weiler Date: Tue, 21 Mar 2023 15:04:40 +0100 Subject: [PATCH 01/11] Add test for static methods of pybind11 class --- test-data/pybind11_mypy_demo/src/main.cpp | 15 +++++++++++++++ .../stubgen/pybind11_mypy_demo/basics.pyi | 11 +++++++++++ 2 files changed, 26 insertions(+) diff --git a/test-data/pybind11_mypy_demo/src/main.cpp b/test-data/pybind11_mypy_demo/src/main.cpp index 00e5b2f4e871..a7f6577da033 100644 --- a/test-data/pybind11_mypy_demo/src/main.cpp +++ b/test-data/pybind11_mypy_demo/src/main.cpp @@ -112,6 +112,13 @@ const Point Point::y_axis = Point(0, 1); Point::LengthUnit Point::length_unit = Point::LengthUnit::mm; Point::AngleUnit Point::angle_unit = Point::AngleUnit::radian; +struct Foo +{ + static int some_static_method(int a, int b) { return a * 42 + b; } + static int overloaded_static_method(int value) { return value * 42; } + static double overloaded_static_method(double value) { return value * 42; } +}; + } // namespace: basics void bind_basics(py::module& basics) { @@ -159,6 +166,14 @@ void bind_basics(py::module& basics) { .value("radian", Point::AngleUnit::radian) .value("degree", Point::AngleUnit::degree); + // Static methods + py::class_ pyFoo(basics, "Foo"); + + pyFoo + .def_static("some_static_method", &Foo::some_static_method, R"#(None)#", py::arg("a"), py::arg("b")) + .def_static("overloaded_static_method", py::overload_cast(&Foo::overloaded_static_method), py::arg("value")) + .def_static("overloaded_static_method", py::overload_cast(&Foo::overloaded_static_method), py::arg("value")); + // Module-level attributes basics.attr("PI") = std::acos(-1); basics.attr("__version__") = "0.0.1"; diff --git a/test-data/pybind11_mypy_demo/stubgen/pybind11_mypy_demo/basics.pyi b/test-data/pybind11_mypy_demo/stubgen/pybind11_mypy_demo/basics.pyi index 6527f5733eaf..8e6eb4d0618a 100644 --- a/test-data/pybind11_mypy_demo/stubgen/pybind11_mypy_demo/basics.pyi +++ b/test-data/pybind11_mypy_demo/stubgen/pybind11_mypy_demo/basics.pyi @@ -3,6 +3,17 @@ from typing import ClassVar, overload PI: float __version__: str +class Foo: + def __init__(self, *args, **kwargs) -> None: ... + @overload + @staticmethod + def overloaded_static_method(value: int) -> int: ... + @overload + @staticmethod + def overloaded_static_method(value: float) -> float: ... + @staticmethod + def some_static_method(a: int, b: int) -> int: ... + class Point: class AngleUnit: __members__: ClassVar[dict] = ... # read-only From feebc700a9efba6bec96836b8cd5eb068ca060cf Mon Sep 17 00:00:00 2001 From: Marcel Weiler Date: Tue, 7 Nov 2023 15:35:07 +0100 Subject: [PATCH 02/11] Fix is_staticmethod for c modules --- mypy/stubgen.py | 1 + mypy/stubgenc.py | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/mypy/stubgen.py b/mypy/stubgen.py index 837cd723c410..99fbd7fdd802 100755 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -1629,6 +1629,7 @@ def generate_stubs(options: Options) -> None: doc_dir=options.doc_dir, include_private=options.include_private, export_less=options.export_less, + include_docstrings=options.include_docstrings, ) num_modules = len(all_modules) if not options.quiet and num_modules > 0: diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index 0ad79a4265b3..ae29145af453 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -509,7 +509,9 @@ def is_classmethod(self, class_info: ClassInfo, name: str, obj: object) -> bool: def is_staticmethod(self, class_info: ClassInfo | None, name: str, obj: object) -> bool: if self.is_c_module: - return False + raw_lookup = getattr(class_info.cls, "__dict__") # noqa: B009 + raw_value = raw_lookup.get(name, obj) + return type(raw_value).__name__ in ("staticmethod") else: return class_info is not None and isinstance( inspect.getattr_static(class_info.cls, name), staticmethod @@ -751,7 +753,9 @@ def generate_class_stub(self, class_name: str, cls: type, output: list[str]) -> continue attr = "__init__" # FIXME: make this nicer - if self.is_classmethod(class_info, attr, value): + if self.is_staticmethod(class_info, attr, value): + class_info.self_var = "" + elif self.is_classmethod(class_info, attr, value): class_info.self_var = "cls" else: class_info.self_var = "self" From a634deeb1da59fcc40b77d90580e0eb43b71578e Mon Sep 17 00:00:00 2001 From: Marcel Weiler Date: Tue, 7 Nov 2023 15:49:42 +0100 Subject: [PATCH 03/11] Updated stub files for stubgen test --- .../pybind11_mypy_demo/__init__.pyi | 1 + .../pybind11_mypy_demo/basics.pyi | 58 +++++++++++++------ 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/test-data/pybind11_mypy_demo/stubgen-include-docs/pybind11_mypy_demo/__init__.pyi b/test-data/pybind11_mypy_demo/stubgen-include-docs/pybind11_mypy_demo/__init__.pyi index e69de29bb2d1..0cb252f00259 100644 --- a/test-data/pybind11_mypy_demo/stubgen-include-docs/pybind11_mypy_demo/__init__.pyi +++ b/test-data/pybind11_mypy_demo/stubgen-include-docs/pybind11_mypy_demo/__init__.pyi @@ -0,0 +1 @@ +from . import basics as basics diff --git a/test-data/pybind11_mypy_demo/stubgen-include-docs/pybind11_mypy_demo/basics.pyi b/test-data/pybind11_mypy_demo/stubgen-include-docs/pybind11_mypy_demo/basics.pyi index 676d7f6d3f15..32f6c3953ac4 100644 --- a/test-data/pybind11_mypy_demo/stubgen-include-docs/pybind11_mypy_demo/basics.pyi +++ b/test-data/pybind11_mypy_demo/stubgen-include-docs/pybind11_mypy_demo/basics.pyi @@ -1,7 +1,34 @@ -from typing import ClassVar +from typing import ClassVar, overload -from typing import overload PI: float +__version__: str + +class Foo: + def __init__(self, *args, **kwargs) -> None: + """Initialize self. See help(type(self)) for accurate signature.""" + @overload + @staticmethod + def overloaded_static_method(value: int) -> int: + """overloaded_static_method(*args, **kwargs) + Overloaded function. + + 1. overloaded_static_method(value: int) -> int + + 2. overloaded_static_method(value: float) -> float""" + @overload + @staticmethod + def overloaded_static_method(value: float) -> float: + """overloaded_static_method(*args, **kwargs) + Overloaded function. + + 1. overloaded_static_method(value: int) -> int + + 2. overloaded_static_method(value: float) -> float""" + @staticmethod + def some_static_method(a: int, b: int) -> int: + """some_static_method(a: int, b: int) -> int + + None""" class Point: class AngleUnit: @@ -13,8 +40,6 @@ class Point: """__init__(self: pybind11_mypy_demo.basics.Point.AngleUnit, value: int) -> None""" def __eq__(self, other: object) -> bool: """__eq__(self: object, other: object) -> bool""" - def __getstate__(self) -> int: - """__getstate__(self: object) -> int""" def __hash__(self) -> int: """__hash__(self: object) -> int""" def __index__(self) -> int: @@ -23,8 +48,6 @@ class Point: """__int__(self: pybind11_mypy_demo.basics.Point.AngleUnit) -> int""" def __ne__(self, other: object) -> bool: """__ne__(self: object, other: object) -> bool""" - def __setstate__(self, state: int) -> None: - """__setstate__(self: pybind11_mypy_demo.basics.Point.AngleUnit, state: int) -> None""" @property def name(self) -> str: ... @property @@ -40,8 +63,6 @@ class Point: """__init__(self: pybind11_mypy_demo.basics.Point.LengthUnit, value: int) -> None""" def __eq__(self, other: object) -> bool: """__eq__(self: object, other: object) -> bool""" - def __getstate__(self) -> int: - """__getstate__(self: object) -> int""" def __hash__(self) -> int: """__hash__(self: object) -> int""" def __index__(self) -> int: @@ -50,8 +71,6 @@ class Point: """__int__(self: pybind11_mypy_demo.basics.Point.LengthUnit) -> int""" def __ne__(self, other: object) -> bool: """__ne__(self: object, other: object) -> bool""" - def __setstate__(self, state: int) -> None: - """__setstate__(self: pybind11_mypy_demo.basics.Point.LengthUnit, state: int) -> None""" @property def name(self) -> str: ... @property @@ -70,7 +89,8 @@ class Point: 1. __init__(self: pybind11_mypy_demo.basics.Point) -> None - 2. __init__(self: pybind11_mypy_demo.basics.Point, x: float, y: float) -> None""" + 2. __init__(self: pybind11_mypy_demo.basics.Point, x: float, y: float) -> None + """ @overload def __init__(self, x: float, y: float) -> None: """__init__(*args, **kwargs) @@ -78,7 +98,8 @@ class Point: 1. __init__(self: pybind11_mypy_demo.basics.Point) -> None - 2. __init__(self: pybind11_mypy_demo.basics.Point, x: float, y: float) -> None""" + 2. __init__(self: pybind11_mypy_demo.basics.Point, x: float, y: float) -> None + """ @overload def distance_to(self, x: float, y: float) -> float: """distance_to(*args, **kwargs) @@ -86,7 +107,8 @@ class Point: 1. distance_to(self: pybind11_mypy_demo.basics.Point, x: float, y: float) -> float - 2. distance_to(self: pybind11_mypy_demo.basics.Point, other: pybind11_mypy_demo.basics.Point) -> float""" + 2. distance_to(self: pybind11_mypy_demo.basics.Point, other: pybind11_mypy_demo.basics.Point) -> float + """ @overload def distance_to(self, other: Point) -> float: """distance_to(*args, **kwargs) @@ -94,19 +116,19 @@ class Point: 1. distance_to(self: pybind11_mypy_demo.basics.Point, x: float, y: float) -> float - 2. distance_to(self: pybind11_mypy_demo.basics.Point, other: pybind11_mypy_demo.basics.Point) -> float""" + 2. distance_to(self: pybind11_mypy_demo.basics.Point, other: pybind11_mypy_demo.basics.Point) -> float + """ @property def length(self) -> float: ... def answer() -> int: - '''answer() -> int + """answer() -> int""" - answer docstring, with end quote"''' def midpoint(left: float, right: float) -> float: """midpoint(left: float, right: float) -> float""" + def sum(arg0: int, arg1: int) -> int: - '''sum(arg0: int, arg1: int) -> int + """sum(arg0: int, arg1: int) -> int""" - multiline docstring test, edge case quotes """\'\'\'''' def weighted_midpoint(left: float, right: float, alpha: float = ...) -> float: """weighted_midpoint(left: float, right: float, alpha: float = 0.5) -> float""" From cae94e66261ef782232b5621dd4018d01bd4a1f5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:53:16 +0000 Subject: [PATCH 04/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- test-data/pybind11_mypy_demo/src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/pybind11_mypy_demo/src/main.cpp b/test-data/pybind11_mypy_demo/src/main.cpp index a7f6577da033..5169ff301811 100644 --- a/test-data/pybind11_mypy_demo/src/main.cpp +++ b/test-data/pybind11_mypy_demo/src/main.cpp @@ -168,7 +168,7 @@ void bind_basics(py::module& basics) { // Static methods py::class_ pyFoo(basics, "Foo"); - + pyFoo .def_static("some_static_method", &Foo::some_static_method, R"#(None)#", py::arg("a"), py::arg("b")) .def_static("overloaded_static_method", py::overload_cast(&Foo::overloaded_static_method), py::arg("value")) From 1aacfcc7bd3798b936f640de150026c315d0d9d0 Mon Sep 17 00:00:00 2001 From: Marcel Weiler Date: Tue, 7 Nov 2023 15:59:44 +0100 Subject: [PATCH 05/11] Fix failing type check --- mypy/stubgenc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index ae29145af453..ee2532a9a562 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -508,14 +508,14 @@ def is_classmethod(self, class_info: ClassInfo, name: str, obj: object) -> bool: return inspect.ismethod(obj) def is_staticmethod(self, class_info: ClassInfo | None, name: str, obj: object) -> bool: - if self.is_c_module: + if class_info is None: + return False + elif self.is_c_module: raw_lookup = getattr(class_info.cls, "__dict__") # noqa: B009 raw_value = raw_lookup.get(name, obj) return type(raw_value).__name__ in ("staticmethod") else: - return class_info is not None and isinstance( - inspect.getattr_static(class_info.cls, name), staticmethod - ) + return isinstance(inspect.getattr_static(class_info.cls, name), staticmethod) @staticmethod def is_abstract_method(obj: object) -> bool: From 8142772abe2484e8496dce18cc6db6256bc690c7 Mon Sep 17 00:00:00 2001 From: Marcel Weiler Date: Tue, 7 Nov 2023 16:38:58 +0100 Subject: [PATCH 06/11] dummy From bc42c574975fc261fca3c23eec9604c0e711da16 Mon Sep 17 00:00:00 2001 From: Marcel Weiler Date: Wed, 8 Nov 2023 10:11:32 +0100 Subject: [PATCH 07/11] dummy to rerun unstable macos tests From 8121e3c393ecd89e1d0e1ae98170bc2c46f7f1c3 Mon Sep 17 00:00:00 2001 From: Marcel Weiler Date: Thu, 9 Nov 2023 12:36:45 +0100 Subject: [PATCH 08/11] Simplify is_staticmethod check --- mypy/stubgenc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index ee2532a9a562..4169ab4a0bf6 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -513,7 +513,7 @@ def is_staticmethod(self, class_info: ClassInfo | None, name: str, obj: object) elif self.is_c_module: raw_lookup = getattr(class_info.cls, "__dict__") # noqa: B009 raw_value = raw_lookup.get(name, obj) - return type(raw_value).__name__ in ("staticmethod") + return type(raw_value).__name__ == "staticmethod" else: return isinstance(inspect.getattr_static(class_info.cls, name), staticmethod) From 2504078b9255e457767b6d8748abceef5246e8cb Mon Sep 17 00:00:00 2001 From: Marcel Weiler Date: Wed, 15 Nov 2023 13:56:31 +0100 Subject: [PATCH 09/11] Add type hint for mapping proxies --- mypy/stubgenc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index 4169ab4a0bf6..7f1710ddac3f 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -511,7 +511,7 @@ def is_staticmethod(self, class_info: ClassInfo | None, name: str, obj: object) if class_info is None: return False elif self.is_c_module: - raw_lookup = getattr(class_info.cls, "__dict__") # noqa: B009 + raw_lookup: Mapping[str, Any] = getattr(class_info.cls, "__dict__") # noqa: B009 raw_value = raw_lookup.get(name, obj) return type(raw_value).__name__ == "staticmethod" else: @@ -721,7 +721,7 @@ def generate_class_stub(self, class_name: str, cls: type, output: list[str]) -> The result lines will be appended to 'output'. If necessary, any required names will be added to 'imports'. """ - raw_lookup = getattr(cls, "__dict__") # noqa: B009 + raw_lookup: Mapping[str, Any] = getattr(cls, "__dict__") # noqa: B009 items = self.get_members(cls) if self.resort_members: items = sorted(items, key=lambda x: method_name_sort_key(x[0])) From e6f61ecfbd89acee8eb4c8ef56d52a9fb517788e Mon Sep 17 00:00:00 2001 From: Marcel Weiler Date: Tue, 5 Dec 2023 10:20:00 +0100 Subject: [PATCH 10/11] Fix stubgenc docstring test --- .../pybind11_mypy_demo/basics.pyi | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test-data/pybind11_mypy_demo/stubgen-include-docs/pybind11_mypy_demo/basics.pyi b/test-data/pybind11_mypy_demo/stubgen-include-docs/pybind11_mypy_demo/basics.pyi index f7bd7c94ffab..3047da622a3e 100644 --- a/test-data/pybind11_mypy_demo/stubgen-include-docs/pybind11_mypy_demo/basics.pyi +++ b/test-data/pybind11_mypy_demo/stubgen-include-docs/pybind11_mypy_demo/basics.pyi @@ -14,7 +14,8 @@ class Foo: 1. overloaded_static_method(value: int) -> int - 2. overloaded_static_method(value: float) -> float""" + 2. overloaded_static_method(value: float) -> float + """ @overload @staticmethod def overloaded_static_method(value: float) -> float: @@ -23,12 +24,14 @@ class Foo: 1. overloaded_static_method(value: int) -> int - 2. overloaded_static_method(value: float) -> float""" + 2. overloaded_static_method(value: float) -> float + """ @staticmethod def some_static_method(a: int, b: int) -> int: """some_static_method(a: int, b: int) -> int - None""" + None + """ class Point: class AngleUnit: @@ -124,12 +127,14 @@ class Point: def length(self) -> float: ... def answer() -> int: - """answer() -> int""" + '''answer() -> int answer docstring, with end quote" ''' def midpoint(left: float, right: float) -> float: """midpoint(left: float, right: float) -> float""" +def sum(arg0: int, arg1: int) -> int: + '''sum(arg0: int, arg1: int) -> int multiline docstring test, edge case quotes """\'\'\' ''' From 14fd95c8cc78df421e91e1144da7381177eefa96 Mon Sep 17 00:00:00 2001 From: Marcel Weiler Date: Mon, 8 Jan 2024 08:46:57 +0100 Subject: [PATCH 11/11] Replace string comparison by isinstance --- mypy/stubgenc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index c70dcddec370..a45e2d6c1a5a 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -535,7 +535,7 @@ def is_staticmethod(self, class_info: ClassInfo | None, name: str, obj: object) elif self.is_c_module: raw_lookup: Mapping[str, Any] = getattr(class_info.cls, "__dict__") # noqa: B009 raw_value = raw_lookup.get(name, obj) - return type(raw_value).__name__ == "staticmethod" + return isinstance(raw_value, staticmethod) else: return isinstance(inspect.getattr_static(class_info.cls, name), staticmethod)