From: Vladimír Vondruš Date: Sat, 13 Jul 2019 17:39:17 +0000 (+0200) Subject: documentation/python: unify and improve formatting of values. X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~cjwatson/git?a=commitdiff_plain;h=3b8f161fa78169b5374193c422a1b352bcfede06;p=blog.git documentation/python: unify and improve formatting of values. Such as function default values or data values. Mainly done in order to turn enum values into links. Also ensuring the value is always HTML escaped. --- diff --git a/documentation/python.py b/documentation/python.py index 91485b18..4f35ddc8 100755 --- a/documentation/python.py +++ b/documentation/python.py @@ -44,7 +44,7 @@ import typing from enum import Enum from types import SimpleNamespace as Empty from importlib.machinery import SourceFileLoader -from typing import Tuple, Dict, Set, Any, List, Callable +from typing import Tuple, Dict, Set, Any, List, Callable, Optional from urllib.parse import urljoin from distutils.version import LooseVersion from docutils.transforms import Transform @@ -542,11 +542,15 @@ def make_name_link(state: State, referrer_path: List[str], type) -> str: if entry.type == EntryType.CLASS: url = state.config['URL_FORMATTER'](entry.type, entry.path)[1] else: - assert entry.type == EntryType.ENUM - parent_entry = state.name_map['.'.join(entry.path[:-1])] + if entry.type == EntryType.ENUM: + parent_index = -1 + else: + assert entry.type == EntryType.ENUM_VALUE + parent_index = -2 + parent_entry = state.name_map['.'.join(entry.path[:parent_index])] url = '{}#{}'.format( state.config['URL_FORMATTER'](parent_entry.type, parent_entry.path)[1], - state.config['ID_FORMATTER'](entry.type, entry.path[-1:])) + state.config['ID_FORMATTER'](entry.type, entry.path[parent_index:])) return '{}'.format(url, '.'.join(shortened_path)) @@ -689,6 +693,23 @@ def parse_pybind_docstring(state: State, referrer_path: List[str], doc: str) -> else: return [parse_pybind_signature(state, referrer_path, doc)] +# Used to format function default arguments and data values. *Not* pybind's +# function default arguments, as those are parsed from a string representation. +def format_value(state: State, referrer_path: List[str], value: str) -> Optional[str]: + if value is None: return str(value) + if isinstance(value, enum.Enum): + return make_name_link(state, referrer_path, '{}.{}.{}'.format(value.__class__.__module__, value.__class__.__qualname__, value.name)) + # pybind enums have the __members__ attribute instead. Since 2.3 pybind11 + # has .name like enum.Enum, but we still need to support 2.2 so hammer it + # out of a str() instead. + elif state.config['PYBIND11_COMPATIBILITY'] and hasattr(value.__class__, '__members__'): + return make_name_link(state, referrer_path, '{}.{}.{}'.format(value.__class__.__module__, value.__class__.__qualname__, str(value).partition('.')[2])) + elif '__repr__' in type(value).__dict__: + # TODO: tuples of non-representable values will still be ugly + return html.escape(repr(value)) + else: + return None + def extract_summary(state: State, external_docs, path: List[str], doc: str) -> str: # Prefer external docs, if available path_str = '.'.join(path) @@ -907,7 +928,17 @@ def extract_function_doc(state: State, parent, path: List[str], function) -> Lis else: param.type = type_link arg_types += [type] - param.default = html.escape(default or '') + if default: + # If the type is a registered enum, try to make a link to + # the value -- for an enum of type `module.EnumType`, + # assuming the default is rendered as `EnumType.VALUE` (not + # fully qualified), concatenate it together to have + # `module.EnumType.VALUE` + if type in state.name_map and state.name_map[type].type == EntryType.ENUM: + param.default = make_name_link(state, path, '.'.join(state.name_map[type].path[:-1] + [default])) + else: param.default = html.escape(default) + else: + param.default = None if type or default: out.has_complex_params = True # *args / **kwargs can still appear in the parsed signatures if @@ -972,7 +1003,7 @@ def extract_function_doc(state: State, parent, path: List[str], function) -> Lis if i.default is inspect.Signature.empty: param.default = None else: - param.default = repr(i.default) + param.default = format_value(state, path, i.default) or '…' out.has_complex_params = True param.kind = str(i.kind) out.params += [param] @@ -1048,12 +1079,7 @@ def extract_data_doc(state: State, parent, path: List[str], data): else: out.type = None - # The autogenerated is useless, so provide the value - # only if __repr__ is implemented for given type - if '__repr__' in type(data).__dict__: - out.value = html.escape(repr(data)) - else: - out.value = None + out.value = format_value(state, path, data) # External data summary, if provided path_str = '.'.join(path) diff --git a/documentation/test_python/inspect_annotations/inspect_annotations.html b/documentation/test_python/inspect_annotations/inspect_annotations.html index 4f62c8ea..f7d3b198 100644 --- a/documentation/test_python/inspect_annotations/inspect_annotations.html +++ b/documentation/test_python/inspect_annotations/inspect_annotations.html @@ -54,7 +54,7 @@
def annotation(param: typing.List[int], another: bool, - third: str = 'hello') -> Foo + third: str = 'hello') -> Foo
An annotated function
@@ -68,7 +68,7 @@
def no_annotation_default_param(param, another, - third = 'hello') + third = 'hello')
Non-annotated function
diff --git a/documentation/test_python/inspect_string/inspect_string.html b/documentation/test_python/inspect_string/inspect_string.html index 846a56cd..17fc86dc 100644 --- a/documentation/test_python/inspect_string/inspect_string.html +++ b/documentation/test_python/inspect_string/inspect_string.html @@ -101,7 +101,19 @@
- ENUM_THING + A_FALSE_VALUE = False +
+
+
+ A_NONE_VALUE = None +
+
+
+ A_ZERO_VALUE = 0 +
+
+
+ ENUM_THING = MyEnum.YAY
diff --git a/documentation/test_python/inspect_string/inspect_string/__init__.py b/documentation/test_python/inspect_string/inspect_string/__init__.py index c98973d9..3c5a019b 100644 --- a/documentation/test_python/inspect_string/inspect_string/__init__.py +++ b/documentation/test_python/inspect_string/inspect_string/__init__.py @@ -146,6 +146,12 @@ A_CONSTANT = 3.24 ENUM_THING = MyEnum.YAY +# These should have their value shown in the docs as well, even though they are +# not true-ish +A_ZERO_VALUE = 0 +A_FALSE_VALUE = False +A_NONE_VALUE = None + _PRIVATE_CONSTANT = -3 foo = Foo() diff --git a/documentation/test_python/inspect_type_links/inspect_type_links.second.html b/documentation/test_python/inspect_type_links/inspect_type_links.second.html index 5e4b3a0b..2e2b6b2f 100644 --- a/documentation/test_python/inspect_type_links/inspect_type_links.second.html +++ b/documentation/test_python/inspect_type_links/inspect_type_links.second.html @@ -57,6 +57,12 @@

