From 94c2db6c20eba75b4fae34ad33aa96814cce383b Mon Sep 17 00:00:00 2001 From: mdartiailh Date: Thu, 29 Oct 2026 12:36:38 +0100 Subject: [PATCH 1/4] fixes for #277 and #278 --- atom/src/atomdict.cpp | 10 +++++++--- atom/src/atomlist.cpp | 4 ++++ tests/test_atomdefaultdict.py | 10 ++++++++++ tests/test_atomlist.py | 10 ++++++++++ 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/atom/src/atomdict.cpp b/atom/src/atomdict.cpp index df754119..0c87309f 100644 --- a/atom/src/atomdict.cpp +++ b/atom/src/atomdict.cpp @@ -155,17 +155,21 @@ PyObject* AtomDict_setdefault( AtomDict* self, PyObject* args ) cppy::ptr key_ptr( validate_key( self, key ) ); if ( !key_ptr ) return 0; - PyObject* value = PyDict_GetItem( pyobject_cast( self ), key_ptr.get() ); + PyObject* value = PyDict_GetItemWithError( pyobject_cast( self ), key_ptr.get() ); if( value ) { return cppy::incref( value ); } + if( PyErr_Occurred() ) + { + return 0; + } if( AtomDict_ass_subscript( self, key_ptr.get(), dfv ) < 0 ) { return 0; } // Get the dictionary from the dict itself in case it was coerced. - return cppy::incref( PyDict_GetItem( pyobject_cast( self ), key_ptr.get() ) ); + return cppy::xincref( PyDict_GetItemWithError( pyobject_cast( self ), key_ptr.get() ) ); } @@ -310,7 +314,7 @@ static PyObject* DefaultAtomDict_missing( DefaultAtomDict* self, PyObject* key ) return 0; } // Get the dictionary from the dict itself in case it was coerced. - value_ptr = cppy::incref( PyDict_GetItem( pyobject_cast( self ), key_ptr.get() ) ); + value_ptr = cppy::xincref( PyDict_GetItem( pyobject_cast( self ), key_ptr.get() ) ); } return value_ptr.release(); } diff --git a/atom/src/atomlist.cpp b/atom/src/atomlist.cpp index 030ef923..6467ced5 100644 --- a/atom/src/atomlist.cpp +++ b/atom/src/atomlist.cpp @@ -727,7 +727,11 @@ class AtomCListHandler : public AtomListHandler // if the superclass call succeeds, then this is safe. Py_ssize_t i = -1; if( nargs == 1 ) + { i = PyLong_AsSsize_t( args[0] ); + if( PyErr_Occurred() ) + return 0; + } if( i < 0 ) i += size; cppy::ptr index( PyLong_FromSsize_t( i ) ); diff --git a/tests/test_atomdefaultdict.py b/tests/test_atomdefaultdict.py index fa5210b3..0ffdc5a2 100644 --- a/tests/test_atomdefaultdict.py +++ b/tests/test_atomdefaultdict.py @@ -191,6 +191,16 @@ class A(Atom): assert content is a.d[1] +def test_missing_raises_key_hash_error(): + class BadKey: + def __hash__(self): + raise RuntimeError("hash failed") + + d = DefaultDict(missing=int) + with pytest.raises(RuntimeError, match="hash failed"): + d[BadKey()] + + def test_update(default_atom_dict): """Test update a dict.""" default_atom_dict.untyped.update({"": 1}) diff --git a/tests/test_atomlist.py b/tests/test_atomlist.py index 34e81bbc..ddfc45e5 100644 --- a/tests/test_atomlist.py +++ b/tests/test_atomlist.py @@ -528,6 +528,16 @@ def test_container_pop(container_model, kind): assert change["item"] == 0 +def test_container_pop_index_object_raises_typeerror(): + model = ContainerModel() + model.untyped = [10, 20, 30] + model.typed = [10, 20, 30] + with pytest.raises(TypeError): + model.untyped.pop(Indexer(0)) + with pytest.raises(TypeError): + model.typed.pop(Indexer(0)) + + @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_reverse(container_model, kind): mlist = getattr(container_model, kind) From b1492a8ff64f745508ba9970e430eb50e98b6166 Mon Sep 17 00:00:00 2001 From: mdartiailh Date: Thu, 29 Oct 2026 12:50:25 +0100 Subject: [PATCH 2/4] fix malformed test --- tests/test_atomdefaultdict.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_atomdefaultdict.py b/tests/test_atomdefaultdict.py index 0ffdc5a2..1276ed03 100644 --- a/tests/test_atomdefaultdict.py +++ b/tests/test_atomdefaultdict.py @@ -196,9 +196,10 @@ class BadKey: def __hash__(self): raise RuntimeError("hash failed") - d = DefaultDict(missing=int) + class A(Atom): + d = DefaultDict(missing=int) with pytest.raises(RuntimeError, match="hash failed"): - d[BadKey()] + A().d[BadKey()] def test_update(default_atom_dict): From 65e05664010a413aae5de29c5a4a57590220a185 Mon Sep 17 00:00:00 2001 From: mdartiailh Date: Thu, 29 Oct 2026 12:53:37 +0100 Subject: [PATCH 3/4] pin pytest (due to pytest-benchmark issue, PR is https://github.com/ionelmc/pytest-benchmark/pull/323) --- test_requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requirements.txt b/test_requirements.txt index d647248c..c4bfc755 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -1,7 +1,7 @@ mypy pyrefly ty -pytest +pytest < 9.2 pytest-cov pytest-benchmark psutil From 694ffc28b1d08f5da838265a37b1e7da104ff285 Mon Sep 17 00:00:00 2001 From: mdartiailh Date: Thu, 29 Oct 2026 12:57:10 +0100 Subject: [PATCH 4/4] fix formatting --- tests/test_atomdefaultdict.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_atomdefaultdict.py b/tests/test_atomdefaultdict.py index 1276ed03..08130b74 100644 --- a/tests/test_atomdefaultdict.py +++ b/tests/test_atomdefaultdict.py @@ -198,6 +198,7 @@ def __hash__(self): class A(Atom): d = DefaultDict(missing=int) + with pytest.raises(RuntimeError, match="hash failed"): A().d[BadKey()]