Trim spaces from RDProp strings to simulate reading from SDFiles (#8760)

* Trim spaces from RDProp strings to simulate reading from SDFiles

* Update documentation

* Use the correct doc strings

---------

Co-authored-by: Brian Kelley <bkelley@glysade.com>
This commit is contained in:
Brian Kelley
2025-09-26 00:14:23 -04:00
committed by GitHub
parent c15eb1a143
commit d304f9f416
8 changed files with 42 additions and 30 deletions

View File

@@ -445,14 +445,14 @@ struct atom_wrapper {
(python::arg("self"), python::arg("includePrivate") = true,
python::arg("includeComputed") = true,
python::arg("autoConvertStrings") = true),
"Returns a dictionary of the properties set on the Atom.\n"
" n.b. some properties cannot be converted to python types.\n")
getPropsAsDictDocString.c_str())
.def("UpdatePropertyCache", &Atom::updatePropertyCache,
(python::arg("self"), python::arg("strict") = true),
"Regenerates computed properties like implicit valence and ring "
"Regenerates computed properties like implicit valence and ring "
"information.\n\n")
.def("NeedsUpdatePropertyCache", &Atom::needsUpdatePropertyCache,
(python::arg("self")),
"Returns true or false depending on whether implicit and explicit "

View File

@@ -301,9 +301,7 @@ struct bond_wrapper {
(python::arg("self"), python::arg("includePrivate") = true,
python::arg("includeComputed") = true,
python::arg("autoConvertStrings") = true),
"Returns a dictionary of the properties set on the Bond.\n"
" n.b. some properties cannot be converted to python types.\n")
getPropsAsDictDocString.c_str())
;
python::enum_<Bond::BondType>("BondType")

View File

@@ -294,17 +294,7 @@ struct conformer_wrapper {
(python::arg("self"), python::arg("includePrivate") = false,
python::arg("includeComputed") = false,
python::arg("autoConvertStrings") = true),
"Returns a dictionary populated with the conformer's properties.\n"
" n.b. Some properties are not able to be converted to python "
"types.\n\n"
" ARGUMENTS:\n"
" - includePrivate: (optional) toggles inclusion of private "
"properties in the result set.\n"
" Defaults to False.\n"
" - includeComputed: (optional) toggles inclusion of computed "
"properties in the result set.\n"
" Defaults to False.\n\n"
" RETURNS: a dictionary\n");
getPropsAsDictDocString.c_str());
};
};
} // namespace RDKit

View File

@@ -797,17 +797,7 @@ struct mol_wrapper {
(python::arg("self"), python::arg("includePrivate") = false,
python::arg("includeComputed") = false,
python::arg("autoConvertStrings") = true),
"Returns a dictionary populated with the molecules properties.\n"
" n.b. Some properties are not able to be converted to python "
"types.\n\n"
" ARGUMENTS:\n"
" - includePrivate: (optional) toggles inclusion of private "
"properties in the result set.\n"
" Defaults to False.\n"
" - includeComputed: (optional) toggles inclusion of computed "
"properties in the result set.\n"
" Defaults to False.\n\n"
" RETURNS: a dictionary\n")
getPropsAsDictDocString.c_str())
.def("GetAromaticAtoms", MolGetAromaticAtoms,
python::return_value_policy<

View File

@@ -36,6 +36,7 @@
#include <RDBoost/Wrap.h>
#include <RDGeneral/Dict.h>
#include <algorithm>
#include <boost/algorithm/string.hpp>
namespace RDKit {
@@ -75,6 +76,20 @@ bool AddToDict(const U &ob, boost::python::dict &dict, const std::string &key) {
return true;
}
const std::string getPropsAsDictDocString = "Returns a dictionary populated with the conformer's properties.\n"
"When possible, string values will be trimmed and converted to integers and doubles\n"
" n.b. Some properties are not able to be converted to python "
"types.\n\n"
" ARGUMENTS:\n"
" - includePrivate: (optional) toggles inclusion of private "
"properties in the result set.\n"
" Defaults to False.\n"
" - includeComputed: (optional) toggles inclusion of computed "
"properties in the result set.\n"
" Defaults to False.\n\n"
" RETURNS: a dictionary\n";
template <class T>
boost::python::dict GetPropsAsDict(const T &obj, bool includePrivate,
bool includeComputed,
@@ -98,6 +113,7 @@ boost::python::dict GetPropsAsDict(const T &obj, bool includePrivate,
break;
case RDTypeTag::StringTag: {
auto value = from_rdvalue<std::string>(rdvalue.val);
boost::trim(value);
if (autoConvertStrings) {
// Auto convert strings to ints and double if possible
int ivalue;

View File

@@ -613,7 +613,9 @@ class TestCase(unittest.TestCase):
m = Chem.MolFromSmiles('C1=CN=CC=C1')
m.SetProp("int", "1000")
m.SetProp("double", "10000.123")
self.assertEqual(m.GetPropsAsDict(), {"int": 1000, "double": 10000.123})
m.SetProp("double spaces", " 10000.123 ")
self.assertEqual(m.GetPropsAsDict(), {
"int": 1000, "double": 10000.123, "double spaces": 10000.123 })
self.assertEqual(type(m.GetPropsAsDict()['int']), int)
self.assertEqual(type(m.GetPropsAsDict()['double']), float)

View File

@@ -39,6 +39,8 @@
#include "RDValue-taggedunion.h"
#endif
#include <boost/algorithm/string.hpp>
namespace RDKit {
// Common Casts (POD Casts are implementation dependent)
// string casts
@@ -274,7 +276,11 @@ typename boost::enable_if<boost::is_arithmetic<T>, T>::type from_rdvalue(
res = rdvalue_cast<T>(arg);
} catch (const std::bad_any_cast &exc) {
try {
res = boost::lexical_cast<T>(rdvalue_cast<std::string>(arg));
std::string val = rdvalue_cast<std::string>(arg);
// trim only the right characters, this mimics how SD values
// work on read, they will be trimmed by the MolFile parser
boost::trim_right(val);
res = boost::lexical_cast<T>(val);
} catch (...) {
throw exc;
}

View File

@@ -276,3 +276,13 @@ TEST_CASE("testIntConversions") {
REQUIRE_THROWS_AS(p.getProp<std::uint16_t>("foo"),
boost::numeric::negative_overflow);
}
TEST_CASE("testStringToDouble") {
RDProps p;
p.setProp<std::string>("foo", "123.0 ");
p.setProp<std::string>("bar", " 123.0 ");
REQUIRE(p.getProp<double>("foo") == 123.0);
REQUIRE(p.getProp<float>("foo") == 123.0f);
REQUIRE_THROWS_AS(p.getProp<double>("bar"),
std::bad_any_cast);
}