diff --git a/atompack-py/python/atompack/__init__.pyi b/atompack-py/python/atompack/__init__.pyi index 880939c..938f4df 100644 --- a/atompack-py/python/atompack/__init__.pyi +++ b/atompack-py/python/atompack/__init__.pyi @@ -327,7 +327,7 @@ class Molecule: Raises ------ - ValueError + KeyError If property key does not exist """ ... diff --git a/atompack-py/python/atompack/_atompack_rs.pyi b/atompack-py/python/atompack/_atompack_rs.pyi index 5235b99..99e8730 100644 --- a/atompack-py/python/atompack/_atompack_rs.pyi +++ b/atompack-py/python/atompack/_atompack_rs.pyi @@ -321,6 +321,11 @@ class PyMolecule: ------- Any Property value + + Raises + ------ + KeyError + If property key does not exist """ ... diff --git a/atompack-py/src/molecule.rs b/atompack-py/src/molecule.rs index f881e2f..78a83e9 100644 --- a/atompack-py/src/molecule.rs +++ b/atompack-py/src/molecule.rs @@ -667,10 +667,7 @@ impl PyMolecule { if let Some(inner) = molecule.as_owned() { return match inner.properties.get(key) { Some(v) => property_value_to_pyobject(py, v), - None => Err(PyValueError::new_err(format!( - "Property '{}' not found", - key - ))), + None => Err(PyKeyError::new_err(format!("Property '{}' not found", key))), }; } let view = molecule.as_view().ok_or_else(|| { @@ -678,10 +675,7 @@ impl PyMolecule { })?; match view.find_custom_section(KIND_MOL_PROP, key)? { Some(section) => property_section_to_pyobject(py, view, section), - None => Err(PyValueError::new_err(format!( - "Property '{}' not found", - key - ))), + None => Err(PyKeyError::new_err(format!("Property '{}' not found", key))), } } diff --git a/atompack-py/tests/test_atom_molecule.py b/atompack-py/tests/test_atom_molecule.py index 0ff034c..50d4caf 100644 --- a/atompack-py/tests/test_atom_molecule.py +++ b/atompack-py/tests/test_atom_molecule.py @@ -225,10 +225,22 @@ def test_molecule_custom_properties() -> None: "vec3_f64", } - with pytest.raises(ValueError, match=r"not found"): + with pytest.raises(KeyError, match=r"not found"): mol.get_property("stress") - with pytest.raises(ValueError, match=r"not found"): + with pytest.raises(KeyError, match=r"not found"): + mol.get_property("does_not_exist") + + +def test_missing_property_raises_keyerror_consistently() -> None: + # Lock in symmetry: both indexing patterns must raise the same exception + # type for missing custom properties. They previously disagreed. + mol = _make_molecule() + mol.set_property("method", "DFT") + + with pytest.raises(KeyError, match=r"not found"): + _ = mol["does_not_exist"] + with pytest.raises(KeyError, match=r"not found"): mol.get_property("does_not_exist") diff --git a/atompack-py/tests/test_database.py b/atompack-py/tests/test_database.py index 7fbb075..52c9946 100644 --- a/atompack-py/tests/test_database.py +++ b/atompack-py/tests/test_database.py @@ -387,7 +387,7 @@ def test_database_single_item_mutation_is_copy_on_write( assert fresh.energy == pytest.approx(-5.0) np.testing.assert_allclose(fresh.forces, original.forces) assert fresh.get_property("tag") == "train" - with pytest.raises(ValueError, match="Property 'new_ids' not found"): + with pytest.raises(KeyError, match="Property 'new_ids' not found"): fresh.get_property("new_ids") diff --git a/atompack-py/tests/test_from_ase.py b/atompack-py/tests/test_from_ase.py index 542cdd4..705a405 100644 --- a/atompack-py/tests/test_from_ase.py +++ b/atompack-py/tests/test_from_ase.py @@ -130,7 +130,7 @@ def test_from_ase_extracts_core_fields() -> None: mol.stress, np.array([[1.1, 1.2, 1.3], [2.1, 2.2, 2.3], [3.1, 3.2, 3.3]], dtype=np.float64), ) - with pytest.raises(ValueError, match=r"not found"): + with pytest.raises(KeyError, match=r"not found"): mol.get_property("stress") assert mol.has_property("unsupported") is False @@ -188,7 +188,7 @@ def test_from_ase_extracts_arrays_and_calc_results() -> None: mol.get_property("magmoms"), np.array([0.3, 0.4], dtype=np.float64) ) np.testing.assert_allclose(mol.stress, np.eye(3, dtype=np.float64) * 3.0) - with pytest.raises(ValueError, match=r"not found"): + with pytest.raises(KeyError, match=r"not found"): mol.get_property("forces")