From 35ded664a4b83a7eef3b68ad8a9f85aa5d4be3bb Mon Sep 17 00:00:00 2001 From: =?utf8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 14 Jul 2019 14:54:36 +0200 Subject: [PATCH] documentation/python: warn when a type link should (but isn't) resolved. Could help with figuring out what types were accidentally omitted from the docs. --- documentation/python.py | 15 +++++- .../inspect_type_links.second.html | 4 ++ .../inspect_type_links/second.py | 6 +++ documentation/test_python/test_pybind.py | 47 ++++++++++--------- 4 files changed, 49 insertions(+), 23 deletions(-) diff --git a/documentation/python.py b/documentation/python.py index ef94bf71..84df56e6 100755 --- a/documentation/python.py +++ b/documentation/python.py @@ -501,8 +501,19 @@ def make_name_link(state: State, referrer_path: List[str], type) -> str: if type is None: return None assert isinstance(type, str) - # Not found, return as-is - if not type in state.name_map: return type + # Not found, return as-is. However, if the prefix is one of the + # INPUT_MODULES, emit a warning to notify the user of a potentially missing + # stuff from the docs. + if not type in state.name_map: + for module in state.config['INPUT_MODULES']: + if isinstance(module, str): + module_name = module + else: + module_name = module.__name__ + if type.startswith(module_name + '.'): + logging.warning("could not resolve a link to %s which is among INPUT_MODULES (referred from %s), possibly hidden/undocumented?", type, '.'.join(referrer_path)) + break + return type entry = state.name_map[type] diff --git a/documentation/test_python/inspect_type_links/inspect_type_links.second.html b/documentation/test_python/inspect_type_links/inspect_type_links.second.html index 2e2b6b2f..cd07de9e 100644 --- a/documentation/test_python/inspect_type_links/inspect_type_links.second.html +++ b/documentation/test_python/inspect_type_links/inspect_type_links.second.html @@ -57,6 +57,10 @@

Functions

+
+ def type_cant_link(a: inspect_type_links.second._Hidden) +
+
Annotation linking to a type that's a part of INPUT_MODULES but not known
def type_default_values(a: Enum = Enum.SECOND, b: typing.Tuple[Foo] = (<class 'inspect_type_links.second.Foo'>,), diff --git a/documentation/test_python/inspect_type_links/inspect_type_links/second.py b/documentation/test_python/inspect_type_links/inspect_type_links/second.py index b77762cc..fbcad3e5 100644 --- a/documentation/test_python/inspect_type_links/inspect_type_links/second.py +++ b/documentation/test_python/inspect_type_links/inspect_type_links/second.py @@ -71,6 +71,12 @@ def type_return_string_nested() -> 'Tuple[Foo, List[Enum], Any]': def type_return_string_invalid(a: Foo) -> 'FooBar': """A function with invalid return string type annotation""" +class _Hidden: + pass + +def type_cant_link(a: _Hidden): + """Annotation linking to a type that's a part of INPUT_MODULES but not known""" + def type_default_values(a: Enum = Enum.SECOND, b: Tuple[Foo] = (Foo, ), c: Foo = Foo()): """A function with default values, one enum, one tuple and the third nonrepresentable (yes, the tuple looks ugly)""" diff --git a/documentation/test_python/test_pybind.py b/documentation/test_python/test_pybind.py index bb7a4da6..e2752694 100644 --- a/documentation/test_python/test_pybind.py +++ b/documentation/test_python/test_pybind.py @@ -22,16 +22,21 @@ # DEALINGS IN THE SOFTWARE. # +import copy import sys import unittest -from python import State, parse_pybind_signature +from python import State, parse_pybind_signature, default_config from . import BaseInspectTestCase class Signature(unittest.TestCase): + # make_type_link() needs state.config['INPUT_MODULES'], simply supply + # everything there + state = State(copy.deepcopy(default_config)) + def test(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: int, a2: module.Thing) -> module.Thing3'), ('foo', '', [ ('a', 'int', 'int', None), @@ -39,7 +44,7 @@ class Signature(unittest.TestCase): ], 'module.Thing3')) def test_newline(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: int, a2: module.Thing) -> module.Thing3\n'), ('foo', '', [ ('a', 'int', 'int', None), @@ -47,7 +52,7 @@ class Signature(unittest.TestCase): ], 'module.Thing3')) def test_docs(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: int, a2: module.Thing) -> module.Thing3\n\nDocs here!!'), ('foo', 'Docs here!!', [ ('a', 'int', 'int', None), @@ -55,19 +60,19 @@ class Signature(unittest.TestCase): ], 'module.Thing3')) def test_no_args(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'thingy() -> str'), ('thingy', '', [], 'str')) def test_no_return(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], '__init__(self: module.Thing)'), ('__init__', '', [ ('self', 'module.Thing', 'module.Thing', None), ], None)) def test_no_arg_types(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'thingy(self, the_other_thing)'), ('thingy', '', [ ('self', None, None, None), @@ -75,7 +80,7 @@ class Signature(unittest.TestCase): ], None)) def test_square_brackets(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: Tuple[int, str], no_really: str) -> List[str]'), ('foo', '', [ ('a', 'Tuple[int, str]', 'Tuple[int, str]', None), @@ -83,7 +88,7 @@ class Signature(unittest.TestCase): ], 'List[str]')) def test_nested_square_brackets(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: Tuple[int, List[Tuple[int, int]]], another: float) -> Union[str, Any]'), ('foo', '', [ ('a', 'Tuple[int, List[Tuple[int, int]]]', 'Tuple[int, List[Tuple[int, int]]]', None), @@ -91,7 +96,7 @@ class Signature(unittest.TestCase): ], 'Union[str, Any]')) def test_callable(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: Callable[[int, Tuple[int, int]], float], another: float)'), ('foo', '', [ ('a', 'Callable[[int, Tuple[int, int]], float]', 'Callable[[int, Tuple[int, int]], float]', None), @@ -99,7 +104,7 @@ class Signature(unittest.TestCase): ], None)) def test_kwargs(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(*args, **kwargs)'), ('foo', '', [ ('*args', None, None, None), @@ -109,7 +114,7 @@ class Signature(unittest.TestCase): # https://github.com/pybind/pybind11/commit/0826b3c10607c8d96e1d89dc819c33af3799a7b8, # released in 2.3.0. We want to support both, so test both. def test_default_values_pybind22(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float=1.0, b: str=\'hello\')'), ('foo', '', [ ('a', 'float', 'float', '1.0'), @@ -117,7 +122,7 @@ class Signature(unittest.TestCase): ], None)) def test_default_values_pybind23(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float = 1.0, b: str = \'hello\')'), ('foo', '', [ ('a', 'float', 'float', '1.0'), @@ -125,42 +130,42 @@ class Signature(unittest.TestCase): ], None)) def test_crazy_stuff(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: int, b: Math::Vector<4, UnsignedInt>)'), ('foo', '', [('…', None, None, None)], None)) def test_crazy_stuff_nested(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: int, b: List[Math::Vector<4, UnsignedInt>])'), ('foo', '', [('…', None, None, None)], None)) def test_crazy_stuff_docs(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: int, b: Math::Vector<4, UnsignedInt>)\n\nThis is text!!'), ('foo', 'This is text!!', [('…', None, None, None)], None)) def test_crazy_return(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: int) -> Math::Vector<4, UnsignedInt>'), ('foo', '', [('…', None, None, None)], None)) def test_crazy_return_nested(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: int) -> List[Math::Vector<4, UnsignedInt>]'), ('foo', '', [('…', None, None, None)], None)) def test_crazy_return_docs(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: int) -> Math::Vector<4, UnsignedInt>\n\nThis returns!'), ('foo', 'This returns!', [('…', None, None, None)], None)) def test_no_name(self): - self.assertEqual(parse_pybind_signature(State({}), [], + self.assertEqual(parse_pybind_signature(self.state, [], '(arg0: MyClass) -> float'), ('', '', [('arg0', 'MyClass', 'MyClass', None)], 'float')) def test_module_mapping(self): - state = State({}) + state = self.state state.module_mapping['module._module'] = 'module' self.assertEqual(parse_pybind_signature(state, [], -- 2.30.2