Functions

+
+ def type_default_values(a: Enum = Enum.SECOND, + b: typing.Tuple[Foo] = (<class 'inspect_type_links.second.Foo'>,), + c: Foo = …) +
+
A function with default values, one enum, one tuple and the third nonrepresentable (yes, the tuple looks ugly)
def type_enum(a: Enum)
@@ -106,6 +112,10 @@ TYPE_DATA: Foo
+
+ TYPE_DATA_ENUM: Enum = Enum.SECOND +
+
TYPE_DATA_STRING_NESTED: typing.Tuple[Foo, typing.List[Enum], typing.Any] = {}
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links/second.py b/documentation/test_python/inspect_type_links/inspect_type_links/second.py index 31db9f1f..6ac7f75d 100644 --- a/documentation/test_python/inspect_type_links/inspect_type_links/second.py +++ b/documentation/test_python/inspect_type_links/inspect_type_links/second.py @@ -59,6 +59,11 @@ def type_return_string_nested() -> 'Tuple[Foo, List[Enum], Any]': def type_return_string_invalid(a: Foo) -> 'FooBar': """A function with invalid return string type annotation""" +def type_default_values(a: Enum = Enum.SECOND, b: Tuple[Foo] = (Foo, ), c: Foo = Foo()): + """A function with default values, one enum, one tuple and the third nonrepresentable (yes, the tuple looks ugly)""" + TYPE_DATA: Foo = Foo() TYPE_DATA_STRING_NESTED: 'Tuple[Foo, List[Enum], Any]' = {} + +TYPE_DATA_ENUM: Enum = Enum.SECOND diff --git a/documentation/test_python/link_formatting/link_formatting/__init__.py b/documentation/test_python/link_formatting/link_formatting/__init__.py index df961e2e..85fe02f1 100644 --- a/documentation/test_python/link_formatting/link_formatting/__init__.py +++ b/documentation/test_python/link_formatting/link_formatting/__init__.py @@ -22,5 +22,5 @@ class Class: def property(self) -> Enum: """A property.""" -def function(a: Class) -> Enum: +def function(a: Enum = Enum.SECOND_VALUE) -> Class: """A function.""" diff --git a/documentation/test_python/link_formatting/m.link_formatting.html b/documentation/test_python/link_formatting/m.link_formatting.html index 76dc48b7..03ad9ab1 100644 --- a/documentation/test_python/link_formatting/m.link_formatting.html +++ b/documentation/test_python/link_formatting/m.link_formatting.html @@ -86,7 +86,7 @@

