From: Vladimír Vondruš Date: Thu, 26 Sep 2024 18:17:40 +0000 (+0200) Subject: documentation/python: when signature fails, leave it up to the template. X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~cjwatson/git?a=commitdiff_plain;h=2d39f769278db8cc77753577fe4a416bcbeef0b3;p=blog.git documentation/python: when signature fails, leave it up to the template. 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. --- diff --git a/doc/documentation/python.rst b/doc/documentation/python.rst index 55126328..5e7f6b80 100644 --- a/doc/documentation/python.rst +++ b/doc/documentation/python.rst @@ -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: diff --git a/documentation/python.py b/documentation/python.py index 37cb02fc..cc0c6bdc 100755 --- a/documentation/python.py +++ b/documentation/python.py @@ -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 diff --git a/documentation/templates/python/details-function.html b/documentation/templates/python/details-function.html index c4008218..c3b9cf19 100644 --- a/documentation/templates/python/details-function.html +++ b/documentation/templates/python/details-function.html @@ -1,7 +1,7 @@

{% set j = joiner('\n ' if function.has_complex_params else ' ') %} - def {{ prefix }}{{ function.name }}({% for param in function.params %}{% if loop.index0 %}{% if function.params[loop.index0 - 1].kind == 'POSITIONAL_OR_KEYWORD' and param.kind == 'KEYWORD_ONLY' %}, *,{% 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') %}, /{% endif %}{% endfor %}){% if function.type_link %} -> {{ function.type_link }}{% endif %}{% if function.is_classmethod %} classmethod{% elif function.is_staticmethod %} staticmethod{% endif %} + def {{ prefix }}{{ function.name }}({% for param in function.params %}{% if loop.index0 %}{% if function.params[loop.index0 - 1].kind == 'POSITIONAL_OR_KEYWORD' and param.kind == 'KEYWORD_ONLY' %}, *,{% 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') %}, /{% endif %}{% endfor %}){% if function.type_link %} -> {{ function.type_link }}{% endif %}{% if function.is_classmethod %} classmethod{% elif function.is_staticmethod %} staticmethod{% endif %}

{% if function.summary %}

{{ function.summary }}

diff --git a/documentation/templates/python/entry-function.html b/documentation/templates/python/entry-function.html index 5e771413..b4c04457 100644 --- a/documentation/templates/python/entry-function.html +++ b/documentation/templates/python/entry-function.html @@ -1,5 +1,5 @@ {% set j = joiner('\n ' if function.has_complex_params else ' ') %} - def {{ function.name }}({% for param in function.params %}{% if loop.index0 %}{% if function.params[loop.index0 - 1].kind == 'POSITIONAL_OR_KEYWORD' and param.kind == 'KEYWORD_ONLY' %}, *,{% 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') %}, /{% endif %}{% endfor %}){% if function.type_link %} -> {{ function.type_link }}{% endif %} + def {{ function.name }}({% for param in function.params %}{% if loop.index0 %}{% if function.params[loop.index0 - 1].kind == 'POSITIONAL_OR_KEYWORD' and param.kind == 'KEYWORD_ONLY' %}, *,{% 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') %}, /{% endif %}{% endfor %}){% if function.type_link %} -> {{ function.type_link }}{% endif %}
{{ function.summary }}
diff --git a/documentation/test_python/inspect_builtin/docs.rst b/documentation/test_python/inspect_builtin/docs.rst new file mode 100644 index 00000000..48e606ec --- /dev/null +++ b/documentation/test_python/inspect_builtin/docs.rst @@ -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. diff --git a/documentation/test_python/inspect_builtin/inspect_builtin.BaseException-310.html b/documentation/test_python/inspect_builtin/inspect_builtin.BaseException-310.html index b14f251b..2514edc4 100644 --- a/documentation/test_python/inspect_builtin/inspect_builtin.BaseException-310.html +++ b/documentation/test_python/inspect_builtin/inspect_builtin.BaseException-310.html @@ -49,8 +49,8 @@ set self.__traceback__ to tb and return self.

Special methods

-
- def __reduce__(...) +
+ def __reduce__(...)
@@ -76,6 +76,16 @@ set self.__traceback__ to tb and return self.
+
+

Method documentation

+
+

+ def 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.

+
+
diff --git a/documentation/test_python/inspect_builtin/inspect_builtin.BaseException.html b/documentation/test_python/inspect_builtin/inspect_builtin.BaseException.html index 0d6f2743..7bee1af8 100644 --- a/documentation/test_python/inspect_builtin/inspect_builtin.BaseException.html +++ b/documentation/test_python/inspect_builtin/inspect_builtin.BaseException.html @@ -54,8 +54,8 @@ set self.__traceback__ to tb and return self.

Special methods

-
- def __reduce__(...) +
+ def __reduce__(...)
@@ -81,6 +81,16 @@ set self.__traceback__ to tb and return self.
+
+

Method documentation

+
+

+ def 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.

+
+
diff --git a/documentation/test_python/pybind_signatures/pybind_signatures.html b/documentation/test_python/pybind_signatures/pybind_signatures.html index 83ef7d4a..a276ed19 100644 --- a/documentation/test_python/pybind_signatures/pybind_signatures.html +++ b/documentation/test_python/pybind_signatures/pybind_signatures.html @@ -50,7 +50,7 @@

Functions

- def crazy_signature(…) + def crazy_signature(...)
Function that failed to get parsed
@@ -62,7 +62,7 @@
A docstring that <em>should</em> be escaped
- def failed_parse_docstring(…) + def failed_parse_docstring(...)
A failed parse should <strong>also</strong> escape the docstring
diff --git a/documentation/test_python/test_inspect.py b/documentation/test_python/test_inspect.py index f7d7f505..c5a9cf0e 100644 --- a/documentation/test_python/test_inspect.py +++ b/documentation/test_python/test_inspect.py @@ -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, diff --git a/documentation/test_python/test_pybind.py b/documentation/test_python/test_pybind.py index 3b581731..3e8582cf 100644 --- a/documentation/test_python/test_pybind.py +++ b/documentation/test_python/test_pybind.py @@ -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 = a)'), bad_signature) self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: Enum = <)'), 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, [],