diff --git a/atom/src/atomdict.cpp b/atom/src/atomdict.cpp index 1c321f5c..df754119 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 ); } @@ -345,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 */ }; @@ -428,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 */ }; @@ -458,8 +458,9 @@ 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) + } 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..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(); } @@ -122,11 +123,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..5e2c414b 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 ); } @@ -401,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()) ) { @@ -474,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/catommodule.cpp b/atom/src/catommodule.cpp index 147b4ce6..c98779e6 100644 --- a/atom/src/catommodule.cpp +++ b/atom/src/catommodule.cpp @@ -76,88 +76,80 @@ 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 ); - 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_AddObjectRef( mod, "GetAttr", PyGetAttr ) < 0) + return false; + if (PyModule_AddObjectRef( mod, "SetAttr", PySetAttr ) < 0) + return false; + if (PyModule_AddObjectRef( mod, "DelAttr", PyDelAttr ) < 0) + return false; + if (PyModule_AddObjectRef( mod, "PostGetAttr", PyPostGetAttr ) < 0) + return false; + if (PyModule_AddObjectRef( mod, "PostSetAttr", PyPostSetAttr ) < 0) + return false; + if (PyModule_AddObjectRef( mod, "DefaultValue", PyDefaultValue ) < 0) + return false; + if (PyModule_AddObjectRef( mod, "Validate", PyValidate ) < 0) + return false; + if (PyModule_AddObjectRef( mod, "PostValidate", PyPostValidate ) < 0) + return false; + if (PyModule_AddObjectRef( mod, "GetState", PyGetState ) < 0) + return false; + if (PyModule_AddObjectRef( mod, "ChangeType", PyChangeType ) < 0) + return false; return true; } 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; } 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(); } diff --git a/atom/src/member.cpp b/atom/src/member.cpp index fca66907..ae6ecb92 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 ); } @@ -917,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..40c7a691 100644 --- a/atom/src/modifyguard.h +++ b/atom/src/modifyguard.h @@ -37,11 +37,19 @@ 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; + if( PyErr_Occurred() ){ + exc = PyErr_GetRaisedException(); + 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 +64,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 ); } 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 + } } } diff --git a/atom/src/utils.h b/atom/src/utils.h index f02c926f..acb0b858 100644 --- a/atom/src/utils.h +++ b/atom/src/utils.h @@ -109,8 +109,12 @@ 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() ) { + // Do not swallow KeyboardInterrupt or SystemExit + if( !PyErr_ExceptionMatches( PyExc_Exception ) ) + 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; } } 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 -------------------