From b00989a6326dfd0d7a3d2146bce161eaf19c3f06 Mon Sep 17 00:00:00 2001 From: Tom Keffer Date: Sun, 5 Feb 2017 17:29:43 -0800 Subject: [PATCH] Ported MySQLdb to the new database exception hierarchy. --- bin/weedb/NOTES.md | 4 +- bin/weedb/mysql.py | 118 ++++++++++++++++------------------ bin/weedb/test/test_errors.py | 18 +++--- docs/changes.txt | 3 + 4 files changed, 70 insertions(+), 73 deletions(-) diff --git a/bin/weedb/NOTES.md b/bin/weedb/NOTES.md index a3c1b711..9075698e 100644 --- a/bin/weedb/NOTES.md +++ b/bin/weedb/NOTES.md @@ -12,9 +12,9 @@ This table shows how the various MySQLdb and sqlite exceptions are mapped to a w | `OperationalError` | *N/A* | `OperationalError` | 1049 | Open non-existent database | | `DatabaseExists` | *N/A* | `ProgrammingError` | 1007 | Database already exists | | `OperationalError` | `OperationalError` | `OperationalError` | 1050 | Table already exists | +| `ProgrammingError` | *N/A* | `ProgrammingError` | 1146 | SELECT on non-existing database | | `ProgrammingError` | `OperationalError` | `ProgrammingError` | 1146 | SELECT non-existing table | | `OperationalError` | `OperationalError` | `OperationalError` | 1054 | SELECT non-existing column | -| `ProgrammingError` | *N/A* | `ProgrammingError` | 1146 | SELECT on non-existing database | | `IntegrityError` | `IntegrityError` | `IntegrityError` | 1062 | Duplicate key | ###V3.6 Exception hierarchy @@ -42,9 +42,9 @@ StandardError | `NoDatabaseError` | *N/A* | `OperationalError` | 1049 | Open non-existent database | | `DatabaseExistsError` | *N/A* | `ProgrammingError` | 1007 | Database already exists | | `TableExistsError` | `OperationalError` | `OperationalError` | 1050 | Table already exists | +| `NoTableError` | *N/A* | `ProgrammingError` | 1146 | SELECT on non-existing database | | `NoTableError` | `OperationalError` | `ProgrammingError` | 1146 | SELECT non-existing table | | `NoColumnError` | `OperationalError` | `OperationalError` | 1054 | SELECT non-existing column | -| `ProgrammingError` | *N/A* | `ProgrammingError` | 1146 | SELECT on non-existing database | | `IntegrityError` | `IntegrityError` | `IntegrityError` | 1062 | Duplicate key | ###V3.7 Exception hierarchy diff --git a/bin/weedb/mysql.py b/bin/weedb/mysql.py index 591ee699..166da031 100644 --- a/bin/weedb/mysql.py +++ b/bin/weedb/mysql.py @@ -1,32 +1,49 @@ # -# Copyright (c) 2009-2015 Tom Keffer +# Copyright (c) 2009-2017 Tom Keffer # # See the file LICENSE.txt for your full rights. # -"""Driver for the MySQL database""" +"""weedb driver for the MySQL database""" import decimal import MySQLdb -from _mysql_exceptions import IntegrityError, ProgrammingError, OperationalError +from _mysql_exceptions import DatabaseError, IntegrityError, ProgrammingError, OperationalError from weeutil.weeutil import to_bool import weedb DEFAULT_ENGINE = 'INNODB' +exception_map = { + 1007: weedb.DatabaseExistsError, + 1008: weedb.NoDatabaseError, + 1044: weedb.PermissionError, + 1045: weedb.BadPasswordError, + 1049: weedb.NoDatabaseError, + 1050: weedb.TableExistsError, + 1054: weedb.NoColumnError, + 1062: weedb.IntegrityError, + 1146: weedb.NoTableError, + 2002: weedb.CannotConnectError, + 2005: weedb.CannotConnectError, + None: weedb.DatabaseError + } + def guard(fn): """Decorator function that converts MySQL exceptions into weedb exceptions.""" def guarded_fn(*args, **kwargs): try: return fn(*args, **kwargs) - except IntegrityError, e: - raise weedb.IntegrityError(e) - except ProgrammingError, e: - raise weedb.ProgrammingError(e) - except OperationalError, e: - raise weedb.OperationalError(e) + except DatabaseError, e: + # Default exception is weedb.DatabaseError + try: + errno = e[0] + except IndexError: + errno = None + klass = exception_map.get(errno, weedb.DatabaseError) + raise klass(e) return guarded_fn @@ -34,37 +51,26 @@ def guard(fn): def connect(host='localhost', user='', password='', database_name='', driver='', engine=DEFAULT_ENGINE, **kwargs): """Connect to the specified database""" - if host not in ('localhost', '127.0.0.1'): - kwargs.setdefault('port', 3306) return Connection(host=host, user=user, password=password, database_name=database_name, engine=engine, **kwargs) def create(host='localhost', user='', password='', database_name='', driver='', engine=DEFAULT_ENGINE, **kwargs): """Create the specified database. If it already exists, - an exception of type weedb.DatabaseExists will be thrown.""" + an exception of type weedb.DatabaseExistsError will be thrown.""" # Open up a connection w/o specifying the database. - if host not in ('localhost', '127.0.0.1'): - kwargs.setdefault('port', 3306) + connect = Connection(host=host, + user=user, + password=password, + **kwargs) + cursor = connect.cursor() + try: - connect = MySQLdb.connect(host=host, - user=user, - passwd=password, - **kwargs) - set_engine(connect, engine) - cursor = connect.cursor() - # An exception will get thrown if the database already exists. - try: - # Now create the database. - cursor.execute("CREATE DATABASE %s" % (database_name,)) - except ProgrammingError: - # The database already exists. Change the type of exception. - raise weedb.DatabaseExists("Database %s already exists" % (database_name,)) - finally: - cursor.close() - connect.close() - except OperationalError, e: - raise weedb.OperationalError(e) + # Now create the database. + cursor.execute("CREATE DATABASE %s" % (database_name,)) + finally: + cursor.close() + connect.close() def drop(host='localhost', user='', password='', database_name='', @@ -73,23 +79,19 @@ def drop(host='localhost', user='', password='', database_name='', if host not in ('localhost', '127.0.0.1'): kwargs.setdefault('port', 3306) # Open up a connection + connect = Connection(host=host, + user=user, + password=password, + **kwargs) + cursor = connect.cursor() + try: - connect = MySQLdb.connect(host=host, - user=user, - passwd=password, - **kwargs) - cursor = connect.cursor() - try: - cursor.execute("DROP DATABASE %s" % database_name) - except OperationalError: - raise weedb.NoDatabase("""Attempt to drop non-existent database %s""" % (database_name,)) - finally: - cursor.close() - connect.close() - except OperationalError, e: - raise weedb.OperationalError(e) - + cursor.execute("DROP DATABASE %s" % database_name) + finally: + cursor.close() + connect.close() +@guard class Connection(weedb.Connection): """A wrapper around a MySQL connection object.""" @@ -111,16 +113,9 @@ class Connection(weedb.Connection): """ if host not in ('localhost', '127.0.0.1'): kwargs.setdefault('port', 3306) - try: - connection = MySQLdb.connect(host=host, user=user, passwd=password, db=database_name, **kwargs) - except OperationalError, e: - # The MySQL driver does not include the database in the - # exception information. Tack it on, in case it might be useful. - msg = str(e) + " while opening database '%s'" % (database_name,) - if e.args[0] == 2002: - raise weedb.CannotConnect(msg) - else: - raise weedb.OperationalError(msg) + + connection = MySQLdb.connect(host=host, user=user, passwd=password, + db=database_name, **kwargs) weedb.Connection.__init__(self, connection, database_name, 'mysql') @@ -223,6 +218,7 @@ class Connection(weedb.Connection): class Cursor(object): """A wrapper around the MySQLdb cursor object""" + @guard def __init__(self, connection): """Initialize a Cursor from a connection. @@ -251,9 +247,9 @@ class Cursor(object): return self def fetchone(self): - # Get a result from the MySQL cursor, then run it through the massage + # Get a result from the MySQL cursor, then run it through the _massage # filter below - return massage(self.cursor.fetchone()) + return _massage(self.cursor.fetchone()) def close(self): try: @@ -279,8 +275,8 @@ class Cursor(object): # This is a utility function for converting a result set that might contain # longs or decimal.Decimals (which MySQLdb uses) to something containing just ints. # -def massage(seq): - # Return the massaged sequence if it exists, otherwise, return None +def _massage(seq): + # Return the _massaged sequence if it exists, otherwise, return None if seq is not None: return [int(i) if isinstance(i, long) or isinstance(i, decimal.Decimal) else i for i in seq] diff --git a/bin/weedb/test/test_errors.py b/bin/weedb/test/test_errors.py index ce618144..88bfa634 100644 --- a/bin/weedb/test/test_errors.py +++ b/bin/weedb/test/test_errors.py @@ -1,5 +1,7 @@ """Test the weedb exception hierarchy""" from __future__ import with_statement +import os +import stat import unittest import MySQLdb @@ -47,22 +49,15 @@ class Cursor(object): class Common(unittest.TestCase): def setUp(self): + """Drop the old databases, in preparation for running a test.""" try: weedb.drop(mysql1_dict) except weedb.NoDatabase: pass - try: - weedb.drop(mysql2_dict) - except weedb.NoDatabase: - pass try: weedb.drop(sqdb1_dict) except weedb.NoDatabase: pass - try: - weedb.drop(sqdb2_dict) - except weedb.NoDatabase: - pass def test_bad_host(self): mysql_dict = dict(mysql1_dict) @@ -86,7 +81,10 @@ class Common(unittest.TestCase): weedb.create(mysql1_dict) with self.assertRaises(weedb.PermissionError): weedb.drop(mysql2_dict) - with self.assertRaises(weedb.PermissionError): + weedb.create(sqdb1_dict) + # Can't really test this one without setting up a file where + # we have no write permission + with self.assertRaises(weedb.NoDatabaseError): weedb.drop(sqdb2_dict) def test_create_nopermission(self): @@ -114,7 +112,7 @@ class Common(unittest.TestCase): mysql_dict.pop('database_name') connect = weedb.connect(mysql_dict) cursor = connect.cursor() - with self.assertRaises(weedb.NoDatabaseError): + with self.assertRaises(weedb.NoTableError): cursor.execute("SELECT foo from test_weewx1.bar") cursor.close() connect.close() diff --git a/docs/changes.txt b/docs/changes.txt index 22e3759a..6498866d 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -127,6 +127,9 @@ with option retry_login. Fixes issue #212. The test suites now use dedicated users 'weewx1' and 'weewx2'. A shell script has been included to setup these users. +A more formal exception hierarchy has been adopted for the internal +database library weedb. See weedb/NOTES.md + 3.6.2 11/08/2016