# classes into account.
return (type.__module__ + '.' if type.__module__ != 'builtins' else '') + type.__qualname__
-def get_type_hints_or_nothing(path: List[str], object):
+def get_type_hints_or_nothing(state: State, path: List[str], object) -> Dict:
+ # Calling get_type_hints on a pybind11 type (from extract_data_doc())
+ # results in KeyError because there's no sys.modules['pybind11_builtins'].
+ # Be pro-active and return an empty dict if that's the case.
+ if state.config['PYBIND11_COMPATIBILITY'] and isinstance(object, type) and 'pybind11_builtins' in [a.__module__ for a in object.__mro__]:
+ return {}
+
try:
return typing.get_type_hints(object)
except Exception as e:
# converted to actual annotations). If that fails (e.g. because a type
# doesn't exist), we'll take the non-dereferenced annotations from
# inspect instead.
- type_hints = get_type_hints_or_nothing(path, function)
+ type_hints = get_type_hints_or_nothing(state, path, function)
try:
signature = inspect.signature(function)
# signature because pybind11 properties would throw TypeError from
# typing.get_type_hints(). This way they throw ValueError from inspect
# and we don't need to handle TypeError in get_type_hints_or_nothing().
- if property.fget: type_hints = get_type_hints_or_nothing(path, property.fget)
+ if property.fget: type_hints = get_type_hints_or_nothing(state, path, property.fget)
if 'return' in type_hints:
out.type = extract_annotation(state, path, type_hints['return'])
# First try to get fully dereferenced type hints (with strings converted to
# actual annotations). If that fails (e.g. because a type doesn't exist),
# we'll take the non-dereferenced annotations instead.
- type_hints = get_type_hints_or_nothing(path, parent)
+ type_hints = get_type_hints_or_nothing(state, path, parent)
if out.name in type_hints:
out.type = extract_annotation(state, path, type_hints[out.name])
.value("FIRST", Enum::First)
.value("SECOND", Enum::Second);
- py::class_<Foo>{m, "Foo", "A class"}
+ py::class_<Foo> foo{m, "Foo", "A class"};
+ foo
.def(py::init<Enum>(), "Constructor")
.def_readwrite("property", &Foo::property, "A property");
.def("type_enum", &typeEnum, "A function taking an enum")
.def("type_return", &typeReturn, "A function returning a type")
.def("type_nested", &typeNested, "A function with nested type annotation");
+
+ /* Test also attributes (annotated from within Python) */
+ m.attr("TYPE_DATA") = Foo{Enum::First};
+ foo.attr("TYPE_DATA") = Enum::Second;
}
super().__init__(__file__, 'type_links', *args, **kwargs)
def test(self):
+ sys.path.append(self.path)
+ import pybind_type_links
+ # Annotate the type of TYPE_DATA (TODO: can this be done from pybind?)
+ pybind_type_links.__annotations__ = {}
+ pybind_type_links.__annotations__['TYPE_DATA'] = pybind_type_links.Foo
+ pybind_type_links.Foo.__annotations__ = {}
+ pybind_type_links.Foo.__annotations__['TYPE_DATA'] = pybind_type_links.Enum
self.run_python({
+ 'INPUT_MODULES': [pybind_type_links],
'PYBIND11_COMPATIBILITY': True
})
self.assertEqual(*self.actual_expected_contents('pybind_type_links.html'))