Skip to content

Proposal for code helper to fix the binding docstring to make code documentation and completion happy #461

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9222203
Fix name in ClassEntry Binding_ObjectFactory.cpp
damienmarchal Sep 23, 2024
d4648bd
Add experimental docstring for stub generation
damienmarchal Sep 23, 2024
0e11a6c
Remove Sofa.Components.
damienmarchal Sep 23, 2024
ddead4d
Example on how to fix pybind11 type forward declaration so stubgens k…
damienmarchal Sep 23, 2024
fca789f
Example on how to fix pybind11 type forward declaration so stubgens k…
damienmarchal Sep 23, 2024
a9a61d9
Example on how to fix pybind11 type forward declaration so stubgens k…
damienmarchal Sep 23, 2024
7fd98e6
Remove Sofa.Components.
damienmarchal Sep 23, 2024
83ae2a9
Merge branch 'master' into pr-typehint-example-how-to-fix-definition
damienmarchal Oct 2, 2024
1df35ab
Completion Binding_Node draft
damienmarchal Oct 2, 2024
992fb0e
First working version
damienmarchal Oct 2, 2024
7d4549e
FIXUP
damienmarchal Oct 2, 2024
4894b15
FIXUP 2
damienmarchal Oct 2, 2024
dd119b7
WIP
damienmarchal Oct 11, 2024
8446233
Update tests to take into account removal of Sofa.Components.
damienmarchal Oct 11, 2024
c315a1e
Merge remote-tracking branch 'origin/master' into pr-remove-sofa-comp…
damienmarchal Oct 14, 2024
c781647
Merge remote-tracking branch 'origin/master' into pr-typehint-example…
damienmarchal Oct 14, 2024
abd221b
Merge remote-tracking branch 'cristal/pr-typehint-example-how-to-fix-…
damienmarchal Oct 14, 2024
6dd65ac
Merge remote-tracking branch 'cristal/pr-remove-sofa-components' into…
damienmarchal Oct 14, 2024
3a8efea
FIXUP
damienmarchal Oct 14, 2024
6cce21c
Add binding for BaseClass
damienmarchal Oct 17, 2024
423ba9e
Add binding for BaseClass
damienmarchal Oct 17, 2024
3ebaf13
Merge remote-tracking branch 'origin/master' into pr-typehint-example…
damienmarchal Oct 17, 2024
6b8f021
Generalize the pybind11 forward declaration of classes.
damienmarchal Oct 17, 2024
cbe2505
Add pybind11 forward declaration to Node
damienmarchal Oct 17, 2024
2bd7ff6
Generalize the pybind11 forward declaration of classes.
damienmarchal Oct 17, 2024
7df316b
Merge remote-tracking branch 'origin/master' into pr-typehint-example…
damienmarchal Nov 8, 2024
43dff61
Merge branch 'xp-component-stubgen' into xp-component-stubgen-2
damienmarchal Nov 8, 2024
56c541f
FIXUP
damienmarchal Nov 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set(HEADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/config.h.in
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/initModule.h
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/PythonEnvironment.h
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/PyBindHelper.h
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/SceneLoaderPY3.h
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/SpellingSuggestionHelper.h

Expand Down
190 changes: 190 additions & 0 deletions Plugin/src/SofaPython3/PyBindHelper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/******************************************************************************
* SofaPython3 plugin *
* (c) 2021 CNRS, University of Lille, INRIA *
* *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as published by *
* the Free Software Foundation; either version 2.1 of the License, or (at *
* your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
* for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*******************************************************************************
* Contact information: [email protected] *
******************************************************************************/
#pragma once

#include <SofaPython3/config.h>
#include <sofa/helper/logging/Messaging.h>
#include <string>
#include <vector>
#include <regex>
#include <pybind11/pybind11.h>

namespace sofapython3
{

class PythonMethodDescription
{
public:
void add_entry(const std::string& signature="")
{
signatures.push_back(signature);
docstrings.push_back("");
}

/// Parse the docstring generated by pybind11 to extract the different method signatures
/// and for each the associated docstring.
void parse_docstring(const std::string& name, std::string docstring)
{
std::smatch m;
std::string last;
std::regex e;

if(signatures.size()==0)
return;

// easy path. Take the signature, then consider the remaining as a docstring
if(signatures.size()==1)
{
e = std::regex("("+name+".*)");
if (std::regex_search (docstring, m,e))
{
for (auto x:m)
last = x;
if(signatures[0].size()==0)
signatures[0] = last;
docstrings[0] = m.suffix().str();
}
return;
}

int idx = 0;
e = std::regex("(1\\. )("+name+".*)");
while (std::regex_search (docstring, m,e))
{
for (auto x:m)
last = x;

if(signatures[idx].size()==0)
signatures[idx] = last;

if(idx>0)
docstrings[idx-1] = m.prefix().str();
idx++;
docstring = m.suffix().str();

auto pex = "("+std::to_string(idx+1)+"\\. )("+name+".*)";
e = std::regex(pex);
}
if(!docstring.empty())
docstrings[idx-1] = docstring;
}

/// Generates the docstring from the extracted ones from pybind11 and applying the
/// customs signatures, if any.
const std::string build_docstring(const std::string& name) const
{
std::stringstream tmp;
if(signatures.size() == 1)
{
tmp << signatures[0];
tmp << docstrings[0];
return tmp.str();
}
else
{
tmp << name <<"(*args, **kwargs)\n";
tmp << "Overloaded function.\n\n";
}
for(unsigned int i=0;i<docstrings.size();i++)
{
tmp << (i+1) << ". " << signatures[i] ;
tmp << docstrings[i] ;
}
return tmp.str();
}

private:
std::vector<std::string> signatures; //< signatures for the method overrides
std::vector<std::string> docstrings; //< docstrings for the method overrides

};

template<typename Class>
class def_method
{
public:
def_method(Class classI, const std::string& name_) :
classInstance(classI), name(name_)
{
}

virtual ~def_method()
{
description.parse_docstring(name, get_current_docstring());
set_docstring(description.build_docstring(name));
}

template<typename ... Args>
def_method& add_override(Args ... args)
{
classInstance.def(name.c_str(), args...);
description.add_entry();
return *this;
}

template<typename ... Args>
def_method& add_override(const char* signature, Args ... args)
{
classInstance.def(name.c_str(), args...);
description.add_entry(signature);
return *this;
}

std::string get_current_docstring() const {
std::string tmp;
// get the raw python object out of the classInstance
auto method = classInstance.attr(name.c_str()).ptr();
// if it is an instance method get the raw CFunction
if(Py_TYPE(method) == &PyInstanceMethod_Type){
method = ((PyInstanceMethodObject*)(method))->func;
}
// if it is a CFunction, get its docstring
if(Py_TYPE(method) == &PyCFunction_Type){
auto cmethod = (PyCFunctionObject*)method;
tmp = std::string(cmethod->m_ml->ml_doc);
}
return tmp;
}

void set_docstring(const std::string& docstring) const
{
// get the raw python object out of the classInstance
auto method = classInstance.attr(name.c_str()).ptr();
// if it is an instance method get the raw CFunction
if(Py_TYPE(method) == &PyInstanceMethod_Type){
method = ((PyInstanceMethodObject*)(method))->func;
}
// if it is a CFunction, get its docstring
if(Py_TYPE(method) == &PyCFunction_Type){
auto cmethod = (PyCFunctionObject*)method;
cmethod->m_ml->ml_doc = strdup(docstring.c_str());
}else
{
msg_error("SofaPython3") << "Unable to build docstring for method " << name;
}
}

private:
Class classInstance; //< stores the pybind::class_<XXX, >
std::string name; //< stores the name of the method we declares
PythonMethodDescription description;
};

}
15 changes: 12 additions & 3 deletions bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -449,12 +449,21 @@ py::object BindingBase::setDataValues(Base& self, py::kwargs kwargs)
return py::none();
}

