PYMEM_DOMAIN_OBJ c:var 1 c-api/memory.html#c.$ -
...
-
`Module, class, enum, function, property and data docs`_
========================================================
.. code:: rst
.. py:function:: mymodule.MyContainer.add
+ :summary: Add a key/value pair to the container
:param key: Key to add
:param value: Corresponding value
:param overwrite_existing: Overwrite existing value if already present
:return: The inserted tuple or the existing
key/value pair in case ``overwrite_existing`` is not set
- Add a key/value pair to the container, optionally overwriting the
- previous value.
+ The operation has a :math:`\mathcal{O}(\log{}n)` complexity.
+
+For overloaded functions (such as those coming from pybind11), it's possible to
+specify the full signature to distinguish between particular overloads.
+Directives with the full signature have a priority, if no signature matches
+given function, a signature-less directive is searched for as a fallback.
+Example:
+
+.. code:: rst
+
+ .. py:function:: magnum.math.dot(a: magnum.Complex, b: magnum.Complex)
+ :summary: Dot product of two complex numbers
+
+ .. py:function:: magnum.math.dot(a: magnum.Quaternion, b: magnum.Quaternion)
+ :summary: Dot product of two quaternions
+
+ .. py:function:: magnum.math.dot
+ :summary: Dot product
+
+ .. this documentation will be used for all other overloads
content = content.strip()
return summary, ('\n'.join(['<p>{}</p>'.format(p) for p in content.split('\n\n')]) if content else None)
-def extract_summary(state: State, external_docs, path: List[str], doc: str) -> str:
+def extract_summary(state: State, external_docs, path: List[str], doc: str, *, signature=None) -> str:
# Prefer external docs, if available
path_str = '.'.join(path)
- if path_str in external_docs and external_docs[path_str]['summary']:
- return render_inline_rst(state, external_docs[path_str]['summary'])
+ if signature and path_str + signature in external_docs:
+ external_doc_entry = external_docs[path_str + signature]
+ elif path_str in external_docs:
+ external_doc_entry = external_docs[path_str]
+ else:
+ external_doc_entry = None
+ if external_doc_entry and external_doc_entry['summary']:
+ return render_inline_rst(state, external_doc_entry['summary'])
# some modules (xml.etree) have None as a docstring :(
# TODO: render as rst (config option for that)
return html.escape(inspect.cleandoc(doc or '').partition('\n\n')[0])
-def extract_docs(state: State, external_docs, path: List[str], doc: str) -> Tuple[str, str]:
+def extract_docs(state: State, external_docs, path: List[str], doc: str, *, signature=None) -> Tuple[str, str]:
path_str = '.'.join(path)
- if path_str in external_docs:
+ # If function signature is supplied, try that first
+ if signature and path_str + signature in external_docs:
+ external_doc_entry = external_docs[path_str + signature]
+ elif path_str in external_docs:
external_doc_entry = external_docs[path_str]
else:
external_doc_entry = None
if not state.config['SEARCH_DISABLED']:
page_url = state.name_map['.'.join(entry.path[:-1])].url
+ overloads = []
+
# Extract the signature from the docstring for pybind11, since it can't
# expose it to the metadata: https://github.com/pybind/pybind11/issues/990
# What's not solvable with metadata, however, are function overloads ---
out.name = entry.path[-1]
out.params = []
out.has_complex_params = False
- out.summary, out.content = extract_docs(state, state.function_docs, entry.path, summary)
- out.has_details = bool(out.content)
+ out.has_details = False
out.type, out.type_link = type, type_link
# There's no other way to check staticmethods than to check for
break
arg_types = []
+ signature = []
for i, arg in enumerate(args):
name, type, type_link, default = arg
param = Empty()
if i == 0 and name == 'self':
param.type, param.type_link = None, None
arg_types += [None]
+ signature += ['self']
else:
param.type, param.type_link = type, type_link
arg_types += [type]
+ signature += ['{}: {}'.format(name, type)]
if default:
# If the type is a registered enum, try to make a link to
# the value -- for an enum of type `module.EnumType`,
# thus name alone is not enough.
out.id = state.config['ID_FORMATTER'](EntryType.OVERLOADED_FUNCTION, entry.path[-1:] + arg_types)
- if not state.config['SEARCH_DISABLED']:
- result = Empty()
- result.flags = ResultFlag.from_type(ResultFlag.NONE, EntryType.FUNCTION)
- result.url = '{}#{}'.format(page_url, out.id)
- result.prefix = entry.path[:-1]
- result.name = entry.path[-1]
- result.params = []
- # If there's more than one overload, add the params as well to
- # disambiguate
- if len(funcs) != 1:
- for i in range(len(out.params)):
- param = out.params[i]
- result.params += ['{}: {}'.format(param.name, make_relative_name(state, entry.path, arg_types[i])) if arg_types[i] else param.name]
- state.search += [result]
+ # Get summary and details. Passing the signature as well, so
+ # different overloads can (but don't need to) have different docs.
+ out.summary, out.content = extract_docs(state, state.function_docs, entry.path, summary, signature='({})'.format(', '.join(signature)))
+ if out.content: out.has_details = True
overloads += [out]
- # TODO: assign docs and particular param docs to overloads
- return overloads
-
# Sane introspection path for non-pybind11 code
else:
out = Empty()
param.kind = str(i.kind)
out.params += [param]
- # Get docs for each param and for the return value
- path_str = '.'.join(entry.path)
- if path_str in state.function_docs:
- # Having no parameters documented is okay, having self
- # undocumented as well. But having the rest documented only
- # partially isn't okay.
- if state.function_docs[path_str]['params']:
- param_docs = state.function_docs[path_str]['params']
- used_params = set()
- for param in out.params:
- if param.name not in param_docs:
- if param.name != 'self':
- logging.warning("%s() parameter %s is not documented", path_str, param.name)
- continue
- param.content = render_inline_rst(state, param_docs[param.name])
- used_params.add(param.name)
- out.has_param_details = True
- out.has_details = True
- # Having unused param docs isn't okay either
- for name, _ in param_docs.items():
- if name not in used_params:
- logging.warning("%s() documents parameter %s, which isn't in the signature", path_str, name)
-
- if state.function_docs[path_str]['return']:
- out.return_value = render_inline_rst(state, state.function_docs[path_str]['return'])
- out.has_details = True
-
# In CPython, some builtin functions (such as math.log) do not provide
# metadata about their arguments. Source:
# https://docs.python.org/3/library/inspect.html#inspect.signature
except ValueError:
param = Empty()
param.name = '...'
- param.name_type = param.name
+ param.type, param.type_link = None, None
out.params = [param]
out.type, out.type_link = None, None
+ overloads = [out]
+
+ # Common path for parameter / 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]))
+
+ # Get docs for each param and for the return value. Try this
+ # particular overload first, if not found then fall back to generic
+ # docs for all overloads.
+ if path_str + signature in state.function_docs:
+ function_docs = state.function_docs[path_str + signature]
+ elif path_str in state.function_docs:
+ function_docs = state.function_docs[path_str]
+ else:
+ function_docs = None
+ if function_docs:
+ # Having no parameters documented is okay, having self
+ # undocumented as well. But having the rest documented only
+ # partially isn't okay.
+ if function_docs['params']:
+ param_docs = function_docs['params']
+ used_params = set()
+ for param in out.params:
+ if param.name not in param_docs:
+ if param.name != 'self':
+ logging.warning("%s%s parameter %s is not documented", path_str, signature, param.name)
+ continue
+ param.content = render_inline_rst(state, param_docs[param.name])
+ used_params.add(param.name)
+ out.has_param_details = True
+ out.has_details = True
+ # Having unused param docs isn't okay either
+ for name, _ in param_docs.items():
+ if name not in used_params:
+ logging.warning("%s%s documents parameter %s, which isn't in the signature", path_str, signature, name)
+
+ if function_docs['return']:
+ out.return_value = render_inline_rst(state, function_docs['return'])
+ out.has_details = True
+
if not state.config['SEARCH_DISABLED']:
result = Empty()
result.flags = ResultFlag.from_type(ResultFlag.NONE, EntryType.FUNCTION)
result.prefix = entry.path[:-1]
result.name = entry.path[-1]
result.params = []
+ # If the function has multiple overloads (e.g. pybind functions),
+ # add arguments to each to distinguish between them
+ if len(overloads) != 1:
+ for i in range(len(out.params)):
+ param = out.params[i]
+ result.params += ['{}: {}'.format(param.name, make_relative_name(state, entry.path, param.type)) if param.type else param.name]
state.search += [result]
- return [out]
+ return overloads
def extract_property_doc(state: State, parent, entry: Empty):
assert inspect.isdatadescriptor(entry.object)
find_package(pybind11 CONFIG REQUIRED)
-foreach(target pybind_signatures pybind_enums pybind_submodules pybind_type_links search_long_suffix_length)
+foreach(target pybind_signatures pybind_enums pybind_external_overload_docs pybind_submodules pybind_type_links search_long_suffix_length)
pybind11_add_module(${target} ${target}/${target}.cpp)
set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${target})
endforeach()
--- /dev/null
+.. py:function:: pybind_external_overload_docs.foo(a: int, b: typing.Tuple[int, str])
+ :param a: First parameter
+ :param b: Second parameter
+
+ Details for the first overload.
+
+.. py:function:: pybind_external_overload_docs.foo(arg0: typing.Callable[[float, typing.List[float]], int])
+ :param arg0: The caller
+
+ Complex signatures in the second overload should be matched properly, too.
+
+.. py:function:: pybind_external_overload_docs.foo
+ :param name: Ha!
+
+ This is a generic documentation and will be caught only by the third
+ overload. Luckily we just document that exact parameter.
+
+.. py:function:: pybind_external_overload_docs.foo(param: int)
+ :param param: This has a default value of 4 but that shouldn't be part of
+ the signature.
+
+ Fourth overload has a default value.
+
+.. py:function:: pybind_external_overload_docs.Class.foo(self, index: int)
+ :return: Nothing at all.
+
+ Class methods don't have type annotation on self.
+
+.. py:function:: pybind_external_overload_docs.Class.foo
+
+ And the fallback matching works there, too.
+
+.. py:function:: pybind_external_overload_docs.foo(first: int)
+ :param second: But the second argument doesn't exist?!
+
+.. py:function:: pybind_external_overload_docs.foo(terrible: wow)
+
+ This docs can't match any overload and thus get unused.
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>pybind_external_overload_docs.Class | 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="pybind_external_overload_docs.html">pybind_external_overload_docs</a>.<wbr/></span>Class <span class="m-thin">class</span>
+ </h1>
+ <p>My fun class!</p>
+ <div class="m-block m-default">
+ <h3>Contents</h3>
+ <ul>
+ <li>
+ Reference
+ <ul>
+ <li><a href="#methods">Methods</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ <section id="methods">
+ <h2><a href="#methods">Methods</a></h2>
+ <dl class="m-doc">
+ <dt>
+ <span class="m-doc-wrap-bumper">def <a href="#foo-745a3" class="m-doc">foo</a>(</span><span class="m-doc-wrap">self,
+ index: int) -> None</span>
+ </dt>
+ <dd>First overload</dd>
+ <dt>
+ <span class="m-doc-wrap-bumper">def <a href="#foo-6315e" class="m-doc">foo</a>(</span><span class="m-doc-wrap">self,
+ name: str) -> None</span>
+ </dt>
+ <dd>Second overload</dd>
+ </dl>
+ </section>
+ <section>
+ <h2>Method documentation</h2>
+ <section class="m-doc-details" id="foo-745a3"><div>
+ <h3>
+ <span class="m-doc-wrap-bumper">def pybind_external_overload_docs.<wbr />Class.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#foo-745a3" class="m-doc-self">foo</a>(</span><span class="m-doc-wrap">self,
+ index: int) -> None</span></span>
+ </h3>
+ <p>First overload</p>
+ <table class="m-table m-fullwidth m-flat">
+ <tfoot>
+ <tr>
+ <th style="width: 1%">Returns</th>
+ <td>Nothing at all.</td>
+ </tr>
+ </tfoot>
+ </table>
+<p>Class methods don't have type annotation on self.</p>
+ </div></section>
+ <section class="m-doc-details" id="foo-6315e"><div>
+ <h3>
+ <span class="m-doc-wrap-bumper">def pybind_external_overload_docs.<wbr />Class.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#foo-6315e" class="m-doc-self">foo</a>(</span><span class="m-doc-wrap">self,
+ name: str) -> None</span></span>
+ </h3>
+ <p>Second overload</p>
+<p>And the fallback matching works there, too.</p>
+ </div></section>
+ </section>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
--- /dev/null
+#include <functional>
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h> /* needed for std::vector! */
+#include <pybind11/functional.h> /* for std::function */
+
+namespace py = pybind11;
+
+void foo1(int, const std::tuple<int, std::string>&) {}
+void foo2(std::function<int(float, std::vector<float>&)>) {}
+void foo3(std::string) {}
+void foo4(int) {}
+
+struct Class {
+ void foo1(int) {}
+ void foo2(std::string) {}
+};
+
+PYBIND11_MODULE(pybind_external_overload_docs, m) {
+ m.doc() = "pybind11 external overload docs";
+
+ m
+ .def("foo", &foo1, "First overload", py::arg("a"), py::arg("b"))
+ .def("foo", &foo2, "Second overload")
+ .def("foo", &foo3, "Third overload", py::arg("name"))
+ .def("foo", &foo4, "Fourth overload", py::arg("param") = 4)
+ .def("foo", &foo4, "This will produce param documentation mismatch warnings", py::arg("first"));
+
+ py::class_<Class>(m, "Class", "My fun class!")
+ .def("foo", &Class::foo1, "First overload", py::arg("index"))
+ .def("foo", &Class::foo2, "Second overload", py::arg("name"));
+}
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>pybind_external_overload_docs | 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>
+ pybind_external_overload_docs <span class="m-thin">module</span>
+ </h1>
+ <p>pybind11 external overload docs</p>
+ <div class="m-block m-default">
+ <h3>Contents</h3>
+ <ul>
+ <li>
+ Reference
+ <ul>
+ <li><a href="#classes">Classes</a></li>
+ <li><a href="#functions">Functions</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ <section id="classes">
+ <h2><a href="#classes">Classes</a></h2>
+ <dl class="m-doc">
+ <dt>class <a href="pybind_external_overload_docs.Class.html" class="m-doc">Class</a></dt>
+ <dd>My fun class!</dd>
+ </dl>
+ </section>
+ <section id="functions">
+ <h2><a href="#functions">Functions</a></h2>
+ <dl class="m-doc">
+ <dt>
+ <span class="m-doc-wrap-bumper">def <a href="#foo-0a6d7" class="m-doc">foo</a>(</span><span class="m-doc-wrap">a: int,
+ b: typing.Tuple[int, str]) -> None</span>
+ </dt>
+ <dd>First overload</dd>
+ <dt>
+ <span class="m-doc-wrap-bumper">def <a href="#foo-515df" class="m-doc">foo</a>(</span><span class="m-doc-wrap">arg0: typing.Callable[[float, typing.List[float]], int]<span class="m-text m-dim">, /</span>) -> None</span>
+ </dt>
+ <dd>Second overload</dd>
+ <dt>
+ <span class="m-doc-wrap-bumper">def <a href="#foo-34424" class="m-doc">foo</a>(</span><span class="m-doc-wrap">name: str) -> None</span>
+ </dt>
+ <dd>Third overload</dd>
+ <dt>
+ <span class="m-doc-wrap-bumper">def <a href="#foo-46f8a" class="m-doc">foo</a>(</span><span class="m-doc-wrap">param: int = 4) -> None</span>
+ </dt>
+ <dd>Fourth overload</dd>
+ <dt id="foo-46f8a">
+ <span class="m-doc-wrap-bumper">def <a href="#foo-46f8a" class="m-doc-self">foo</a>(</span><span class="m-doc-wrap">first: int) -> None</span>
+ </dt>
+ <dd>This will produce param documentation mismatch warnings</dd>
+ </dl>
+ </section>
+ <section>
+ <h2>Function documentation</h2>
+ <section class="m-doc-details" id="foo-0a6d7"><div>
+ <h3>
+ <span class="m-doc-wrap-bumper">def pybind_external_overload_docs.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#foo-0a6d7" class="m-doc-self">foo</a>(</span><span class="m-doc-wrap">a: int,
+ b: typing.Tuple[int, str]) -> None</span></span>
+ </h3>
+ <p>First overload</p>
+ <table class="m-table m-fullwidth m-flat">
+ <thead>
+ <tr><th colspan="2">Parameters</th></tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td style="width: 1%">a</td>
+ <td>First parameter</td>
+ </tr>
+ <tr>
+ <td>b</td>
+ <td>Second parameter</td>
+ </tr>
+ </tbody>
+ </table>
+<p>Details for the first overload.</p>
+ </div></section>
+ <section class="m-doc-details" id="foo-515df"><div>
+ <h3>
+ <span class="m-doc-wrap-bumper">def pybind_external_overload_docs.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#foo-515df" class="m-doc-self">foo</a>(</span><span class="m-doc-wrap">arg0: typing.Callable[[float, typing.List[float]], int]<span class="m-text m-dim">, /</span>) -> None</span></span>
+ </h3>
+ <p>Second overload</p>
+ <table class="m-table m-fullwidth m-flat">
+ <thead>
+ <tr><th colspan="2">Parameters</th></tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td style="width: 1%">arg0</td>
+ <td>The caller</td>
+ </tr>
+ </tbody>
+ </table>
+<p>Complex signatures in the second overload should be matched properly, too.</p>
+ </div></section>
+ <section class="m-doc-details" id="foo-34424"><div>
+ <h3>
+ <span class="m-doc-wrap-bumper">def pybind_external_overload_docs.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#foo-34424" class="m-doc-self">foo</a>(</span><span class="m-doc-wrap">name: str) -> None</span></span>
+ </h3>
+ <p>Third overload</p>
+ <table class="m-table m-fullwidth m-flat">
+ <thead>
+ <tr><th colspan="2">Parameters</th></tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td style="width: 1%">name</td>
+ <td>Ha!</td>
+ </tr>
+ </tbody>
+ </table>
+<p>This is a generic documentation and will be caught only by the third
+overload. Luckily we just document that exact parameter.</p>
+ </div></section>
+ <section class="m-doc-details" id="foo-46f8a"><div>
+ <h3>
+ <span class="m-doc-wrap-bumper">def pybind_external_overload_docs.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#foo-46f8a" class="m-doc-self">foo</a>(</span><span class="m-doc-wrap">param: int = 4) -> None</span></span>
+ </h3>
+ <p>Fourth overload</p>
+ <table class="m-table m-fullwidth m-flat">
+ <thead>
+ <tr><th colspan="2">Parameters</th></tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td style="width: 1%">param</td>
+ <td>This has a default value of 4 but that shouldn't be part of
+the signature.</td>
+ </tr>
+ </tbody>
+ </table>
+<p>Fourth overload has a default value.</p>
+ </div></section>
+ </section>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
})
self.assertEqual(*self.actual_expected_contents('pybind_type_links.html'))
self.assertEqual(*self.actual_expected_contents('pybind_type_links.Foo.html'))
+
+class ExternalOverloadDocs(BaseInspectTestCase):
+ def test(self):
+ self.run_python({
+ 'PLUGINS': ['m.sphinx'],
+ 'INPUT_DOCS': ['docs.rst'],
+ 'PYBIND11_COMPATIBILITY': True
+ })
+ self.assertEqual(*self.actual_expected_contents('pybind_external_overload_docs.html'))
+ self.assertEqual(*self.actual_expected_contents('pybind_external_overload_docs.Class.html'))