5
1
I would like feedback on the class I've written. The purpose is to dynamically create and interact with SQLite3
databases, accepting lists of complete or incomplete statements.
import sqlite3
class DB(object):
"""DB initializes and manipulates SQLite3 databases."""
self.connect()
self.close()
def connect(self):
"""Connect to the SQLite3 database."""
self.connection = sqlite3.connect(self.database)
self.cursor = self.connection.cursor()
self.connected = True
self.statement = ''
def close(self):
"""Close the SQLite3 database."""
self.connection.commit()
self.connection.close()
self.connected = False
self.statement += statement
if self.statement.count(';') > 1:
print ('An error has occurerd: ' +
'You may only execute one statement at a time.')
print 'For the statement: %s' % self.statement
self.statement = ''
if sqlite3.complete_statement(self.statement):
#the statement is not incomplete, it's complete
return False
else:
#the statement is incomplete
return True
queries = []
close = False
if not self.connected:
#open a previously closed connection
self.connect()
#mark the connection to be closed once complete
close = True
if type(statements) == str:
#all statements must be in a list
statements = [statements]
for statement in statements:
if self.incomplete(statement):
#the statement is incomplete
continue
#the statement is complete
try:
statement = self.statement.strip()
#reset the test statement
self.statement = ''
self.cursor.execute(statement)
#retrieve selected data
data = self.cursor.fetchall()
if statement.upper().startswith('SELECT'):
#append query results
queries.append(data)
def terminal(self):
"""A simple SQLite3 terminal.
self.connect()
self.display = True
while True:
statement = raw_input('')
if statement == '':
user = raw_input(
'Type discard, exit (commit), or press enter (commit): ')
if not user:
self.connection.commit()
elif user == 'discard':
self.connect()
elif user == 'exit':
break
self.execute(statement)
self.display = False
self.close()
if __name__ == '__main__':
statement = ('CREATE TABLE %s (id INTEGER, filename TEXT);')
tables = ['source', 'query']
database = 'io.db'
statements = [statement % table for table in tables]
#setup
db = DB(database, statements)
#a single statement
db.execute(
["INSERT INTO source (id, filename) values (8, 'reference.txt');"])
[(8, u'reference.txt')]
[(8, u'one.txt'), (9, u'two.txt'), (10, u'three.txt')]
12345678910111213
3071415
This looks rather interesting, but also rather hard-coded. Did you by chance figure out a method of creating tables
in a more generic manner? – Mast Dec 5 '16 at 10:33
@Mast I've been trying to brainstorm a more generic way. One idea was to not allow incomplete statements. In the
big picture, no program can do everything. Maybe it's better to focus on accepting only good input. Also, In the
future I'd like to completely omit print statements and terminals in favor of logging. – 12345678910111213Dec 7 '16 at
9:58
add a comment
1 Answer
activeoldest votes
2
Overall this looks good. Documentation and comments look pretty good, and the code is fairly sensible. Some
comments:
Beware of mutable default arguments! In particular, statements=[]. If the statements variable gets
mutated within the class, that persists through all instances of the class. Better to have a default value
of None, and compare to that, i.e.:
def __init__(self, database='database.db', statements=None):
if statements is None:
statements = []
Although perhaps it would be better to defer this checking to the point where you’re about to connect and
run the command – if there are no commands to execute (whether the user skipped that argument, or
supplied an empty list) – you could skip setting up and closing the DB connection entirely.
It’s common to print errors to stderr, so I’d modify the print() on line 46–7 accordingly. Also, you’ve
misspelt occurred.
It’s better to use exceptions to indicate control flow, not just printing errors. This allows to caller to
handle them accordingly. Within DB.incomplete(), I’d consider throwing a ValueError if the user passes
multiple statements. Likewise lines 96–8 in DB.execute().
In DB.incomplete(), you can simplify the return statement:
return not sqlite3.complete_statement(self.statement)
This is both simpler and more Pythonic.
PEP 8 convention is that comments start by a hash following by a space, not a hash and then straight into
prose.
There’s use of print as a statement and as a function. You should be consistent – I’d recommend going
to the print function, and adding
from __future__ import print_function
to the top of the file. This is future-proofing for Python 3.
Because you have separate connect() and close() methods, it’s possible that somebody could
call connect(), execute some statements, and then hit an exception before they could close(). This
means the database connection could stick around for longer than you were expecting.
You might want to consider defining a context manager for your class. This allows people to
use with statement and your class, similar to the with open(file) construction:
with DB(database='chat.db') as mydb:
# do stuff with mydb
And then the cleanup code on the context manager always runs, even if you hit an exception in the body
of the with statement.
I don’t know what the two lists defined at the end of the file are. Left-over cleanup code?
shareimprove this answer