From bd0257cbe5bc65f11377c9bd7b53b1c6ea58b9fc Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Wed, 7 Aug 2002 23:03:02 +0000 Subject: [PATCH] Full docstring support [SVN r14734] --- include/boost/python/class.hpp | 57 ++++++++++--------- include/boost/python/detail/module_base.hpp | 2 +- include/boost/python/module.hpp | 24 +++++--- include/boost/python/object/class.hpp | 1 + include/boost/python/object/function.hpp | 8 +++ src/module.cpp | 4 +- src/object/class.cpp | 5 +- src/object/function.cpp | 42 +++++++++++--- test/Jamfile | 1 + test/docstring.cpp | 61 +++++++++++++++++++++ test/docstring.py | 51 +++++++++++++++++ 11 files changed, 211 insertions(+), 45 deletions(-) create mode 100644 test/docstring.cpp create mode 100644 test/docstring.py diff --git a/include/boost/python/class.hpp b/include/boost/python/class.hpp index 799fd498..4af8d3e7 100644 --- a/include/boost/python/class.hpp +++ b/include/boost/python/class.hpp @@ -27,6 +27,7 @@ # include # include # include +# include namespace boost { namespace python { @@ -57,7 +58,7 @@ namespace detail template static inline void register_copy_constructor(mpl::bool_t const&, Holder*, object const&, T* = 0) { - } + } } // @@ -93,7 +94,7 @@ class class_ : public objects::class_base // Construct with the class name. [ Would have used a default // argument but gcc-2.95.2 choked on typeid(T).name() as a default // parameter value] - class_(char const* name); + class_(char const* name, char const* doc = 0); // Wrap a member function or a non-member function which can take @@ -102,27 +103,25 @@ class class_ : public objects::class_base template self& def(char const* name, F f) { - this->def_impl(name, f, 0, &f); + this->def_impl(name, f, default_call_policies(), 0, &f); return *this; } - template - self& def(char const* name, Fn fn, CallPolicy policy) + template + self& def(char const* name, Fn fn, CallPolicyOrDoc const& policy_or_doc, char const* doc = 0) { - return this->def(name - , boost::python::make_function( - // This bit of nastiness casts F to a member function of T if possible. - detail::member_function_cast::stage1(fn).stage2((T*)0).stage3(fn) - , policy) - ); + typedef detail::def_helper helper; + + this->def_impl( + name, fn, helper::get_policy(policy_or_doc), helper::get_doc(policy_or_doc, doc), &fn); + + return *this; } template self& def(detail::operator_ const& op) { typedef detail::operator_ op_t; - // Use function::add_to_namespace to achieve overloading if - // appropriate. return this->def(op.name(), &op_t::template apply::execute); } @@ -139,15 +138,19 @@ class class_ : public objects::class_base ); } - template - self& def_init(Args const&, CallPolicy policy) + template + self& def_init(Args const&, CallPolicyOrDoc const& policy_or_doc, char const* doc = 0) { - return this->def("__init__", + typedef detail::def_helper helper; + + return this->def( + "__init__", python::make_constructor( - policy + helper::get_policy(policy_or_doc) // Using runtime type selection works around a CWPro7 bug. , objects::select_holder((held_type*)0).get() ) + , helper::get_doc(policy_or_doc, doc) ); } @@ -201,22 +204,26 @@ class class_ : public objects::class_base private: // helper functions - template - inline void def_impl(char const* name, F const& f, char const* doc, ...) + template + inline void def_impl(char const* name, Fn fn, Policies const& policies + , char const* doc, ...) { objects::add_to_namespace( - *this, name, make_function( + *this, name, + make_function( // This bit of nastiness casts F to a member function of T if possible. - detail::member_function_cast::stage1(f).stage2((T*)0).stage3(f)) + detail::member_function_cast::stage1(fn).stage2((T*)0).stage3(fn) + , policies) , doc); } template - inline void def_impl(char const* name, F const& f, char const* doc, object const volatile*) + inline void def_impl(char const* name, F f, default_call_policies const& + , char const* doc, object const*) { objects::add_to_namespace(*this, name, f, doc); } - + private: // types typedef objects::class_id class_id; @@ -266,8 +273,8 @@ inline class_::class_() } template -inline class_::class_(char const* name) - : base(name, id_vector::size, id_vector().ids) +inline class_::class_(char const* name, char const* doc) + : base(name, id_vector::size, id_vector().ids, doc) { // register converters objects::register_class_from_python(); diff --git a/include/boost/python/detail/module_base.hpp b/include/boost/python/detail/module_base.hpp index 4ef00cfd..218caccf 100644 --- a/include/boost/python/detail/module_base.hpp +++ b/include/boost/python/detail/module_base.hpp @@ -15,7 +15,7 @@ class BOOST_PYTHON_DECL module_base { public: // Create a module. REQUIRES: only one module is created per module. - module_base(const char* name); + module_base(char const* name, char const* doc = 0); ~module_base(); // Add elements to the module diff --git a/include/boost/python/module.hpp b/include/boost/python/module.hpp index a4d7a4db..af6434ce 100644 --- a/include/boost/python/module.hpp +++ b/include/boost/python/module.hpp @@ -13,6 +13,7 @@ # include # include # include +# include namespace boost { namespace python { @@ -21,8 +22,8 @@ class module : public detail::module_base public: typedef detail::module_base base; - module(const char* name) - : base(name) {} + module(char const* name, char const* doc = 0) + : base(name, doc) {} // Add elements to the module template @@ -42,17 +43,22 @@ class module : public detail::module_base } template - module& def(char const* name, Fn fn, char const* doc = 0) + module& def(char const* name, Fn fn) { - this->setattr_doc(name, boost::python::make_function(fn), doc); + this->setattr_doc( + name, boost::python::make_function(fn), 0); + return *this; } - - - template - module& def(char const* name, Fn fn, ResultHandler handler, char const* doc = 0) + template + module& def(char const* name, Fn fn, CallPolicyOrDoc const& policy_or_doc, char const* doc = 0) { - this->setattr_doc(name, boost::python::make_function(fn, handler), doc); + typedef detail::def_helper helper; + + this->setattr_doc( + name, boost::python::make_function(fn, helper::get_policy(policy_or_doc)), + helper::get_doc(policy_or_doc, doc)); + return *this; } }; diff --git a/include/boost/python/object/class.hpp b/include/boost/python/object/class.hpp index d6af07e1..44aa31f6 100644 --- a/include/boost/python/object/class.hpp +++ b/include/boost/python/object/class.hpp @@ -33,6 +33,7 @@ struct BOOST_PYTHON_DECL class_base : python::api::object , std::size_t num_types // A list of class_ids. The first is the type , class_id const*const types // this is wrapping. The rest are the types of // any bases. + , char const* doc = 0 // Docstring, if any. ); // Retrieve the underlying object diff --git a/include/boost/python/object/function.hpp b/include/boost/python/object/function.hpp index 1e5fdfe8..c14f57e3 100644 --- a/include/boost/python/object/function.hpp +++ b/include/boost/python/object/function.hpp @@ -34,6 +34,8 @@ struct BOOST_PYTHON_DECL function : PyObject object const& doc() const; void doc(object const& x); + object const& name() const; + private: // helper functions void argument_error(PyObject* args, PyObject* keywords) const; void add_overload(handle const&); @@ -43,6 +45,7 @@ struct BOOST_PYTHON_DECL function : PyObject unsigned m_min_args; unsigned m_max_args; handle m_overloads; + object m_name; object m_doc; }; @@ -59,6 +62,11 @@ inline void function::doc(object const& x) this->m_doc = x; } +inline object const& function::name() const +{ + return this->m_name; +} + }}} // namespace boost::python::objects #endif // FUNCTION_DWA20011214_HPP diff --git a/src/module.cpp b/src/module.cpp index 4563db31..549ba782 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -16,12 +16,14 @@ namespace boost { namespace python { namespace detail { -module_base::module_base(const char* name) +module_base::module_base(char const* name, char const* doc) : m_module( allow_null(python::borrowed( scope().ptr() ))) { + if (doc != 0) + scope().attr("__doc__") = doc; } module_base::~module_base() diff --git a/src/object/class.cpp b/src/object/class.cpp index 4306a684..83287125 100644 --- a/src/object/class.cpp +++ b/src/object/class.cpp @@ -279,7 +279,7 @@ namespace objects } class_base::class_base( - char const* name, std::size_t num_types, class_id const* const types) + char const* name, std::size_t num_types, class_id const* const types, char const* doc) : object(new_class(name, num_types, types)) { // Insert the new class object in the registry @@ -288,6 +288,9 @@ namespace objects // Class object is leaked, for now converters.class_object = (PyTypeObject*)incref(this->ptr()); + + if (doc) + this->attr("__doc__") = doc; } extern "C" diff --git a/src/object/function.cpp b/src/object/function.cpp index a09717fc..ec461383 100644 --- a/src/object/function.cpp +++ b/src/object/function.cpp @@ -24,6 +24,7 @@ function::function(py_function const& implementation, unsigned min_args, unsigne , m_max_args(std::max(max_args,min_args)) { PyObject* p = this; + ::PyType_Ready(&function_type); PyObject_INIT(p, &function_type); } @@ -162,6 +163,7 @@ void function::add_to_namespace( if (attribute.ptr()->ob_type == &function_type) { + function* new_func = downcast(attribute.ptr()); PyObject* dict = 0; if (PyClass_Check(ns)) @@ -180,17 +182,24 @@ void function::add_to_namespace( allow_null(downcast(::PyObject_GetItem(dict, name.ptr()))) ); - if (existing.get()) + if (existing) { if (existing->ob_type == &function_type) - static_cast(attribute.ptr())->add_overload(existing); + new_func->add_overload(existing); } - // Binary operators need an additional overload which returns NotImplemented else if (is_binary_operator(name_)) { - static_cast(attribute.ptr())->add_overload( - not_implemented_function()); + // Binary operators need an additional overload which + // returns NotImplemented, so that Python will try the + // lxxx functions on the other operand. We add this + // overload already when no overloads for the operator + // already exist. + new_func->add_overload(not_implemented_function()); } + + // A function is named the first time it is added to a namespace. + if (new_func->name().ptr() == Py_None) + new_func->m_name = name; } // The PyObject_GetAttrString() call above left an active error @@ -259,21 +268,38 @@ extern "C" return result; } + // + // Here we're using the function's tp_getset rather than its + // tp_members to set up __doc__ and __name__, because tp_members + // really depends on having a POD object type (it relies on + // offsets). It might make sense to reformulate function as a POD + // at some point, but this is much more expedient. + // static PyObject* function_get_doc(PyObject* op, void*) { function* f = downcast(op); - return incref(f->doc().ptr()); + return python::incref(f->doc().ptr()); } - static int function_set_doc(PyObject* op, PyObject* doc) + static int function_set_doc(PyObject* op, PyObject* doc, void*) { function* f = downcast(op); f->doc(doc ? object(python::detail::borrowed_reference(doc)) : object()); return 0; } + static PyObject* function_get_name(PyObject* op, void*) + { + function* f = downcast(op); + if (f->name().ptr() == Py_None) + return PyString_InternFromString(""); + else + return python::incref(f->name().ptr()); + } + static PyGetSetDef function_getsetlist[] = { - {"__doc__", function_get_doc, (setter)function_set_doc}, + {"__name__", function_get_name, 0 }, + {"__doc__", function_get_doc, function_set_doc}, {NULL} /* Sentinel */ }; } diff --git a/test/Jamfile b/test/Jamfile index 21be34fe..0752ddc2 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -55,6 +55,7 @@ rule bpl-test ( name ? : files * ) } bpl-test minimal ; +bpl-test docstring ; bpl-test pearu1 : test_cltree.py cltree.cpp ; bpl-test try : newtest.py m1.cpp m2.cpp ; bpl-test builtin_converters : test_builtin_converters.py test_builtin_converters.cpp ; diff --git a/test/docstring.cpp b/test/docstring.cpp new file mode 100644 index 00000000..e8f63971 --- /dev/null +++ b/test/docstring.cpp @@ -0,0 +1,61 @@ +// Copyright David Abrahams 2002. Permission to copy, use, +// modify, sell and distribute this software is granted provided this +// copyright notice appears in all copies. This software is provided +// "as is" without express or implied warranty, and with no claim as +// to its suitability for any purpose. +#include +#include +#include +#include +#include +#include "test_class.hpp" + +// Just use math.h here; trying to use std::pow() causes too much +// trouble for non-conforming compilers and libraries. +#include + +using namespace boost::python; + +typedef test_class<> X; + +X* create(int x) +{ + return new X(x); +} + +unsigned long fact(unsigned long n) +{ + return n <= 1 ? n : n * fact(n - 1); +} + +BOOST_PYTHON_MODULE_INIT(docstring_ext) +{ + module("docstring_ext", + + "A simple test module for documentation strings\n" + "Exercised by docstring.py" + ) + + .add( + class_("X", + "A simple class wrapper around a C++ int\n" + "includes some error-checking" + ) + + .def_init(args(), + "this is the __init__ function\n" + "its documentation has two lines." + ) + + .def("value", &X::value, + "gets the value of the object") + ) + + .def("create", create, return_value_policy(), + "creates a new X object") + + .def("fact", fact, "compute the factorial") + ; +} + +#include "module_tail.cpp" diff --git a/test/docstring.py b/test/docstring.py new file mode 100644 index 00000000..54bbfc45 --- /dev/null +++ b/test/docstring.py @@ -0,0 +1,51 @@ +''' +>>> from docstring_ext import * + +>>> def printdoc(x): +... print x.__doc__ + +>>> printdoc(X) +A simple class wrapper around a C++ int +includes some error-checking + +>>> printdoc(X.__init__) +this is the __init__ function +its documentation has two lines. + +>>> printdoc(X.value) +gets the value of the object + +>>> printdoc(create) +creates a new X object + +>>> printdoc(fact) +compute the factorial +''' + +def run(args = None): + import sys + import doctest + + if args is not None: + sys.argv = args + + import docstring_ext + + result = doctest.testmod(sys.modules.get(__name__)) + + try: + print 'printing module help:' + help(docstring_ext) + except object, x: + print '********* failed **********' + print x + result = list(result) + result[0] += 1 + return tuple(result) + + return result + +if __name__ == '__main__': + print "running..." + import sys + sys.exit(run()[0])