Managing a simple database with Python, SQLite and wxPython, 8
Phase 2, wxPython April 22nd, 2009
- Image via Wikipedia
Thanks to the comments and suggestions to the last post, it’s possible to make now a more pythonic and clearly generic database update class. Let’s check how the “generic” update/edit entry function is currently:
def update_data(self, values_list):
'''edits and updates fields'''
if sys.platform == 'darwin':
(cursor, database) = link_db(self.db_path)
else:
(cursor, database) = link_db()
cursor.execute("UPDATE bac SET projects = ?, comments = ?, temperature = ?, cell = ?, box = ?, tubes = ?, chromosome = ?, sdate = ?, clone = ?, source
= ?, location1 = ?, startpos = ?, endpos = ?,
gene = ?, genelink = ?, dnaex = ?, validation = ?, pcr = ?, refs = ?, antibiotic = ? WHERE idbac = ?",
values_list['projects'], values_list['comments'], values_list['temperature'], values_list['cell'], values_list['box'], values_list['tubes'],
values_list['chromo'], values_list['date'], values_list['clone'], values_list['source'], values_list['location'], values_list['start']
values_list['end'], values_list['gene'], values_list['genelink'], values_list['dna'], values_list['validation'], values_list['pcr'],
values_list['refs'], values_list['antibiotic'], values_list['idbac']))
database.commit()
database.close()
which is really ugly and, although it works, is not really useful outside this small project. Based on the comments the best option was to use placeholders and a dictionary, similar to the approach used on the insert data function. Pre-formatting a string to have both the field name to be updated and a placeholder (for instance :idbac) that will receive the values
update = ','.join(['%s=:%s' % (y, y) for y in values_list])
where update is the string we want and values_list is the dictionary with all the key-value pairs. I tried this approach, using this structure in the generic function, but then I decided that the best alternative was to put this join in the derived class function and pre-populate the string with the values and then send this string directly to the update function. In the end I opted to use this
update = ','.join(['%s=\"%s\"' % (y, values_list[y]) for y in values_list])
The latter is slightly different to what was suggested. The original one would create a tuple with the keys from the dictionary, making for instance sdate:sdate. With all these place holders just pass the dictionary and you have all the values inserted. This would be handy if the insert string was being created on the “generic” function. If we move this to the derived class, we can use the the alternative, keeping in mind that the values parsed should be surrounded by quotes, otherwise the SQL UPDATE statement will have problems with spaces and other foreign characters that should not be there. So instead of placeholders we will have gene:"PTEN" and we can attache this joined string to the actual commands. We then can move all the machinery from the “generic” function that can be written as
def update_data(self, update_string):
'''edits and updates fields'''
if sys.platform == 'darwin':
(cursor, database) = link_db(self.db_path)
else:
(cursor, database) = link_db()
cursor.execute(update_string)
database.commit()
database.close()
That’s it, very elegant (we will see the derived class in the next post). And finishing our generic class, we would need a delete function, so the user can eliminate entries that he/she doesn’t want anymore. It’s also a very simple function
def delete_data(self, delete_string):
'''deletes one field'''
if sys.platform == 'darwin':
(cursor, database) = link_db(self.db_path)
else:
(cursor, database) = link_db()
cursor.execute(delete_string)
database.commit()
database.close()
We will check the delete string next time. Again, I would like to thank for all the comments, it has been really helpful for me.
Previously in the series:
Part 1
Part 2
Part 3
Part 4
Part 5
Part 6
Part 7
![Reblog this post [with Zemanta]](http://img.zemanta.com/reblog_a.png?x-id=e8dc77f5-e3de-4d4f-8ec1-8c0006225743)
April 23rd, 2009 at 3:07 am
Much better would have been something like:
values = {“col1″: 47, “col2″: 48}
set_clause = “, “.join(["%s=:%s" % (key, key) for key in values.keys()])
statement = “update mytable set ” + set_caluse + ” where id=12345″
cursor.execute(statement, values)
execute()’s second parameter can be a sequence or a mapping. With mappings you can do neat stuff and DRY
April 23rd, 2009 at 3:27 am
There are a couple of reason why placeholders are still the best option for the update
1. Using placeholders, the sql query does not change from one update the to next, so some rdbms will be able to cache the query for reuse. If you’re doing a lot of similar inserts/updates this can be a big saving.
2. Using raw values in the sql insert/update string leaves you open to sql injection attacks. If a hacker can put the following into your dictionary
{‘gene’:'PTEN”;truncate bac;update bac set gene=”PTEN’}
then you’ve just lost all the data in your bac table. Using placeholders will still allow a hacker to insert malicious code into a table column, but will not allow this kind of attack. I’m no security expert, but its worth reading up on this as it’s quite a common exploit.
April 24th, 2009 at 10:55 am
Yes, Richard, that would be a concern. I will investigate further and learn more about it. Of course for this application I have no fear of hackers because it will have very limited use in the lab, but if anyone uses the same code for a a release that might be problematic. I will go over the derived class and then will come back to this matter when I have done more research.
April 24th, 2009 at 10:56 am
Thanks, Gerhard. I will try your suggestion and see how it goes. Really appreciate your comment.