chiark / gitweb /
documentation/python: when signature fails, leave it up to the template.
authorVladimír Vondruš <mosra@centrum.cz>
Thu, 26 Sep 2024 18:17:40 +0000 (20:17 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Sat, 28 Sep 2024 01:44:29 +0000 (03:44 +0200)
Passing an ellipsis to the template made sense when there was just a HTML
output, but for generated stubs that's not a valid syntax. So output a
single parameter with name being None instead, and let the template
decide what to do there.

In case of pybind, the code was inconsistently using the Unicode
ellipsis. Now it returns None too, and thus the template handles both the
same.

doc/documentation/python.rst
documentation/python.py
documentation/templates/python/details-function.html
documentation/templates/python/entry-function.html
documentation/test_python/inspect_builtin/docs.rst [new file with mode: 0644]
documentation/test_python/inspect_builtin/inspect_builtin.BaseException-310.html
documentation/test_python/inspect_builtin/inspect_builtin.BaseException.html
documentation/test_python/pybind_signatures/pybind_signatures.html
documentation/test_python/test_inspect.py
documentation/test_python/test_pybind.py

index 55126328413dca13360d8ad137a47b24ded87f27..5e7f6b80d25daaa5dc68300d5267288cf28cfe7e 100644 (file)
@@ -1368,7 +1368,10 @@ Property                        Description
 
 In some cases (for example in case of native APIs), the parameters can't be
 introspected. In that case, the parameter list is a single entry with ``name``
-set to :py:`"..."` and the rest being empty.
+set to :py:`None` and the rest being :py:`None` as well. It's then up to the
+template what it does. For example the builtin Python :py:`help()` shows
+``...`` (which isn't valid syntax), if you need a valid syntax you can also use
+:py:`*args` instead.
 
 The :py:`function.exceptions` is a list of exceptions types and descriptions.
 Each item has the following properties:
index 37cb02fc6f5e7bb71a7462a655e1a9ef3343a79b..cc0c6bdc4272ce33500df30dc04c93059c5a063c 100755 (executable)
@@ -1033,7 +1033,8 @@ def parse_pybind_signature(state: State, referrer_path: List[str], signature: st
         # Expecting end of the signature line now, if not there, we failed
         if signature and signature[0] != '\n': raise SyntaxError("Expected end of the signature, got `{}`".format(signature))
 
-    # Failed to parse, return an ellipsis and docs
+    # Failed to parse, return with a single parameter with name being None and
+    # docs
     except SyntaxError as e:
         end = original_signature.find('\n')
         logging.warning("cannot parse pybind11 function signature %s: %s", (original_signature[:end if end != -1 else None]), e)
@@ -1041,7 +1042,7 @@ def parse_pybind_signature(state: State, referrer_path: List[str], signature: st
             docstring = inspect.cleandoc(original_signature[end + 1:])
         else:
             docstring = ''
-        return (name, docstring, [('…', None, None, None, None)], None, None, None)
+        return (name, docstring, [(None, None, None, None, None)], None, None, None)
 
     if len(signature) > 1 and signature[1] == '\n':
         docstring = inspect.cleandoc(signature[2:])
@@ -1734,7 +1735,7 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
         # https://docs.python.org/3/library/inspect.html#inspect.signature
         except ValueError:
             param = Empty()
-            param.name = '...'
+            param.name = None
             param.type, param.type_relative, param.type_link = None, None, None
             param.default, param.default_relative, param.default_link = None, None, None
             out.params = [param]
@@ -1759,7 +1760,10 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
     # Common path for parameter / exception / return value docs and search
     path_str = '.'.join(entry.path)
     for out in overloads:
-        signature = '({})'.format(', '.join(['{}: {}'.format(param.name, param.type) if param.type else param.name for param in out.params]))
+        # In case of introspection error, there's just a single param with name
+        # and everything else being None, replace it with ... to match what the
+        # HTML output shows
+        signature = '({})'.format(', '.join(['{}: {}'.format(param.name, param.type) if param.type else param.name or '...' for param in out.params]))
         param_names = [param.name for param in out.params]
 
         # Call all scope enter hooks for this particular overload
index c4008218b6d70e761e7f15c772b535d5362bc7f1..c3b9cf1946f5518128b65bafa3bd8abb1a29a333 100644 (file)
@@ -1,7 +1,7 @@
           <section class="m-doc-details" id="{{ function.id }}"><div>
             <h3>
               {% set j = joiner('\n              ' if function.has_complex_params else ' ') %}
-              <span class="m-doc-wrap-bumper">def {{ prefix }}</span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#{{ function.id }}" class="m-doc-self">{{ function.name }}</a>(</span><span class="m-doc-wrap">{% for param in function.params %}{% if loop.index0 %}{% if function.params[loop.index0 - 1].kind == 'POSITIONAL_OR_KEYWORD' and param.kind == 'KEYWORD_ONLY' %},<span class="m-text m-dim"> *,</span>{% else %},{% endif %}{% endif %}{{ j() }}{% if param.kind == 'VAR_POSITIONAL' %}*{% elif param.kind == 'VAR_KEYWORD' %}**{% endif %}{{ param.name }}{% if param.type_link %}: {{ param.type_link }}{% endif %}{% if param.default_link %} = {{ param.default_link }}{% endif %}{% if param.kind == 'POSITIONAL_ONLY' and (loop.last or function.params[loop.index0 + 1].kind != 'POSITIONAL_ONLY') %}<span class="m-text m-dim">, /</span>{% endif %}{% endfor %}){% if function.type_link %} -&gt; {{ function.type_link }}{% endif %}{% if function.is_classmethod %} <span class="m-label m-success">classmethod</span>{% elif function.is_staticmethod %} <span class="m-label m-info">staticmethod</span>{% endif %}</span></span>
+              <span class="m-doc-wrap-bumper">def {{ prefix }}</span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#{{ function.id }}" class="m-doc-self">{{ function.name }}</a>(</span><span class="m-doc-wrap">{% for param in function.params %}{% if loop.index0 %}{% if function.params[loop.index0 - 1].kind == 'POSITIONAL_OR_KEYWORD' and param.kind == 'KEYWORD_ONLY' %},<span class="m-text m-dim"> *,</span>{% else %},{% endif %}{% endif %}{{ j() }}{% if param.kind == 'VAR_POSITIONAL' %}*{% elif param.kind == 'VAR_KEYWORD' %}**{% endif %}{{ param.name or '...' }}{% if param.type_link %}: {{ param.type_link }}{% endif %}{% if param.default_link %} = {{ param.default_link }}{% endif %}{% if param.kind == 'POSITIONAL_ONLY' and (loop.last or function.params[loop.index0 + 1].kind != 'POSITIONAL_ONLY') %}<span class="m-text m-dim">, /</span>{% endif %}{% endfor %}){% if function.type_link %} -&gt; {{ function.type_link }}{% endif %}{% if function.is_classmethod %} <span class="m-label m-success">classmethod</span>{% elif function.is_staticmethod %} <span class="m-label m-info">staticmethod</span>{% endif %}</span></span>
             </h3>
             {% if function.summary %}
             <p>{{ function.summary }}</p>
index 5e7714132648a936f761fa430dfc7cbff6eaf333..b4c04457bc0218903f9930160590dd5b31da1f52 100644 (file)
@@ -1,5 +1,5 @@
             <dt{% if not function.has_details %} id="{{ function.id }}"{% endif %}>
               {% set j = joiner('\n              ' if function.has_complex_params else ' ') %}
-              <span class="m-doc-wrap-bumper">def <a href="#{{ function.id }}" class="m-doc{% if not function.has_details %}-self{% endif %}">{{ function.name }}</a>(</span><span class="m-doc-wrap">{% for param in function.params %}{% if loop.index0 %}{% if function.params[loop.index0 - 1].kind == 'POSITIONAL_OR_KEYWORD' and param.kind == 'KEYWORD_ONLY' %},<span class="m-text m-dim"> *,</span>{% else %},{% endif %}{% endif %}{{ j() }}{% if param.kind == 'VAR_POSITIONAL' %}*{% elif param.kind == 'VAR_KEYWORD' %}**{% endif %}{{ param.name }}{% if param.type_link %}: {{ param.type_link }}{% endif %}{% if param.default_link %} = {{ param.default_link }}{% endif %}{% if param.kind == 'POSITIONAL_ONLY' and (loop.last or function.params[loop.index0 + 1].kind != 'POSITIONAL_ONLY') %}<span class="m-text m-dim">, /</span>{% endif %}{% endfor %}){% if function.type_link %} -&gt; {{ function.type_link }}{% endif %}</span>
+              <span class="m-doc-wrap-bumper">def <a href="#{{ function.id }}" class="m-doc{% if not function.has_details %}-self{% endif %}">{{ function.name }}</a>(</span><span class="m-doc-wrap">{% for param in function.params %}{% if loop.index0 %}{% if function.params[loop.index0 - 1].kind == 'POSITIONAL_OR_KEYWORD' and param.kind == 'KEYWORD_ONLY' %},<span class="m-text m-dim"> *,</span>{% else %},{% endif %}{% endif %}{{ j() }}{% if param.kind == 'VAR_POSITIONAL' %}*{% elif param.kind == 'VAR_KEYWORD' %}**{% endif %}{{ param.name or '...' }}{% if param.type_link %}: {{ param.type_link }}{% endif %}{% if param.default_link %} = {{ param.default_link }}{% endif %}{% if param.kind == 'POSITIONAL_ONLY' and (loop.last or function.params[loop.index0 + 1].kind != 'POSITIONAL_ONLY') %}<span class="m-text m-dim">, /</span>{% endif %}{% endfor %}){% if function.type_link %} -&gt; {{ function.type_link }}{% endif %}</span>
             </dt>
             <dd>{{ function.summary }}</dd>
diff --git a/documentation/test_python/inspect_builtin/docs.rst b/documentation/test_python/inspect_builtin/docs.rst
new file mode 100644 (file)
index 0000000..48e606e
--- /dev/null
@@ -0,0 +1,4 @@
+.. py:function:: inspect_builtin.BaseException.__reduce__
+
+    External docs to trigger the function to be shown in a detailed view as
+    well to test that the ellipsis gets correctly shown there too.
index b14f251b2a84e3e8550a9b8b6663d3d0a24aa8e2..2514edc4441b596551cc05fd2a30b8361b3cac4c 100644 (file)
@@ -49,8 +49,8 @@ set self.__traceback__ to tb and return self.</dd>
         <section id="dunder-methods">
           <h2><a href="#dunder-methods">Special methods</a></h2>
           <dl class="m-doc">
-            <dt id="__reduce__">
-              <span class="m-doc-wrap-bumper">def <a href="#__reduce__" class="m-doc-self">__reduce__</a>(</span><span class="m-doc-wrap">...)</span>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#__reduce__" class="m-doc">__reduce__</a>(</span><span class="m-doc-wrap">...)</span>
             </dt>
             <dd></dd>
             <dt id="__setstate__">
@@ -76,6 +76,16 @@ set self.__traceback__ to tb and return self.</dd>
             <dd></dd>
           </dl>
         </section>
+        <section>
+          <h2>Method documentation</h2>
+          <section class="m-doc-details" id="__reduce__"><div>
+            <h3>
+              <span class="m-doc-wrap-bumper">def inspect_builtin.<wbr />BaseException.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#__reduce__" class="m-doc-self">__reduce__</a>(</span><span class="m-doc-wrap">...)</span></span>
+            </h3>
+<p>External docs to trigger the function to be shown in a detailed view as
+well to test that the ellipsis gets correctly shown there too.</p>
+          </div></section>
+        </section>
       </div>
     </div>
   </div>
index 0d6f2743c52672531e0e164197146534dbcfef84..7bee1af895fa63c89f37b4ddb3f5df9a1d5e469f 100644 (file)
@@ -54,8 +54,8 @@ set self.__traceback__ to tb and return self.</dd>
         <section id="dunder-methods">
           <h2><a href="#dunder-methods">Special methods</a></h2>
           <dl class="m-doc">
-            <dt id="__reduce__">
-              <span class="m-doc-wrap-bumper">def <a href="#__reduce__" class="m-doc-self">__reduce__</a>(</span><span class="m-doc-wrap">...)</span>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#__reduce__" class="m-doc">__reduce__</a>(</span><span class="m-doc-wrap">...)</span>
             </dt>
             <dd></dd>
             <dt id="__setstate__">
@@ -81,6 +81,16 @@ set self.__traceback__ to tb and return self.</dd>
             <dd></dd>
           </dl>
         </section>
+        <section>
+          <h2>Method documentation</h2>
+          <section class="m-doc-details" id="__reduce__"><div>
+            <h3>
+              <span class="m-doc-wrap-bumper">def inspect_builtin.<wbr />BaseException.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#__reduce__" class="m-doc-self">__reduce__</a>(</span><span class="m-doc-wrap">...)</span></span>
+            </h3>
+<p>External docs to trigger the function to be shown in a detailed view as
+well to test that the ellipsis gets correctly shown there too.</p>
+          </div></section>
+        </section>
       </div>
     </div>
   </div>
index 83ef7d4a5fe2dad23607dee7329df95901334f26..a276ed198f36e2e1dbe2b650ae1ab80d5b77f9f9 100644 (file)
@@ -50,7 +50,7 @@
           <h2><a href="#functions">Functions</a></h2>
           <dl class="m-doc">
             <dt id="crazy_signature">
-              <span class="m-doc-wrap-bumper">def <a href="#crazy_signature" class="m-doc-self">crazy_signature</a>(</span><span class="m-doc-wrap">)</span>
+              <span class="m-doc-wrap-bumper">def <a href="#crazy_signature" class="m-doc-self">crazy_signature</a>(</span><span class="m-doc-wrap">...)</span>
             </dt>
             <dd>Function that failed to get parsed</dd>
             <dt id="duck">
@@ -62,7 +62,7 @@
             </dt>
             <dd>A docstring that &lt;em&gt;should&lt;/em&gt; be escaped</dd>
             <dt id="failed_parse_docstring">
-              <span class="m-doc-wrap-bumper">def <a href="#failed_parse_docstring" class="m-doc-self">failed_parse_docstring</a>(</span><span class="m-doc-wrap">)</span>
+              <span class="m-doc-wrap-bumper">def <a href="#failed_parse_docstring" class="m-doc-self">failed_parse_docstring</a>(</span><span class="m-doc-wrap">...)</span>
             </dt>
             <dd>A failed parse should &lt;strong&gt;also&lt;/strong&gt; escape the docstring</dd>
             <dt>
index f7d7f505158fb957369a59a1b093829367876fae..c5a9cf0e50782e7cde7d373125ba370bcf0df1d0 100644 (file)
@@ -111,7 +111,10 @@ class Annotations(BaseInspectTestCase):
 
 class Builtin(BaseInspectTestCase):
     def test(self):
-        self.run_python()
+        self.run_python({
+            'PLUGINS': ['m.sphinx'],
+            'INPUT_DOCS': ['docs.rst'],
+        })
 
         # log() and pow() from the builtin math module. 3.12 improves a
         # docstring. It got seemingly backported to 3.11.3 and 3.10.11 as well,
index 3b581731a5d4a2f8e495110a3e3df2cdbf669d49..3e8582cf7db46c0255199974887e20c582884643 100644 (file)
@@ -187,7 +187,7 @@ class Signature(unittest.TestCase):
                  ('a', 'str', 'str', 'str', '[dict(key="A", value=\'B\')["key"][0], None][0]')
              ], None, None, None))
 
-        bad_signature = ('foo', '', [('…', None, None, None, None)], None, None, None)
+        bad_signature = ('foo', '', [(None, None, None, None, None)], None, None, None)
 
         self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float = [0][)'), bad_signature)
         self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float = ()'), bad_signature)
@@ -246,12 +246,12 @@ class Signature(unittest.TestCase):
             ], None, None, None))
 
         # This will fail