Functions

- def function(a: Class) -> Enum + def function(a: Enum = Enum.SECOND_VALUE) -> Class
A function.
@@ -95,7 +95,7 @@

Data

- SOME_DATA: Enum + SOME_DATA: Enum = Enum.FIRST_VALUE
diff --git a/documentation/test_python/pybind_type_links/pybind_type_links.Foo.html b/documentation/test_python/pybind_type_links/pybind_type_links.Foo.html index baa2bd66..424c5bcc 100644 --- a/documentation/test_python/pybind_type_links/pybind_type_links.Foo.html +++ b/documentation/test_python/pybind_type_links/pybind_type_links.Foo.html @@ -59,7 +59,7 @@

Data

- TYPE_DATA: Enum = Enum.SECOND + TYPE_DATA: Enum = Enum.SECOND
diff --git a/documentation/test_python/pybind_type_links/pybind_type_links.cpp b/documentation/test_python/pybind_type_links/pybind_type_links.cpp index f6b3f8e6..eb54661a 100644 --- a/documentation/test_python/pybind_type_links/pybind_type_links.cpp +++ b/documentation/test_python/pybind_type_links/pybind_type_links.cpp @@ -34,7 +34,7 @@ PYBIND11_MODULE(pybind_type_links, m) { .def_readwrite("property", &Foo::property, "A property"); m - .def("type_enum", &typeEnum, "A function taking an enum") + .def("type_enum", &typeEnum, "A function taking an enum", py::arg("value") = Enum::Second) .def("type_return", &typeReturn, "A function returning a type") .def("type_nested", &typeNested, "A function with nested type annotation"); diff --git a/documentation/test_python/pybind_type_links/pybind_type_links.html b/documentation/test_python/pybind_type_links/pybind_type_links.html index 3cf47e7a..b15e23bb 100644 --- a/documentation/test_python/pybind_type_links/pybind_type_links.html +++ b/documentation/test_python/pybind_type_links/pybind_type_links.html @@ -58,7 +58,7 @@

Functions

- def type_enum(arg0: Enum, /) + def type_enum(value: Enum = Enum.SECOND)
A function taking an enum