Fix sqlite Connection initialization check (#6199)

* Fix sqlite3 Connection initialization check

Add proper __init__ validation for sqlite3.Connection to ensure base class
__init__ is called before using connection methods. This fixes the
test_connection_constructor_call_check test case.

Changes:
- Modified Connection.py_new to detect subclassing
- For base Connection class, initialization happens immediately in py_new
- For subclassed Connection, db is initialized as None
- Added __init__ method that performs actual database initialization
- Updated _db_lock error message to match CPython: 'Base Connection.__init__ not called.'

This ensures CPython compatibility where attempting to use a Connection
subclass instance without calling the base __init__ raises ProgrammingError.

* use Initializer trait
This commit is contained in:
Jiseok CHOI authored and GitHub committed 2025-10-21 11:11:31 +09:00
1 parent 25a464eeae
commit 2faa05dcfb
2 files changed
+47 -19

No files matched your search

-2
View File
@@ -219,8 +219,6 @@ class RegressionTests(unittest.TestCase):
class MyStr(str): pass
self.con.execute("select ?", (MyStr("abc"),))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_connection_constructor_call_check(self):
"""
Verifies that connection methods check whether base class __init__ was
+47 -17
View File
@@ -74,8 +74,8 @@ mod _sqlite {
},
sliceable::{SaturatedSliceIter, SliceableSequenceOp},
types::{
AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Hashable, IterNext,
Iterable, PyComparisonOp, SelfIter, Unconstructible,
AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Hashable,
Initializer, IterNext, Iterable, PyComparisonOp, SelfIter, Unconstructible,
},
utils::ToCString,
};
@@ -851,7 +851,31 @@ mod _sqlite {
type Args = ConnectArgs;
fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult {
Ok(Self::new(args, vm)?.into_ref_with_type(vm, cls)?.into())
let text_factory = PyStr::class(&vm.ctx).to_owned().into_object();
// For non-subclassed Connection, initialize in __new__
// For subclassed Connection, leave db as None and require __init__ to be called
let is_base_class = cls.is(Connection::class(&vm.ctx).as_object());
let db = if is_base_class {
// Initialize immediately for base class
Some(Connection::initialize_db(&args, vm)?)
} else {
// For subclasses, require __init__ to be called
None
};
let conn = Self {
db: PyMutex::new(db),
detect_types: args.detect_types,
isolation_level: PyAtomicRef::from(args.isolation_level),
check_same_thread: args.check_same_thread,
thread_ident: std::thread::current().id(),
row_factory: PyAtomicRef::from(None),
text_factory: PyAtomicRef::from(text_factory),
};
Ok(conn.into_ref_with_type(vm, cls)?.into())
}
}
@@ -871,9 +895,25 @@ mod _sqlite {
}
}
#[pyclass(with(Constructor, Callable), flags(BASETYPE))]
impl Initializer for Connection {
type Args = ConnectArgs;
fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
let mut guard = zelf.db.lock();
if guard.is_some() {
// Already initialized
return Ok(());
}
let db = Self::initialize_db(&args, vm)?;
*guard = Some(db);
Ok(())
}
}
#[pyclass(with(Constructor, Callable, Initializer), flags(BASETYPE))]
impl Connection {
fn new(args: ConnectArgs, vm: &VirtualMachine) -> PyResult<Self> {
fn initialize_db(args: &ConnectArgs, vm: &VirtualMachine) -> PyResult<Sqlite> {
let path = args.database.to_cstring(vm)?;
let db = Sqlite::from(SqliteRaw::open(path.as_ptr(), args.uri, vm)?);
let timeout = (args.timeout * 1000.0) as c_int;
@@ -881,17 +921,7 @@ mod _sqlite {
if let Some(isolation_level) = &args.isolation_level {
begin_statement_ptr_from_isolation_level(isolation_level, vm)?;
}
let text_factory = PyStr::class(&vm.ctx).to_owned().into_object();
Ok(Self {
db: PyMutex::new(Some(db)),
detect_types: args.detect_types,
isolation_level: PyAtomicRef::from(args.isolation_level),
check_same_thread: args.check_same_thread,
thread_ident: std::thread::current().id(),
row_factory: PyAtomicRef::from(None),
text_factory: PyAtomicRef::from(text_factory),
})
Ok(db)
}
fn db_lock(&self, vm: &VirtualMachine) -> PyResult<PyMappedMutexGuard<'_, Sqlite>> {
@@ -908,7 +938,7 @@ mod _sqlite {
} else {
Err(new_programming_error(
vm,
"Cannot operate on a closed database.".to_owned(),
"Base Connection.__init__ not called.".to_owned(),
))
}
}