-        bad_signature = ('foo', '', [('…', None, None, None, None)], None, None, None)
+        bad_signature = ('foo', '', [(None, None, None, None, None)], None, None, None)
         self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: Enum = <Enum.CHARACTERS_AFTER: 17>a)'), bad_signature)
         self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: Enum = <Enum.CHARACTERS_AFTER: 89><)'), bad_signature)
 
     def test_bad_return_type(self):
-        bad_signature = ('foo', '', [('…', None, None, None, None)], None, None, None)
+        bad_signature = ('foo', '', [(None, None, None, None, None)], None, None, None)
         for i in [
             # pybind11 2.11 and older
             'foo() -> List[[]',
@@ -268,7 +268,7 @@ class Signature(unittest.TestCase):
     def test_crazy_stuff(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: int, b: Math::Vector<4, UnsignedInt>)'),
-            ('foo', '', [('…', None, None, None, None)], None, None, None))
+            ('foo', '', [(None, None, None, None, None)], None, None, None))
 
     def test_crazy_stuff_nested(self):
         for i in [
@@ -278,17 +278,17 @@ class Signature(unittest.TestCase):
             'foo(a: int, b: list[Math::Vector<4, UnsignedInt>])'
         ]:
             self.assertEqual(parse_pybind_signature(self.state, [], i),
-                ('foo', '', [('…', None, None, None, None)], None, None, None))
+                ('foo', '', [(None, None, None, None, None)], None, None, None))
 
     def test_crazy_stuff_docs(self):
         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)], None, None, None))
+            ('foo', 'This is text!!', [(None, None, None, None, None)], None, None, None))
 
     def test_crazy_return(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: int) -> Math::Vector<4, UnsignedInt>'),
-            ('foo', '', [('…', None, None, None, None)], None, None, None))
+            ('foo', '', [(None, None, None, None, None)], None, None, None))
 
     def test_crazy_return_nested(self):
         for i in [
@@ -298,12 +298,12 @@ class Signature(unittest.TestCase):
             'foo(a: int) -> list[Math::Vector<4, UnsignedInt>]'
         ]:
             self.assertEqual(parse_pybind_signature(self.state, [], i),
-                ('foo', '', [('…', None, None, None, None)], None, None, None))
+                ('foo', '', [(None, None, None, None, None)], None, None, None))
 
     def test_crazy_return_docs(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: int) -> Math::Vector<4, UnsignedInt>\n\nThis returns!'),
-            ('foo', 'This returns!', [('…', None, None, None, None)], None, None, None))
+            ('foo', 'This returns!', [(None, None, None, None, None)], None, None, None))
 
     def test_no_name(self):
         self.assertEqual(parse_pybind_signature(self.state, [],