[Tutor] Proper SQLite cursor handling?
Cameron Simpson
cs at cskk.id.au
Mon Jul 5 04:18:10 EDT 2021
On 04Jul2021 20:33, boB Stepp <robertvstepp at gmail.com> wrote:
>3) Am I handling the database stuff properly so far?
Just to this, there's some stuff to criticise here.
> (
> self.game_id,
> _, # Already have game_name value.
> self.strategy,
> self.num_reserve_cards,
> self.num_foundation_cards,
> self.reserve_card_value,
> self.foundation_card_value,
> self.total_score,
> ) = self._get_game_data()
This is quite fragile. If the columns in your game table change, even in
their order, this will assign the wrong things to the various
attributes. You can have sqlite3 return a Row object instead of a bare
tuple of values for each row. There's an example in the module docs in
the "Row Objects" section. Adapting it, after you connect to the
database, add:
self.con.row_factory = sqlite3.Row
Then the row fetch methods return Row insteance, which still act like
tuples but also have attributes named after the columns. So the
assignment above would become:
game_row = self._get_game_data()
self.game_id = game_row.game_id
self.strategy = game_row.strategy
self.num_reserve_cards = game_row.num_reserve_cards
self.num_foundation_cards = game_row.num_foundation_cards
self.reserve_card_value = game_row.reserve_card_value
self.foundation_card_value = game_row.foundation_card_value
self.total_score = game_row.total_score
> def _get_game_data(self) -> Tuple[int, str, str, int, int, int,
> int, int]:
This would return an sqlite3.Row now.
> """Retrieve game data from database."""
> cur = self.con.cursor()
> cur.execute(
> f"""
> SELECT * FROM games WHERE game_name = {self.game_name};
> """
> )
Normally you try not to SELECT * because it returns all the column
values, which makes things hairy if you add columns - instead you'd
select exactly what you want and nothing more. For example, you're not
using the game_name.
However, with a Row object being returned you can pick what you want out
of the row as above. Still, it is better practice to SELECT just the
columns you want. If nothing else, this reduces what gets returned. In
larger programmes and databases that can be important.
Also, very importantly, this is the wrong way to insert values into an
SQL query. The format field {self.game_name} will just get the bare
game name inserted raw into the SQL, which will likely be a syntax error
or be misread as a column name. See this document:
https://xkcd.com/327/
The execute method accepts a dict for things to fill in in the SQL.
There's an example in the docs:
cur.execute("select * from people where name_last=:who and age=:age", {"who": who, "age": age})
That will place syntacticly correct values at the ":who" and ":age"
placeholders in the SQL string, using the values from the dict. Do that
instead.
> game_data = cur.fetchall()[0]
You ccould just fetchone().
> if not game_data:
This test is too late. game_data won't be empty or false, you'll get an
exception during the fetch. You could go:
game_rows = cur.fetchall()
if not game_rows:
# new game
else:
# we expect exactly one row, so the assignment below expects
# exactly one element in game_rows
game_data, = game_rows
Cheers,
Cameron Simpson <cs at cskk.id.au>
More information about the Tutor
mailing list