Provide more control over automatic generation of docstrings (#486)

Added the docstring_options class, which gives global control over the generation of docstrings and function signatures.
This commit is contained in:
Alexander Stukowski 2016-11-15 12:38:05 +01:00 committed by Wenzel Jakob
parent 617fbcfc1e
commit 9a110e6da8
7 changed files with 190 additions and 11 deletions

View File

@ -160,6 +160,7 @@ set(PYBIND11_HEADERS
include/pybind11/common.h include/pybind11/common.h
include/pybind11/complex.h include/pybind11/complex.h
include/pybind11/descr.h include/pybind11/descr.h
include/pybind11/options.h
include/pybind11/eigen.h include/pybind11/eigen.h
include/pybind11/eval.h include/pybind11/eval.h
include/pybind11/functional.h include/pybind11/functional.h

View File

@ -202,5 +202,28 @@ work, it is important that all lines are indented consistently, i.e.:
---------- ----------
)mydelimiter"); )mydelimiter");
By default, pybind11 automatically generates and prepends a signature to the docstring of a function
registered with ``module::def()`` and ``class_::def()``. Sometimes this
behavior is not desirable, because you want to provide your own signature or remove
the docstring completely to exclude the function from the Sphinx documentation.
The class ``options`` allows you to selectively suppress auto-generated signatures:
.. code-block:: cpp
PYBIND11_PLUGIN(example) {
py::module m("example", "pybind11 example plugin");
py::options options;
options.disable_function_signatures();
m.def("add", [](int a, int b) { return a + b; }, "A function which adds two numbers");
return m.ptr();
}
Note that changes to the settings affect only function bindings created during the
lifetime of the ``options`` instance. When it goes out of scope at the end of the module's init function,
the default settings are restored to prevent unwanted side effects.
.. [#f4] http://www.sphinx-doc.org .. [#f4] http://www.sphinx-doc.org
.. [#f5] http://github.com/pybind/python_example .. [#f5] http://github.com/pybind/python_example

View File

@ -0,0 +1,65 @@
/*
pybind11/options.h: global settings that are configurable at runtime.
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "common.h"
NAMESPACE_BEGIN(pybind11)
class options {
public:
// Default RAII constructor, which leaves settings as they currently are.
options() : previous_state(global_state()) {}
// Class is non-copyable.
options(const options&) = delete;
options& operator=(const options&) = delete;
// Destructor, which restores settings that were in effect before.
~options() {
global_state() = previous_state;
}
// Setter methods (affect the global state):
options& disable_user_defined_docstrings() & { global_state().show_user_defined_docstrings = false; return *this; }
options& enable_user_defined_docstrings() & { global_state().show_user_defined_docstrings = true; return *this; }
options& disable_function_signatures() & { global_state().show_function_signatures = false; return *this; }
options& enable_function_signatures() & { global_state().show_function_signatures = true; return *this; }
// Getter methods (return the global state):
static bool show_user_defined_docstrings() { return global_state().show_user_defined_docstrings; }
static bool show_function_signatures() { return global_state().show_function_signatures; }
// This type is not meant to be allocated on the heap.
void* operator new(size_t) = delete;
private:
struct state {
bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings.
bool show_function_signatures = true; //< Include auto-generated function signatures in docstrings.
};
static state &global_state() {
static state instance;
return instance;
}
state previous_state;
};
NAMESPACE_END(pybind11)

View File

@ -34,6 +34,7 @@
#endif #endif
#include "attr.h" #include "attr.h"
#include "options.h"
NAMESPACE_BEGIN(pybind11) NAMESPACE_BEGIN(pybind11)
@ -306,7 +307,7 @@ protected:
int index = 0; int index = 0;
/* Create a nice pydoc rec including all signatures and /* Create a nice pydoc rec including all signatures and
docstrings of the functions in the overload chain */ docstrings of the functions in the overload chain */
if (chain) { if (chain && options::show_function_signatures()) {
// First a generic signature // First a generic signature
signatures += rec->name; signatures += rec->name;
signatures += "(*args, **kwargs)\n"; signatures += "(*args, **kwargs)\n";
@ -314,15 +315,17 @@ protected:
} }
// Then specific overload signatures // Then specific overload signatures
for (auto it = chain_start; it != nullptr; it = it->next) { for (auto it = chain_start; it != nullptr; it = it->next) {
if (chain) if (options::show_function_signatures()) {
signatures += std::to_string(++index) + ". "; if (chain)
signatures += rec->name; signatures += std::to_string(++index) + ". ";
signatures += it->signature; signatures += rec->name;
signatures += "\n"; signatures += it->signature;
if (it->doc && strlen(it->doc) > 0) {
signatures += "\n"; signatures += "\n";
}
if (it->doc && strlen(it->doc) > 0 && options::show_user_defined_docstrings()) {
if (options::show_function_signatures()) signatures += "\n";
signatures += it->doc; signatures += it->doc;
signatures += "\n"; if (options::show_function_signatures()) signatures += "\n";
} }
if (it->next) if (it->next)
signatures += "\n"; signatures += "\n";
@ -532,6 +535,7 @@ public:
PYBIND11_OBJECT_DEFAULT(module, object, PyModule_Check) PYBIND11_OBJECT_DEFAULT(module, object, PyModule_Check)
explicit module(const char *name, const char *doc = nullptr) { explicit module(const char *name, const char *doc = nullptr) {
if (!options::show_user_defined_docstrings()) doc = nullptr;
#if PY_MAJOR_VERSION >= 3 #if PY_MAJOR_VERSION >= 3
PyModuleDef *def = new PyModuleDef(); PyModuleDef *def = new PyModuleDef();
memset(def, 0, sizeof(PyModuleDef)); memset(def, 0, sizeof(PyModuleDef));
@ -562,7 +566,7 @@ public:
std::string full_name = std::string(PyModule_GetName(m_ptr)) std::string full_name = std::string(PyModule_GetName(m_ptr))
+ std::string(".") + std::string(name); + std::string(".") + std::string(name);
module result(PyImport_AddModule(full_name.c_str()), true); module result(PyImport_AddModule(full_name.c_str()), true);
if (doc) if (doc && options::show_user_defined_docstrings())
result.attr("__doc__") = pybind11::str(doc); result.attr("__doc__") = pybind11::str(doc);
attr(name) = result; attr(name) = result;
return result; return result;
@ -669,7 +673,7 @@ protected:
: std::string(rec->name)); : std::string(rec->name));
char *tp_doc = nullptr; char *tp_doc = nullptr;
if (rec->doc) { if (rec->doc && options::show_user_defined_docstrings()) {
/* Allocate memory for docstring (using PyObject_MALLOC, since /* Allocate memory for docstring (using PyObject_MALLOC, since
Python will free this later on) */ Python will free this later on) */
size_t size = strlen(rec->doc) + 1; size_t size = strlen(rec->doc) + 1;
@ -1114,7 +1118,7 @@ public:
rec_fset->doc = strdup(rec_fset->doc); rec_fset->doc = strdup(rec_fset->doc);
} }
} }
pybind11::str doc_obj = pybind11::str(rec_fget->doc ? rec_fget->doc : ""); pybind11::str doc_obj = pybind11::str((rec_fget->doc && pybind11::options::show_user_defined_docstrings()) ? rec_fget->doc : "");
object property( object property(
PyObject_CallFunctionObjArgs((PyObject *) &PyProperty_Type, fget.ptr() ? fget.ptr() : Py_None, PyObject_CallFunctionObjArgs((PyObject *) &PyProperty_Type, fget.ptr() ? fget.ptr() : Py_None,
fset.ptr() ? fset.ptr() : Py_None, Py_None, doc_obj.ptr(), nullptr), false); fset.ptr() ? fset.ptr() : Py_None, Py_None, doc_obj.ptr(), nullptr), false);

View File

@ -14,6 +14,7 @@ set(PYBIND11_TEST_FILES
test_class_args.cpp test_class_args.cpp
test_constants_and_functions.cpp test_constants_and_functions.cpp
test_copy_move_policies.cpp test_copy_move_policies.cpp
test_docstring_options.cpp
test_eigen.cpp test_eigen.cpp
test_enum.cpp test_enum.cpp
test_eval.cpp test_eval.cpp

View File

@ -0,0 +1,53 @@
/*
tests/test_docstring_options.cpp -- generation of docstrings and signatures
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "pybind11_tests.h"
struct DocstringTestFoo {
int value;
void setValue(int v) { value = v; }
int getValue() const { return value; }
};
test_initializer docstring_generation([](py::module &m) {
{
py::options options;
options.disable_function_signatures();
m.def("test_function1", [](int, int) {}, py::arg("a"), py::arg("b"));
m.def("test_function2", [](int, int) {}, py::arg("a"), py::arg("b"), "A custom docstring");
options.enable_function_signatures();
m.def("test_function3", [](int, int) {}, py::arg("a"), py::arg("b"));
m.def("test_function4", [](int, int) {}, py::arg("a"), py::arg("b"), "A custom docstring");
options.disable_function_signatures().disable_user_defined_docstrings();
m.def("test_function5", [](int, int) {}, py::arg("a"), py::arg("b"), "A custom docstring");
{
py::options nested_options;
nested_options.enable_user_defined_docstrings();
m.def("test_function6", [](int, int) {}, py::arg("a"), py::arg("b"), "A custom docstring");
}
}
m.def("test_function7", [](int, int) {}, py::arg("a"), py::arg("b"), "A custom docstring");
{
py::options options;
options.disable_user_defined_docstrings();
py::class_<DocstringTestFoo>(m, "DocstringTestFoo", "This is a class docstring")
.def_property("value_prop", &DocstringTestFoo::getValue, &DocstringTestFoo::setValue, "This is a property docstring")
;
}
});

View File

@ -0,0 +1,32 @@
from pybind11_tests import ConstructorStats
def test_docstring_options(capture):
from pybind11_tests import (test_function1, test_function2, test_function3,
test_function4, test_function5, test_function6,
test_function7, DocstringTestFoo)
# options.disable_function_signatures()
assert not test_function1.__doc__
assert test_function2.__doc__ == "A custom docstring"
# options.enable_function_signatures()
assert test_function3.__doc__ .startswith("test_function3(a: int, b: int) -> None")
assert test_function4.__doc__ .startswith("test_function4(a: int, b: int) -> None")
assert test_function4.__doc__ .endswith("A custom docstring\n")
# options.disable_function_signatures()
# options.disable_user_defined_docstrings()
assert not test_function5.__doc__
# nested options.enable_user_defined_docstrings()
assert test_function6.__doc__ == "A custom docstring"
# RAII destructor
assert test_function7.__doc__ .startswith("test_function7(a: int, b: int) -> None")
assert test_function7.__doc__ .endswith("A custom docstring\n")
# Suppression of user-defined docstrings for non-function objects
assert not DocstringTestFoo.__doc__
assert not DocstringTestFoo.value_prop.__doc__