From 38e9fc54e766ff19ed47bb4916b7ec102ef0d4fa Mon Sep 17 00:00:00 2001 From: Jiseok CHOI Date: Mon, 20 Oct 2025 16:35:58 +0900 Subject: [PATCH 1/2] 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. --- Lib/test/test_sqlite3/test_regression.py | 2 - stdlib/src/sqlite.rs | 53 ++++++++++++++++++------ 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_sqlite3/test_regression.py b/Lib/test/test_sqlite3/test_regression.py index a658ff1f3ce..50ad94d1707 100644 --- a/Lib/test/test_sqlite3/test_regression.py +++ b/Lib/test/test_sqlite3/test_regression.py @@ -221,8 +221,6 @@ def test_str_subclass(self): 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 diff --git a/stdlib/src/sqlite.rs b/stdlib/src/sqlite.rs index aba410c42bb..2baa6116409 100644 --- a/stdlib/src/sqlite.rs +++ b/stdlib/src/sqlite.rs @@ -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()) } } @@ -873,7 +897,7 @@ mod _sqlite { #[pyclass(with(Constructor, Callable), flags(BASETYPE))] impl Connection { - fn new(args: ConnectArgs, vm: &VirtualMachine) -> PyResult { + fn initialize_db(args: &ConnectArgs, vm: &VirtualMachine) -> PyResult { 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 +905,20 @@ 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(db) + } - 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), - }) + #[pymethod] + fn __init__(&self, args: ConnectArgs, vm: &VirtualMachine) -> PyResult<()> { + let mut guard = self.db.lock(); + if guard.is_some() { + // Already initialized + return Ok(()); + } + + let db = Self::initialize_db(&args, vm)?; + *guard = Some(db); + Ok(()) } fn db_lock(&self, vm: &VirtualMachine) -> PyResult> { @@ -908,7 +935,7 @@ mod _sqlite { } else { Err(new_programming_error( vm, - "Cannot operate on a closed database.".to_owned(), + "Base Connection.__init__ not called.".to_owned(), )) } } From df09182b7785d12eb9f10d243fbebd8c4453fe5f Mon Sep 17 00:00:00 2001 From: Jiseok CHOI Date: Tue, 21 Oct 2025 10:43:13 +0900 Subject: [PATCH 2/2] use Initializer trait --- stdlib/src/sqlite.rs | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/stdlib/src/sqlite.rs b/stdlib/src/sqlite.rs index 2baa6116409..4dadd8d0395 100644 --- a/stdlib/src/sqlite.rs +++ b/stdlib/src/sqlite.rs @@ -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, }; @@ -895,7 +895,23 @@ mod _sqlite { } } - #[pyclass(with(Constructor, Callable), flags(BASETYPE))] + impl Initializer for Connection { + type Args = ConnectArgs; + + fn init(zelf: PyRef, 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 initialize_db(args: &ConnectArgs, vm: &VirtualMachine) -> PyResult { let path = args.database.to_cstring(vm)?; @@ -908,19 +924,6 @@ mod _sqlite { Ok(db) } - #[pymethod] - fn __init__(&self, args: ConnectArgs, vm: &VirtualMachine) -> PyResult<()> { - let mut guard = self.db.lock(); - if guard.is_some() { - // Already initialized - return Ok(()); - } - - let db = Self::initialize_db(&args, vm)?; - *guard = Some(db); - Ok(()) - } - fn db_lock(&self, vm: &VirtualMachine) -> PyResult> { self.check_thread(vm)?; self._db_lock(vm)