Skip to content

Commit 1433b78

Browse files
committed
[Sofa.Core] Use SpellingSuggestionHelper.h in Binding_Base and Binding_Node
1 parent 5977e5a commit 1433b78

2 files changed

Lines changed: 73 additions & 62 deletions

File tree

bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* Contact information: contact@sofa-framework.org *
1919
******************************************************************************/
2020

21+
#include "SofaPython3/SpellingSuggestionHelper.h"
2122
#include <pybind11/pybind11.h>
2223

2324
#include <pybind11/numpy.h>
@@ -339,15 +340,39 @@ py::list BindingBase::__dir__(Base* self)
339340
return list;
340341
}
341342

342-
py::object BindingBase::__getattr__(py::object self, const std::string& s)
343+
py::object BindingBase::__getattr__(py::object self, const std::string& attributeName)
343344
{
344-
py::object res = BindingBase::GetAttr( py::cast<Base*>(self), s, false );
345-
if( res.is_none() )
345+
// Search for attribute s.
346+
py::object res = BindingBase::GetAttr( py::cast<Base*>(self), attributeName, false );
347+
348+
// If there is one, then return it
349+
if( !res.is_none() )
350+
return res;
351+
352+
// If there is none, then search into the python dictionnary
353+
if( py::hasattr(self.attr("__dict__"), attributeName.c_str()) )
354+
return self.attr("__dict__")[attributeName.c_str()];
355+
356+
// If we reach this line, this indicate that no attribute was found. Maybe it is a misspelling
357+
// so let's build misspelling hints for the user.
358+
Base* selfbase = py::cast<Base*>(self);
359+
std::stringstream tmp;
360+
emitSpellingMessage(tmp, " - The data field named ", selfbase->getDataFields(), attributeName, 2, 0.6);
361+
emitSpellingMessage(tmp, " - The link named ", selfbase->getLinks(), attributeName, 2, 0.6);
362+
363+
// Also provide spelling hints on python functions.
364+
emitSpellingMessage(tmp, " - The python attribute named ", py::cast<py::dict>(py::type::of(self).attr("__dict__")), attributeName, 5, 0.8,
365+
[](const std::pair<py::handle, py::handle>& kv) { return py::cast<std::string>(std::get<0>(kv)); });
366+
367+
std::stringstream message;
368+
message << "Unable to find attribute: "+attributeName;
369+
if(!tmp.str().empty())
346370
{
347-
return self.attr("__dict__")[s.c_str()];
371+
message << msgendl;
372+
message << " You possibly wanted to access: " << msgendl;
373+
message << tmp.rdbuf();
348374
}
349-
350-
return res;
375+
throw py::attribute_error(message.str());
351376
}
352377

353378
void BindingBase::__setattr__(py::object self, const std::string& s, py::object value)

bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp

Lines changed: 42 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ using sofapython3::PythonEnvironment;
6262
#include <SofaPython3/Sofa/Core/Binding_NodeIterator.h>
6363
#include <SofaPython3/Sofa/Core/Binding_PythonScriptEvent.h>
6464

65+
#include <SofaPython3/SpellingSuggestionHelper.h>
66+
6567
using sofa::core::objectmodel::BaseObjectDescription;
6668

6769
#include <queue>
@@ -72,30 +74,11 @@ namespace py { using namespace pybind11; }
7274

7375
using sofa::simulation::Node;
7476

