chiark / gitweb /
documentation/python: pull additional stuff from __annotations__.
authorVladimír Vondruš <mosra@centrum.cz>
Fri, 6 Sep 2019 00:07:51 +0000 (02:07 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Fri, 6 Sep 2019 10:26:59 +0000 (12:26 +0200)
doc/documentation/python.rst
documentation/python.py
documentation/test_python/inspect_attrs/inspect_attrs.MyClass.html
documentation/test_python/inspect_string/inspect_string.Foo.html
documentation/test_python/inspect_string/inspect_string/__init__.py
documentation/test_python/inspect_underscored/docs.rst
documentation/test_python/inspect_underscored/inspect_underscored.Class.html
documentation/test_python/inspect_underscored/inspect_underscored/__init__.py
documentation/test_python/search/search/__init__.py
documentation/test_python/test_search.py

index c13a05ec1fc0738b81c1fe1da642d668a0dcac9a..d7b59707580a0839cab133e15e39cf3fbb3acb9f 100644 (file)
@@ -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
index 7ffeda0dd479b14dc9c4dfa8ad937d84744dc032..fe77bb959d5443fc7dc4b1a0783de80f12bcd3d9 100755 (executable)
@@ -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
 
index 28c14bda79291e2cc1033844e08e8aa5d6f3f3a9..dc298f8d8eb9b13bc02531f4bd1a069667f98c0c 100644 (file)
               <a href="#annotated" class="m-doc-self">annotated</a>: float <span class="m-label m-flat m-success">get set del</span>
             </dt>
             <dd></dd>
-            <dt id="unannotated">
-              <a href="#unannotated" class="m-doc-self">unannotated</a> <span class="m-label m-flat m-success">get set del</span>
-            </dt>
-            <dd>External docs for this property</dd>
             <dt id="complex_annotation">
               <a href="#complex_annotation" class="m-doc-self">complex_annotation</a>: typing.List[typing.Tuple[int, float]] <span class="m-label m-flat m-success">get set del</span>
             </dt>
             <dd></dd>
+            <dt id="unannotated">
+              <a href="#unannotated" class="m-doc-self">unannotated</a> <span class="m-label m-flat m-success">get set del</span>
+            </dt>
+            <dd>External docs for this property</dd>
             <dt id="complex_annotation_in_attr">
               <a href="#complex_annotation_in_attr" class="m-doc-self">complex_annotation_in_attr</a>: typing.List[typing.Tuple[int, float]] <span class="m-label m-flat m-success">get set del</span>
             </dt>
index d3abbd90ab53d6e4d8816feab8059da8418e6dc4..17da2734a122fb13eb5e89533b0d3b9dbbb98cbd 100644 (file)
               <a href="#A_DATA" class="m-doc-self">A_DATA</a> = &#x27;BOO&#x27;
             </dt>
             <dd></dd>
+            <dt id="DATA_DECLARATION">
+              <a href="#DATA_DECLARATION" class="m-doc-self">DATA_DECLARATION</a>: int = None
+            </dt>
+            <dd></dd>
           </dl>
         </section>
         <section>
index 6775132629aa723ed6713c9addccaf57b4557aa6..68ce6ac91357072ebb86f7fabd2de40715f90f17 100644 (file)
@@ -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
index 9f0872b57822726a6cee5e43bf6688b98cb277d4..5f202344dde26aa78a5348195e0de12359089b34 100644 (file)
@@ -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
index b413fc883cebd1e257a6693443d76b2cf15e3a38..f5cb470c1bb8001640d2562675ac5ee4aca61313 100644 (file)
@@ -78,6 +78,15 @@ underscored property</dd>
             </dt>
             <dd>Externally in-class documented underscored
 data</dd>
+            <dt id="_DATA_DECLARATION_EXTERNAL">
+              <a href="#_DATA_DECLARATION_EXTERNAL" class="m-doc-self">_DATA_DECLARATION_EXTERNAL</a>: float = None
+            </dt>
+            <dd>Externally documented underscored data declaration</dd>
+            <dt id="_DATA_DECLARATION_EXTERNAL_IN_CLASS">
+              <a href="#_DATA_DECLARATION_EXTERNAL_IN_CLASS" class="m-doc-self">_DATA_DECLARATION_EXTERNAL_IN_CLASS</a>: float = None
+            </dt>
+            <dd>Externally in-class documented
+underscored data declaration</dd>
           </dl>
         </section>
       </div>
index 49ca41132d741febee08f816776b62ab2abfe2a5..f7e3e269911edb99e14e0845002f613fe855e54b 100644 (file)
@@ -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
index 4f4c19330a928a77a95cd1349533c8f7f4614a29..990a9bbd3cf842af356ccc8c3344c41a0d51df0b 100644 (file)
@@ -14,6 +14,8 @@ class Foo:
         A_VALUE = 1
         ANOTHER = 2
 
+    DATA_DECLARATION: int
+
 def a_function():
     pass
 
index 8100f65f6804b40d8c332d25a51cc01f2be967cd..eae9ef2cabdbda30801c0236e06d6f863f46a131 100644 (file)
@@ -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'),