I wrote a webapp that frequently needs to read from, but rarely needs to write to, a sqlite database. I was not using an ORM for the project, and frequently had to write boilerplate code by hand to open a database connection, get a cursor, lock the database if I was doing a write, and so forth. I got tired of doing this, and eventually wrote a with-able class to handle both read-only and read/write connections to my database, called LockableSqliteConnection.
From its documentation:
LockableSqliteConnection: Awith-able class that wraps a SQLite connection, a cursor, and a lockWhen the
withstatement is begun, the internal cursor object is allocated, and the internal lock is acquired. When thewithstatements terminates, the internal cursor object is closed, the internal connection object is committed, and the internal lock object is released. Exiting thewithstatement does not close the connection; the caller is responsible for this, but we do provide a convenience method to do it.Usable like so:
lockableconn = LockableSqliteConnection( "file:///some/database.sqlite?cache=shared") with lockableconn.ro as connection: connection.cursor.execute("SELECT * FROM SOMEWHERE") results = connection.cursor.fetchall() with lockableconn.rw as connection: connection.cursor.execute("INSERT SOMETHING INTO SOMEWHERE") lockableconn.close()WARNING: This class has the read/write property
LockableSqliteConnection.rwand the read-only propertyLockableSqliteConnection.ro, but the read-only property is not a security boundary. It exists so that the class knows whether it needs to lock the database, NOT to enforce that database changes don’t happen in SQL that you pass to its cursor. Using the.rwproperty will also handle committing changes made in read/write mode, so if you’re doing complicated database transactions which you might have to roll back halfway through, you will have to handle that yourself (or perhapsLockableSqliteConnectionisn’t a good tool for your use case).WARNING: Inside of the
withstatement, take care not to call other code that will use awithstatement on the same LockableSqliteConnection object in read/write mode. This sounds obvious, but it’s easy to do when thewithstatement might be in another function which is itself called inside awithstatement. For instance, this code will deadlock, becausefunc2()locks the connection untilfunc1()completes, butfunc1()will not finish until the lock is released:lockableconn = LockablesqliteConnection( "file:///some/database.sqlite?cache=shared") def func1(): with lockableconn.rw as connection: connection.cursor.execute("SOME SQL HERE") results = connection.cursor.fetchall() def func2(): with lockableconn.rw as connection: func1()This class is intended to take the place of more cumbersome syntax like:
lock = threading.Lock() dbconn = sqlite3.connect( "file:///some/database.sqlite?cache=shared", uri=True, check_same_thread=False) with lock: with dbconn as connection: cursor = connection.cursor() cursor.execute("SOME SQL HERE") results = cursor.fetchall() connection.commit() cursor.close()