75-
namespace sofapython3 {
76-
77-
78-
template<class Iterable, class UnaryOperation, class PickingFunction>
79-
void fillVectorOfStringFrom(const Iterable& v, const UnaryOperation& op, const PickingFunction func)
77+
namespace sofapython3
8078
{
81-
std::transform(v.begin(), v.end(), op, func);
82-
}
8379

84-
template<class Iterable>
85-
std::ostream& emitSpellingMessage(std::ostream& ostream, const std::string& message, const Iterable& iterable, const std::string& name, sofa::Size numEntries=5, double thresold=0.6)
80+
namespace
8681
{
87-
std::vector<std::string> possibleNames;
88-
fillVectorOfStringFrom(iterable, std::back_inserter(possibleNames), [](const typename Iterable::value_type d) { return d->getName(); });
89-
90-
auto spellingSuggestions = getClosestMatch(name, possibleNames, numEntries, thresold);
91-
if(!spellingSuggestions.empty())
92-
{
93-
for(auto& [name, score] : spellingSuggestions)
94-
ostream << message << "'" << name << "' ("<< std::to_string((int)(100*score))+"% match)" << msgendl;
95-
}
96-
return ostream;
97-
}
98-
9982
bool checkParamUsage(BaseObjectDescription& desc, const Base* base)
10083
{
10184
std::vector<std::tuple<std::string, std::string>> paramErrors;
@@ -115,10 +98,8 @@ bool checkParamUsage(BaseObjectDescription& desc, const Base* base)
11598
std::vector<std::string> possibleNames;
11699
if(base)
117100
{
118-
for(auto& data : base->getDataFields())
119-
possibleNames.emplace_back(data->getName());
120-
for(auto& link : base->getLinks())
121-
possibleNames.emplace_back(link->getName());
101+
fillVectorOfStringFrom(base->getDataFields(), std::back_inserter(possibleNames), [](const BaseData* d){return d->getName();});
102+
fillVectorOfStringFrom(base->getLinks(), std::back_inserter(possibleNames), [](const BaseLink* l){return l->getName();});
122103
}
123104

124105
for(auto& [name, value] : paramErrors)
@@ -223,7 +204,7 @@ py::object getObject(Node &n, const std::string &name, const py::kwargs& kwargs)
223204
msg_deprecated(&n) << "Calling the method getObject() with extra arguments is not supported anymore."
224205
<< "To remove this message please refer to the documentation of the getObject method"
225206
<< msgendl
226-
<< PythonEnvironment::getPythonCallingPointString() ;
207+
<< PythonEnvironment::getPythonCallingPointString() ;
227208
}
228209

229210
BaseObject *object = n.getObject(name);
@@ -442,67 +423,70 @@ py::object removeChildByName(Node& n, const std::string name)
442423
std::unique_ptr<NodeIterator> property_children(Node* node)
443424
{
444425
return std::make_unique<NodeIterator>(node,
445-
[](Node* n) -> size_t { return n->child.size(); },
446-
[](Node* n, unsigned int index) -> Base::SPtr { return n->child[index]; },
447-
[](const Node* n, const std::string& name) { return n->getChild(name); },
448-
[](Node* n, unsigned int index) { n->removeChild(n->child[index]); }
449-
);
426+
[](Node* n) -> size_t { return n->child.size(); },
427+
[](Node* n, unsigned int index) -> Base::SPtr { return n->child[index]; },
428+
[](const Node* n, const std::string& name) { return n->getChild(name); },
429+
[](Node* n, unsigned int index) { n->removeChild(n->child[index]); }
430+
);
450431
}
451432

452433
std::unique_ptr<NodeIterator> property_parents(Node* node)
453434
{
454435
return std::make_unique<NodeIterator>(node,
455-
[](Node* n) -> size_t { return n->getNbParents(); },
456-
[](Node* n, unsigned int index) -> Node::SPtr {
457-
auto p = n->getParents();
458-
return static_cast<Node*>(p[index]);
459-
},
460-
[](const Node* n, const std::string& name) -> sofa::core::Base* {
461-
const auto& parents = n->getParents();
462-
return *std::find_if(parents.begin(),
463-
parents.end(),
464-
[name](BaseNode* child){ return child->getName() == name; });
465-
},
466-
[](Node*, unsigned int) {
467-
throw std::runtime_error("Removing a parent is not a supported operation. Please detach the node from the corresponding graph node.");
468-
});
436+
[](Node* n) -> size_t { return n->getNbParents(); },
437+
[](Node* n, unsigned int index) -> Node::SPtr {
438+
auto p = n->getParents();
439+
return static_cast<Node*>(p[index]);
440+
},
441+
[](const Node* n, const std::string& name) -> sofa::core::Base* {
442+
const auto& parents = n->getParents();
443+
return *std::find_if(parents.begin(),
444+
parents.end(),
445+
[name](BaseNode* child){ return child->getName() == name; });
446+
},
447+
[](Node*, unsigned int) {
448+
throw std::runtime_error("Removing a parent is not a supported operation. Please detach the node from the corresponding graph node.");
449+
});
469450
}
470451

