chiark / gitweb /
documentation/python: inspect pybind type __annotations__ carefully.
authorVladimír Vondruš <mosra@centrum.cz>
Sat, 13 Jul 2019 16:30:26 +0000 (18:30 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Sun, 14 Jul 2019 17:11:08 +0000 (19:11 +0200)
The typing.get_type_annotations() crashes with a KeyError there because
there's no pybind11_builtins module. I don't know and don't want to know
what's happening there.

documentation/python.py
documentation/test_python/pybind_type_links/pybind_type_links.Foo.html
documentation/test_python/pybind_type_links/pybind_type_links.cpp
documentation/test_python/pybind_type_links/pybind_type_links.html
documentation/test_python/test_pybind.py

index a56de157d29a608d847e76e1287bde9ba9ea9aa5..91485b183937aaa193fbebb46c814ff56ea00429 100755 (executable)
@@ -707,7 +707,13 @@ def extract_type(type) -> str:
     # 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:
@@ -945,7 +951,7 @@ def extract_function_doc(state: State, parent, path: List[str], function) -> Lis
         # 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)
@@ -1005,7 +1011,7 @@ def extract_property_doc(state: State, path: List[str], property):
         # 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'])
@@ -1033,7 +1039,7 @@ def extract_data_doc(state: State, parent, path: List[str], data):
     # 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])
index c9ca00ae8e983defbcadcc337d6adb2b9aa17767..baa2bd66c691c85963147ce82ec8ab698e4ffdb0 100644 (file)
@@ -31,6 +31,7 @@
               <ul>
                 <li><a href="#dunder-methods">Special methods</a></li>
                 <li><a href="#properties">Properties</a></li>
+                <li><a href="#data">Data</a></li>
               </ul>
             </li>
           </ul>
             <dd>A property</dd>
           </dl>
         </section>
+        <section id="data">
+          <h2><a href="#data">Data</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <a href="#TYPE_DATA" class="m-doc-self" id="TYPE_DATA">TYPE_DATA</a>: <a href="pybind_type_links.html#Enum" class="m-doc">Enum</a> = Enum.SECOND
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
       </div>
     </div>
   </div>
index 54d0c432e0f8ed22ccd7bfd7cbc726deefdd3f55..f6b3f8e6458a2c96945139a0ede9766547af3148 100644 (file)
@@ -28,7 +28,8 @@ PYBIND11_MODULE(pybind_type_links, m) {
         .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");
 
@@ -36,4 +37,8 @@ PYBIND11_MODULE(pybind_type_links, m) {
         .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;
 }
index 85e6e06e28e96a1be74c4cc76853eaf268757514..3cf47e7a815f12241c69236e70ed8d28b129bcab 100644 (file)
@@ -32,6 +32,7 @@
                 <li><a href="#classes">Classes</a></li>
                 <li><a href="#enums">Enums</a></li>
                 <li><a href="#functions">Functions</a></li>
+                <li><a href="#data">Data</a></li>
               </ul>
             </li>
           </ul>
             <dd>A function returning a type</dd>
           </dl>
         </section>
+        <section id="data">
+          <h2><a href="#data">Data</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <a href="#TYPE_DATA" class="m-doc-self" id="TYPE_DATA">TYPE_DATA</a>: <a href="pybind_type_links.Foo.html" class="m-doc">Foo</a>
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
       </div>
     </div>
   </div>
index 098c9de4075ebaac9d82b716befc253fb6b87c26..b4fd37adeed5cfd02bd6f64999d5a85fd12caf2c 100644 (file)
@@ -239,7 +239,15 @@ class TypeLinks(BaseInspectTestCase):
         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'))