From f3e22f64aeb3f3a3b9c67a9ae36646f1a9ecc73f Mon Sep 17 00:00:00 2001 From: =?utf8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 6 Sep 2019 02:07:51 +0200 Subject: [PATCH] documentation/python: pull additional stuff from __annotations__. --- doc/documentation/python.rst | 13 +- documentation/python.py | 26 ++++ .../inspect_attrs/inspect_attrs.MyClass.html | 8 +- .../inspect_string/inspect_string.Foo.html | 4 + .../inspect_string/inspect_string/__init__.py | 5 + .../test_python/inspect_underscored/docs.rst | 5 + .../inspect_underscored.Class.html | 9 ++ .../inspect_underscored/__init__.py | 8 ++ .../test_python/search/search/__init__.py | 2 + documentation/test_python/test_search.py | 134 +++++++++--------- 10 files changed, 144 insertions(+), 70 deletions(-) diff --git a/doc/documentation/python.rst b/doc/documentation/python.rst index c13a05ec..d7b59707 100644 --- a/doc/documentation/python.rst +++ b/doc/documentation/python.rst @@ -429,6 +429,15 @@ By default, if a module contains the :py:`__all__` attribute, *all* names listed there are exposed in the documentation. Otherwise, all module (and class) members are extracted using :py:`inspect.getmembers()`, skipping names :py:`import`\ ed from elsewhere and undocumented underscored names. +Additionally, class data members with type annotations (but with no values) are +pulled out from :py:`__annotations__`, allowing you to expose (and document) +also fields that might otherwise only be populated from :py:`__init__()`: + +.. code:: py + + class MyClass: + a_float: float + string_value: str Detecting if a module is a submodule of the current package or if it's :py:`import`\ ed from elsewhere is tricky, the script thus includes only @@ -509,7 +518,9 @@ Using just docstrings, however, comes with a few limitations: output contains everything, including undocumented names - Instance variables added inside :py:`__init__()` are not extracted, as this would require parsing Python code directly (which is what Sphinx has to do - to support these). + to support these). You can work around this by adding annotated + "declarations" to the class as `shown above <#module-inspection>`_, however + no docstrings can be specified for those either. To overcome the limitations, `externally-supplied documentation <#external-documentation-content>`_ provides means to document names that can't have a docstring attached, and diff --git a/documentation/python.py b/documentation/python.py index 7ffeda0d..fe77bb95 100755 --- a/documentation/python.py +++ b/documentation/python.py @@ -439,6 +439,30 @@ def crawl_class(state: State, path: List[str], class_): class_entry.members += [name] + # Data that don't have values but just type annotations are hidden here. + # inspect.getmembers() ignores those probably because trying to access any + # of them results in an AttributeError. + if hasattr(class_, '__annotations__'): + for name, type in class_.__annotations__.items(): + subpath = path + [name] + + # No docstrings (the best we could get would be a docstring of the + # variable type, nope to that) + if is_underscored_and_undocumented(state, EntryType.DATA, subpath, None): continue + + # If this name is known already, skip it -- here we don't have + # anything except name and type, data inspected the classic way + # have at least an object to point to (and a value) + if name in class_entry.members: continue + + entry = Empty() + entry.type = EntryType.DATA + entry.object = None # TODO will this break things? + entry.path = subpath + entry.url = '{}#{}'.format(class_entry.url, state.config['ID_FORMATTER'](EntryType.DATA, subpath[-1:])) + state.name_map['.'.join(subpath)] = entry + class_entry.members += [name] + # If attrs compatibility is enabled, look for more properties in hidden # places. if state.config['ATTRS_COMPATIBILITY'] and hasattr(class_, '__attrs_attrs__'): @@ -645,6 +669,8 @@ def crawl_module(state: State, path: List[str], module) -> List[Tuple[List[str], module_entry.members += [name] + # TODO: expose what's in __attributes__ as well (interaction with __all__?) + # Add itself to the name map state.name_map['.'.join(path)] = module_entry diff --git a/documentation/test_python/inspect_attrs/inspect_attrs.MyClass.html b/documentation/test_python/inspect_attrs/inspect_attrs.MyClass.html index 28c14bda..dc298f8d 100644 --- a/documentation/test_python/inspect_attrs/inspect_attrs.MyClass.html +++ b/documentation/test_python/inspect_attrs/inspect_attrs.MyClass.html @@ -57,14 +57,14 @@ annotated: float get set del
-
- unannotated get set del -
-
External docs for this property
complex_annotation: typing.List[typing.Tuple[int, float]] get set del
+
+ unannotated get set del +
+
External docs for this property
complex_annotation_in_attr: typing.List[typing.Tuple[int, float]] get set del
diff --git a/documentation/test_python/inspect_string/inspect_string.Foo.html b/documentation/test_python/inspect_string/inspect_string.Foo.html index d3abbd90..17da2734 100644 --- a/documentation/test_python/inspect_string/inspect_string.Foo.html +++ b/documentation/test_python/inspect_string/inspect_string.Foo.html @@ -130,6 +130,10 @@ A_DATA = 'BOO'
+
+ DATA_DECLARATION: int = None +
+
diff --git a/documentation/test_python/inspect_string/inspect_string/__init__.py b/documentation/test_python/inspect_string/inspect_string/__init__.py index 67751326..68ce6ac9 100644 --- a/documentation/test_python/inspect_string/inspect_string/__init__.py +++ b/documentation/test_python/inspect_string/inspect_string/__init__.py @@ -22,6 +22,8 @@ class Foo: A_DATA = 'BOO' + DATA_DECLARATION: int + class InnerEnum(enum.Enum): """Inner enum""" @@ -129,6 +131,9 @@ def function(): A_CONSTANT = 3.24 +# TODO: not visible ATM, need to figure out interaction with __all__ +DATA_DECLARATION: int + ENUM_THING = MyEnum.YAY # These should have their value shown in the docs as well, even though they are diff --git a/documentation/test_python/inspect_underscored/docs.rst b/documentation/test_python/inspect_underscored/docs.rst index 9f0872b5..5f202344 100644 --- a/documentation/test_python/inspect_underscored/docs.rst +++ b/documentation/test_python/inspect_underscored/docs.rst @@ -22,6 +22,8 @@ underscored property :data _DATA_EXTERNAL_IN_CLASS: Externally in-class documented underscored data + :data _DATA_DECLARATION_EXTERNAL_IN_CLASS: Externally in-class documented + underscored data declaration .. py:function:: inspect_underscored.Class._function_external :summary: Externally documented underscored function @@ -31,3 +33,6 @@ .. py:data:: inspect_underscored.Class._DATA_EXTERNAL :summary: Externally documented underscored data + +.. py:data:: inspect_underscored.Class._DATA_DECLARATION_EXTERNAL + :summary: Externally documented underscored data declaration diff --git a/documentation/test_python/inspect_underscored/inspect_underscored.Class.html b/documentation/test_python/inspect_underscored/inspect_underscored.Class.html index b413fc88..f5cb470c 100644 --- a/documentation/test_python/inspect_underscored/inspect_underscored.Class.html +++ b/documentation/test_python/inspect_underscored/inspect_underscored.Class.html @@ -78,6 +78,15 @@ underscored property
Externally in-class documented underscored data
+
+ _DATA_DECLARATION_EXTERNAL: float = None +
+
Externally documented underscored data declaration
+
+ _DATA_DECLARATION_EXTERNAL_IN_CLASS: float = None +
+
Externally in-class documented +underscored data declaration
diff --git a/documentation/test_python/inspect_underscored/inspect_underscored/__init__.py b/documentation/test_python/inspect_underscored/inspect_underscored/__init__.py index 49ca4113..f7e3e269 100644 --- a/documentation/test_python/inspect_underscored/inspect_underscored/__init__.py +++ b/documentation/test_python/inspect_underscored/inspect_underscored/__init__.py @@ -44,6 +44,9 @@ class Class: :data _DATA_IN_CLASS: In-class documented underscored data. This won't be picked up by the initial crawl, unfortunately, as the docstrings are processed much later. + :data _DATA_DECLARATION_IN_CLASS: In-class documented underscored data. + This won't be picked up by the initial crawl, unfortunately, as the + docstrings are processed much later. """ def _function(self): @@ -73,3 +76,8 @@ class Class: _DATA_EXTERNAL: int = 5 _DATA_EXTERNAL_IN_CLASS: int = 6 _DATA_UNDOCUMENTED: int = 7 + + _DATA_DECLARATION_IN_CLASS: float + _DATA_DECLARATION_EXTERNAL: float + _DATA_DECLARATION_EXTERNAL_IN_CLASS: float + _DATA_DECLARATION_UNDOCUMENTED: float diff --git a/documentation/test_python/search/search/__init__.py b/documentation/test_python/search/search/__init__.py index 4f4c1933..990a9bbd 100644 --- a/documentation/test_python/search/search/__init__.py +++ b/documentation/test_python/search/search/__init__.py @@ -14,6 +14,8 @@ class Foo: A_VALUE = 1 ANOTHER = 2 + DATA_DECLARATION: int + def a_function(): pass diff --git a/documentation/test_python/test_search.py b/documentation/test_python/test_search.py index 8100f65f..eae9ef2c 100644 --- a/documentation/test_python/test_search.py +++ b/documentation/test_python/test_search.py @@ -43,12 +43,12 @@ class Search(BaseInspectTestCase): serialized = f.read() search_data_pretty = pretty_print(serialized, entryTypeClass=EntryType)[0] #print(search_data_pretty) - self.assertEqual(len(serialized), 1910) + self.assertEqual(len(serialized), 2088) self.assertEqual(search_data_pretty, """ -18 symbols -search [11] +19 symbols +search [12] || .$ -|| foo [6] +|| foo [7] || || .$ || || enum [0] || || | .$ @@ -58,32 +58,33 @@ search [11] || || | | ($ || || | | ) [4] || || | property [5] -|| |unc_with_params [9] +|| || data_declaration [6] +|| |unc_with_params [10] || || ($ -|| || ) [10] -|| a_function [7] +|| || ) [11] +|| a_function [8] || | ($ -|| | ) [8] -|| pybind [23] +|| | ) [9] +|| pybind [24] || | .$ -|| | foo [18] +|| | foo [19] || | | .$ -|| | | overloaded_method [14, 16, 12] +|| | | overloaded_method [15, 17, 13] || | | ($ -|| | | ) [15, 17, 13] -|| | unction [19] +|| | | ) [16, 18, 14] +|| | unction [20] || | | ($ -|| | | ) [20] -|| | | _with_params [21] +|| | | ) [21] +|| | | _with_params [22] || | | | ($ -|| | | | ) [22] -|| sub [25] +|| | | | ) [23] +|| sub [26] || | .$ -|| | data_in_a_submodule [24] -|ub [25] +|| | data_in_a_submodule [25] +|ub [26] || .$ -|| data_in_a_submodule [24] -foo [6, 18] +|| data_in_a_submodule [25] +foo [7, 19] || .$ || enum [0] || | .$ @@ -93,18 +94,19 @@ foo [6, 18] || | | ($ || | | ) [4] || | property [5] -|| overloaded_method [14, 16, 12] +|| data_declaration [6] +|| overloaded_method [15, 17, 13] || | ($ -|| | ) [15, 17, 13] -|unc_with_params [9] +|| | ) [16, 18, 14] +|unc_with_params [10] || | ($ -|| | ) [10] -|| tion [19] +|| | ) [11] +|| tion [20] || | ($ -|| | ) [20] -|| | _with_params [21] +|| | ) [21] +|| | _with_params [22] || | | ($ -|| | | ) [22] +|| | | ) [23] enum [0] | .$ | a_value [1] @@ -114,53 +116,55 @@ a_value [1] ||| ($ ||| ) [4] ||property [5] -||function [7] +||function [8] ||| ($ -||| ) [8] +||| ) [9] |nother [2] -pybind [23] +data_declaration [6] +| in_a_submodule [25] +pybind [24] | .$ -| foo [18] +| foo [19] | | .$ -| | overloaded_method [14, 16, 12] +| | overloaded_method [15, 17, 13] | | ($ -| | ) [15, 17, 13] -| unction [19] +| | ) [16, 18, 14] +| unction [20] | | ($ -| | ) [20] -| | _with_params [21] +| | ) [21] +| | _with_params [22] | | | ($ -| | | ) [22] -overloaded_method [14, 16, 12] +| | | ) [23] +overloaded_method [15, 17, 13] | ($ -| ) [15, 17, 13] -data_in_a_submodule [24] -0: .Enum [prefix=6[:15], type=ENUM] -> #Enum +| ) [16, 18, 14] +0: .Enum [prefix=7[:15], type=ENUM] -> #Enum 1: .A_VALUE [prefix=0[:20], type=ENUM_VALUE] -> -A_VALUE 2: .ANOTHER [prefix=0[:20], type=ENUM_VALUE] -> -ANOTHER -3: .a_method() [prefix=6[:15], suffix_length=2, type=FUNCTION] -> #a_method +3: .a_method() [prefix=7[:15], suffix_length=2, type=FUNCTION] -> #a_method 4: [prefix=3[:24], type=FUNCTION] -> -5: .a_property [prefix=6[:15], type=PROPERTY] -> #a_property -6: .Foo [prefix=11[:7], type=CLASS] -> Foo.html -7: .a_function() [prefix=11[:11], suffix_length=2, type=FUNCTION] -> #a_function -8: [prefix=7[:22], type=FUNCTION] -> -9: .func_with_params() [prefix=11[:11], suffix_length=2, type=FUNCTION] -> #func_with_params -10: [prefix=9[:28], type=FUNCTION] -> -11: search [type=MODULE] -> search.html -12: .overloaded_method(self, first: int, second: float) [prefix=18[:22], suffix_length=33, type=FUNCTION] -> #overloaded_method-27269 -13: [prefix=12[:46], suffix_length=31, type=FUNCTION] -> -14: .overloaded_method(self, arg0: int) [prefix=18[:22], suffix_length=17, type=FUNCTION] -> #overloaded_method-745a3 -15: [prefix=14[:46], suffix_length=15, type=FUNCTION] -> -16: .overloaded_method(self, arg0: int, arg1: Foo) [prefix=18[:22], suffix_length=28, type=FUNCTION] -> #overloaded_method-41cfb -17: [prefix=16[:46], suffix_length=26, type=FUNCTION] -> -18: .Foo [prefix=23[:14], type=CLASS] -> Foo.html -19: .function() [prefix=23[:18], suffix_length=2, type=FUNCTION] -> #function-da39a -20: [prefix=19[:33], type=FUNCTION] -> -21: .function_with_params() [prefix=23[:18], suffix_length=2, type=FUNCTION] -> #function_with_params-8f19c -22: [prefix=21[:45], type=FUNCTION] -> -23: .pybind [prefix=11[:7], type=MODULE] -> pybind.html -24: .DATA_IN_A_SUBMODULE [prefix=25[:15], type=DATA] -> #DATA_IN_A_SUBMODULE -25: .sub [prefix=11[:7], type=MODULE] -> sub.html +5: .a_property [prefix=7[:15], type=PROPERTY] -> #a_property +6: .DATA_DECLARATION [prefix=7[:15], type=DATA] -> #DATA_DECLARATION +7: .Foo [prefix=12[:7], type=CLASS] -> Foo.html +8: .a_function() [prefix=12[:11], suffix_length=2, type=FUNCTION] -> #a_function +9: [prefix=8[:22], type=FUNCTION] -> +10: .func_with_params() [prefix=12[:11], suffix_length=2, type=FUNCTION] -> #func_with_params +11: [prefix=10[:28], type=FUNCTION] -> +12: search [type=MODULE] -> search.html +13: .overloaded_method(self, first: int, second: float) [prefix=19[:22], suffix_length=33, type=FUNCTION] -> #overloaded_method-27269 +14: [prefix=13[:46], suffix_length=31, type=FUNCTION] -> +15: .overloaded_method(self, arg0: int) [prefix=19[:22], suffix_length=17, type=FUNCTION] -> #overloaded_method-745a3 +16: [prefix=15[:46], suffix_length=15, type=FUNCTION] -> +17: .overloaded_method(self, arg0: int, arg1: Foo) [prefix=19[:22], suffix_length=28, type=FUNCTION] -> #overloaded_method-41cfb +18: [prefix=17[:46], suffix_length=26, type=FUNCTION] -> +19: .Foo [prefix=24[:14], type=CLASS] -> Foo.html +20: .function() [prefix=24[:18], suffix_length=2, type=FUNCTION] -> #function-da39a +21: [prefix=20[:33], type=FUNCTION] -> +22: .function_with_params() [prefix=24[:18], suffix_length=2, type=FUNCTION] -> #function_with_params-8f19c +23: [prefix=22[:45], type=FUNCTION] -> +24: .pybind [prefix=12[:7], type=MODULE] -> pybind.html +25: .DATA_IN_A_SUBMODULE [prefix=26[:15], type=DATA] -> #DATA_IN_A_SUBMODULE +26: .sub [prefix=12[:7], type=MODULE] -> sub.html (EntryType.PAGE, CssClass.SUCCESS, 'page'), (EntryType.MODULE, CssClass.PRIMARY, 'module'), (EntryType.CLASS, CssClass.PRIMARY, 'class'), -- 2.30.2