471452
std::unique_ptr<NodeIterator> property_objects(Node* node)
472453
{
473454
return std::make_unique<NodeIterator>(node,
474-
[](Node* n) -> size_t { return n->object.size(); },
475-
[](Node* n, unsigned int index) -> Base::SPtr { return (n->object[index]);},
476-
[](const Node* n, const std::string& name) { return n->getObject(name); },
477-
[](Node* n, unsigned int index) { n->removeObject(n->object[index]);}
478-
);
455+
[](Node* n) -> size_t { return n->object.size(); },
456+
[](Node* n, unsigned int index) -> Base::SPtr { return (n->object[index]);},
457+
[](const Node* n, const std::string& name) { return n->getObject(name); },
458+
[](Node* n, unsigned int index) { n->removeObject(n->object[index]);}
459+
);
479460
}
480461

481-
py::object __getattr__(Node& self, const std::string& name)
462+
py::object __getattr__(py::object pyself, const std::string& name)
482463
{
464+
Node* selfnode = py::cast<Node*>(pyself);
483465
/// Search in the object lists
484-
BaseObject *object = self.getObject(name);
466+
BaseObject *object = selfnode->getObject(name);
485467
if (object)
486468
return PythonFactory::toPython(object);
487469

488470
/// Search in the child lists
489-
Node *child = self.getChild(name);
471+
Node *child = selfnode->getChild(name);
490472
if (child)
491473
return PythonFactory::toPython(child);
492474

493475
/// Search in the data & link lists
494-
py::object result = BindingBase::GetAttr(&self, name, false);
476+
py::object result = BindingBase::GetAttr(selfnode, name, false);
495477
if(!result.is_none())
496478
return result;
497479

498-
Node* selfnode = &self;
499-
500480
std::stringstream tmp;
501481
emitSpellingMessage(tmp, " - The data field named ", selfnode->getDataFields(), name, 2, 0.8);
502482
emitSpellingMessage(tmp, " - The link named ", selfnode->getDataFields(), name, 2, 0.8);
503483
emitSpellingMessage(tmp, " - The object named ", selfnode->getNodeObjects(), name, 2, 0.8);
504484
emitSpellingMessage(tmp, " - The child node named ", selfnode->getChildren(), name, 2, 0.8);
505485

486+
// Also provide spelling hints on python functions.
487+
emitSpellingMessage(tmp, " - The python attribute named ", py::cast<py::dict>(py::type::of(pyself).attr("__dict__")), name, 5, 0.8,
488+
[](const std::pair<py::handle, py::handle>& kv) { return py::cast<std::string>(std::get<0>(kv)); });
489+
506490
std::stringstream message;
507491
message << "Unable to find attribute: "+name;
508492
if(!tmp.str().empty())
@@ -613,6 +597,8 @@ void sendEvent(Node* self, py::object pyUserData, char* eventName)
613597
self->propagateEvent(sofa::core::execparams::defaultInstance(), &event);
614598
}
615599

600+
}
601+
616602
void moduleAddNode(py::module &m) {
617603
/// Register the complete parent-child relationship between Base and Node to the pybind11
618604
/// typing system.

0 commit comments

Comments
 (0)