Add Commit Writes From Executed SQLite Statements as a Python TIL

This commit is contained in:
jbranchaud
2026-07-24 17:10:10 -05:00
parent 25f5029ad3
commit eb7b54b0cd
2 changed files with 74 additions and 1 deletions
+2 -1
View File
@@ -10,7 +10,7 @@ working across different projects via [VisualMode](https://www.visualmode.dev/).
For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter). For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter).
_1833 TILs and counting..._ _1834 TILs and counting..._
See some of the other learning resources I work on: See some of the other learning resources I work on:
@@ -1076,6 +1076,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Break Debugger On First Line Of Program](python/break-debugger-on-first-line-of-program.md) - [Break Debugger On First Line Of Program](python/break-debugger-on-first-line-of-program.md)
- [Check If Package Is Installed With Pip](python/check-if-package-is-installed-with-pip.md) - [Check If Package Is Installed With Pip](python/check-if-package-is-installed-with-pip.md)
- [Check Precondition Before Click Arg Parsing](python/check-precondition-before-click-arg-parsing.md) - [Check Precondition Before Click Arg Parsing](python/check-precondition-before-click-arg-parsing.md)
- [Commit Writes From Executed SQLite Statements](python/commit-writes-from-executed-sqlite-statements.md)
- [Control Passing Of Time In Tests](python/control-passing-of-time-in-tests.md) - [Control Passing Of Time In Tests](python/control-passing-of-time-in-tests.md)
- [Create A Dummy DataFrame In Pandas](python/create-a-dummy-dataframe-in-pandas.md) - [Create A Dummy DataFrame In Pandas](python/create-a-dummy-dataframe-in-pandas.md)
- [Create A Range Of Descending Values](python/create-a-range-of-descending-values.md) - [Create A Range Of Descending Values](python/create-a-range-of-descending-values.md)
@@ -0,0 +1,72 @@
# Commit Writes From Executed SQLite Statements
Let's look at a method that uses a
[`sqlite3`](https://docs.python.org/3/library/sqlite3.html) connection to
execute a couple statements against a SQLite database.
```python
def write_session_with_project(self, session, *, active=False) -> None:
# Delete the current active session if there is one
self.conn.execute("""
delete from sessions where active = 1;
""")
# Upsert (find or create) the project based on `session.project_name`
cursor = self.conn.execute(
"""
insert into projects (name) values (:project_name)
on conflict (name) do update set name = excluded.name
returning id;
""",
{"project_name": session.project_name},
)
project_id = cursor.fetchone()[0]
# ...
self.conn.commit()
```
The first `conn.execute` call is going to implicitly start a database
transaction before executing the statement. Subsequent statements are going to
take place within that transaction. To apply all the changes in the transaction
I have to eventually run `conn.commit()`. If there isn't an issue committing all
the changes and nothing else raised before I committed, then those changes will
all be applied atomically.
I will need to do my own exception handling with a try/catch that handles any
rollback and closes the connection.
I'd rather not have to manage those extra pieces which is the kind of thing
context managers typically help with. Let's improve upon this with the
[Connection context
manager](https://docs.python.org/3/library/sqlite3.html#how-to-use-the-connection-context-manager):
```python
def write_session_with_project(self, session, *, active=False) -> None:
with self.conn:
# Delete the current active session if there is one
self.conn.execute("""
delete from sessions where active = 1;
""")
# Upsert (find or create) the project based on `session.project_name`
cursor = self.conn.execute(
"""
insert into projects (name) values (:project_name)
on conflict (name) do update set name = excluded.name
returning id;
""",
{"project_name": session.project_name},
)
project_id = cursor.fetchone()[0]
# ...
```
I get the same transactional behavior as before for everything in the context
manager body. However, now the `commit` is handled and the `rollback` and
`close` is handled if there is an exception.
Note: the connection context manager will still re-propagate an exception that
occurred, so I may need to handle that with a try/catch somewhere.