Skip to content

Commit

Permalink
Add support for PyODBC and PyPyODBC for MySQL
Browse files Browse the repository at this point in the history
  • Loading branch information
phdru committed Apr 6, 2017
1 parent 0fa4628 commit 0d02d45
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 66 deletions.
40 changes: 20 additions & 20 deletions appveyor.yml
Expand Up @@ -56,6 +56,26 @@ environment:
PYTHON_ARCH: "32"
db: mysql
TOX_ENV: "py36-mysql-connector-w32"
- PYTHON_HOME: "C:\\Python27-x64"
PYTHON_VERSION: "2.7"
PYTHON_ARCH: "64"
db: mysql
TOX_ENV: "py27-mysql-pyodbc-w32"
- PYTHON_HOME: "C:\\Python34-x64"
PYTHON_VERSION: "3.4"
PYTHON_ARCH: "64"
db: mysql
TOX_ENV: "py34-mysql-pyodbc-w32"
- PYTHON_HOME: "C:\\Python35-x64"
PYTHON_VERSION: "3.5"
PYTHON_ARCH: "64"
db: mysql
TOX_ENV: "py35-mysql-pyodbc-w32"
- PYTHON_HOME: "C:\\Python36-x64"
PYTHON_VERSION: "3.6"
PYTHON_ARCH: "64"
db: mysql
TOX_ENV: "py36-mysql-pyodbc-w32"
- PYTHON_HOME: "C:\\Python27"
PYTHON_VERSION: "2.7"
PYTHON_ARCH: "32"
Expand Down Expand Up @@ -116,26 +136,6 @@ environment:
PYTHON_ARCH: "64"
db: postgresql
TOX_ENV: "py36-postgres-pyodbc-w32"
- PYTHON_HOME: "C:\\Python27-x64"
PYTHON_VERSION: "2.7"
PYTHON_ARCH: "64"
db: postgresql
TOX_ENV: "py27-postgres-pypyodbc-w32"
- PYTHON_HOME: "C:\\Python34-x64"
PYTHON_VERSION: "3.4"
PYTHON_ARCH: "64"
db: postgresql
TOX_ENV: "py34-postgres-pypyodbc-w32"
- PYTHON_HOME: "C:\\Python35-x64"
PYTHON_VERSION: "3.5"
PYTHON_ARCH: "64"
db: postgresql
TOX_ENV: "py35-postgres-pypyodbc-w32"
- PYTHON_HOME: "C:\\Python36-x64"
PYTHON_VERSION: "3.6"
PYTHON_ARCH: "64"
db: postgresql
TOX_ENV: "py36-postgres-pypyodbc-w32"
- PYTHON_HOME: "C:\\Python27"
PYTHON_VERSION: "2.7"
PYTHON_ARCH: "32"
Expand Down
4 changes: 2 additions & 2 deletions docs/News.rst
Expand Up @@ -21,8 +21,8 @@ Drivers (work in progress)
--------------------------

* Add support for PyODBC and PyPyODBC (pure-python ODBC DB API driver) for
PostgreSQL. Driver names are ``pyodbc``, ``pypyodbc`` or ``odbc`` (try
``pyodbc`` and ``pypyodbc``). There are some problems.
MySQL and PostgreSQL. Driver names are ``pyodbc``, ``pypyodbc`` or
``odbc`` (try ``pyodbc`` and ``pypyodbc``). There are some problems.

Tests
-----
Expand Down
20 changes: 11 additions & 9 deletions docs/SQLObject.rst
Expand Up @@ -47,14 +47,14 @@ Requirements
============

Currently SQLObject supports MySQL_ via MySQLdb_ aka MySQL-python (called
mysqlclient_ for Python 3), `MySQL Connector`_, oursql_ and PyMySQL_. For
PostgreSQL_ psycopg2_ or psycopg1 are recommended; PyGreSQL_,
py-postgresql_, PyODBC_ and PyPyODBC_ are supported but have problems (not
all tests passed). SQLite_ has a built-in driver or PySQLite_. Firebird_
is supported via fdb_ or kinterbasdb_; pyfirebirdsql_ is supported but has
problems. `MAX DB`_ (also known as SAP DB) is supported via sapdb_. Sybase
via Sybase_. `MSSQL Server`_ via pymssql_ (+ FreeTDS_) or adodbapi_
(Win32).
mysqlclient_ for Python 3), `MySQL Connector`_, oursql_, PyMySQL_, PyODBC_
and PyPyODBC_. For PostgreSQL_ psycopg2_ or psycopg1 are recommended;
PyGreSQL_, py-postgresql_, PyODBC_ and PyPyODBC_ are supported but have
problems (not all tests passed). SQLite_ has a built-in driver or
PySQLite_. Firebird_ is supported via fdb_ or kinterbasdb_; pyfirebirdsql_
is supported but has problems. `MAX DB`_ (also known as SAP DB) is
supported via sapdb_. Sybase via Sybase_. `MSSQL Server`_ via pymssql_ (+
FreeTDS_) or adodbapi_ (Win32).

