I'm working on class creation in Python together with some SQLite tests and I'm wondering if the way I'm handling the connections is right (and most importantly, safe).
Inside class Test
, I have two methods: simple_operation
, which inserts a new value in a nested set model table, and coupled_operation
, which executes two simple_operation
in a row to avoid redundancy.
Currently, all other methods involving any insertion or updates on the database open the connection and close it upon completing the operation, like simple_operation
is planned originally:
class Test:
def simple_operation(self, value):
conn = sqlite3.connect(mydatabase)
cur = conn.cursor()
with conn:
cur.execute('UPDATE nested SET rgt = rgt + 2 WHERE rgt >= ?;', (value,))
cur.execute('UPDATE nested SET lft = lft + 2 WHERE lft >= ?;', (value,))
cur.execute('INSERT INTO nested(lft, rgt) VALUES(?, ?);', (value, value+1))
conn.close()
However, since coupled_operation
will execute it twice and its purpose is to pair two simple operations that have something in common, it would have its own transaction leading back to the simple method:
class Test:
def coupled_operation(self, value):
connd = sqlite3.connect(mydatabase)
with connd:
self.simple_operation(value)
self.simple_operation(value+2)
conn.close()
However, since simple_operation
implements its own transaction, handling it the same way would commit every simple_operation
it makes, defeating the purpose of connd
as a context manager. In a practical level, this implementation does 2x conn.commit
with 3x cur.execute
each, instead of 6x cur.execute
and then connd.commit
.
My solution is currently passing through leaving the cursor as an optional argument in simple_operation
, changing behaviour whether it is provided or not:
class Test:
def coupled_operation(self, value):
connd = sqlite3.connect(mydatabase)
curd = connd.cursor()
with connd:
self.simple_operation(value, curd)
self.simple_operation(value+2, curd) # A
conn.close()
def simple_operation(self, value, outer_cursor=None):
if outer_cursor is None:
conn = sqlite3.connect(mydatabase)
cur = conn.cursor()
with conn:
cur.execute('UPDATE nested SET rgt = rgt + 2 WHERE rgt >= ?;', (value,))
cur.execute('UPDATE nested SET lft = lft + 2 WHERE lft >= ?;', (value,))
cur.execute('INSERT INTO nested(lft, rgt) VALUES(?, ?);', (value, value+1))
connd.close()
else:
# Executes, but doesn't commit, leaves for coupled_operation to integrate into transaction
outer_cursor.execute('UPDATE nested SET rgt = rgt + 2 WHERE rgt >= ?;', (value,))
outer_cursor.execute('UPDATE nested SET lft = lft + 2 WHERE lft >= ?;', (value,))
outer_cursor.execute('INSERT INTO nested(lft, rgt) VALUES(?, ?);', (value, value+1)) # B
I'm not sure, however, if done this way it is successfully contained within the desired transaction, in such a way that should something go wrong with # B
when coming from # A
the entire operation would be rolled back.
Just for curiosity's sake, I'm opening and closing connections every time I handle the database. Previously I set a single connection as a class property and started messing with other methods, but I ended up locking my database when instantiating the class. Would you guys consider this way or the one shown in the examples more efficient?
question from:
https://stackoverflow.com/questions/65895593/how-to-nest-methods-to-execute-a-sqlite-transaction-safely-in-python