chiark / gitweb /
documentation/python: properly handle inspection of slot properties.
authorVladimír Vondruš <mosra@centrum.cz>
Sun, 14 Jul 2019 15:49:39 +0000 (17:49 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Sun, 14 Jul 2019 17:19:44 +0000 (19:19 +0200)
There's no fget/fset/fdel for those and we also can't __doc__ them.

13 files changed:
documentation/python.py
documentation/test_python/inspect_annotations/inspect_annotations.FooSlots.html [new file with mode: 0644]
documentation/test_python/inspect_annotations/inspect_annotations.html
documentation/test_python/inspect_annotations/inspect_annotations.py
documentation/test_python/inspect_string/classes.html
documentation/test_python/inspect_string/inspect_string.FooSlots.html [new file with mode: 0644]
documentation/test_python/inspect_string/inspect_string.html
documentation/test_python/inspect_string/inspect_string/__init__.py
documentation/test_python/inspect_type_links/inspect_type_links.second.FooSlots.html [new file with mode: 0644]
documentation/test_python/inspect_type_links/inspect_type_links.second.FooSlotsInvalid.html [new file with mode: 0644]
documentation/test_python/inspect_type_links/inspect_type_links.second.html
documentation/test_python/inspect_type_links/inspect_type_links/second.py
documentation/test_python/test_inspect.py

index 84df56e616b94fec5dd1338871b67fb6da7e1388..602d70890cdc04b1e99761af9340157cb08d7fc2 100755 (executable)
@@ -1085,12 +1085,42 @@ def extract_function_doc(state: State, parent, path: List[str], function) -> Lis
 
         return [out]
 
-def extract_property_doc(state: State, path: List[str], property):
+def extract_property_doc(state: State, parent, path: List[str], property):
     assert inspect.isdatadescriptor(property)
 
     out = Empty()
     out.name = path[-1]
     out.id = state.config['ID_FORMATTER'](EntryType.PROPERTY, path[-1:])
+
+    # If this is a slot, there won't be any fget / fset / fdel. Assume they're
+    # gettable and settable (couldn't find any way to make them *inspectably*
+    # readonly, all solutions involved throwing from __setattr__()) and
+    # deletable as well (calling del on it seems to simply remove any
+    # previously set value). Unfortunately we can't get any docstring for these
+    # either.
+    # TODO: any better way to detect that those are slots?
+    if property.__class__.__name__ == 'member_descriptor' and property.__class__.__module__ == 'builtins':
+        out.is_gettable = True
+        out.is_settable = True
+        out.is_deletable = True
+        # TODO: external summary for properties
+        out.summary = ''
+        out.has_details = False
+
+        # 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(state, path, parent)
+
+        if out.name in type_hints:
+            out.type = extract_annotation(state, path, type_hints[out.name])
+        elif hasattr(parent, '__annotations__') and out.name in parent.__annotations__:
+            out.type = extract_annotation(state, path, parent.__annotations__[out.name])
+        else:
+            out.type = None
+
+        return out
+
     # TODO: external summary for properties
     out.is_gettable = property.fget is not None
     if property.fget or (property.fset and property.__doc__):
@@ -1334,7 +1364,7 @@ def render_class(state: State, path, class_, env):
                 else:
                     page.methods += [function]
         elif member_entry.type == EntryType.PROPERTY:
-            page.properties += [extract_property_doc(state, subpath, member_entry.object)]
+            page.properties += [extract_property_doc(state, class_, subpath, member_entry.object)]
         elif member_entry.type == EntryType.DATA:
             page.data += [extract_data_doc(state, class_, subpath, member_entry.object)]
         else: # pragma: no cover
diff --git a/documentation/test_python/inspect_annotations/inspect_annotations.FooSlots.html b/documentation/test_python/inspect_annotations/inspect_annotations.FooSlots.html
new file mode 100644 (file)
index 0000000..6273b60
--- /dev/null
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>inspect_annotations.FooSlots | My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          <span class="m-breadcrumb"><a href="inspect_annotations.html">inspect_annotations</a>.<wbr/></span>FooSlots <span class="m-thin">class</span>
+        </h1>
+        <p>A class with slots</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#properties">Properties</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="properties">
+          <h2><a href="#properties">Properties</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <a href="#annotated" class="m-doc-self" id="annotated">annotated</a>: typing.List[str] <span class="m-label m-flat m-success">get set del</span>
+            </dt>
+            <dd></dd>
+            <dt>
+              <a href="#unannotated" class="m-doc-self" id="unannotated">unannotated</a> <span class="m-label m-flat m-success">get set del</span>
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
index cc80ecf13d72d47558117bdb39a23bc4fe814340..50f2489cc5d1068670b5866e54e0775f6d223f21 100644 (file)
@@ -41,6 +41,8 @@
           <dl class="m-doc">
             <dt>class <a href="inspect_annotations.Foo.html" class="m-doc">Foo</a></dt>
             <dd>A class with properties</dd>
+            <dt>class <a href="inspect_annotations.FooSlots.html" class="m-doc">FooSlots</a></dt>
+            <dd>A class with slots</dd>
           </dl>
         </section>
         <section id="functions">
index 27e8a3dab14359fc050cb166792a1e545df15818..65594737295d1fa2d4731314db1696aa20d4aa6c 100644 (file)
@@ -12,6 +12,13 @@ class Foo:
         """A property with a type annotation"""
         pass
 
+class FooSlots:
+    """A class with slots"""
+
+    __slots__ = ['unannotated', 'annotated']
+
+    annotated: List[str]
+
 def annotation(param: List[int], another: bool, third: str = "hello") -> float:
     """An annotated function"""
     pass
index 1b28b2359fee27b0d35ad4c746616f25dd94bb95..617190ca58147f91d0134b49b607d3eec1a00bb9 100644 (file)
@@ -49,6 +49,7 @@
                   <li>class <a href="inspect_string.Foo.Subclass.html" class="m-doc">Subclass</a> <span class="m-doc">A subclass of Foo</span></li>
                 </ul>
               </li>
+              <li>class <a href="inspect_string.FooSlots.html" class="m-doc">FooSlots</a> <span class="m-doc">A class with slots. Can&#x27;t have docstrings for these.</span></li>
               <li>class <a href="inspect_string.Specials.html" class="m-doc">Specials</a> <span class="m-doc">Special class members</span></li>
             </ul>
           </li>
diff --git a/documentation/test_python/inspect_string/inspect_string.FooSlots.html b/documentation/test_python/inspect_string/inspect_string.FooSlots.html
new file mode 100644 (file)
index 0000000..02f30ee
--- /dev/null
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>inspect_string.FooSlots | My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+      <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+        <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+        <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+      </div>
+      <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+        <div class="m-row">
+          <ol class="m-col-t-12 m-col-m-none">
+            <li><a href="modules.html">Modules</a></li>
+            <li><a href="classes.html">Classes</a></li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          <span class="m-breadcrumb"><a href="inspect_string.html">inspect_string</a>.<wbr/></span>FooSlots <span class="m-thin">class</span>
+        </h1>
+        <p>A class with slots. Can&#x27;t have docstrings for these.</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#properties">Properties</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="properties">
+          <h2><a href="#properties">Properties</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <a href="#first" class="m-doc-self" id="first">first</a> <span class="m-label m-flat m-success">get set del</span>
+            </dt>
+            <dd></dd>
+            <dt>
+              <a href="#second" class="m-doc-self" id="second">second</a> <span class="m-label m-flat m-success">get set del</span>
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
index 17fc86dc35a4248c6a4810dafdb4c14bef1ea118..5b7cf9701a4629c6237a2a58b48d660c90346c47 100644 (file)
@@ -64,6 +64,8 @@
           <dl class="m-doc">
             <dt>class <a href="inspect_string.Foo.html" class="m-doc">Foo</a></dt>
             <dd>The foo class</dd>
+            <dt>class <a href="inspect_string.FooSlots.html" class="m-doc">FooSlots</a></dt>
+            <dd>A class with slots. Can&#x27;t have docstrings for these.</dd>
             <dt>class <a href="inspect_string.Specials.html" class="m-doc">Specials</a></dt>
             <dd>Special class members</dd>
           </dl>
index 425511bce7dcdfa7837110841e78a2a3f80ca267..e9034255a760034c756567bc73b7f0eb88e86122 100644 (file)
@@ -107,6 +107,11 @@ class Foo:
         """A private property"""
         pass
 
+class FooSlots:
+    """A class with slots. Can't have docstrings for these."""
+
+    __slots__ = ['first', 'second']
+
 class Specials:
     """Special class members"""
 
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links.second.FooSlots.html b/documentation/test_python/inspect_type_links/inspect_type_links.second.FooSlots.html
new file mode 100644 (file)
index 0000000..1c79b01
--- /dev/null
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>inspect_type_links.second.FooSlots | My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          <span class="m-breadcrumb"><a href="inspect_type_links.html">inspect_type_links</a>.<wbr/></span><span class="m-breadcrumb"><a href="inspect_type_links.second.html">second</a>.<wbr/></span>FooSlots <span class="m-thin">class</span>
+        </h1>
+        <p>A slot class</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#properties">Properties</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="properties">
+          <h2><a href="#properties">Properties</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <a href="#type_slot" class="m-doc-self" id="type_slot">type_slot</a>: <a href="inspect_type_links.second.html#Enum" class="m-doc">Enum</a> <span class="m-label m-flat m-success">get set del</span>
+            </dt>
+            <dd></dd>
+            <dt>
+              <a href="#type_slot_string_nested" class="m-doc-self" id="type_slot_string_nested">type_slot_string_nested</a>: typing.Tuple[<a href="inspect_type_links.second.Foo.html" class="m-doc">Foo</a>, typing.List[<a href="inspect_type_links.second.html#Enum" class="m-doc">Enum</a>], typing.Any] <span class="m-label m-flat m-success">get set del</span>
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links.second.FooSlotsInvalid.html b/documentation/test_python/inspect_type_links/inspect_type_links.second.FooSlotsInvalid.html
new file mode 100644 (file)
index 0000000..73b2434
--- /dev/null
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>inspect_type_links.second.FooSlotsInvalid | My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          <span class="m-breadcrumb"><a href="inspect_type_links.html">inspect_type_links</a>.<wbr/></span><span class="m-breadcrumb"><a href="inspect_type_links.second.html">second</a>.<wbr/></span>FooSlotsInvalid <span class="m-thin">class</span>
+        </h1>
+        <p>A slot class with an invalid annotation. Has to be separate because otherwise it would invalidate all other slot annotations in FooSlots as well.</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#properties">Properties</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="properties">
+          <h2><a href="#properties">Properties</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <a href="#type_slot_string_invalid" class="m-doc-self" id="type_slot_string_invalid">type_slot_string_invalid</a>: typing.List[FooBar] <span class="m-label m-flat m-success">get set del</span>
+            </dt>
+            <dd></dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
index cd07de9eedff2e0318699043162079c97e084944..368458f30bda21311b07aa5a749921b9efcbb74c 100644 (file)
           <dl class="m-doc">
             <dt>class <a href="inspect_type_links.second.Foo.html" class="m-doc">Foo</a></dt>
             <dd>A class in the second module</dd>
+            <dt>class <a href="inspect_type_links.second.FooSlots.html" class="m-doc">FooSlots</a></dt>
+            <dd>A slot class</dd>
+            <dt>class <a href="inspect_type_links.second.FooSlotsInvalid.html" class="m-doc">FooSlotsInvalid</a></dt>
+            <dd>A slot class with an invalid annotation. Has to be separate because otherwise it would invalidate all other slot annotations in FooSlots as well.</dd>
           </dl>
         </section>
         <section id="enums">
index fbcad3e5e38653711966f6ca3b0714cfbf8f4f82..e4f00baf84a330389694fb0c32ce3a1279279d20 100644 (file)
@@ -44,6 +44,21 @@ class Foo:
     # other data annotations from being retrieved
     TYPE_DATA_STRING_INVALID: 'Foo.Bar' = 3
 
+class FooSlots:
+    """A slot class"""
+
+    __slots__ = ['type_slot', 'type_slot_string_nested']
+
+    type_slot: Enum
+    type_slot_string_nested: 'Tuple[Foo, List[Enum], Any]'
+
+class FooSlotsInvalid:
+    """A slot class with an invalid annotation. Has to be separate because otherwise it would invalidate all other slot annotations in FooSlots as well."""
+
+    __slots__ = ['type_slot_string_invalid']
+
+    type_slot_string_invalid: List['FooBar']
+
 def type_string(a: 'Foo'):
     """A function with string type annotation"""
 
index c6d767910e6726987aed2c3b453c42fa2a14877d..25c0d75fa90cea212e84f09e2028022f8726dc1c 100644 (file)
@@ -43,6 +43,7 @@ class String(BaseInspectTestCase):
         self.assertEqual(*self.actual_expected_contents('inspect_string.html'))
         self.assertEqual(*self.actual_expected_contents('inspect_string.another_module.html'))
         self.assertEqual(*self.actual_expected_contents('inspect_string.Foo.html'))
+        self.assertEqual(*self.actual_expected_contents('inspect_string.FooSlots.html'))
         self.assertEqual(*self.actual_expected_contents('inspect_string.Specials.html'))
 
         self.assertEqual(*self.actual_expected_contents('classes.html'))
@@ -65,6 +66,7 @@ class Object(BaseInspectTestCase):
         self.assertEqual(*self.actual_expected_contents('inspect_string.html', '../inspect_string/inspect_string.html'))
         self.assertEqual(*self.actual_expected_contents('inspect_string.another_module.html', '../inspect_string/inspect_string.another_module.html'))
         self.assertEqual(*self.actual_expected_contents('inspect_string.Foo.html', '../inspect_string/inspect_string.Foo.html'))
+        self.assertEqual(*self.actual_expected_contents('inspect_string.FooSlots.html', '../inspect_string/inspect_string.FooSlots.html'))
         self.assertEqual(*self.actual_expected_contents('inspect_string.Specials.html', '../inspect_string/inspect_string.Specials.html'))
 
         self.assertEqual(*self.actual_expected_contents('classes.html', '../inspect_string/classes.html'))
@@ -80,6 +82,7 @@ class Annotations(BaseInspectTestCase):
         self.run_python()
         self.assertEqual(*self.actual_expected_contents('inspect_annotations.html'))
         self.assertEqual(*self.actual_expected_contents('inspect_annotations.Foo.html'))
+        self.assertEqual(*self.actual_expected_contents('inspect_annotations.FooSlots.html'))
 
     @unittest.skipUnless(LooseVersion(sys.version) >= LooseVersion('3.7'),
         "signature with / for pow() is not present in 3.6")
@@ -140,3 +143,5 @@ class TypeLinks(BaseInspectTestCase):
 
         self.assertEqual(*self.actual_expected_contents('inspect_type_links.second.html'))
         self.assertEqual(*self.actual_expected_contents('inspect_type_links.second.Foo.html'))
+        self.assertEqual(*self.actual_expected_contents('inspect_type_links.second.FooSlots.html'))
+        self.assertEqual(*self.actual_expected_contents('inspect_type_links.second.FooSlotsInvalid.html'))