auto getBaseBinding(py::module& m)
{
static py::class_<Base, py_shared_ptr<Base>> base(m, "Base", py::dynamic_attr(), doc::base::BaseClass);
return base;
}

void moduleForwardAddBase(py::module& m)
{
getBaseBinding(m);
}

void moduleAddBase(py::module &m)
{
moduleForwardAddBaseData(m);
moduleForwardAddBaseLink(m);
auto base = getBaseBinding(m);

py::class_<Base, py_shared_ptr<Base>> base(m, "Base", py::dynamic_attr(), doc::base::BaseClass);
/// set & get the name as string. The alternative is to access the data field using
/// obj.name.value = "aName"
base.def("getName", [](Base& b){ return b.getName(); }, sofapython3::doc::base::getName);
Expand Down
4 changes: 3 additions & 1 deletion bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ class BindingBase
static std::string getLinkPath(sofa::core::objectmodel::Base& self);
};


/// Forward declaration in pybind11.
/// more details in: https://github.com/sofa-framework/SofaPython3/pull/457
void moduleForwardAddBase(pybind11::module& m);
void moduleAddBase(pybind11::module& m);

} /// namespace sofapython3
48 changes: 48 additions & 0 deletions bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseClass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/******************************************************************************
* SOFA, Simulation Open-Framework Architecture *
* (c) 2021 INRIA, USTL, UJF, CNRS, MGH *
* *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as published by *
* the Free Software Foundation; either version 2.1 of the License, or (at *
* your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
* for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*******************************************************************************
* Contact information: [email protected] *
******************************************************************************/

