Add optional default_val parameter to GetProp() (#9242)

* SHARED-12256: Add test and change function.

* SHARED-12256: Update to only wrapping changes.

* SHARED-12256: Parameterize tests.

* SHARED-12256: GetPropIfPresent changes.

* Revert "SHARED-12256: GetPropIfPresent changes."

This reverts commit f598f8c161.

* SHARED-12256: Make default the keyword in the boost wrappings.

* SHARED-12256: Overload function instead of using a sentinel.

* SHARED-12256: Extend GetProp changes.

* SHARED-12256: Add entry point for tests and fix tests.
This commit is contained in:
Emily Rhodes
2026-05-06 00:09:11 -04:00
committed by GitHub
parent b54cbac151
commit 3836049ab2
8 changed files with 430 additions and 6 deletions

View File

@@ -344,6 +344,18 @@ struct atom_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def(
"GetProp", GetPyPropOrDefault<Atom>,
(python::arg("self"), python::arg("key"),
python::arg("autoConvert") = false,
python::arg("default")),
"Returns the value of the property.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a string).\n\n"
" - autoConvert: if True attempt to convert the property into a python object\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: the property value, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("SetIntProp", AtomSetProp<int>,
(python::arg("self"), python::arg("key"), python::arg("val")),
@@ -369,6 +381,14 @@ struct atom_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetIntProp", GetPropOrDefault<Atom, int>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the value of the property.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (an int).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: an int, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetUnsignedProp", GetProp<Atom, unsigned>,
python::args("self", "key"),
@@ -381,6 +401,14 @@ struct atom_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetUnsignedProp", GetPropOrDefault<Atom, unsigned>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the value of the property.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (an unsigned integer).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: an integer, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("SetDoubleProp", AtomSetProp<double>,
(python::arg("self"), python::arg("key"), python::arg("val")),
"Sets an atomic property\n\n"
@@ -398,6 +426,14 @@ struct atom_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetDoubleProp", GetPropOrDefault<Atom, double>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the value of the property.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a double).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: a double, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("SetBoolProp", AtomSetProp<bool>,
(python::arg("self"), python::arg("key"), python::arg("val")),
@@ -415,6 +451,14 @@ struct atom_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetBoolProp", GetPropOrDefault<Atom, bool>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the value of the property.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a bool).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: a bool, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("SetExplicitBitVectProp", AtomSetProp<ExplicitBitVect>,
(python::arg("self"), python::arg("key"), python::arg("val")),

View File

@@ -216,6 +216,18 @@ struct bond_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def(
"GetProp", GetPyPropOrDefault<Bond>,
(python::arg("self"), python::arg("key"),
python::arg("autoConvert") = false,
python::arg("default")),
"Returns the value of the property.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a string).\n\n"
" - autoConvert: if True attempt to convert the property into a python object\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: the property value, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("SetIntProp", BondSetProp<int>,
(python::arg("self"), python::arg("key"), python::arg("val")),
"Sets a bond property\n\n"
@@ -239,6 +251,14 @@ struct bond_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetIntProp", GetPropOrDefault<Bond, int>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the value of the property.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (an int).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: an int, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetUnsignedProp", GetProp<Bond, unsigned int>,
python::args("self", "key"),
@@ -251,6 +271,14 @@ struct bond_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetUnsignedProp", GetPropOrDefault<Bond, unsigned int>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the value of the property.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (an unsigned integer).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: an integer, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("SetDoubleProp", BondSetProp<double>,
(python::arg("self"), python::arg("key"), python::arg("val")),
@@ -269,6 +297,14 @@ struct bond_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetDoubleProp", GetPropOrDefault<Bond, double>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the value of the property.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a double).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: a double, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("SetBoolProp", BondSetProp<bool>,
(python::arg("self"), python::arg("key"), python::arg("val")),
@@ -286,6 +322,14 @@ struct bond_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetBoolProp", GetPropOrDefault<Bond, bool>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the value of the property.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a boolean).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: a bool, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("HasProp", BondHasProp, python::args("self", "key"),
"Queries a Bond to see if a particular property has been "

View File

@@ -61,6 +61,9 @@ endif(RDK_BUILD_COMPRESSED_SUPPLIERS)
add_pytest(pyGraphMolWrap
${CMAKE_CURRENT_SOURCE_DIR}/rough_test.py)
add_pytest(pyTestGetPropDefault
${CMAKE_CURRENT_SOURCE_DIR}/testGetPropDefault.py)
add_pytest(pyTestConformerWrap
${CMAKE_CURRENT_SOURCE_DIR}/testConformer.py)

View File

@@ -240,6 +240,18 @@ struct conformer_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def(
"GetProp", GetPyPropOrDefault<Conformer>,
(python::arg("self"), python::arg("key"),
python::arg("autoConvert") = false,
python::arg("default")),
"Returns the value of the property.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a string).\n\n"
" - autoConvert: if True attempt to convert the property into a python object\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: the property value, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetDoubleProp", GetProp<Conformer, double>,
python::args("self", "key"),
"Returns the double value of the property if possible.\n\n"
@@ -249,6 +261,14 @@ struct conformer_wrapper {
" NOTE:\n"
" - If the property has not been set, a KeyError exception "
"will be raised.\n")
.def("GetDoubleProp", GetPropOrDefault<Conformer, double>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the double value of the property if possible.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a string).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: a double, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetIntProp", GetProp<Conformer, int>, python::args("self", "key"),
"Returns the integer value of the property if possible.\n\n"
" ARGUMENTS:\n"
@@ -257,6 +277,14 @@ struct conformer_wrapper {
" NOTE:\n"
" - If the property has not been set, a KeyError exception "
"will be raised.\n")
.def("GetIntProp", GetPropOrDefault<Conformer, int>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the integer value of the property if possible.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a string).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: an integer, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetUnsignedProp", GetProp<Conformer, unsigned int>,
python::args("self", "key"),
"Returns the unsigned int value of the property if possible.\n\n"
@@ -266,6 +294,14 @@ struct conformer_wrapper {
" NOTE:\n"
" - If the property has not been set, a KeyError exception "
"will be raised.\n")
.def("GetUnsignedProp", GetPropOrDefault<Conformer, unsigned int>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the unsigned int value of the property if possible.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a string).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: an unsigned integer, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetBoolProp", GetProp<Conformer, bool>,
python::args("self", "key"),
"Returns the Bool value of the property if possible.\n\n"
@@ -275,6 +311,14 @@ struct conformer_wrapper {
" NOTE:\n"
" - If the property has not been set, a KeyError exception "
"will be raised.\n")
.def("GetBoolProp", GetPropOrDefault<Conformer, bool>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the Bool value of the property if possible.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a string).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: a bool, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("ClearProp", MolClearProp<Conformer>, python::args("self", "key"),
"Removes a property from the conformer.\n\n"
" ARGUMENTS:\n"

View File

@@ -757,6 +757,18 @@ struct mol_wrapper {
" NOTE:\n"
" - If the property has not been set, a KeyError exception will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def(
"GetProp", GetPyPropOrDefault<ROMol>,
(python::arg("self"), python::arg("key"),
python::arg("autoConvert") = false,
python::arg("default")),
"Returns the value of the property.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a string).\n\n"
" - autoConvert: if True attempt to convert the property into a python object\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: the property value, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetDoubleProp", GetProp<ROMol, double>,
python::args("self", "key"),
"Returns the double value of the property if possible.\n\n"
@@ -767,6 +779,14 @@ struct mol_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetDoubleProp", GetPropOrDefault<ROMol, double>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the double value of the property if possible.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a string).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: a double, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetIntProp", GetProp<ROMol, int>, python::args("self", "key"),
"Returns the integer value of the property if possible.\n\n"
" ARGUMENTS:\n"
@@ -776,6 +796,14 @@ struct mol_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetIntProp", GetPropOrDefault<ROMol, int>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the integer value of the property if possible.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a string).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: an integer, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetUnsignedProp", GetProp<ROMol, unsigned int>,
python::args("self", "key"),
"Returns the unsigned int value of the property if possible.\n\n"
@@ -786,6 +814,14 @@ struct mol_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetUnsignedProp", GetPropOrDefault<ROMol, unsigned int>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the unsigned int value of the property if possible.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a string).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: an unsigned integer, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetBoolProp", GetProp<ROMol, bool>, python::args("self", "key"),
"Returns the Bool value of the property if possible.\n\n"
" ARGUMENTS:\n"
@@ -795,6 +831,14 @@ struct mol_wrapper {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetBoolProp", GetPropOrDefault<ROMol, bool>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"Returns the Bool value of the property if possible.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a string).\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: a bool, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("ClearProp", MolClearProp<ROMol>, python::args("self", "key"),
"Removes a property from the molecule.\n\n"
" ARGUMENTS:\n"

View File

@@ -260,26 +260,54 @@ struct sgroup_wrap {
" - If the property has not been set, a KeyError exception "
"will be raised.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def(
"GetProp", GetPyPropOrDefault<SubstanceGroup>,
(python::arg("self"), python::arg("key"),
python::arg("autoConvert") = false,
python::arg("default")),
"Returns the value of the property.\n\n"
" ARGUMENTS:\n"
" - key: the name of the property to return (a string).\n\n"
" - autoConvert: if True attempt to convert the property into a python object\n\n"
" - default: value to return if the property is not present.\n\n"
" RETURNS: the property value, or default if the property is not present.\n",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetIntProp",
(int (RDProps::*)(const std::string_view) const) &
SubstanceGroup::getProp<int>,
python::args("self", "key"),
"returns the value of a particular property")
.def("GetIntProp", GetPropOrDefault<SubstanceGroup, int>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"returns the value of a particular property, or default if not present",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetUnsignedProp",
(unsigned int (RDProps::*)(const std::string_view) const) &
SubstanceGroup::getProp<unsigned int>,
python::args("self", "key"),
"returns the value of a particular property")
.def("GetUnsignedProp", GetPropOrDefault<SubstanceGroup, unsigned int>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"returns the value of a particular property, or default if not present",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetDoubleProp",
(double (RDProps::*)(const std::string_view) const) &
SubstanceGroup::getProp<double>,
python::args("self", "key"),
"returns the value of a particular property")
.def("GetDoubleProp", GetPropOrDefault<SubstanceGroup, double>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"returns the value of a particular property, or default if not present",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetBoolProp",
(bool (RDProps::*)(const std::string_view) const) &
SubstanceGroup::getProp<bool>,
python::args("self", "key"),
"returns the value of a particular property")
.def("GetBoolProp", GetPropOrDefault<SubstanceGroup, bool>,
(python::arg("self"), python::arg("key"), python::arg("default")),
"returns the value of a particular property, or default if not present",
boost::python::return_value_policy<return_pyobject_passthrough>())
.def("GetUnsignedVectProp",
(std::vector<unsigned int> (RDProps::*)(const std::string_view)
const) &

View File

@@ -212,30 +212,59 @@ PyObject *GetProp(const RDOb *ob, const std::string &key) {
return rawPy(std::move(res));
}
template <class RDOb, class T>
PyObject *GetPropOrDefault(const RDOb *ob, const std::string &key,
T default_val) {
T res;
try {
if (!ob->getPropIfPresent(key, res)) {
return rawPy(std::move(default_val));
}
} catch (const std::exception &e) {
auto msg = std::string("key `") + key + "` exists but does not result in " +
GetTypeName<T>() + " reason: " + e.what();
PyErr_SetString(PyExc_ValueError, msg.c_str());
return nullptr;
}
return rawPy(std::move(res));
}
template <class RDOb>
python::object autoConvertString(const RDOb *ob, const std::string &key) {
int ivalue;
double dvalue;
std::string svalue;
if (ob->getPropIfPresent(key, ivalue)) {
return python::object(ivalue);
} else if (ob->getPropIfPresent(key, dvalue)) {
return python::object(dvalue);
} else if (ob->getPropIfPresent(key, svalue)) {
try {
if (ob->getPropIfPresent(key, ivalue)) {
return python::object(ivalue);
}
} catch (const std::bad_any_cast &) {}
try {
if (ob->getPropIfPresent(key, dvalue)) {
return python::object(dvalue);
}
} catch (const std::bad_any_cast &) {}
if (ob->getPropIfPresent(key, svalue)) {
return python::object(svalue);
}
return python::object();
}
// nullptr = raise KeyError; non-null = return *default_val_ptr as fallback
template <class RDOb>
PyObject *GetPyProp(const RDOb *obj, const std::string &key, bool autoConvert) {
PyObject *GetPyPropImpl(const RDOb *obj, const std::string &key,
bool autoConvert, python::object *default_val_ptr) {
python::object pobj;
if (!autoConvert) {
std::string res;
if (obj->getPropIfPresent(key, res)) {
return rawPy(res);
} else if (default_val_ptr && !obj->hasProp(key)) {
return rawPy(*default_val_ptr);
} else {
PyErr_SetString(PyExc_KeyError, key.c_str());
return nullptr;
@@ -256,6 +285,7 @@ PyObject *GetPyProp(const RDOb *obj, const std::string &key, bool autoConvert) {
case RDTypeTag::StringTag:
if (autoConvert) {
pobj = autoConvertString(obj, rdvalue.key);
return rawPy(pobj);
}
return rawPy(from_rdvalue<std::string>(rdvalue.val));
case RDTypeTag::FloatTag:
@@ -309,10 +339,28 @@ PyObject *GetPyProp(const RDOb *obj, const std::string &key, bool autoConvert) {
}
}
}
// We reach here only when autoConvert=true and the key was not returned by
// the loop above. Two cases: (a) key not in dict at all — hasProp is false,
// return default; (b) key exists as AnyTag (skipped by the loop) — hasProp
// is true, raise KeyError as the property cannot be converted.
if (default_val_ptr && !obj->hasProp(key)) {
return rawPy(*default_val_ptr);
}
PyErr_SetString(PyExc_KeyError, key.c_str());
return nullptr;
}
template <class RDOb>
PyObject *GetPyProp(const RDOb *obj, const std::string &key, bool autoConvert) {
return GetPyPropImpl(obj, key, autoConvert, nullptr);
}
template <class RDOb>
PyObject *GetPyPropOrDefault(const RDOb *obj, const std::string &key,
bool autoConvert, python::object default_val) {
return GetPyPropImpl(obj, key, autoConvert, &default_val);
}
// Return policy for functions that directly return a PyObject* and
// are fully responsible for setting the Python error state.
struct return_pyobject_passthrough {

View File

@@ -0,0 +1,169 @@
import pytest
from rdkit import Chem
from rdkit.Chem import AllChem
# ---------------------------------------------------------------------------
# GetProp with default
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("auto_convert", [False, True])
@pytest.mark.parametrize("default", [None, "fallback", 0, 0.0])
def test_get_prop_with_default_missing(auto_convert, default):
"""Default is returned when the property is not set."""
m = Chem.MolFromSmiles("CC")
assert m.GetProp("test_prop", autoConvert=auto_convert,
default=default) == default
@pytest.mark.parametrize("auto_convert", [False, True])
@pytest.mark.parametrize("prop_value, default, unconverted_value", [
("value", "fallback", "value"),
("value", None, "value"),
(42, 0, '42'),
(42.0, 0.0, '42'),
])
def test_get_prop_with_default_present(auto_convert, prop_value, default, unconverted_value):
"""The stored property value is returned, not the default."""
m = Chem.MolFromSmiles("CC")
if isinstance(prop_value, int):
m.SetIntProp("test_prop", prop_value)
elif isinstance(prop_value, float):
m.SetDoubleProp("test_prop", prop_value)
else:
m.SetProp("test_prop", prop_value)
expected = prop_value if auto_convert else unconverted_value
assert m.GetProp("test_prop", autoConvert=auto_convert,
default=default) == expected
@pytest.mark.parametrize("auto_convert", [False, True])
def test_get_prop_no_default_not_set(auto_convert):
"""KeyError is raised when the property is missing and no default is given."""
m = Chem.MolFromSmiles("CC")
with pytest.raises(KeyError):
m.GetProp("test_prop", autoConvert=auto_convert)
@pytest.mark.parametrize("auto_convert", [False, True])
@pytest.mark.parametrize("prop_value, unconverted_value", [
("hello", "hello"),
(42, "42"),
(42.0, "42"),
])
def test_get_prop_no_default_set(auto_convert, prop_value, unconverted_value):
"""GetProp without default returns the stored value normally."""
m = Chem.MolFromSmiles("CC")
if isinstance(prop_value, int):
m.SetIntProp("test_prop", prop_value)
elif isinstance(prop_value, float):
m.SetDoubleProp("test_prop", prop_value)
else:
m.SetProp("test_prop", prop_value)
expected = prop_value if auto_convert else unconverted_value
assert m.GetProp("test_prop", autoConvert=auto_convert) == expected
def test_get_prop_default_positional():
"""default can be supplied positionally as the third argument."""
m = Chem.MolFromSmiles("CC")
with pytest.raises(KeyError):
m.GetProp("test_prop", True)
default_val = m.GetProp("test_prop", True, True)
assert default_val is True
# ---------------------------------------------------------------------------
# GetProp with default on Atom, Bond, Conformer, SubstanceGroup
# ---------------------------------------------------------------------------
def test_get_prop_default_on_atom():
m = Chem.MolFromSmiles("CC")
atom = m.GetAtomWithIdx(0)
assert atom.GetProp("missing", default="x") == "x"
with pytest.raises(KeyError):
atom.GetProp("missing")
atom.SetProp("p", "v")
assert atom.GetProp("p", default="x") == "v"
def test_get_prop_default_on_bond():
m = Chem.MolFromSmiles("CC")
bond = m.GetBondWithIdx(0)
assert bond.GetProp("missing", default="x") == "x"
with pytest.raises(KeyError):
bond.GetProp("missing")
bond.SetProp("p", "v")
assert bond.GetProp("p", default="x") == "v"
def test_get_prop_default_on_conformer():
m = Chem.MolFromSmiles("CC")
AllChem.EmbedMolecule(m, randomSeed=42)
conf = m.GetConformer()
assert conf.GetProp("missing", default="x") == "x"
with pytest.raises(KeyError):
conf.GetProp("missing")
conf.SetProp("p", "v")
assert conf.GetProp("p", default="x") == "v"
def test_get_prop_default_on_substance_group():
m = Chem.MolFromSmiles("CC")
sg = Chem.CreateMolSubstanceGroup(m, "SRU")
assert sg.GetProp("missing", default="x") == "x"
with pytest.raises(KeyError):
sg.GetProp("missing")
sg.SetProp("p", "v")
assert sg.GetProp("p", default="x") == "v"
# ---------------------------------------------------------------------------
# Typed getters with default — Mol
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("set_fn, get_fn, val, default", [
("SetIntProp", "GetIntProp", 42, 0),
("SetDoubleProp", "GetDoubleProp", 3.14, 0.0),
("SetBoolProp", "GetBoolProp", True, False),
("SetUnsignedProp", "GetUnsignedProp", 7, 0),
])
def test_typed_getter_with_default_present(set_fn, get_fn, val, default):
"""Stored typed value is returned even when a default is provided."""
m = Chem.MolFromSmiles("CC")
getattr(m, set_fn)("p", val)
assert getattr(m, get_fn)("p", default=default) == val
@pytest.mark.parametrize("get_fn, default", [
("GetIntProp", 0),
("GetDoubleProp", 0.0),
("GetBoolProp", False),
("GetUnsignedProp", 0),
])
def test_typed_getter_with_default_missing(get_fn, default):
"""Default is returned when the property is not set."""
m = Chem.MolFromSmiles("CC")
assert getattr(m, get_fn)("missing", default=default) == default
@pytest.mark.parametrize("get_fn", ["GetIntProp", "GetDoubleProp", "GetBoolProp", "GetUnsignedProp"])
def test_typed_getter_no_default_raises(get_fn):
"""Without a default, KeyError is raised for a missing property."""
m = Chem.MolFromSmiles("CC")
with pytest.raises(KeyError):
getattr(m, get_fn)("missing")
def test_typed_getter_default_wrong_type_raises_value_error():
"""GetIntProp(default=x) raises ValueError when the key exists but holds the wrong type."""
m = Chem.MolFromSmiles("CC")
m.SetDoubleProp("dbl_key", 3.14)
with pytest.raises(ValueError):
m.GetIntProp("dbl_key", default=0)
if __name__ == '__main__':
import sys
sys.exit(pytest.main([__file__]))