From bcd1f59d797a11f48348edaf53fd4b9b7d28e2ee Mon Sep 17 00:00:00 2001 From: mdartiailh Date: Thu, 8 Oct 2026 22:31:34 +0200 Subject: [PATCH 01/10] Close remaining issues identified in #254 --- atom/src/atomdict.cpp | 6 +++++- atom/src/atomlist.cpp | 4 ++++ atom/src/atomset.cpp | 2 ++ atom/src/catom.cpp | 4 +++- atom/src/member.cpp | 4 +++- 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/atom/src/atomdict.cpp b/atom/src/atomdict.cpp index 1c321f5c..ef5ffacd 100644 --- a/atom/src/atomdict.cpp +++ b/atom/src/atomdict.cpp @@ -110,12 +110,14 @@ int AtomDict_traverse( AtomDict* self, visitproc visit, void* arg ) void AtomDict_dealloc( AtomDict* self ) { + PyTypeObject* tp = Py_TYPE( self ); PyObject_GC_UnTrack( self ); cppy::clear( &self->m_key_validator ); cppy::clear( &self->m_value_validator ); delete atomdict_cast( self )->pointer; atomdict_cast( self )->pointer = 0; PyDict_Type.tp_dealloc( pyobject_cast( self ) ); + Py_DECREF( tp ); } @@ -458,8 +460,10 @@ bool DefaultAtomDict::Ready() // This will work only if we create this type after the standard AtomDict // The reference will be handled by the module to which we will add the type cppy::ptr bases( PyTuple_New( 1 ) ); - if ( !bases ) + if ( !bases ) { return false; // LCOV_EXCL_LINE (failed tuple creation) + } + Py_INCREF( pyobject_cast( AtomDict::TypeObject ) ); PyTuple_SET_ITEM( bases.get(), 0, cppy::incref( pyobject_cast( AtomDict::TypeObject ) ) ); TypeObject = pytype_cast( PyType_FromSpecWithBases( &TypeObject_Spec, bases.get() ) diff --git a/atom/src/atomlist.cpp b/atom/src/atomlist.cpp index f22a461e..030ef923 100644 --- a/atom/src/atomlist.cpp +++ b/atom/src/atomlist.cpp @@ -296,11 +296,13 @@ int AtomList_traverse( AtomList* self, visitproc visit, void* arg ) void AtomList_dealloc( AtomList* self ) { + PyTypeObject* tp = Py_TYPE( self ); PyObject_GC_UnTrack( self ); cppy::clear( &self->validator ); delete self->pointer; self->pointer = 0; PyList_Type.tp_dealloc( pyobject_cast( self ) ); + Py_DECREF( tp ); } @@ -1034,12 +1036,14 @@ int AtomCList_traverse( AtomCList* self, visitproc visit, void* arg ) void AtomCList_dealloc( AtomCList* self ) { + PyTypeObject* tp = Py_TYPE( self ); PyObject_GC_UnTrack( self ); cppy::clear( &self->member ); cppy::clear( &atomlist_cast( self )->validator ); delete atomlist_cast( self )->pointer; atomlist_cast( self )->pointer = 0; PyList_Type.tp_dealloc( pyobject_cast( self ) ); + Py_DECREF( tp ); } diff --git a/atom/src/atomset.cpp b/atom/src/atomset.cpp index fb09bf45..7869b94c 100644 --- a/atom/src/atomset.cpp +++ b/atom/src/atomset.cpp @@ -122,11 +122,13 @@ int AtomSet_traverse( AtomSet* self, visitproc visit, void* arg ) void AtomSet_dealloc( AtomSet* self ) { + PyTypeObject* tp = Py_TYPE( self ); PyObject_GC_UnTrack( self ); cppy::clear( &self->m_value_validator ); delete atomset_cast( self )->pointer; atomset_cast( self )->pointer = 0; PySet_Type.tp_dealloc( pyobject_cast( self ) ); + Py_DECREF( tp ); } diff --git a/atom/src/catom.cpp b/atom/src/catom.cpp index 539ea2ab..50ef0a0d 100644 --- a/atom/src/catom.cpp +++ b/atom/src/catom.cpp @@ -130,6 +130,7 @@ CAtom_traverse( CAtom* self, visitproc visit, void* arg ) void CAtom_dealloc( CAtom* self ) { + PyTypeObject* tp = Py_TYPE( self ); if( self->has_guards() ) { CAtom::clear_guards( self ); @@ -146,7 +147,8 @@ CAtom_dealloc( CAtom* self ) } delete self->observers; self->observers = 0; - Py_TYPE(self)->tp_free( pyobject_cast( self ) ); + tp->tp_free( pyobject_cast( self ) ); + Py_DECREF( tp ); } diff --git a/atom/src/member.cpp b/atom/src/member.cpp index fca66907..468ca416 100644 --- a/atom/src/member.cpp +++ b/atom/src/member.cpp @@ -94,11 +94,13 @@ Member_traverse( Member* self, visitproc visit, void* arg ) void Member_dealloc( Member* self ) { + PyTypeObject* tp = Py_TYPE( self ); PyObject_GC_UnTrack( self ); Member_clear( self ); delete self->static_observers; self->static_observers = 0; - Py_TYPE(self)->tp_free( pyobject_cast( self ) ); + tp->tp_free( pyobject_cast( self ) ); + Py_DECREF( tp ); } From 8a85f275adc269ebf33567eb221dfa2b6ea90d89 Mon Sep 17 00:00:00 2001 From: mdartiailh Date: Thu, 8 Oct 2026 23:23:43 +0200 Subject: [PATCH 02/10] complete closing of #256 --- atom/src/utils.h | 5 ++++- atom/src/validatebehavior.cpp | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/atom/src/utils.h b/atom/src/utils.h index f02c926f..fb0d5951 100644 --- a/atom/src/utils.h +++ b/atom/src/utils.h @@ -109,8 +109,11 @@ inline bool safe_richcompare( PyObject* first, PyObject* second, int opid ) // Clear the error if one happened because we attempted an invalid // comparison. - if( PyErr_Occurred() ) + if( PyErr_Occurred() ) { + if( !PyErr_ExceptionMatches( PyExc_TypeError ) ) + return false; // or propagate somehow PyErr_Clear(); + } // Fallback to the Python 2 default 3 way compare. int c = fallback_3way_compare( first, second ); diff --git a/atom/src/validatebehavior.cpp b/atom/src/validatebehavior.cpp index e6da684b..19d9f74a 100644 --- a/atom/src/validatebehavior.cpp +++ b/atom/src/validatebehavior.cpp @@ -893,6 +893,7 @@ range_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newval Py_TYPE( pyobject_cast( atom ) )->tp_name ); default: + // Exception occurred during comparison return 0; } } @@ -910,6 +911,7 @@ range_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newval Py_TYPE( pyobject_cast( atom ) )->tp_name ); default: + // Exception occurred during comparison return 0; } } From f4be55300aa0fd8dbf198f88a21b2a865996b105 Mon Sep 17 00:00:00 2001 From: mdartiailh Date: Fri, 9 Oct 2026 13:13:10 +0200 Subject: [PATCH 03/10] close 257 --- atom/src/enumtypes.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/src/enumtypes.cpp b/atom/src/enumtypes.cpp index adf658fb..449c3407 100644 --- a/atom/src/enumtypes.cpp +++ b/atom/src/enumtypes.cpp @@ -38,11 +38,12 @@ add_long( cppy::ptr& dict_ptr, const char* name, T value ) { return false; // LCOV_EXCL_LINE (failed long creation, impossible) } + // PyDict_SetItemString does not steal a reference so cppy::ptr + // must decref the pyint reference after insertion. if( PyDict_SetItemString( dict_ptr.get(), name, pyint.get() ) != 0 ) { return false; // LCOV_EXCL_LINE (failed dict insertion, impossible) } - pyint.release(); // Release the reference since the operation succeeded return true; } From fff548cef2e0d4b1ecfe6210cde9a74fbdf44124 Mon Sep 17 00:00:00 2001 From: mdartiailh Date: Fri, 9 Oct 2026 13:18:48 +0200 Subject: [PATCH 04/10] close #258 --- atom/src/atomset.cpp | 3 ++- atom/src/catom.cpp | 4 ++++ atom/src/sortedmap.cpp | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/atom/src/atomset.cpp b/atom/src/atomset.cpp index 7869b94c..a609a413 100644 --- a/atom/src/atomset.cpp +++ b/atom/src/atomset.cpp @@ -87,8 +87,9 @@ PyObject* validate_set( AtomSet* set, PyObject* value ) return 0; } } - if ( PyErr_Occurred() ) + if ( PyErr_Occurred() ) { return 0; + } return val_set.release(); } diff --git a/atom/src/catom.cpp b/atom/src/catom.cpp index 50ef0a0d..5e2c414b 100644 --- a/atom/src/catom.cpp +++ b/atom/src/catom.cpp @@ -403,6 +403,7 @@ CAtom_getstate( CAtom* self ) if (!value ) { // Following CPython impl it is not an error if the attribute is // not present. + PyErr_Clear(); continue; } else if ( PyDict_SetItem(stateptr.get(), name, value.get()) ) { @@ -476,6 +477,9 @@ CAtom_setstate( CAtom* self, PyObject* state ) if ( PyObject_SetAttr( pyobject_cast(self), key.get(), value.get() ) ) return 0; } + if ( PyErr_Occurred() ) { + return 0; + } if ( frozen ) self->set_frozen(true); diff --git a/atom/src/sortedmap.cpp b/atom/src/sortedmap.cpp index 518fb2db..616ad151 100644 --- a/atom/src/sortedmap.cpp +++ b/atom/src/sortedmap.cpp @@ -170,8 +170,9 @@ struct SortedMap self->setitem( PySequence_Fast_GET_ITEM( pair.get(), 0 ), PySequence_Fast_GET_ITEM( pair.get(), 1 ) ); } - if ( PyErr_Occurred() ) + if ( PyErr_Occurred() ) { return 0; // error during iteration + } } } From cb589c740dec924dc3a90ebd517c7925626bffdf Mon Sep 17 00:00:00 2001 From: mdartiailh Date: Fri, 9 Oct 2026 13:25:42 +0200 Subject: [PATCH 05/10] completely close #261 --- atom/src/catommodule.cpp | 30 ++++++++++++++++++++---------- atom/src/getattrbehavior.cpp | 2 ++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/atom/src/catommodule.cpp b/atom/src/catommodule.cpp index 147b4ce6..9c52c91c 100644 --- a/atom/src/catommodule.cpp +++ b/atom/src/catommodule.cpp @@ -148,16 +148,26 @@ bool add_objects( PyObject* mod ) cppy::incref( PyPostValidate ); cppy::incref( PyGetState ); cppy::incref( PyChangeType ); - PyModule_AddObject( mod, "GetAttr", PyGetAttr ); - PyModule_AddObject( mod, "SetAttr", PySetAttr ); - PyModule_AddObject( mod, "DelAttr", PyDelAttr ); - PyModule_AddObject( mod, "PostGetAttr", PyPostGetAttr ); - PyModule_AddObject( mod, "PostSetAttr", PyPostSetAttr ); - PyModule_AddObject( mod, "DefaultValue", PyDefaultValue ); - PyModule_AddObject( mod, "Validate", PyValidate ); - PyModule_AddObject( mod, "PostValidate", PyPostValidate ); - PyModule_AddObject( mod, "GetState", PyGetState ); - PyModule_AddObject( mod, "ChangeType", PyChangeType ); + if (PyModule_AddObject( mod, "GetAttr", PyGetAttr ) < 0) + return false; + if (PyModule_AddObject( mod, "SetAttr", PySetAttr ) < 0) + return false; + if (PyModule_AddObject( mod, "DelAttr", PyDelAttr ) < 0) + return false; + if (PyModule_AddObject( mod, "PostGetAttr", PyPostGetAttr ) < 0) + return false; + if (PyModule_AddObject( mod, "PostSetAttr", PyPostSetAttr ) < 0) + return false; + if (PyModule_AddObject( mod, "DefaultValue", PyDefaultValue ) < 0) + return false; + if (PyModule_AddObject( mod, "Validate", PyValidate ) < 0) + return false; + if (PyModule_AddObject( mod, "PostValidate", PyPostValidate ) < 0) + return false; + if (PyModule_AddObject( mod, "GetState", PyGetState ) < 0) + return false; + if (PyModule_AddObject( mod, "ChangeType", PyChangeType ) < 0) + return false; return true; } diff --git a/atom/src/getattrbehavior.cpp b/atom/src/getattrbehavior.cpp index 809724bb..36a344a6 100644 --- a/atom/src/getattrbehavior.cpp +++ b/atom/src/getattrbehavior.cpp @@ -183,6 +183,8 @@ cached_property_handler( Member* member, CAtom* atom ) if( value ) return value.release(); value = property_handler( member, atom ); + if( !value ) + return 0; atom->set_slot( member->index, value.get() ); // exception-safe return value.release(); } From 28d64211d1979dbecb40ffa2aed7f3c54b4d28fc Mon Sep 17 00:00:00 2001 From: mdartiailh Date: Fri, 9 Oct 2026 13:43:51 +0200 Subject: [PATCH 06/10] close part of #262 --- atom/src/atomdict.cpp | 6 ++-- atom/src/catommodule.cpp | 56 +++++++++++++------------------------- atom/src/member.cpp | 2 -- atom/src/methodwrapper.cpp | 6 ++-- atom/src/modifyguard.h | 14 +++++++++- 5 files changed, 37 insertions(+), 47 deletions(-) diff --git a/atom/src/atomdict.cpp b/atom/src/atomdict.cpp index ef5ffacd..cbec90dd 100644 --- a/atom/src/atomdict.cpp +++ b/atom/src/atomdict.cpp @@ -347,8 +347,7 @@ PyType_Spec AtomDict::TypeObject_Spec = { 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE - | Py_TPFLAGS_HAVE_GC - | Py_TPFLAGS_HAVE_VERSION_TAG, /* tp_flags */ + | Py_TPFLAGS_HAVE_GC, /* tp_flags */ AtomDict_Type_slots /* slots */ }; @@ -430,8 +429,7 @@ PyType_Spec DefaultAtomDict::TypeObject_Spec = { 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE - | Py_TPFLAGS_HAVE_GC - | Py_TPFLAGS_HAVE_VERSION_TAG, /* tp_flags */ + | Py_TPFLAGS_HAVE_GC, /* tp_flags */ DefaultAtomDict_Type_slots /* slots */ }; diff --git a/atom/src/catommodule.cpp b/atom/src/catommodule.cpp index 9c52c91c..c98779e6 100644 --- a/atom/src/catommodule.cpp +++ b/atom/src/catommodule.cpp @@ -76,97 +76,79 @@ bool add_objects( PyObject* mod ) // atomlist cppy::ptr atom_list( pyobject_cast( AtomList::TypeObject ) ); - if( PyModule_AddObject( mod, "atomlist", atom_list.get() ) < 0 ) // LCOV_EXCL_BR_LINE + if( PyModule_AddObjectRef( mod, "atomlist", atom_list.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } - atom_list.release(); // atomclist cppy::ptr atom_clist( pyobject_cast( AtomCList::TypeObject ) ); - if( PyModule_AddObject( mod, "atomclist", atom_clist.get() ) < 0 ) // LCOV_EXCL_BR_LINE + if( PyModule_AddObjectRef( mod, "atomclist", atom_clist.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } - atom_clist.release(); // atomdict cppy::ptr atom_dict( pyobject_cast( AtomDict::TypeObject ) ); - if( PyModule_AddObject( mod, "atomdict", atom_dict.get() ) < 0 ) // LCOV_EXCL_BR_LINE + if( PyModule_AddObjectRef( mod, "atomdict", atom_dict.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } - atom_dict.release(); // defaultatomdict cppy::ptr defaultatom_dict( pyobject_cast( DefaultAtomDict::TypeObject ) ); - if( PyModule_AddObject( mod, "defaultatomdict", defaultatom_dict.get() ) < 0 ) // LCOV_EXCL_BR_LINE + if( PyModule_AddObjectRef( mod, "defaultatomdict", defaultatom_dict.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } - defaultatom_dict.release(); // atomset cppy::ptr atom_set( pyobject_cast( AtomSet::TypeObject ) ); - if( PyModule_AddObject( mod, "atomset", atom_set.get() ) < 0 ) // LCOV_EXCL_BR_LINE + if( PyModule_AddObjectRef( mod, "atomset", atom_set.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } - atom_set.release(); // atomref cppy::ptr atom_ref( pyobject_cast( AtomRef::TypeObject ) ); - if( PyModule_AddObject( mod, "atomref", atom_ref.get() ) < 0 ) // LCOV_EXCL_BR_LINE + if( PyModule_AddObjectRef( mod, "atomref", atom_ref.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } - atom_ref.release(); // Member cppy::ptr member( pyobject_cast( Member::TypeObject ) ); - if( PyModule_AddObject( mod, "Member", member.get() ) < 0 ) // LCOV_EXCL_BR_LINE + if( PyModule_AddObjectRef( mod, "Member", member.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } - member.release(); // CAtom cppy::ptr catom( pyobject_cast( CAtom::TypeObject ) ); - if( PyModule_AddObject( mod, "CAtom", catom.get() ) < 0 ) + if( PyModule_AddObjectRef( mod, "CAtom", catom.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } - catom.release(); - - cppy::incref( PyGetAttr ); - cppy::incref( PySetAttr ); - cppy::incref( PyDelAttr ); - cppy::incref( PyPostGetAttr ); - cppy::incref( PyPostSetAttr ); - cppy::incref( PyDefaultValue ); - cppy::incref( PyValidate ); - cppy::incref( PyPostValidate ); - cppy::incref( PyGetState ); - cppy::incref( PyChangeType ); - if (PyModule_AddObject( mod, "GetAttr", PyGetAttr ) < 0) + + if (PyModule_AddObjectRef( mod, "GetAttr", PyGetAttr ) < 0) return false; - if (PyModule_AddObject( mod, "SetAttr", PySetAttr ) < 0) + if (PyModule_AddObjectRef( mod, "SetAttr", PySetAttr ) < 0) return false; - if (PyModule_AddObject( mod, "DelAttr", PyDelAttr ) < 0) + if (PyModule_AddObjectRef( mod, "DelAttr", PyDelAttr ) < 0) return false; - if (PyModule_AddObject( mod, "PostGetAttr", PyPostGetAttr ) < 0) + if (PyModule_AddObjectRef( mod, "PostGetAttr", PyPostGetAttr ) < 0) return false; - if (PyModule_AddObject( mod, "PostSetAttr", PyPostSetAttr ) < 0) + if (PyModule_AddObjectRef( mod, "PostSetAttr", PyPostSetAttr ) < 0) return false; - if (PyModule_AddObject( mod, "DefaultValue", PyDefaultValue ) < 0) + if (PyModule_AddObjectRef( mod, "DefaultValue", PyDefaultValue ) < 0) return false; - if (PyModule_AddObject( mod, "Validate", PyValidate ) < 0) + if (PyModule_AddObjectRef( mod, "Validate", PyValidate ) < 0) return false; - if (PyModule_AddObject( mod, "PostValidate", PyPostValidate ) < 0) + if (PyModule_AddObjectRef( mod, "PostValidate", PyPostValidate ) < 0) return false; - if (PyModule_AddObject( mod, "GetState", PyGetState ) < 0) + if (PyModule_AddObjectRef( mod, "GetState", PyGetState ) < 0) return false; - if (PyModule_AddObject( mod, "ChangeType", PyChangeType ) < 0) + if (PyModule_AddObjectRef( mod, "ChangeType", PyChangeType ) < 0) return false; return true; diff --git a/atom/src/member.cpp b/atom/src/member.cpp index 468ca416..ae6ecb92 100644 --- a/atom/src/member.cpp +++ b/atom/src/member.cpp @@ -919,12 +919,10 @@ Member_methods[] = { "Notify the static observers for the given member and atom." }, { "tag", ( PyCFunction )Member_tag, METH_VARARGS | METH_KEYWORDS, "Tag the member with metatdata. " }, -#if PY_VERSION_HEX >= 0x03090000 // Generic aliases have been added in 3.9 and allow to index types // This removes the need to quote explicit member type annotations { "__class_getitem__", (PyCFunction)Py_GenericAlias, METH_O|METH_CLASS, "See PEP 585"}, -#endif { 0 } // sentinel }; diff --git a/atom/src/methodwrapper.cpp b/atom/src/methodwrapper.cpp index 6a4ec38d..7c41b5bd 100644 --- a/atom/src/methodwrapper.cpp +++ b/atom/src/methodwrapper.cpp @@ -234,9 +234,9 @@ PyTypeObject* AtomMethodWrapper::TypeObject = NULL; PyType_Spec AtomMethodWrapper::TypeObject_Spec = { PACKAGE_TYPENAME( "AtomMethodWrapper" ), /* tp_name */ sizeof( AtomMethodWrapper ), /* tp_basicsize */ - 0, /* tp_itemsize */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ - AtomMethodWrapper_Type_slots /* slots */ + 0, /* tp_itemsize */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + AtomMethodWrapper_Type_slots /* slots */ }; diff --git a/atom/src/modifyguard.h b/atom/src/modifyguard.h index a1d0c2d7..cfdb1e07 100644 --- a/atom/src/modifyguard.h +++ b/atom/src/modifyguard.h @@ -37,11 +37,18 @@ class ModifyGuard // modification have been done as otherwise it can (Python 3) cause // boolean tests (PyObject_IsTrue) to fail for wrong reasons. bool exception_set = false; +#if PY_VERSION_HEX >= 0x030D0000 + PyObject *exc = PyErr_GetRaisedException(); + if( PyErr_Occurred() ){ + exception_set = true; + } +#else PyObject *type, *value, *traceback; if( PyErr_Occurred() ){ PyErr_Fetch(&type, &value, &traceback); exception_set = true; } +#endif if( m_owner.get_modify_guard() == this ) { @@ -56,8 +63,13 @@ class ModifyGuard } // Restore previous exception if one was set. - if( exception_set ) + if( exception_set ) { +#if PY_VERSION_HEX >= 0x030D0000 + PyErr_SetRaisedException(exc); +#else PyErr_Restore(type, value, traceback); +#endif + } } void add_task( ModifyTask* task ) { m_tasks.push_back( task ); } From 2f83a5d4dd057edff3010301b46af61fc5ba7f5b Mon Sep 17 00:00:00 2001 From: mdartiailh Date: Fri, 9 Oct 2026 15:31:50 +0200 Subject: [PATCH 07/10] narrower fix for #256 --- atom/src/utils.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/src/utils.h b/atom/src/utils.h index fb0d5951..acb0b858 100644 --- a/atom/src/utils.h +++ b/atom/src/utils.h @@ -110,7 +110,8 @@ inline bool safe_richcompare( PyObject* first, PyObject* second, int opid ) // Clear the error if one happened because we attempted an invalid // comparison. if( PyErr_Occurred() ) { - if( !PyErr_ExceptionMatches( PyExc_TypeError ) ) + // Do not swallow KeyboardInterrupt or SystemExit + if( !PyErr_ExceptionMatches( PyExc_Exception ) ) return false; // or propagate somehow PyErr_Clear(); } From 40f7e4194cc7800a41dd22451fa0ae6f4f65feb2 Mon Sep 17 00:00:00 2001 From: mdartiailh Date: Fri, 9 Oct 2026 15:45:10 +0200 Subject: [PATCH 08/10] proper use of PyErr_GetRaisedException in modifyguard --- atom/src/modifyguard.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/src/modifyguard.h b/atom/src/modifyguard.h index cfdb1e07..40c7a691 100644 --- a/atom/src/modifyguard.h +++ b/atom/src/modifyguard.h @@ -38,8 +38,9 @@ class ModifyGuard // boolean tests (PyObject_IsTrue) to fail for wrong reasons. bool exception_set = false; #if PY_VERSION_HEX >= 0x030D0000 - PyObject *exc = PyErr_GetRaisedException(); + PyObject *exc; if( PyErr_Occurred() ){ + exc = PyErr_GetRaisedException(); exception_set = true; } #else From d5e7a02876109bfd84f778d27776bafb0dc9a421 Mon Sep 17 00:00:00 2001 From: mdartiailh Date: Fri, 9 Oct 2026 15:57:22 +0200 Subject: [PATCH 09/10] remove double incref --- atom/src/atomdict.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/atom/src/atomdict.cpp b/atom/src/atomdict.cpp index cbec90dd..df754119 100644 --- a/atom/src/atomdict.cpp +++ b/atom/src/atomdict.cpp @@ -461,7 +461,6 @@ bool DefaultAtomDict::Ready() if ( !bases ) { return false; // LCOV_EXCL_LINE (failed tuple creation) } - Py_INCREF( pyobject_cast( AtomDict::TypeObject ) ); PyTuple_SET_ITEM( bases.get(), 0, cppy::incref( pyobject_cast( AtomDict::TypeObject ) ) ); TypeObject = pytype_cast( PyType_FromSpecWithBases( &TypeObject_Spec, bases.get() ) From 0470aa031bb36b24e2d6f294fc79dbb9657e216a Mon Sep 17 00:00:00 2001 From: mdartiailh Date: Fri, 9 Oct 2026 16:05:01 +0200 Subject: [PATCH 10/10] update release notes --- releasenotes.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/releasenotes.rst b/releasenotes.rst index 749ddaab..d1978978 100644 --- a/releasenotes.rst +++ b/releasenotes.rst @@ -1,6 +1,13 @@ Atom Release Notes ================== +0.13.0 - unreleased +------------------- + +- add support for Python 3.15 PR #273 +- improve type stubs and checks them against mypy, pyrefly and ty PR #274 +- C API improvements + 0.12.1 - 02/10/2025 -------------------