#include <SofaPython3/Sofa/Core/Binding_BaseClass.h>
#include <SofaPython3/Sofa/Core/Binding_BaseClass_doc.h>
#include <sofa/core/objectmodel/BaseClass.h>

namespace sofapython3
{
using namespace sofa::core::objectmodel;

/// Makes an alias for the pybind11 namespace to increase readability.
namespace py { using namespace pybind11; }

auto getBaseClassBinding(py::module& m)
{
static py::class_<BaseClass, std::unique_ptr<BaseClass, py::nodelete>> base(m, "BaseClass", doc::baseclass::classdocstring);
return base;
}

void moduleForwardAddBaseClass(py::module& m)
{
getBaseClassBinding(m);
}

void moduleAddBaseClass(py::module &m)
{
// adds here the BaseClass's binded function (if any).
}

} /// namespace sofapython3
32 changes: 32 additions & 0 deletions bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseClass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/******************************************************************************
* SOFA, Simulation Open-Framework Architecture *
* (c) 2021 INRIA, USTL, UJF, CNRS, MGH *
* *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as published by *
* the Free Software Foundation; either version 2.1 of the License, or (at *
* your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
* for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*******************************************************************************
* Contact information: [email protected] *
******************************************************************************/

#pragma once

#include <pybind11/pybind11.h>

namespace sofapython3 {

/// Forward declaration in pybind11.
/// more details in: https://github.com/sofa-framework/SofaPython3/pull/457
void moduleForwardAddBaseClass(pybind11::module& m);
void moduleAddBaseClass(pybind11::module& m);

} /// namespace sofapython3
31 changes: 31 additions & 0 deletions bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseClass_doc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/******************************************************************************
* SOFA, Simulation Open-Framework Architecture *
* (c) 2021 INRIA, USTL, UJF, CNRS, MGH *
* *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as published by *
* the Free Software Foundation; either version 2.1 of the License, or (at *
* your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
* for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*******************************************************************************
* Contact information: [email protected] *
******************************************************************************/

#pragma once

namespace sofapython3::doc::baseclass {

static auto classdocstring =
R"(Class hierarchy reflection base class
This class provides information on the class and parent classes of components.
It is created by using the SOFA_CLASS macro on each new class declaration.
All classes deriving from Base should use the SOFA_CLASS macro within their declaration.)";

}
2 changes: 0 additions & 2 deletions bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,6 @@ void moduleForwardAddBaseData(py::module& m)
void moduleAddBaseData(py::module& m)
{
/// Register the BaseData binding into the pybind11 system.
//py::class_<BaseData, std::unique_ptr<sofa::core::objectmodel::BaseData, pybind11::nodelete>> data(m, "Data", sofapython3::doc::baseData::BaseDataClass);

auto data =getPythonClassForBaseData(m);
data.def("getName", [](BaseData& b){ return b.getName(); }, sofapython3::doc::baseData::getName);
data.def("setName", [](BaseData& b, const std::string& s){ b.setName(s); }, sofapython3::doc::baseData::setName);
Expand Down
2 changes: 2 additions & 0 deletions bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

namespace sofapython3 {

/// Forward declaration in pybind11.
/// more details in: https://github.com/sofa-framework/SofaPython3/pull/457
void moduleForwardAddBaseData(pybind11::module& m);
void moduleAddBaseData(pybind11::module& m);

Expand Down
2 changes: 2 additions & 0 deletions bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseLink.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

namespace sofapython3 {

/// Forward declaration in pybind11.
/// more details in: https://github.com/sofa-framework/SofaPython3/pull/457
void moduleForwardAddBaseLink(pybind11::module& m);
void moduleAddBaseLink(pybind11::module& m);

Expand Down
Loading
Loading