.. _MySQL: https://www.mysql.com/
.. _MySQLdb: https://sourceforge.net/projects/mysql-python/
Expand Down Expand Up @@ -1785,7 +1785,9 @@ transactions_ when using the InnoDB backend; SQLObject can explicitly
define the backend using ``sqlmeta.createSQL``.

Supported drivers are ``mysqldb``, ``connector``, ``oursql`` and
``pymysql``; defualt is ``mysqldb``.
``pymysql``, ``pyodbc``, ``pypyodbc`` or ``odbc`` (try ``pyodbc`` and
``pypyodbc``); defualt is ``mysqldb``.


Keyword argument ``conv`` allows to pass a list of custom converters.
Example::
Expand Down
52 changes: 42 additions & 10 deletions sqlobject/mysql/mysqlconnection.py
Expand Up @@ -8,7 +8,10 @@
class ErrorMessage(str):
def __new__(cls, e, append_msg=''):
obj = str.__new__(cls, e.args[1] + append_msg)
obj.code = int(e.args[0])
try:
obj.code = int(e.args[0])
except ValueError:
obj.code = e.args[0]
obj.module = e.__module__
obj.exception = e.__class__.__name__
return obj
Expand Down Expand Up @@ -67,11 +70,24 @@ def __init__(self, db, user, password='', host='localhost', port=0, **kw):
oursql.errnos['CR_SERVER_GONE_ERROR']
self.CR_SERVER_LOST = oursql.errnos['CR_SERVER_LOST']
self.ER_DUP_ENTRY = oursql.errnos['ER_DUP_ENTRY']
elif driver == 'pyodbc':
import pyodbc
self.module = pyodbc
elif driver == 'pypyodbc':
import pypyodbc
self.module = pypyodbc
elif driver == 'odbc':
try:
import pyodbc
except ImportError:
import pypyodbc as pyodbc
self.module = pyodbc
else:
raise ValueError(
'Unknown MySQL driver "%s", '
'expected mysqldb, connector, '
'oursql or pymysql' % driver)
'oursql, pymysql, '
'odbc, pyodbc or pypyodbc' % driver)
except ImportError:
pass
else:
Expand Down Expand Up @@ -104,6 +120,11 @@ def __init__(self, db, user, password='', host='localhost', port=0, **kw):
self.dbEncoding = None
self.driver = driver

if driver in ('odbc', 'pyodbc', 'pypyodbc'):
self.make_odbc_conn_str(db, host, port, user, password,
kw.pop('odbcdrv',
'MySQL ODBC 5.3 ANSI Driver'))

global mysql_Bin
if not PY2 and mysql_Bin is None:
mysql_Bin = self.module.Binary
Expand Down Expand Up @@ -133,12 +154,17 @@ def character_set_name(self):
if self.driver == 'connector':
self.kw['consume_results'] = True
try:
conn = self.module.connect(
host=self.host, port=self.port, db=self.db,
user=self.user, passwd=self.password, **self.kw)
if self.driver != 'oursql':
# Attempt to reconnect. This setting is persistent.
conn.ping(True)
if self.driver in ('odbc', 'pyodbc', 'pypyodbc'):
self.debugWriter.write(
"ODBC connect string: " + self.odbc_conn_str)
conn = self.module.connect(self.odbc_conn_str)
else:
conn = self.module.connect(
host=self.host, port=self.port, db=self.db,
user=self.user, passwd=self.password, **self.kw)
if self.driver != 'oursql':
# Attempt to reconnect. This setting is persistent.
conn.ping(True)
except self.module.OperationalError as e:
conninfo = ("; used connection string: "
"host=%(host)s, port=%(port)s, "
Expand Down Expand Up @@ -236,7 +262,13 @@ def _queryInsertID(self, conn, soInstance, id, names, values):
try:
id = c.lastrowid
except AttributeError:
id = c.insert_id()
try:
id = c.insert_id
except AttributeError:
self._executeRetry(conn, c, "SELECT LAST_INSERT_ID();")
id = c.fetchone()[0]
else:
id = c.insert_id()
if self.debugOutput:
self.printDebug(conn, id, 'QueryIns', 'result')
return id
Expand Down Expand Up @@ -274,7 +306,7 @@ def tableExists(self, tableName):
self.query('DESCRIBE %s' % (tableName))
return True
except dberrors.ProgrammingError as e:
if e.args[0].code == 1146: # ER_NO_SUCH_TABLE
if e.args[0].code in (1146, '42S02'): # ER_NO_SUCH_TABLE
return False
raise

Expand Down

0 comments on commit 0d02d45

Please sign in to comment.