3535#define Py_TPFLAGS_MANAGED_DICT 0
3636#endif
3737
38+ #ifndef Py_TPFLAGS_IS_ABSTRACT
39+ #define Py_TPFLAGS_IS_ABSTRACT (1UL << 20 )
40+ #endif
41+
3842namespace db0 ::python
3943
4044{
@@ -82,10 +86,23 @@ namespace db0::python
8286 }
8387 return result;
8488 }
89+
90+ bool checkMemoTypeAbstractInstantiation (PyTypeObject *py_type)
91+ {
92+ if (!(py_type->tp_flags & Py_TPFLAGS_IS_ABSTRACT)) {
93+ return true ;
94+ }
95+ PyErr_Format (PyExc_TypeError, " Can't instantiate abstract class %s" , py_type->tp_name );
96+ return false ;
97+ }
8598
8699 template <typename MemoImplT>
87100 MemoImplT *tryMemoObject_new (const MemoTypeDecoration &decor, PyTypeObject *py_type, PyObject *, PyObject *)
88101 {
102+ if (!checkMemoTypeAbstractInstantiation (py_type)) {
103+ return nullptr ;
104+ }
105+
89106 // NOTE: read-only fixture access is sufficient here since objects are lazy-initialized
90107 // i.e. the actual dbzero instance is created on postInit
91108 // this is also important for dynamically scoped clases (where read/write access may not be possible on default fixture)
@@ -193,6 +210,10 @@ namespace db0::python
193210
194211 PyObject *tryMemoObject_new_singleton (PyTypeObject *py_type, PyObject *args, PyObject *kwargs)
195212 {
213+ if (!checkMemoTypeAbstractInstantiation (py_type)) {
214+ return nullptr ;
215+ }
216+
196217 std::string px_name;
197218 std::uint64_t fixture_uuid = 0 ;
198219 // try resolve type specific scope: static, dynamic or default
@@ -206,6 +227,12 @@ namespace db0::python
206227 if (result) {
207228 return result;
208229 }
230+ if (fixture->getAccessType () == AccessType::READ_ONLY ) {
231+ THROWF (db0::InputException)
232+ << " Cannot create singleton " << PyToolkit::getTypeName (py_type)
233+ << " in read-only prefix '" << fixture->getPrefix ().getName ()
234+ << " ': singleton instance does not exist" ;
235+ }
209236 }
210237
211238 // fixture, does not exist, try creating a new one
@@ -972,6 +999,9 @@ namespace db0::python
972999
9731000 // Enable GC for Python 3.10 compatibility - required for inheritance hierarchies
9741001 std::uint32_t flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
1002+ if (base_class->tp_flags & Py_TPFLAGS_IS_ABSTRACT) {
1003+ flags |= Py_TPFLAGS_IS_ABSTRACT;
1004+ }
9751005#if PY_VERSION_HEX < 0x030B0000 // Python < 3.11
9761006 flags |= Py_TPFLAGS_HAVE_GC;
9771007#endif
0 commit comments