]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- New DBAPI support for pymysql, a pure Python port
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 26 Jan 2011 16:18:03 +0000 (11:18 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 26 Jan 2011 16:18:03 +0000 (11:18 -0500)
of MySQL-python.  [ticket:1991]

16 files changed:
CHANGES
doc/build/core/engines.rst
doc/build/dialects/mysql.rst
lib/sqlalchemy/connectors/mysqldb.py
lib/sqlalchemy/dialects/drizzle/base.py
lib/sqlalchemy/dialects/mysql/__init__.py
lib/sqlalchemy/dialects/mysql/mysqldb.py
lib/sqlalchemy/dialects/mysql/pymysql.py [new file with mode: 0644]
test/aaa_profiling/test_orm.py
test/bootstrap/config.py
test/dialect/test_mysql.py
test/engine/test_execute.py
test/engine/test_reconnect.py
test/lib/engines.py
test/lib/requires.py
test/sql/test_types.py

diff --git a/CHANGES b/CHANGES
index 1c9241478621a97964b0d523b16f21affbc32e12..963addd9674f46db417b41f7b7904dfc184437f2 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -166,6 +166,10 @@ CHANGES
     VARCHAR type which is similarly unbounded when no length
     specified.
 
+- mysql
+  - New DBAPI support for pymysql, a pure Python port
+    of MySQL-python.  [ticket:1991]
+
 - drizzle
   - New dialect for Drizzle, a MySQL variant.  Uses MySQL-python
     for the DBAPI.  [ticket:2003]
index 082b50a2193f38c2b91114671d770b73026ef8a8..2d135f459d96f69aeb919a7975c6a7b4c5689914 100644 (file)
@@ -62,7 +62,7 @@ Driver                     Connect string               Py2K         Py3K
 **DB2/Informix IDS**
 ibm-db_                    thirdparty                   thirdparty   thirdparty    thirdparty   thirdparty         thirdparty
 **Drizzle**
-drizzle_                   ``drizzle+mysqldb``\*        yes          development   no           yes                yes
+mysql-python_              ``drizzle+mysqldb``\*        yes          development   no           yes                yes
 **Firebird**
 kinterbasdb_               ``firebird+kinterbasdb``\*   yes          development   no           yes                yes
 **Informix**
@@ -82,6 +82,7 @@ pymssql_                   ``mssql+pymssql``            yes          development
 `MySQL Connector/Python`_  ``mysql+mysqlconnector``     yes          yes           no           yes                yes
 mysql-python_              ``mysql+mysqldb``\*          yes          development   no           yes                yes
 OurSQL_                    ``mysql+oursql``             yes          yes           no           yes                yes
+pymysql_                   ``mysql+pymysql``            yes          development   no           yes                yes
 **Oracle**
 cx_oracle_                 ``oracle+cx_oracle``\*       yes          development   no           yes                yes
 `Oracle JDBC Driver`_      ``oracle+zxjdbc``            no           no            yes          yes                yes
@@ -106,6 +107,7 @@ python-sybase_             ``sybase+pysybase``          yes [1]_     development
 .. _mysql-python: http://sourceforge.net/projects/mysql-python
 .. _MySQL Connector/Python: https://launchpad.net/myconnpy
 .. _OurSQL: http://packages.python.org/oursql/
+.. _pymysql: http://code.google.com/p/pymysql/
 .. _PostgreSQL JDBC Driver: http://jdbc.postgresql.org/
 .. _sqlite3: http://docs.python.org/library/sqlite3.html
 .. _pysqlite: http://pypi.python.org/pypi/pysqlite/
@@ -123,7 +125,6 @@ python-sybase_             ``sybase+pysybase``          yes [1]_     development
 .. _informixdb: http://informixdb.sourceforge.net/
 .. _sapdb: http://www.sapdb.org/sapdbapi.html
 .. _python-sybase: http://python-sybase.sourceforge.net/
-.. _drizzle: http://drizzle.org/
 
 Further detail on dialects is available at :ref:`dialect_toplevel`.
 
index 8796adb3e39807ef6d247420f42bdd3797605692..c8edbceab922ebebc100e79f12f984f2ff390f24 100644 (file)
@@ -162,6 +162,11 @@ OurSQL Notes
 
 .. automodule:: sqlalchemy.dialects.mysql.oursql
 
+pymysql Notes
+-------------
+
+.. automodule:: sqlalchemy.dialects.mysql.pymysql
+
 MySQL-Connector Notes
 ----------------------
 
index 7696cdb797912bd64c539206621e280ec5562df8..59744f22840cd1ee3ec828e48b473cc52a261343 100644 (file)
@@ -48,6 +48,7 @@ class MySQLDBConnector(Connector):
 
     @classmethod
     def dbapi(cls):
+        # is overridden when pymysql is used
         return __import__('MySQLdb')
 
     def do_executemany(self, cursor, statement, parameters, context=None):
@@ -87,7 +88,9 @@ class MySQLDBConnector(Connector):
         client_flag = opts.get('client_flag', 0)
         if self.dbapi is not None:
             try:
-                from MySQLdb.constants import CLIENT as CLIENT_FLAGS
+                CLIENT_FLAGS = __import__(self.dbapi.__package__+'.constants',
+                                          globals(), locals(),
+                                          ['CLIENT'], 0).CLIENT
                 client_flag |= CLIENT_FLAGS.FOUND_ROWS
             except:
                 pass
index 5c268fb60382febb5d5aa00c9b63ad69a08ccd23..bec0562a95cd59b56d5e4b0e1bce84304daf2052 100644 (file)
@@ -1,8 +1,6 @@
-# -*- fill-column: 78 -*-
 # drizzle/base.py
-# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Michael Bayer mike_mp@zzzcomputing.com
-# and Jason Kirtland.
-# Copyright (C) 2010 Monty Taylor <mordred@inaugust.com>
+# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
+# Copyright (C) 2010-2011 Monty Taylor <mordred@inaugust.com>
 #
 # This module is part of SQLAlchemy and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
index fe1ef49b26f161c7157306f2df4bdd13321d5992..7cab573e3e541d6bc7db8199c8882f894a46bad1 100644 (file)
@@ -5,7 +5,7 @@
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
 
 from sqlalchemy.dialects.mysql import base, mysqldb, oursql, \
-                                pyodbc, zxjdbc, mysqlconnector
+                                pyodbc, zxjdbc, mysqlconnector, pymysql
 
 # default dialect
 base.dialect = mysqldb.dialect
index 003502b13c3600aa49a3d8a9415b5236088478e7..1b0ea85cb9005b269ddc555ec89ca7eddf257804 100644 (file)
@@ -41,10 +41,11 @@ strings, also pass ``use_unicode=0`` in the connection arguments::
 Known Issues
 -------------
 
-MySQL-python at least as of version 1.2.2 has a serious memory leak related
+MySQL-python version 1.2.2 has a serious memory leak related
 to unicode conversion, a feature which is disabled via ``use_unicode=0``.
-The recommended connection form with SQLAlchemy is::
-
+Using a more recent version of MySQL-python is recommended. The 
+recommended connection form with SQLAlchemy is::
+  
     engine = create_engine('mysql://scott:tiger@localhost/test?charset=utf8&use_unicode=0', pool_recycle=3600)
 
 
diff --git a/lib/sqlalchemy/dialects/mysql/pymysql.py b/lib/sqlalchemy/dialects/mysql/pymysql.py
new file mode 100644 (file)
index 0000000..dee3dfe
--- /dev/null
@@ -0,0 +1,38 @@
+# mysql/pymysql.py
+# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
+"""Support for the MySQL database via the pymysql adapter.
+
+pymysql is available at:
+
+    http://code.google.com/p/pymysql/
+
+Connecting
+----------
+
+Connect string::
+
+    mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
+
+MySQL-Python Compatibility
+--------------------------
+
+The pymysql DBAPI is a pure Python port of the MySQL-python (MySQLdb) driver, 
+and targets 100% compatibility.   Most behavioral notes for MySQL-python apply to 
+the pymysql driver as well.
+
+"""
+
+from sqlalchemy.dialects.mysql.mysqldb import MySQLDialect_mysqldb 
+
+class MySQLDialect_pymysql(MySQLDialect_mysqldb): 
+    driver = 'pymysql'
+
+    @classmethod 
+    def dbapi(cls): 
+        return __import__('pymysql') 
+
+dialect = MySQLDialect_pymysql 
\ No newline at end of file
index 7c89d22fd4d7647a78ee025c2c7cb07560485483..d028fa715e58a39b3d46a62ddaec32981f8ce9e4 100644 (file)
@@ -109,7 +109,7 @@ class LoadManyToOneFromIdentityTest(_base.MappedTest):
     # so remove some platforms that have wildly divergent
     # callcounts.
     __requires__ = 'python25',
-    __unsupported_on__ = 'postgresql+pg8000',
+    __unsupported_on__ = 'postgresql+pg8000', 'mysql+pymysql'
 
     @classmethod
     def define_tables(cls, metadata):
index fd43d0ca758c118cf6c4b3bf990ecc6260b9bd91..5bb301177dc53125423afc6df486e8b05f39b9a0 100644 (file)
@@ -23,6 +23,7 @@ pg8000=postgresql+pg8000://scott:tiger@127.0.0.1:5432/test
 postgresql_jython=postgresql+zxjdbc://scott:tiger@127.0.0.1:5432/test
 mysql_jython=mysql+zxjdbc://scott:tiger@127.0.0.1:5432/test
 mysql=mysql://scott:tiger@127.0.0.1:3306/test
+pymysql=mysql+pymysql://scott:tiger@127.0.0.1:3306/test?use_unicode=0&charset=utf8
 oracle=oracle://scott:tiger@127.0.0.1:1521
 oracle8=oracle://scott:tiger@127.0.0.1:1521/?use_ansi=0
 mssql=mssql://scott:tiger@SQUAWK\\SQLEXPRESS/test
index 2fe9e7533101c335f6ecffd92002d5feff840e03..183f227c381768b87890d4796cc0f8aa4385898b 100644 (file)
@@ -1404,6 +1404,7 @@ class MatchTest(TestBase, AssertsCompiledSQL):
             "MATCH (matchtable.title) AGAINST (%s IN BOOLEAN MODE)" % format)
 
     @testing.fails_on('mysql+mysqldb', 'uses format')
+    @testing.fails_on('mysql+pymysql', 'uses format')
     @testing.fails_on('mysql+oursql', 'uses format')
     @testing.fails_on('mysql+pyodbc', 'uses format')
     @testing.fails_on('mysql+zxjdbc', 'uses format')
index 9379e207f6e2b4b9b79f9f1bcafd44eb7e7d3917..318bc15d502bc8f2abcb08a42dfaf43109b2acd9 100644 (file)
@@ -63,7 +63,7 @@ class ExecuteTest(TestBase):
             conn.execute('delete from users')
 
     # some psycopg2 versions bomb this.
-    @testing.fails_on_everything_except('mysql+mysqldb',
+    @testing.fails_on_everything_except('mysql+mysqldb', 'mysql+pymysql',
             'mysql+mysqlconnector', 'postgresql')
     @testing.fails_on('postgresql+zxjdbc', 'sprintf not supported')
     def test_raw_sprintf(self):
@@ -87,7 +87,8 @@ class ExecuteTest(TestBase):
     @testing.skip_if(lambda : testing.against('mysql+mysqldb'),
                      'db-api flaky')
     @testing.fails_on_everything_except('postgresql+psycopg2',
-            'postgresql+pypostgresql', 'mysql+mysqlconnector')
+            'postgresql+pypostgresql', 'mysql+mysqlconnector', 
+            'mysql+pymysql')
     def test_raw_python(self):
         for conn in testing.db, testing.db.connect():
             conn.execute('insert into users (user_id, user_name) '
index ed68fa475fdc896ec4a96e0c016d0ef064feaa0b..31a2b705a96d55f8318d94286d5b6d5269e428ee 100644 (file)
@@ -403,6 +403,9 @@ class InvalidateDuringResultTest(TestBase):
         meta.drop_all()
         engine.dispose()
 
+    @testing.fails_on('+pymysql',
+                      "Buffers the result set and doesn't check for "
+                      "connection close")
     @testing.fails_on('+mysqldb',
                       "Buffers the result set and doesn't check for "
                       "connection close")
index e6ea246e60769eca62767c75a84ffb077f30eb9f..1acbdaf274ebdefcebb47a254ea4d56efa693179 100644 (file)
@@ -147,19 +147,15 @@ def utf8_engine(url=None, options=None):
 
     from sqlalchemy.engine import url as engine_url
 
-    if config.db.driver == 'mysqldb' and config.db.dialect.name != 'drizzle':
-        dbapi_ver = config.db.dialect.dbapi.version_info
-        if (dbapi_ver < (1, 2, 1) or
-            dbapi_ver in ((1, 2, 1, 'gamma', 1), (1, 2, 1, 'gamma', 2),
-                          (1, 2, 1, 'gamma', 3), (1, 2, 1, 'gamma', 5))):
-            raise RuntimeError('Character set support unavailable with this '
-                               'driver version: %s' % repr(dbapi_ver))
-        else:
-            url = url or config.db_url
-            url = engine_url.make_url(url)
-            url.query['charset'] = 'utf8'
-            url.query['use_unicode'] = '0'
-            url = str(url)
+    if config.db.dialect.name == 'mysql' and \
+        config.db.driver in ['mysqldb', 'pymysql']:
+        # note 1.2.1.gamma.6 or greater of MySQLdb 
+        # needed here
+        url = url or config.db_url
+        url = engine_url.make_url(url)
+        url.query['charset'] = 'utf8'
+        url.query['use_unicode'] = '0'
+        url = str(url)
 
     return testing_engine(url, options)
 
index 29c7d1ee4f98c97cb6ab2db53170ca2880f4ddb0..f26ba9c8938ca17fd6da5bd2e8f46b46158cf39c 100644 (file)
@@ -279,7 +279,8 @@ def cextensions(fn):
 def dbapi_lastrowid(fn):
     return _chain_decorators_on(
         fn,
-        fails_on_everything_except('mysql+mysqldb', 'mysql+oursql', 'sqlite+pysqlite')
+        fails_on_everything_except('mysql+mysqldb', 'mysql+oursql',
+                                   'sqlite+pysqlite', 'mysql+pymysql')
     )
 
 def sane_multi_rowcount(fn):
index e41eab140778ce36331073c4dc6984d02ff00390..52db03d76912efebef167ade45434b59316fd8d3 100644 (file)
@@ -477,7 +477,7 @@ class UnicodeTest(TestBase, AssertsExecutionResults):
 
     def test_round_trip(self):
         unicodedata = u"Alors vous imaginez ma surprise, au lever du jour, "\
-                    u"quand une drôle de petit voix m’a réveillé. Elle "\
+                    u"quand une drôle de petite voix m’a réveillé. Elle "\
                     u"disait: « S’il vous plaît… dessine-moi un mouton! »"
 
         unicode_table.insert().execute(unicode_varchar=unicodedata,unicode_text=unicodedata)
@@ -493,7 +493,7 @@ class UnicodeTest(TestBase, AssertsExecutionResults):
         # vs. cursor.execute()
 
         unicodedata = u"Alors vous imaginez ma surprise, au lever du jour, quand "\
-                        u"une drôle de petit voix m’a réveillé. "\
+                        u"une drôle de petite voix m’a réveillé. "\
                         u"Elle disait: « S’il vous plaît… dessine-moi un mouton! »"
 
         unicode_table.insert().execute(
@@ -511,7 +511,7 @@ class UnicodeTest(TestBase, AssertsExecutionResults):
         """ensure compiler processing works for UNIONs"""
 
         unicodedata = u"Alors vous imaginez ma surprise, au lever du jour, quand "\
-                        u"une drôle de petit voix m’a réveillé. "\
+                        u"une drôle de petite voix m’a réveillé. "\
                         u"Elle disait: « S’il vous plaît… dessine-moi un mouton! »"
 
         unicode_table.insert().execute(unicode_varchar=unicodedata,unicode_text=unicodedata)
@@ -536,7 +536,7 @@ class UnicodeTest(TestBase, AssertsExecutionResults):
         """
 
         unicodedata = u"Alors vous imaginez ma surprise, au lever du jour, quand "\
-                        u"une drôle de petit voix m’a réveillé. "\
+                        u"une drôle de petite voix m’a réveillé. "\
                         u"Elle disait: « S’il vous plaît… dessine-moi un mouton! »"
 
         # using Unicode explicly - warning should be emitted
@@ -589,7 +589,7 @@ class UnicodeTest(TestBase, AssertsExecutionResults):
         """checks String(unicode_error='ignore') is passed to underlying codec."""
 
         unicodedata = u"Alors vous imaginez ma surprise, au lever du jour, quand "\
-                        u"une drôle de petit voix m’a réveillé. "\
+                        u"une drôle de petite voix m’a réveillé. "\
                         u"Elle disait: « S’il vous plaît… dessine-moi un mouton! »"
 
         asciidata = unicodedata.encode('ascii', 'ignore')
@@ -666,7 +666,7 @@ class UnicodeTest(TestBase, AssertsExecutionResults):
                 eq_(
                       x,
                       u'Alors vous imaginez ma surprise, au lever du jour, quand une '
-                      u'drle de petit voix ma rveill. Elle disait:  Sil vous plat '
+                      u'drle de petite voix ma rveill. Elle disait:  Sil vous plat '
                       u'dessine-moi un mouton! '
                 )
             elif engine.dialect.returns_unicode_strings:
@@ -754,6 +754,7 @@ class EnumTest(TestBase):
         eq_(e1.adapt(ENUM).schema, 'bar')
 
     @testing.fails_on('mysql+mysqldb', "MySQL seems to issue a 'data truncated' warning.")
+    @testing.fails_on('mysql+pymysql', "MySQL seems to issue a 'data truncated' warning.")
     def test_constraint(self):
         assert_raises(exc.DBAPIError, 
             enum_table.insert().execute,